feat(promote-release): use crane tag so per-arch moving tags stay signed - #197
Conversation
docker buildx imagetools create is digest-preserving only for an already multi-arch index. On a bare single-platform manifest (per-arch tags like :vX.Y.Z-amd64) it wraps the manifest in a NEW index, changing the digest and orphaning the digest-scoped cosign signature. That blocks promoting the per-arch moving tags (latest-amd64, latest-fips-arm64v8, ...), which GHCR pull stats show are heavily used. Switch the retag primitive to `crane tag`, which re-points a tag at the exact same manifest digest for both single-platform manifests and multi-arch indexes -- one uniform path covering the whole moving-tag matrix, per-arch included, with signatures intact. Pre-flight existence check moves from `imagetools inspect` to `crane digest`. action.yml installs crane (imjasonh/setup-crane) and crane reads the docker config the existing GHCR login writes, so no auth change is needed. Verified live (real GHCR + cosign): crane preserves per-arch and index digests and cosign verify passes on the retagged per-arch tag, while imagetools changes the per-arch digest and cosign verify then fails. DEVOPS-1083
Validation — exactly what was testedTwo layers: unit (mocked, in-repo CI) and end-to-end (real GHCR + real cosign, driving this branch's action). 1. Unit (bats, runs in PR CI — ✅ green)
2. End-to-end (real, not mocked)Repo:
Results (run 30006092627):
Why this is the fix (contrast, verified separately)In Not exercised here (unchanged by this PR)
Cleanup owed (need
|
Match the guard on the GHCR login step (if: inputs.dry-run != 'true'). crane is only invoked on a real run (DRY_RUN != "true"), so installing it during an exact dry-run is wasted work; the guard keeps the two setup steps consistent.
The dry-run test spot-checked only the first planned `crane tag` line, so an early loop exit that only manifested under dry-run would still pass. Pin the second entry's line too, so the plural "prints planned retags" claim holds across entries.
The Testing section still said the bats suite runs with a stubbed `docker` on PATH; this branch deletes docker_mock.bash and stubs `crane` instead.
What
Switches the
promote-releaseretag primitive fromdocker buildx imagetools createtocrane tag.Why
docker buildx imagetools createis digest-preserving only when the source is already a multi-arch index. On a bare single-platform manifest (the per-arch tags, e.g.:vX.Y.Z-amd64) it wraps the manifest in a new index, changing its digest and orphaning the digest-scoped cosign signature.That blocks promoting the per-arch moving tags (
latest-amd64,latest-fips-arm64v8,{major}-amd64, ...). GHCR pull stats show these are heavily used (the-amd64group ~1,637 pulls vs ~1,849 for the multi-arch:latestonvcluster-pro), so they can't be silently dropped — see DEVOPS-1083.crane tagre-points a tag at the exact same manifest digest for both single-platform manifests and multi-arch indexes. One uniform path now covers the whole moving-tag matrix, per-arch included, with signatures intact.Changes
src/action.sh: retagimagetools create→crane tag; pre-flightimagetools inspect→crane digest.action.yml: install crane (imjasonh/setup-crane, pinned); crane reads the docker config the existing GHCRdocker/login-actionwrites, so no auth change. Per-arch usage documented on theimagesinput.docker_mock.bash→crane_mock.bash; added a per-arch-suffix retag test. 46 bats tests, actionlint + zizmor clean, docs regenerated.Validation
The
imagetools-vs-craneclaim and the per-arch fix are verified live against a throwaway experiments repo (real GHCR + real cosign keyless), and this PR is exercised end-to-end via a caller invClusterLabs-Experiments. Exact test matrix documented in a PR comment below.DEVOPS-1083