Skip to content

feat(harbor): pick the runtime image per run; tag builds by commit - #588

Merged
ShuxinLin merged 3 commits into
feature/harbor-integrationfrom
feat/harbor-runtime-image-param
Sep 30, 2026
Merged

ShuxinLin merged 3 commits into
feature/harbor-integrationfrom
feat/harbor-runtime-image-param

Conversation

@ShuxinLin

@ShuxinLin ShuxinLin commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Description

You can now choose, for each run, which runtime image the Harbor tasks build FROM, and every runtime build gets a tag that pins it.

Before this PR, the base was fixed by the task Dockerfile's ARG default, assetopsbench/runtime:dev, and every rebuild overwrote that tag. Using a published image meant retagging it locally, and a finished run couldn't name the build it ran on.

1. Choosing the image per run (e91aa07)

  • template/environment/docker-compose.yaml passes AOB_RUNTIME_IMAGE to the task Dockerfile's ARG as a build arg.
    • Harbor runs docker compose build with the shell's environment and every compose file, so AOB_RUNTIME_IMAGE=<image> harbor run … builds FROM that image.
    • The tasks don't need regenerating.
    • When the variable is unset, the local assetopsbench/runtime:dev is used, as before.
  • run.sh -r IMAGE (the default comes from AOB_RUNTIME_IMAGE):
    • A registry reference such as quay.io/… or localhost:5000/… is pulled first. Otherwise a stale local copy would satisfy FROM.
    • A bare name must already exist locally, as before.
    • It exports the variable, so harbor jobs resume uses it too, and it prints the image id it runs on.

2. Tagging builds by commit (3bb4f12)

  • build-runtime-image.sh: when no -t is given, it tags both assetopsbench/runtime:dev and assetopsbench/runtime:<short commit>.
    • :dev moves with every build. The commit tag never does, so -r assetopsbench/runtime:<commit> pins a run to one build.
    • The tag is always the right commit, because the image is built from git archive HEAD whatever the working tree holds.
  • publish-images.sh pushes <namespace>/runtime:<commit> next to :TAG and :latest.

3. Review fixes (1ad4eea)

  • Resolution order. run.sh now takes -r, then the shell's AOB_RUNTIME_IMAGE, then .env's, then the local default. Before, a value in .env was ignored.
  • When it pulls. It pulls whenever the image isn't local, or the local copy came from that same repository. Docker Hub images are now refreshed too. A local build is never pulled over.
  • Pinning. It pins the resolved image for the whole run under a tag only this process uses, removed on exit. A rebuild or pull of :dev mid-run no longer switches later trials' base. (FROM can't take a bare image id, which is why this uses a tag.)
  • Resume guard. It records the image beside each job (<job>.runtime-image) and refuses to resume a job on a different image. Harbor's resume lock doesn't cover the base image.
  • Exit status. It exits non-zero when a model's job couldn't start or resume. It also checks the cheap inputs before the image pull.
  • build-runtime-image.sh: -tNAME and -t=NAME now count as naming a tag. The comments are corrected: rebuilding the same commit moves its tag, and old commit tags keep their images until you remove them.
  • Publish note: publish-images.sh's note now points at the commit tag.

Docs

README, QUICKSTART and the note printed by publish-images.sh now say to export AOB_RUNTIME_IMAGE (or set it in .env) instead of retagging, and describe the commit tag. Tasks generated before this PR ignore the variable, so the docs say to regenerate them once.

What doesn't change

The image changes only the base. Each task still adds its own scenario_<id>/manifest.json, and shared/ still comes from overlays/private-data.yaml. For the open profile, shared/ is the repo copy inside the chosen image, so an image built from another commit brings that commit's open data.

Usage

bash benchmarks/harbor/run.sh -s <suite> -l <out> -r quay.io/assetopsbench/runtime:dev ...
bash benchmarks/harbor/run.sh -s <suite> -l <out> -r assetopsbench/runtime:3bb4f12 ...
AOB_RUNTIME_IMAGE=assetopsbench/runtime:3bb4f12 uv run harbor run ...

Caveats

  • Old jobs can't be resumed. The template change alters every generated task, so Harbor won't resume a job started before this PR. run.sh already reports that and suggests moving the job aside.
  • The quay.io image is arm64 only. quay.io/assetopsbench/runtime:dev is linux/arm64, so an amd64 host can't pull it. publish-images.sh builds both architectures.

Type of Change

  • Infrastructure / Tooling Improvement

Industry Relevance

A published result can name the exact runtime build it ran on, and third parties can run a published image without retagging it.

Related Issues

Testing & Validation

  • Oracle runs with AOB_RUNTIME_IMAGE set to a labelled copy of runtime:dev: the 3 open tasks, plus tsfm-1019 and fmsr-913 from the private suite with the private-data overlay. All 5 trials scored 1.000 with no errors.
  • The variable is honoured: with AOB_RUNTIME_IMAGE=aob-test/does-not-exist:nope, the trial's build failed trying to pull exactly that name.
  • What a trial sees: the tsfm-1019 task image built on the labelled base carries the label. With the overlay's mount it sees scenario_1019/manifest.json and a read-only shared/, and all five shared/… paths in its manifest resolve.
  • run.sh image step: it pulls a quay.io reference and rejects a missing local tag. Registry detection gave the expected result for quay.io/…, icr.io/…, localhost:5000/…, localhost/…, assetopsbench/runtime:* and ubuntu:24.04.
  • Commit tag: the build script with no arguments produced one image (bbb99529) tagged both assetopsbench/runtime:dev and assetopsbench/runtime:3bb4f12, and its /opt/aob/.aob-commit reads 3bb4f12409ee….
  • run.sh end to end, with a one-scenario profile and a fake model id, so trials fail fast without calling a provider:
    • The first run printed the resolved id. The pin tag existed during the run and was gone after it, the job's .runtime-image record was written, and the trial built, ran and was scored.
    • A second run on the same image resumed the job, dropped the crashed trial, reran it, and exited 0.
    • A third run with -r set to a different image refused to resume, named the original image, and exited 1.
  • Helpers: image_repo strips tags and digests. runtime:dev and :3bb4f12 count as local builds, while quay.io/… and couchdb:3.5 count as registry images. -r and the shell beat .env, which beats the default.
  • bash -n passes on all three scripts.
  • uv run pytest src/ -k "not integration": 714 passed, 19 failed. The base branch has the same 19 failures: the scorer and trace-exporter tests, and the iot invalid-site tests, which need a running CouchDB or no .env.
  • Data integrity: no data or images are committed.

Checklist

  • I have performed a self-review of my code.
  • I have updated the documentation (README or /docs) accordingly.
  • I have signed off my commits (DCO).

ShuxinLin and others added 2 commits September 30, 2026 15:22
Every task image builds FROM the runtime image, and the only way to choose it
was the Dockerfile's ARG default, so using a published image meant retagging it
as the local assetopsbench/runtime:dev. The template's docker-compose.yaml now
passes AOB_RUNTIME_IMAGE to that ARG as a build arg. Harbor runs `docker
compose build` with the shell's environment and every compose file, so
`AOB_RUNTIME_IMAGE=<image> harbor run ...` builds FROM that image, with no
task regeneration; unset, it keeps the local tag.

run.sh takes it as -r (or AOB_RUNTIME_IMAGE). A registry reference is pulled
first, because a local copy satisfies FROM and the build would otherwise run on
a stale one; a bare name must already exist locally, as before. It exports the
variable so `harbor jobs resume` sees it too, and prints the image id it uses.

The image changes only the base. Each task still adds its own manifest.json,
and shared/ still comes from overlays/private-data.yaml.

The template change alters every generated task, so Harbor will not resume a
job started before it; run.sh already says so and suggests moving it aside.

Verified with Harbor's oracle agent, AOB_RUNTIME_IMAGE set to a labelled copy
of runtime:dev: the 3 open tasks, and tsfm-1019 and fmsr-913 from the private
suite with the private-data overlay. All 5 trials scored 1.000 with no errors.
A nonexistent AOB_RUNTIME_IMAGE failed the build pulling exactly that name. The
tsfm-1019 task image built on the labelled base carries the label. With the
overlay's mount it sees scenario_1019/manifest.json, and a read-only shared/
where all five manifest paths resolve. run.sh's image step pulls a quay.io
reference and rejects a missing local tag. 22 assetops_harbor tests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Shuxin Lin <linshuhsin@gmail.com>
build-runtime-image.sh tagged only assetopsbench/runtime:dev, which every build
overwrites, so a run could not name the build it used after a rebuild. With no
-t given it now also tags assetopsbench/runtime:<short commit>. That tag does not
move, so `run.sh -r assetopsbench/runtime:<commit>` (or AOB_RUNTIME_IMAGE) pins a
run to one build while :dev stays the default the tasks build FROM.

publish-images.sh adds <namespace>/runtime:<commit> beside :TAG and :latest.
The commit is HEAD's, which is exactly what the image holds, because the build
comes from `git archive HEAD` whatever the working tree contains.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Shuxin Lin <linshuhsin@gmail.com>
@ShuxinLin ShuxinLin changed the title feat(harbor): pick the runtime image at run time (run.sh -r) feat(harbor): pick the runtime image per run; tag builds by commit Sep 30, 2026
Review fixes for the runtime-image selection.

run.sh:
- Resolves the image as -r, then the shell's AOB_RUNTIME_IMAGE, then ENV_FILE's,
  then the local default. It used to take only the shell's value and export a
  default over it, so an AOB_RUNTIME_IMAGE in .env was ignored; `uv run
  --env-file` never overrides a variable the shell already has.
- Pulls whenever the image is not local, or its local copy came from that same
  repository. The old rule pulled only references naming a registry host, so a
  Docker Hub image such as publish-images.sh's own <ns>/runtime:latest was never
  refreshed. A local build (no RepoDigest for its name) is never pulled over.
- Pins the resolved image for the whole run under a tag private to the process
  (aob-runtime-pin:<id>-<pid>, removed on exit) and exports that. Every trial
  resolves FROM when it builds, so exporting the movable :dev let a rebuild or
  another run's pull switch later trials' base mid-run. FROM cannot name a bare
  image id, which is why this uses a tag.
- Records the image beside each job (<job>.runtime-image) and refuses to resume
  a job on a different one. Harbor's resume lock hashes the task files, not the
  base they build FROM, so a resume with another -r mixed two images in one job.
- Exits non-zero when a model's job could not start or resume.
- Runs the image step after the cheap input checks, and strips a sha256: prefix
  only if present when printing the id.

build-runtime-image.sh: treats -tNAME and -t=NAME as naming a tag, so they no
longer also get the default :dev and :<commit> tags. Its comment no longer
claims the commit tag never moves (rebuilding the same commit moves it), and it
says how to remove old commit tags, which now keep their images alive.

publish-images.sh: the NOTE points users at the commit tag it just pushed, with
:latest as the moving option.

Docs: QUICKSTART says tasks generated before the build arg existed ignore the
variable and need regenerating once, that the variable must be set per shell or
in .env, and its pull-error entry now covers an unset AOB_RUNTIME_IMAGE. README
describes the resolution order, pin, per-job record and exit status.

Verified end to end with run.sh, a one-scenario profile and a fake model id, so
trials fail fast without calling a provider:
- The first run printed the resolved id. The pin tag existed during the run and
  was gone after it, the job's .runtime-image record was written, and the
  trial built, ran and was scored (reward 0).
- A second run on the same image resumed the job, dropped the crashed trial and
  reran it, and exited 0.
- A third run with -r set to a different image refused to resume, named the
  job's original image, and exited 1.
- Helper checks: image_repo strips tags and digests; runtime:dev and :3bb4f12
  count as local builds; quay.io/... and couchdb:3.5 count as registry images.
  -r and the shell beat .env, which beats the default.
- `uv run pytest src/ -k "not integration"`: 714 passed, 19 failed. The base
  branch has the same 19 failures, in the scorer and trace-exporter tests and in
  the iot invalid-site tests, which need a CouchDB or no .env.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Shuxin Lin <linshuhsin@gmail.com>
@ShuxinLin
ShuxinLin merged commit 401197f into feature/harbor-integration Sep 30, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant