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

A dispatch for 12.11.1-ci7 without sign_only rebuilt and re-pushed the
tag. The registry dropped the old manifest, so flux-config's
12.11.1-ci7@sha256:86d7e3b4 pin now 404s on the next pull. That is the
zegit#143 threat shape, done by accident. Like zegit-arc-runner, a build
now refuses a tag the registry already holds, and sign_only refuses one
it does not.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
agentandClaude Opus 5.5 committed 2026-10-19 14:25:29 +02:00
1 parent ba3e7c0090
commit f08fce4ab6
1 file changed
+21 -3
+21 -3
View File
@@ -78,13 +78,31 @@ jobs:
# A master push VALIDATES that the Dockerfile still builds on both arches
# but publishes nothing; only an explicit dispatch names a tag and pushes.
# 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
env:
EVENT: ${{ github.event_name }}
INPUT_TAG: ${{ inputs.tag }}
SIGN_ONLY: ${{ inputs.sign_only }}
run: |
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 "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
echo "sign_only=${{ inputs.sign_only }}" >> "$GITHUB_OUTPUT"
echo "tag=${INPUT_TAG}" >> "$GITHUB_OUTPUT"
echo "sign_only=${SIGN_ONLY}" >> "$GITHUB_OUTPUT"
else
echo "push=false" >> "$GITHUB_OUTPUT"
echo "tag=ci-validation" >> "$GITHUB_OUTPUT"