From 1e6cd052acbbaade68385e759fd94d46b095a38e Mon Sep 17 00:00:00 2001 From: Chris Shuttlesworth Date: Tue, 4 Aug 2026 10:16:12 -0400 Subject: [PATCH] feat: allow independently-built component images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The same-commit requirement assumed one workflow builds every image in a release, which is true for Atlas and false for VirtualWindow: its SPA and its config service are built by separate path-filtered workflows, so they are almost never built by the same commit — today they are pinned 5 commits apart. Requiring one commit would make that repo unreleasable. Keep the check as the default, because for an app and its sidecar a mismatch means a half-updated pair shipping under one version. Add require-same-commit so a repo of independent components can opt out; each image is still verified against the branch's history, and the tag lands on the newest resolved commit rather than the oldest, which would name a commit predating shipped code. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/release-image.yml | 36 ++++++++++++++++++++++++----- README.md | 7 ++++++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release-image.yml b/.github/workflows/release-image.yml index c72845b..02786b7 100644 --- a/.github/workflows/release-image.yml +++ b/.github/workflows/release-image.yml @@ -101,6 +101,19 @@ on: type: boolean default: true required: false + require-same-commit: + description: >- + Require every image to resolve to ONE build commit. Correct when a + single workflow builds them together (an app and its sidecar), where a + mismatch means a half-updated pair shipping under one version. WRONG + for a repo whose images are built by separate, path-filtered workflows: + those components change independently and almost never share a commit, + so the release is legitimately "SPA built at X plus config built at Y". + Set false there — each image is still verified against this branch's + history, and the tag lands on the newest resolved commit. + type: boolean + default: true + required: false verify-depth: description: >- How many commits back to search for the build. Needs to cover the gap @@ -220,6 +233,7 @@ jobs: SOURCE_TAG: ${{ inputs.source-tag }} IMAGE_TAG: ${{ steps.plan.outputs.image_tag }} VERIFY: ${{ inputs.verify-source-commit }} + SAME_COMMIT: ${{ inputs.require-same-commit }} DEPTH: ${{ inputs.verify-depth }} SHA: ${{ github.sha }} run: | @@ -289,13 +303,23 @@ jobs: behind="$(git rev-list --count "$release_commit..$SHA")" echo " verified: built from $release_commit (tag $matched), $behind commit(s) behind the ref" if [ -n "$RELEASE_COMMIT" ] && [ "$RELEASE_COMMIT" != "$release_commit" ]; then - echo "::error::images disagree about which commit they were built from:" - echo "::error:: $RELEASE_COMMIT vs $release_commit ($img)." - echo "::error::Images released together must come from one build." - exit 1 + if [ "$SAME_COMMIT" = "true" ]; then + echo "::error::images disagree about which commit they were built from:" + echo "::error:: $RELEASE_COMMIT vs $release_commit ($img)." + echo "::error::Images released together must come from one build." + echo "::error::If these components are built by separate path-filtered" + echo "::error::workflows, they change independently — set require-same-commit: false." + exit 1 + fi + echo " note: differs from $RELEASE_COMMIT — independent components, allowed" + fi + # Keep the NEWEST resolved commit: with independent components the + # release is "everything up to here", and the oldest would name a + # commit that predates code being shipped. + if [ -z "$RELEASE_COMMIT" ] || [ "$behind" -lt "$BEHIND" ]; then + RELEASE_COMMIT="$release_commit" + BEHIND="$behind" fi - RELEASE_COMMIT="$release_commit" - BEHIND="$behind" fi # Tag BY DIGEST, not by re-resolving :latest. Between the check above diff --git a/README.md b/README.md index 821cce8..d8dceac 100644 --- a/README.md +++ b/README.md @@ -226,6 +226,13 @@ jobs: the image tag: `/` is not legal in a docker tag. - Notes are always GitHub-generated; `summary` is pre-pended when supplied, and the promoted digests are listed under it. +- **`require-same-commit: false` for independently-built components.** By default + every image must resolve to one build commit, which catches a half-updated + pair shipping under one version. That is wrong for a repo whose images come + from separate path-filtered workflows — those change independently and almost + never share a commit, so the release legitimately means "SPA built at X plus + config built at Y". Each image is still verified against the branch's history; + the tag lands on the newest resolved commit. - **`ghcr-token` when the package is user-owned.** On a personal account a package bootstrapped by a manual push is owned by the user, not the repo, and `GITHUB_TOKEN` gets 403 on it — including on reads, which the registry reports