From cc56f6e1a9b4ee9390b26065ccbb47f942be9f7a Mon Sep 17 00:00:00 2001 From: Dave Kowalski Date: Mon, 24 Aug 2026 23:03:30 +0000 Subject: [PATCH] =?UTF-8?q?fix(image-deploy):=20monotonic=20tag=20guard=20?= =?UTF-8?q?=E2=80=94=20refuse=20backwards=20newTag=20writes=20(#2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- action.yaml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/action.yaml b/action.yaml index 9a0b97c..1ffd7c3 100644 --- a/action.yaml +++ b/action.yaml @@ -2,6 +2,7 @@ name: Deploy Image (kustomize image-pin in apps repo) description: | Pin an image tag in fritzlab/apps via `kustomize edit set image`, validate the rendered manifests, and push to apps-repo main. Retries on push conflict. + Refuses to lower the pinned tag (monotonic write guard). inputs: image: description: Full image name without tag (must match an entry in the target kustomization.yaml `images:` block) @@ -52,7 +53,35 @@ runs: git config user.name ci-bot git config user.email ci-bot@fritzlab.net + # Read the numeric newTag currently pinned for IMAGE in origin/main. + # Returns empty string when the image entry is absent. + _origin_tag() { + git -C "$WORK" show "origin/main:${PATH_IN_REPO}/kustomization.yaml" 2>/dev/null \ + | awk -v img="$IMAGE" ' + /^images:/ { in_b=1 } + in_b && /- name:/ { found=(index($0, img) > 0) } + found && /newTag:/ { gsub(/[^0-9]/, ""); print; exit } + ' + } + + # Refuse to write a tag older than what origin/main already carries. + # Two concurrent releases can race such that the older build's deploy step + # runs after the newer build already committed its tag; without this guard + # the older run silently reverts the manifest to a stale image. + _monotonic_check() { + local existing + existing="$(_origin_tag)" + if [ -n "$existing" ] && [ "$existing" -gt "$TAG" ] 2>/dev/null; then + echo "SKIP: origin/main already has ${NAME}:${existing} > ${NAME}:${TAG} — refusing backwards write (bug-313bg8bgezp3)" + exit 0 + fi + } + cd "$PATH_IN_REPO" + + # Monotonic guard before touching the kustomization. + _monotonic_check + kustomize edit set image "${IMAGE}=${IMAGE}:${TAG}" # Validate the kustomization renders cleanly before we push. @@ -112,6 +141,9 @@ runs: fi echo "push rejected, attempt ${ATTEMPTS}; rebasing and retrying" git -C "$WORK" pull --rebase origin main + # After rebase origin/main refs are updated; re-check the monotonic + # invariant before attempting to push the rebased commit. + _monotonic_check sleep $((ATTEMPTS * 2)) done