From c2043b206a943e236bba3d8f7c49a589f3f3b697 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 6 Oct 2026 02:27:10 +0000 Subject: [PATCH] ci: pass publish-server-image digests via GHCR handoff tags Build jobs already push each architecture by digest. Tag those manifests with ephemeral ci-${run_id}-${image}-${arch} refs instead of uploading digest marker files to Actions artifact storage, which is blocked by the account-wide quota. Publish resolves handoff tags back into RUNNER_TEMP digest markers, then assembles semver/edge tags and attestation as before. Handoff tags are removed after publish with continue-on-error. Co-authored-by: cynarAI --- .github/workflows/publish-server-image.yml | 52 +++++++++++++--------- infra/updater/src/publish-workflow.test.ts | 7 +++ 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/.github/workflows/publish-server-image.yml b/.github/workflows/publish-server-image.yml index fb28d54..b55cda2 100644 --- a/.github/workflows/publish-server-image.yml +++ b/.github/workflows/publish-server-image.yml @@ -27,7 +27,7 @@ permissions: env: # docker/metadata-action otherwise truncates commit tags to seven collision-prone characters. DOCKER_METADATA_SHORT_SHA_LENGTH: 40 - # build-push-action v6 uploads large *.dockerbuild records by default; publish only needs digest artifacts. + # build-push-action v6 uploads large *.dockerbuild records by default; publish reads digests from GHCR handoff tags. DOCKER_BUILD_RECORD_UPLOAD: false concurrency: @@ -162,22 +162,15 @@ jobs: cache-to: type=gha,mode=max,scope=${{ matrix.name }}-${{ matrix.arch }} provenance: mode=max sbom: true - - name: Record the architecture digest - id: digest + - name: Tag digest for publish job handoff env: DIGEST: ${{ steps.build.outputs.digest }} + IMAGE: ghcr.io/${{ steps.ns.outputs.repo }}/${{ matrix.name }} run: | set -euo pipefail - digest_dir="${RUNNER_TEMP}/digest" - mkdir -p "$digest_dir" - touch "${digest_dir}/${DIGEST#sha256:}" - echo "dir=${digest_dir}" >> "$GITHUB_OUTPUT" - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: digest-${{ matrix.name }}-${{ matrix.arch }} - path: ${{ steps.digest.outputs.dir }}/* - if-no-files-found: error - retention-days: 1 + docker buildx imagetools create \ + -t "${IMAGE}:ci-${GITHUB_RUN_ID}-${{ matrix.name }}-${{ matrix.arch }}" \ + "${IMAGE}@${DIGEST}" publish: name: Publish ${{ matrix.name }} image @@ -198,14 +191,6 @@ jobs: - name: updater - name: computer steps: - - name: Prepare an isolated digest directory - id: digest_dir - run: echo "path=${RUNNER_TEMP}/digests" >> "$GITHUB_OUTPUT" - - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - pattern: digest-${{ matrix.name }}-* - path: ${{ steps.digest_dir.outputs.path }} - merge-multiple: true - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Log in to GHCR uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 @@ -217,6 +202,21 @@ jobs: id: ns # GHCR rejects repository names with capitals; the GitHub repository slug has them. run: echo "repo=${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT" + - name: Collect architecture digests from registry handoff tags + id: digest_dir + env: + IMAGE: ghcr.io/${{ steps.ns.outputs.repo }}/${{ matrix.name }} + IMAGE_NAME: ${{ matrix.name }} + run: | + set -euo pipefail + digest_dir="${RUNNER_TEMP}/digests" + mkdir -p "$digest_dir" + for arch in amd64; do + handoff="${IMAGE}:ci-${GITHUB_RUN_ID}-${IMAGE_NAME}-${arch}" + digest="$(docker buildx imagetools inspect "$handoff" --format '{{.Manifest.Digest}}')" + touch "${digest_dir}/${digest#sha256:}" + done + echo "path=${digest_dir}" >> "$GITHUB_OUTPUT" - id: meta uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 with: @@ -280,3 +280,13 @@ jobs: subject-name: ghcr.io/${{ steps.ns.outputs.repo }}/${{ matrix.name }} subject-digest: ${{ steps.manifest.outputs.digest }} push-to-registry: true + - name: Remove CI handoff tags + continue-on-error: true + env: + IMAGE: ghcr.io/${{ steps.ns.outputs.repo }}/${{ matrix.name }} + IMAGE_NAME: ${{ matrix.name }} + run: | + set -euo pipefail + for arch in amd64; do + docker buildx imagetools rm "${IMAGE}:ci-${GITHUB_RUN_ID}-${IMAGE_NAME}-${arch}" + done diff --git a/infra/updater/src/publish-workflow.test.ts b/infra/updater/src/publish-workflow.test.ts index 9c80853..e7a8d18 100644 --- a/infra/updater/src/publish-workflow.test.ts +++ b/infra/updater/src/publish-workflow.test.ts @@ -55,6 +55,13 @@ describe("server image publish workflow", () => { expect(workflowText).toContain("RUNNER_TEMP"); }); + it("passes build digests to publish through GHCR handoff tags, not Actions artifact storage", () => { + expect(workflowText).not.toContain("actions/upload-artifact@"); + expect(workflowText).not.toContain("actions/download-artifact@"); + expect(workflowText).toContain("Tag digest for publish job handoff"); + expect(workflowText).toContain("ci-${GITHUB_RUN_ID}-"); + }); + it("names every image under the lowercase repository namespace", () => { expect(workflowText).not.toContain(`ghcr.io/\${{ github.repository }}`); const references =