fix(build-push-docker-manifest): idempotent manifest + cosign reruns (RANE-4683)#1577
fix(build-push-docker-manifest): idempotent manifest + cosign reruns (RANE-4683)#1577HashWrangler wants to merge 20 commits into
Conversation
|
👋 HashWrangler, thanks for creating this pull request! To help reviewers, please consider creating future PRs as drafts first. This allows you to self-review and make any final changes before notifying the team. Once you're ready, you can mark it as "Ready for review" to request feedback. Thanks! |
…(RANE-4683) - Skip imagetools create when the tag already references the expected platform digests; fail if the tag exists with different digests - Skip cosign sign when verify already succeeds (idempotent rerun) - Retry cosign verify after sign for Sigstore propagation flakes Together these changes make build-publish manifest jobs safe to rerun without digest drift or redundant signing, while still failing loudly on real conflicts.
856ffb7 to
b6b6a44
Compare
There was a problem hiding this comment.
Pull request overview
This PR hardens the build-push-docker-manifest composite action to make build-publish reruns idempotent and reduce flakiness around Cosign verification/signature propagation.
Changes:
- Adds an idempotency guard for manifest creation by comparing expected vs existing platform digests and skipping
imagetools createwhen they match. - Skips
cosign signwhen an existing valid signature is already present, and adds retry logic tocosign verifyto absorb Sigstore propagation lag. - Adds a changeset to release these hardening updates as a minor version bump.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| actions/build-push-docker-manifest/action.yml | Adds digest comparison to avoid manifest digest drift on reruns; makes cosign signing/verification rerun-safe with skip + retry behavior. |
| .changeset/rane-4683-manifest-cosign-hardening.md | Declares a minor release for the manifest + cosign hardening changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Ensure jq is installed before digest comparison - Validate docker-image-name-digests is non-empty - Only skip cosign sign when OIDC identity constraints verify - Fail fast on unexpected imagetools inspect errors (not just missing tag)
Keep MANIFEST_CREATE_SKIPPED expression on one line and only sleep between cosign verify retries, not after the final failed attempt.
- Fail fast when jq is missing and apt-get/sudo are unavailable
- Shorten step/output ids so summary env expressions stay on one line
under Prettier (avoids split ${{ }} expressions)
…empotency Treat digest lists as sets: sort -u in normalization and existing-manifest inspection, and reuse normalized digests when building imagetools create args.
- Only sleep between manifest digest inspect retries, not after the final fail - Fail fast when an existing tag has no extractable platform digests instead of falling through to imagetools create (digest drift on rerun)
Reject non-sha256 tokens in normalize_digest_csv so bad docker-image-name-digests input fails fast with a clear error instead of deferring to imagetools create.
… (RANE-4683) Per review (Erik/Chad): move cosign signature verification out of the build-push-docker-manifest action into a standalone verify-manifest-signature job in reusable-docker-build-publish, so a Sigstore propagation-lag flake can be retried in isolation without re-running manifest create/sign. That rerun was the root cause of index digest drift stranding the tag on an unsigned wrapper (hit again on v2.55.0-rc.0 GA). - reusable-docker-build-publish: new verify-manifest-signature job (needs docker-manifest); expose docker-manifest name-with-digest output; remove the inline verify step. - build-push-docker-manifest: always cosign sign (safe now that verify is decoupled and imagetools create is idempotent); drop sign-skip verification shell; drop the jq availability guard (preinstalled on ubuntu-24.04); tidy summary. - regenerate synced .github/workflows copy; update changeset.
|
Pushed
@chainchad re: "is it ever unsigned, or just a delay to verify" — concrete answer from today: it was signed, then a rerun stranded the tag. This exact flake blocked v2.55.0 GA this morning. On Jul 7 the RC index |
…ature action Address "too much bash in the job" (Chad): move the standalone verify job's inline cosign install + retry loop into a dedicated composite action `verify-docker-signature`, with the retry logic in scripts/verify-with-retry.sh. The verify-manifest-signature job in reusable-docker-build-publish is now declarative (aws login -> ecr login -> uses: verify-docker-signature). - new action actions/verify-docker-signature (action.yml, scripts, README, package.json, project.json); cosign verify with configurable retry for Sigstore propagation lag. - reusable-docker-build-publish: verify job uses the new action (pinned @main pending first release; TODO to pin to /v1). - changeset + regenerated .github/workflows copy.
Revert the verify-docker-signature composite action / external script; per follow-up review we keep the logic directly in the workflow (matching the existing inline style in reusable-docker-build-publish). Verification stays in its own verify-manifest-signature job so a Sigstore propagation flake retries without re-running create/sign, but the cosign install + retry loop live inline.
Per review (Chad: the retry loop isn't good; Erik: keep verify as its own isolated step): drop the 5x10s hand-rolled retry loop and verify once. The verify-manifest-signature job is already isolated and independently re-runnable, so it is the retry unit for the Sigstore propagation-lag case — re-running it never touches manifest create/sign, so no index digest drift (RANE-4683).
Per Chad: replace the fixed retry with an exponential-backoff retry (10s, 20s, 40s ... capped at 120s/wait) bounded to 10 minutes total, in the isolated verify-manifest-signature job. Absorbs Sigstore propagation lag without ever re-running manifest create/sign (RANE-4683).
Job timeout was 5m while the in-step retry budget is 600s, so GitHub could kill the job before Sigstore propagation retries finished.
Ready for re-reviewAll open review threads replied + resolved. Tip: Final shape
Copilot today
@erikburt @chainchad — please take another look when you can. |
Explain why we compare platform digests (not index digests), skip vs fail-hard paths, and how create idempotency relates to the isolated verify job (RANE-4683).
Makes auth/identity mismatches distinguishable from Sigstore propagation lag when the 10-minute retry budget is exhausted.
Descriptors without .platform (e.g. SBOMs/attachments) no longer pass the idempotency/propagation digest comparison.
There was a problem hiding this comment.
There's a lot of complexity that we're adding to this action.
Since we're moving the verify step to its own job, what if we simplified this to make the manifest index idempotent by checking if the manifest index tag exists and then running successive steps only if it does not?
Example:
- name: Check if manifest already exists
id: manifest-check
shell: bash
env:
MANIFEST: ${{ steps.manifest-name.outputs.name }}:${{ inputs.docker-manifest-tag }}
run: |
if docker buildx imagetools inspect "${MANIFEST}" >/dev/null 2>&1; then
echo "exists=true" | tee -a "$GITHUB_OUTPUT"
else
echo "exists=false" | tee -a "$GITHUB_OUTPUT"
fi
- name: Create and push Docker manifest
if: steps.manifest-check.outputs.exists != 'true'
...
Summary
Hardens build-publish against manifest job flakes and non-idempotent reruns (RANE-4683).
imagetools createagain → new index digest (digest drift)no signatures foundright after sign; job rerun used to re-create the indexverify-manifest-signaturejob (needs: docker-manifest) with exponential backoff up to 10 minutes (jobtimeout-minutes: 12)Parent epic: RANE-4695 private release improvements.
Design (final)
Evidence
v2.990.5-beta.0— cosign verify flake; rerun passed1070bfbf…while signed digest wasff93d395…(identical platform children)Out of scope (separate PR)
Test plan
build-push-docker-manifest+ patchreusable-docker-build-publishreusable-docker-build-publish@build-push-docker-manifest/v1picks up release