Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 30 additions & 13 deletions .github/workflows/release-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ jobs:
fi
echo " $SOURCE_TAG = $digest"

release_commit=""
if [ "$VERIFY" = "true" ]; then
# WHICH COMMIT IS THIS IMAGE? Not necessarily HEAD. Build workflows
# carry paths filters, so a docs- or CI-only commit moves the branch
Expand Down Expand Up @@ -329,7 +330,12 @@ jobs:
echo " tagged $IMAGE_TAG"

[ -n "$first_digest" ] || first_digest="$digest"
printf -- '- `%s:%s` — `%s`\n' "$img" "$IMAGE_TAG" "$digest" >> /tmp/released-images.md
if [ -n "$release_commit" ]; then
printf -- '- `%s:%s` — `%s`, built from `%s`\n' \
"$img" "$IMAGE_TAG" "$digest" "$(printf '%s' "$release_commit" | cut -c1-7)" >> /tmp/released-images.md
else
printf -- '- `%s:%s` — `%s`\n' "$img" "$IMAGE_TAG" "$digest" >> /tmp/released-images.md
fi
done

[ -n "$RELEASE_COMMIT" ] || RELEASE_COMMIT="$SHA"
Expand All @@ -342,15 +348,22 @@ jobs:
echo 'RELEASE_IMAGES_EOF'
} >> "$GITHUB_OUTPUT"

# Created through the git-data API, NOT `git push`. A token-authenticated
# push is rejected whenever the ref being pushed carries a workflow file
# that differs from the default branch's — "refusing to allow a GitHub App
# to create or update workflow ... without `workflows` permission` — and
# GITHUB_TOKEN cannot hold that scope at all. That happens routinely here:
# the tag lands on the commit the image came from, and any later CI change
# makes its .github/workflows differ from main. The API creates a tag
# object pointing at an existing commit, introducing no file changes, so
# the check does not apply.
# TAGS THE REF THIS RAN ON, not the commit the image was built from.
#
# Tagging the build commit reads better — a version naming code actually in
# the image — and it is what this workflow did until it met GitHub's
# workflow-file protection: a GITHUB_TOKEN cannot create a ref pointing at
# a commit whose .github/workflows differ from the default branch's. That
# is not a push-vs-API distinction (the API returns the same refusal as
# "Resource not accessible by integration"); it is the token. And the
# condition is routine — any CI change after the last image build triggers
# it, which is precisely when a release is most likely.
#
# The alternative was a long-lived PAT carrying the `workflow` scope in
# every releasing repo. Not worth it: provenance does not need to live in
# the ref. The build commit of every image is recorded in the tag message
# and in the release body, so "which commit is this version?" is still
# answerable — from the release, rather than from where the tag points.
- name: create the annotated tag
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
env:
Expand All @@ -360,7 +373,8 @@ jobs:
with:
script: |
const tag = process.env.GIT_TAG;
const sha = process.env.RELEASE_COMMIT;
const sha = context.sha; // the ref being released
const builtFrom = process.env.RELEASE_COMMIT; // newest image build commit
const behind = process.env.BEHIND;
// Annotated, not lightweight: `git show <tag>` should answer who cut
// this and when without a round trip to the API.
Expand All @@ -369,8 +383,11 @@ jobs:
repo: context.repo.repo,
tag,
message: `${tag}\n\nReleased by ${context.actor} via the release-image workflow. `
+ `Promoted the image digest built from ${sha} — no rebuild. `
+ `(${behind} commit(s) on ${context.ref.replace('refs/heads/', '')} after it changed no image inputs.)`,
+ `Promoted existing image digests — no rebuild. `
+ `Newest image built from ${builtFrom}`
+ (Number(behind) > 0
? `, ${behind} commit(s) behind this ref (they changed no image inputs).`
: ` (this ref).`),
object: sha,
type: 'commit',
});
Expand Down
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,19 @@ jobs:
the build minutes. `images` takes several images that version together (an
app and its sidecar are one release, not two).
- **Refuses to release a mismatched build.** Unless `verify-source-commit` is
false, the source tag's digest must also be reachable under a commit-sha tag
for the running ref, so a build that is still in flight — or that failed after
the merge — cannot be released by accident. Repos tag per-commit differently,
so `<sha>`, the 7-char short sha and `sha-<short>` are all tried.
false, each image's digest must be reachable under a commit-sha tag somewhere
in the running ref's recent history, so a build still in flight — or one that
failed after the merge, or came from another branch — cannot be released by
accident. Repos tag per-commit differently, so `<sha>`, the 7-char short sha
and `sha-<short>` are all tried.
- **The tag lands on the ref, and provenance is recorded rather than implied.**
Tagging each image's own build commit reads better, but a `GITHUB_TOKEN`
cannot create a ref pointing at a commit whose `.github/workflows` differ from
the default branch's — the API refuses it exactly as a push does. Any CI change
after the last build triggers that, which is when you are most likely to be
releasing. Rather than require a PAT with the `workflow` scope in every repo,
the tag marks the release point and the **tag message and release body name
the build commit of every image**.
- **Refuses to reuse a tag**, checked against the remote rather than the local
clone. Released tags are immutable; supersede with a patch instead.
- **A dispatch button, because the version is a human decision** — a major means
Expand Down