ci: pass publish-server-image digests via GHCR handoff tags (#169)
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: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
This commit is contained in:
parent
a55ed44394
commit
6fc734b0ae
52
.github/workflows/publish-server-image.yml
vendored
52
.github/workflows/publish-server-image.yml
vendored
@ -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
|
||||
|
||||
@ -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 =
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user