Files
cozystack/hack/image-pin-consistency.bats
Myasnikov Daniil 427ed68761 fix(backupstrategy-controller): give chBackupClientImage a producer
platform-migrations was pinned at two different digests: v1.5.0 in
packages/core/platform/values.yaml, stamped by its Makefile, and
v1.4.0-rc.2 as .backupStrategyController.chBackupClientImage, stamped by
nothing at all. The second is the Altinity strategy Pod's curl+jq runner,
deliberately reusing the migrations image rather than shipping a second
one-binary tag that release CI would have to re-digest every cut. That
reuse only holds while the two move together, and the values.yaml comment
said so -- but a comment was the only thing enforcing it, so the copy with
no producer froze two minors back.

This is a release blocker, not just staleness. Promotion retags by digest,
copying every collected <repo>@<digest> to <repo>:<stable>. Two digests
under one repository produce two copies competing for one destination tag:
the first wins and the second hits the write-once guard in
hack/promote-retag.sh, which fails the promotion. A dry run against the
tree emitted exactly that pair. v1.6.0 is the first release cut through
promotion rather than a full rebuild, so it would have been the first to
hit it -- every earlier release ran `make build`, which restamped both
copies as a side effect and hid the missing producer.

packages/core/platform/Makefile now stamps both keys with the same ref, so
they are in lockstep by construction rather than by instruction, and the
committed value is aligned to the digest core/platform already carried.
The image is used only as a curl + jq + ca-certificates runner, so moving
it forward two minors changes no behaviour the strategy depends on; it
renders and the chart's helm-unittest suite passes.

hack/image-pin-consistency.bats adds the general guard -- no repository
pinned at more than one digest, driven through the real promotion selector
so it tracks whatever set promotion acts on -- plus a specific lockstep
assertion so a recurrence names its cause. The general check could not be
added before now: it fails on the very duplicate this commit removes.

Relates to #3143 (the unowned-pin class) and unblocks the v1.6.0 promote.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-07-21 23:15:46 +05:00

95 lines
4.2 KiB
Bash

#!/usr/bin/env bats
# Asserts that no first-party image repository is pinned at more than one
# digest across the committed tree.
#
# Promotion retags by digest: hack/promote-retag.sh copies every collected
# <repo>@<digest> to <repo>:<stable-version>. Two different digests under one
# repository therefore produce two copies competing for the same destination
# tag. The first wins, the second hits the write-once guard, and the promotion
# fails — on release day, in a workflow that runs once per release.
#
# This is not hypothetical. platform-migrations was pinned twice: once in
# packages/core/platform/values.yaml (stamped by its Makefile) and once as
# .backupStrategyController.chBackupClientImage, which had no producer at all
# and so froze at v1.4.0-rc.2 while the other advanced to v1.5.0. v1.6.0 is the
# first release cut through the promote path rather than a full rebuild, so it
# would have been the first to hit it.
#
# The general invariant is cheaper to hold than the specific one: a duplicate
# pin can arise from any package reusing another's image, which the tree does
# deliberately (backupstrategy-controller reuses platform-migrations as a
# curl+jq runner rather than shipping a second one-binary tag).
#
# Harness note: the CI path is hack/cozytest.sh, NOT real bats. There is no
# `run`, `$status`, `$output`, `skip`, or setup()/teardown(); each test runs as
# a shell function under `set -eu -x`, so a non-zero exit is the failure.
# Paths are repo-root-relative: BATS_TEST_DIRNAME is unset and would abort the
# whole suite under `set -u`.
#
# Run with: hack/cozytest.sh hack/image-pin-consistency.bats
@test "no image repository is pinned at more than one digest" {
tmp=$(mktemp -d)
# Drive the real promotion selector rather than a reimplementation of it, so
# this tracks whatever set promotion actually acts on. `env -u REGISTRY`:
# the CI workflow exports REGISTRY=<OCIR build registry> for every job, but
# the committed tree vendors its digests under the script's default
# ghcr.io/cozystack/cozystack — inheriting the ambient value would filter for
# the wrong registry and match nothing.
rc=0
env -u REGISTRY hack/promote-retag.sh v9.9.9 --dry-run \
>"$tmp/out" 2>"$tmp/err" || rc=$?
if [ "$rc" -ne 0 ]; then
echo "promote-retag.sh exited $rc" >&2
cat "$tmp/err" >&2
rm -rf "$tmp"
return "$rc"
fi
# Every planned copy ends in docker://<repo>:v9.9.9. A repository appearing
# twice means two source digests aimed at one destination tag.
sed -n 's|.*docker://\(.*\):v9\.9\.9$|\1|p' "$tmp/out" | sort > "$tmp/dests"
dupes=$(uniq -d < "$tmp/dests")
if [ -n "$dupes" ]; then
echo "these repositories are pinned at more than one digest, so promotion" >&2
echo "would try to retag several digests to the same stable tag and fail:" >&2
printf '%s\n' "$dupes" >&2
echo >&2
echo "Offending pins:" >&2
for repo in $dupes; do
grep -rn --include='*.yaml' --include='*.tag' --exclude-dir=charts \
-- "${repo#ghcr.io/cozystack/cozystack/}" packages/ >&2 || true
done
echo >&2
echo "Give the duplicate key a producer that stamps the same ref (see" >&2
echo "packages/core/platform/Makefile), or drop the duplicate pin." >&2
rm -rf "$tmp"
return 1
fi
rm -rf "$tmp"
}
@test "platform-migrations is pinned identically in both consumers" {
# The specific instance, pinned separately so a regression names its cause
# rather than only tripping the general check above. The two must match
# exactly, not merely resolve to the same digest: backupstrategy-controller
# renders its copy into a Pod spec, so a stale tag string there is what an
# operator reads when asking what is running.
a=$(yq -r '.migrations.image' packages/core/platform/values.yaml)
b=$(yq -r '.backupStrategyController.chBackupClientImage' \
packages/system/backupstrategy-controller/values.yaml)
if [ "$a" != "$b" ]; then
echo "platform-migrations pins have drifted:" >&2
echo " packages/core/platform/values.yaml .migrations.image" >&2
echo " $a" >&2
echo " backupstrategy-controller/values.yaml .chBackupClientImage" >&2
echo " $b" >&2
echo >&2
echo "packages/core/platform/Makefile stamps both; do not hand-edit either." >&2
return 1
fi
}