[Backport release-1.6] fix(release): make promote-retag digest verification media-type-agnostic - #3473
Merged
Merged
Conversation
skopeo inspect --format '{{.Digest}}' fails on OCI artifacts: for
cozystack-packages (config.mediaType
application/vnd.cncf.flux.config.v1+json) it prints nothing and exits
nonzero, so the v1.6.0 finalize aborted its post-copy verification right
after both copies had already succeeded, leaving 32 of 43 repos without
stable tags and skipping the cozy-installer publish. --dry-run never
executes this branch, so rehearsals could not catch it.
Compute the digest as sha256 of the raw manifest instead, which is how
registries define it and works for any media type. The helper gates the
hash on skopeo's exit status via a temp file rather than a pipeline:
a missing tag still yields an empty string (pre-check reads that as
"tag absent, proceed to copy"), never the empty-input hash
sha256:e3b0c442..., and never an abort under set -eu.
Verified against live GHCR: the raw-manifest hash of
cozystack-packages:v1.6.0 matches the digest finalize expected
(bf68208730860fa8...), and agrees byte-for-byte with the old method on
regular multi-arch images. New cozytest cases cover the OCI-artifact
digest, a post-copy mismatch, and the missing-tag path.
Assisted-By: GPT-5 <noreply@openai.com>
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
(cherry picked from commit 5d8dd51)
Review notes from #3435, and a correction to my first attempt at them. manifest_digest gated on skopeo`s exit status alone, so a zero exit with no bytes hashed the empty string to sha256:e3b0c442... — a digest that looks real and belongs to no manifest. The first fix simply required non-empty bytes, which was worse than it looked: the caller turns an empty result into "not published, safe to copy", so a registry answering 200 with an empty body went from an accidental refusal (the bogus digest mismatched, so the write-once check refused) to an actual copy over whatever the tag really held. Verified against real skopeo 1.23: a 200 with Content-Length 0 does exit 0 with no stdout. The helper is now explicitly three-state. A digest, or empty output for a tag the registry PROVED absent, or non-zero for anything indeterminate — which under set -e aborts the promotion before it writes. Absence has to be reported as "manifest unknown"; 429, 5xx, auth and network failures all exit non-zero with no bytes too, and reading those as "unpublished" is the same fail-open one level down. finalize retags ~42 refs with no retry wrapper, so a single rate-limit was enough to reach that path. skopeo`s stderr is captured instead of discarded, so the diagnostics now name the real failure. Tests: the write-once refusal branch the review found untested (an existing stable tag at a different digest must be refused before any write), the empty- body case (refuses, writes nothing, and the empty-input hash never appears in the diagnostics), and a 429 that must not read as an absent tag. The mock now emits realistic registry errors, since a bare non-zero exit is no longer proof of absence. Both new guards are mutation-checked: accepting any non-zero exit as absence fails the 429 test, dropping the empty-bytes check fails the empty-body test. 11/11 tests green, hack/image-pin-consistency.bats still 2/2, sh -n clean. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com> (cherry picked from commit ae39475)
This was referenced Aug 3, 2026
myasnikovdaniil
added a commit
that referenced
this pull request
Aug 4, 2026
## Why `release-1.6` was created at v1.6.0's merge commit on 2026-07-22, and the finalize fixes landed on `main` after that. Workflow files run from the ref they fire on, so a promote PR based on `release-1.6` runs **this** copy of `pull-requests-release.yaml`, not `main`'s. Shipping v1.6.1 from this line today would repeat two defects v1.6.0 already hit. ## What this carries Three upstream commits, after which this file is **byte-identical to `main`'s copy** — so later backports touching it will not conflict. | Upstream | Change | | --- | --- | | `01e1e7188` | `persist-credentials: false` on the checkout | | `f49d54a68` | publish the release with the merged changelog as its body | | `ba67fea7d` | drop `paths-ignore` from the trigger | ### persist-credentials Without it the checkout persists `GITHUB_TOKEN` as an `http.extraheader`, which silently wins over the app token injected by `git remote set-url`. The stable tag then pushes as `GITHUB_TOKEN`, which creates no workflow run, so `tags.yaml` never fires and its `generate-changelog` and `update-website-docs` backstops stay silent. That is exactly what happened to v1.6.0. ### Changelog as release body Without it the release publishes with the draft's own body, `"Promoted from vX.Y.Z-rc.N"`. **These two compound into a permanent defect on a maintenance line.** A patch's changelog is committed to `release-1.6.1`, merged into `release-1.6`, and never reaches `main` — and `update-releasenotes.yaml` only watches `main`, so nothing would ever sync it. The tag-time backstop that would have ported it is dead because of the credential bug. v1.6.1 would ship with placeholder release notes permanently, with no error anywhere. ### paths-ignore Latent rather than active. A promote PR carrying only `docs/changelogs/vX.Y.Z.md` would be dropped by the filter, producing no finalize run, no tag and no error. v1.6.1 will carry tag-string rewrites so it would not have fired, but the filter has no remaining purpose now that the promote PR always carries a changelog. ## On the cherry-pick policy `docs/release.md`'s skip rule says CI-only changes do not belong in a patch. That rule governs release *contents*; backporting release *machinery* so the line is releasable at all is a different thing, and there is precedent already on this branch — #3473, #3474 and #3514 are all CI/release backports. ## Verification `actionlint` clean. `zizmor` clean, no findings. YAML parses. No bats suite references this workflow, and `hack/promote-gate-contract.bats` does not exist on this line, so there is no stale contract test to trip. The diff against `origin/main`'s copy of the file is empty. ## Not included Three known gaps on this line are deliberately out of scope, none of which needs a code change to ship v1.6.1: - `e2e-tag.yaml` is absent and `tags.yaml` has no `rc-e2e` job, so no E2E runs on the rc. Handled by dispatching **E2E Release Tag from `main`** against the published rc tag, which also supplies the promote gate's alternate evidence. - `pull-requests.yaml` has no `labeled` trigger, so adding `full-e2e` to the promote PR starts nothing. Backporting that needs the label-event guards too, and rc-time validation makes it unnecessary here. - finalize is still the pre-#3456 monolith, so a mid-registry failure is not re-runnable and needs the documented hand recovery. ```release-note NONE ```
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Backport of #3435 to
release-1.6.