ci: refuse to re-publish an existing image tag (zegit#262) #14

Merged
markus merged 1 commits from claude/ci/refuse-existing-tag into master 2026-10-19 12:32:29 +00:00
+21 -3
View File
@@ -78,13 +78,31 @@ jobs:
# A master push VALIDATES that the Dockerfile still builds on both arches # A master push VALIDATES that the Dockerfile still builds on both arches
# but publishes nothing; only an explicit dispatch names a tag and pushes. # but publishes nothing; only an explicit dispatch names a tag and pushes.
# Guessing a tag from a branch push would silently mint versions. # Guessing a tag from a branch push would silently mint versions.
#
# Tags are immutable (zegit#262): consumers pin tag@digest, and the
# registry drops the old manifest when a tag moves, so re-publishing a
# tag breaks every pin on it. A build refuses an existing tag; sign_only
# refuses a tag that does not exist.
- id: meta - id: meta
env:
EVENT: ${{ github.event_name }}
INPUT_TAG: ${{ inputs.tag }}
SIGN_ONLY: ${{ inputs.sign_only }}
run: | run: |
set -euo pipefail set -euo pipefail
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then if [ "$EVENT" = "workflow_dispatch" ]; then
pull=$(curl -fsS "https://${REGISTRY}/v2/token?scope=repository:${IMAGE_PATH}:pull&service=container_registry" | python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])')
tags=$(curl -fsS -H "Authorization: Bearer ${pull}" "https://${REGISTRY}/v2/${IMAGE_PATH}/tags/list?n=10000" | python3 -c 'import json,sys; print("\n".join(json.load(sys.stdin).get("tags") or []))')
if printf '%s\n' "$tags" | grep -qxF "$INPUT_TAG"; then
if [ "$SIGN_ONLY" != "true" ]; then
echo "::error::${IMAGE_PATH}:${INPUT_TAG} already exists; tags are immutable, so nothing is overwritten (to sign it, dispatch with sign_only)"; exit 1
fi
elif [ "$SIGN_ONLY" = "true" ]; then
echo "::error::${IMAGE_PATH}:${INPUT_TAG} is not published; nothing to sign"; exit 1
fi
echo "push=true" >> "$GITHUB_OUTPUT" echo "push=true" >> "$GITHUB_OUTPUT"
echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT" echo "tag=${INPUT_TAG}" >> "$GITHUB_OUTPUT"
echo "sign_only=${{ inputs.sign_only }}" >> "$GITHUB_OUTPUT" echo "sign_only=${SIGN_ONLY}" >> "$GITHUB_OUTPUT"
else else
echo "push=false" >> "$GITHUB_OUTPUT" echo "push=false" >> "$GITHUB_OUTPUT"
echo "tag=ci-validation" >> "$GITHUB_OUTPUT" echo "tag=ci-validation" >> "$GITHUB_OUTPUT"