fix(image-deploy): monotonic tag guard — refuse backwards newTag writes #2

Merged
dev merged 1 commits from dev/bug-313bg8bgezp3/monotonic-tag-guard into main 2026-08-24 23:03:31 +00:00
+32
View File
@@ -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