Skip to content

Pull paired moveit_pro PR images from the private ECR repository - #921

Open
JWhitleyWork wants to merge 1 commit into
mainfrom
ci/pull-pr-images-from-ecr
Open

Pull paired moveit_pro PR images from the private ECR repository#921
JWhitleyWork wants to merge 1 commit into
mainfrom
ci/pull-pr-images-from-ecr

Conversation

@JWhitleyWork

Copy link
Copy Markdown
Member

[written by AI]

needs: moveit_pro/#22374

Paired PR for PickNikRobotics/moveit_pro#22374, the sixth and last PR of that CI overhaul series. Blocked on the same rollout prerequisite it is: the moveit-pro-ci ECR repository does not exist yet.

Motivation

moveit_pro#22374 stops publishing a public Docker Hub tag for every pull request. Each validated candidate is retagged into the private ECR repository moveit-pro-ci as pr-<number>-<distro>-<arch><gpu>, keyed on the PR number rather than the sanitized branch name.

The needs: moveit_pro/#N path in this repository composes its image reference from the paired PR's branch name and Docker Hub. After that change it points at an image nobody publishes, so every paired PR would fail on an image pull with no hint as to why.

The repository_dispatch path needs less than it looks like it does. It already takes image_ref from the payload verbatim, and moveit_pro sends the full ECR reference there, so it keeps working untouched. What it lacked was a floor: on an empty image_ref it fell through to a Docker Hub reference built from image_tag, and image_tag is now pr-<number>.

Brief description

  • The needs: path resolves <registry>/moveit-pro-ci:pr-<N>-{0}-amd64, byte-identical up to the CUDA suffix to the template buildDispatchPayload sends in moveit_pro's .github/scripts/dispatch_example_ws.cjs. sanitizeBranch goes with it: nothing composes a branch-named tag any more. The PR number is normalized through parseInt first, so a body reading #022374 cannot compose pr-022374 against a producer that writes pr-22374.
  • That path also gained the two pairing checks moveit_pro already applies from its side: the paired PR must be open, and must target the same base branch. The base-ref check is not just parity. A cross-release-line pairing (an example_ws PR on main naming a moveit_pro PR that targets v10.1) used to resolve, because Docker Hub carried a branch-named tag for every line. The release lines retag nothing into ECR, so it now has no image at all and is better caught by name than by a pull failure inside container:.
  • A repository_dispatch carrying no image_ref fails with that as the reason instead of 404ing later on a Docker Hub tag that was never published.
  • Every failing path publishes moveit_pro_sha before it returns. integration-status builds its post-back targets from that output alone, and on a dispatch it is the only target there is, so a bare return would leave the moveit_pro PR sitting on the pending status its own dispatch job wrote, with the evidence in a repository its author has no reason to open.
  • The composed reference is pinned to an approved repository before it is used, the way moveit_pro's lane-e2e.yaml pins its own. It becomes the container: image of a job on a self-hosted GPU runner, and on the dispatch path it arrives verbatim in the client payload.
  • The registry comes from vars.CONTAINER_REGISTRY_URL with a hardcoded default, mirroring how DOCKERHUB_USERNAME is already handled a few lines above. This repository has no Actions variables defined at all, so without the default nothing resolves.
  • No login step, and no new permission. The integration job runs inside container: in moveit_pro_ci's workspace_integration_test.yaml, which has no credentials: block, so a login step in this workflow could not help even if one were added. The pull works because the picknik-16-amd64-gpu pod (ARC scale set arc-16-amd64-gpu) runs as the arc-runner-ecr service account with amazon-ecr-credential-helper merged into ~/.docker/config.json, and that IRSA role grants pull on repository/* rather than an enumerated list, so the new repository is covered the day it is created.

Why the base-branch default stays on Docker Hub

moveit_pro#22374 leaves this open: the default could stay on Docker Hub or move to the fresher ECR branch-<base> image. It stays.

The default has to resolve for every base this file serves, and that includes release branches, where it runs with image_tag=v9.4. moveit-pro-ci:branch-<sanitized> exists only for branches whose pipeline has run under the new publish-runtime.yaml; moveit_pro's v9.4, v10.0 and v10.1 lines write no ECR branch tag at all. Docker Hub is the only registry carrying a tag for every base, and full runs keep those current.

Worth being explicit, because my first draft of this comment got it wrong: this is not about fork pull requests lacking credentials. The ECR pull rides the runner pod's identity, not the trigger's, so a fork run landing on picknik-16-amd64-gpu would pull from ECR fine. The lfs_cache_s3_bucket gate further down is the one that genuinely turns on OIDC.

Rollout

  • The ECR repository moveit-pro-ci still has to exist. In moveit_pro_ci_runner_config, terraform/ecr_ci_images.tf's ci_images map holds only moveit-pro-source, moveit-studio-frontend and moveit-pro-buildcaches. Adding moveit-pro-ci there also extends the push role, whose policy is [for r in aws_ecr_repository.ci : r.arn], so moveit_pro's pr-image gets push access from the same edit.
  • Two properties of that repository this design depends on. It must be created MUTABLE, since pr-<n>-jazzy-amd64-cuda13.2-cudnn9 is rewritten on every push to the paired moveit_pro PR. And its retention window needs to exceed a realistic paired-PR lifetime: #22374 specifies 14 days for pr-*, while a paired example_ws PR can sit open for weeks re-running its needs: pull against a tag written once. Either the window grows or an aged-out image needs a better failure than a pull error.
  • The runners' pull-only IRSA role needs no change: terraform/irsa_runner.tf grants pull on repository/*.
  • Merge order is the usual one. moveit_pro#22374 lands first; this merges after it.
  • Defining CONTAINER_REGISTRY_URL as a repository or org variable visible here would let the hardcoded default go. It is the second copy of that account id in this file, so for now there is a comment tying it to the lfs_cache_s3_bucket literal.
  • Do not backport this to a release branch of example_ws until the paired moveit_pro line has the pr-image ECR retag. There is no Docker Hub fallback left on the needs: path. The comment beside the template says so.

What happens when a guard fires

Nothing runs and the PR cannot merge, which is the intent. Worth spelling out because the mechanism is not the one the file's own comment implies.

build-status calls itself "the single aggregator job that branch protection requires" and it is not: main has no classic branch protection, and the Default ruleset requires four contexts, none of them build-status. Two of them are the nested reusable-workflow job names integration-test (lab_sim) / integration-test-in-studio-container (jazzy) and the hangar_sim equivalent. Those check runs exist only when integration-test actually invokes the called workflow, so a resolve failure skips the caller, the contexts are never created, they stay Expected, and the ruleset blocks. Verified against run 34015630299, where the skipped integration-test appears as one bare job with no matrix expansion and no nested name, against run 31206499436 where it runs and both nested contexts appear.

One honest gap: that run is an if:-skip rather than a needs-failure skip. The mechanism is the same, since neither invokes the called workflow, but no run in this repository exercises the needs-failure path yet, because resolve had no failure path before this PR. The first paired PR that trips a guard will close it for real.

The distinction is fragile and worth knowing about: a check run whose conclusion is skipped does satisfy a required context of that name. It does not here only because the required contexts are nested names that never come into existence. If anyone ever changes the required context to the caller job name integration-test, or turns that job into a plain steps: job, a skip starts satisfying the requirement and these guards would go green while nothing ran.

The Default ruleset also targets refs/heads/v9.* and refs/heads/v10.*, so the premature-backport hazard above is fail-closed on the release lines by the same mechanism.

Follow-ups, deliberately not in this PR

  • build-status gates nothing, and its comment tells you otherwise. It is required by no ruleset, yet it instructs "Add new gating jobs to the needs: list", so anyone following that instruction believes they have made a job gating when they have not. validate-workspace-dependencies and integration-status are already in that list and required by nothing. Untangling that is unrelated to this change, so it is not in here, but it is the reason I did not add resolve to that list either: the real gate already fails closed.

  • The Default ruleset hardcodes the two matrix cells by name, while the config_package matrix comment says the list expands one PR at a time. Each added sim needs a matching ruleset entry or the new cell runs ungated.

  • image_tag now names no real image on the dispatch path, yet it still forms the reusable workflow's ccache key and every restore-key prefix, giving each moveit_pro PR its own namespace that can never fall back to the base branch's entries. Passing the base branch as the image_tag input while keeping the payload value for the run log would align both paired paths on one namespace. That is a caching behavior change, so it belongs on its own.

  • The resolve script is ~150 lines of JavaScript embedded in YAML with no test in the repository. moveit_pro extracted its equivalent to .github/scripts/dispatch_example_ws.cjs with a node --test suite in this same series. Doing the same here is the obvious next step.

How it was tested

  • Extracted the resolve script and ran it against 28 scenarios: the ECR reference and its suffix, PR-number normalization, the open and base-ref guards, a throwing paired-PR lookup, the empty-image_ref fail-fast, that every failing path still publishes moveit_pro_sha or the PR number, the currently-deployed Docker Hub payload shapes from main/v10.0/v10.1 and from v9.4, a digest-pinned reference being accepted, four prefix-collision vectors being refused, image_tag and git_ref on each path, and two scenarios with both env defaults deleted, since a repository with no Actions variables is the only path production takes. All 28 pass. The harness is not committed; see the follow-up above.
  • Checked which moveit_pro branches still construct a dispatch payload at all. Only v9.4, v10.0, v10.1 and main do, and v9.4 sends picknikciuser/moveit-studio:... where the other two send picknikciuser/moveit-pro:.... A dispatch always runs this file from the default branch, so the allowlist carries the pre-rename name too; without it every v9.4 pairing would break.
  • pre-commit run --files .github/workflows/ci.yaml clean.
  • actionlint: one pre-existing shellcheck style note, in a job this PR does not touch. Nothing new.
  • The integration suite itself cannot run until the ECR repository exists.

Release notes

None

Claude agent checks

  • platform-architect-bot
    • two rounds. 5 required applied in round one; round two raised 2 more, of which the pulls.get throw is fixed and the build-status one was withdrawn by the reviewer after I showed the live ruleset. 8 suggestions applied, 3 recorded as follow-ups, 1 declined with the reason in the code. No blocking findings on the final candidate.
  • security-auditor
    • two rounds. Round one: 1 medium fixed (failing paths now publish the status sha), 1 low applied (approved-repository pin). Round two: 1 further medium fixed (the paired-PR lookup could throw before publishing anything) and 3 harness gaps closed. Allowlist placement and bypass resistance both confirmed clean.
  • SKIPPED code-reviewer
    • no application source changed; one CI workflow file
  • SKIPPED roboticist-bot
    • no robotics-domain code
  • SKIPPED frontend-noah-bot
    • no frontend code
  • SKIPPED compatibility-bot
    • no public API or contract surface
  • SKIPPED sonar-bot
    • no analyzable source changed
  • SKIPPED documentation-bot
    • the paired-PR docs live in moveit_pro and #22374 updates them there
  • SKIPPED test-runner
    • no package build; the integration suite is blocked on the ECR repository

🤖 Generated with Claude Code

https://claude.ai/code/session_019JqMtkYqqzdXUEYRVbe3Xp

moveit_pro #22374 stops publishing per-PR images to Docker Hub and retags
each validated candidate into the private `moveit-pro-ci` ECR repository as
`pr-<number>-<distro>-<arch><gpu>`. The `needs: moveit_pro/#N` path here
composed a Docker Hub reference from the paired PR's branch name, which that
change leaves pointing at an image nobody publishes.

Resolve the paired image from ECR by PR number instead, normalizing the number
so a zero-padded token cannot compose a tag the producer never writes, and
mirror the two pairing checks moveit_pro applies from its side: the paired PR
must be open and must target the same base branch. A cross-release-line
pairing used to resolve against Docker Hub and now has no image at all, so it
fails with both refs named rather than deep inside a container pull.

Fail a `repository_dispatch` that carries no `image_ref` rather than falling
back to a Docker Hub reference built from `image_tag`, which is now
`pr-<number>`. Every failing path publishes `moveit_pro_sha` first: the rollup
job posts the required `example_ws / integration` status from it alone, and on
a dispatch that is its only target, so returning without it would leave the
moveit_pro PR pending forever.

Pin the composed reference to an approved repository before use, the way
moveit_pro's lane-e2e.yaml does, since it becomes a `container:` image on a
self-hosted GPU runner and arrives verbatim in the payload on the dispatch
path. The pre-rename `moveit-studio` repository stays approved because v9.4
still dispatches under that name.

The base-branch default stays on Docker Hub: it has to resolve for every base
this file serves, including release branches, and moveit_pro's v9.4, v10.0 and
v10.1 lines write no ECR branch tag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019JqMtkYqqzdXUEYRVbe3Xp
@JWhitleyWork JWhitleyWork added this to the 10.2.0 milestone Sep 9, 2026
@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 31a0bf59-079d-4e3c-9cc1-b1a1129d9855

📥 Commits

Reviewing files that changed from the base of the PR and between b6150c3 and 83de013.

📒 Files selected for processing (1)
  • .github/workflows/ci.yaml

Included review availability: 5 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 6 reviews per hour.


📝 Summary

Summary by CodeRabbit

  • Chores
    • Improved automated workflow support for private container registries.
    • Added validation for dispatched and paired pull request builds, including pairing and base-branch compatibility checks.
    • Improved reliability of build outputs when validation fails.
    • Added fallback handling for container registry configuration.
    • Standardized architecture-specific container image references.

Walkthrough

Changes

The CI workflow now validates dispatch and paired pull-request image inputs, supports private ECR references, centralizes pairing outputs, and restricts fallback images to approved repositories with -amd64 tags.

CI image resolution

Layer / File(s) Summary
Dispatch inputs and pairing outputs
.github/workflows/ci.yaml
The workflow adds registry configuration, validates dispatch image references, preserves pairing identity, and centralizes pairing-output publication.
Paired pull-request resolution
.github/workflows/ci.yaml
The workflow normalizes paired pull-request numbers, validates lookup results and release compatibility, and creates private ECR references from pull-request numbers.
Fallback image construction and allowlisting
.github/workflows/ci.yaml
Fallback tags always include -amd64. Base-branch images continue to use Docker Hub. Image repositories are restricted to approved ECR and Docker Hub repositories.

Suggested reviewers: shaur-k

Merge Risk: ⚪ Minimal · up to 83de0

CI image resolution now uses validated paired-PR ECR references while retaining supported Docker Hub defaults. No merge-blocking risk is currently identified.


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore (reviewers only)

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Human Review Check ❌ Error The PR changes CI/CD configuration in .github/workflows/ci.yaml and can affect runner use and cost. The diff adds 208 lines and changes image resolution for paired PRs from Docker Hub branch tags to… This PR requires review by a requested human reviewer. After review, a non-author requested reviewer should override this pre-merge check.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description directly explains the CI workflow changes, including private ECR image resolution, paired PR validation, failure handling, rollout dependency, and testing status.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Human Review Check

Explanation

The PR changes CI/CD configuration in .github/workflows/ci.yaml and can affect runner use and cost. The diff adds 208 lines and changes image resolution for paired PRs from Docker Hub branch tags to private ECR tags, adds repository-dispatch failure paths, and changes the image reference consumed by the GPU integration workflow. That workflow runs on the self-hosted picknik-16-amd64-gpu runner with enable_gpu: true, OIDC permissions, and CI secrets. This matches the explicit failure condition for CI/CD changes that could negatively affect hosting or runner costs.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

⚠️ This PR modifies 1 file(s) that also exist in PickNikRobotics/moveit_pro_empty_ws.

Consider whether the change should land upstream in moveit_pro_empty_ws first so downstream forks pick it up on the next sync.

Overlapping files
  • .github/workflows/ci.yaml

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

  • lab_sim
    • jazzy: no report produced — see run logs
  • hangar_sim
    • jazzy: no report produced — see run logs

@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@JWhitleyWork
JWhitleyWork marked this pull request as ready for review September 10, 2026 01:29
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