Files
Munitor/tests/test_release_guards.sh
james.maesandClaude Opus 5.5 57cb7a2aca fix: atomic release versions and immutable image tags
Two main merges built minutes apart both computed the same GitVersion
version. The later pipeline overwrote the image tag in GHCR, github-release
tagged whatever main pointed at (--target main), and the GitOps bump was a
no-op, so production ran an image without the later merge (Website-Backend
0.1.24 and 0.1.32).

- allocate_version: on main, reserve vX.Y.Z by creating the git tag through
  the GitHub refs API (create-if-absent). On conflict with another commit,
  take the next patch; reruns of the same commit reuse their tag. Existing
  tags are never moved. Runs in mvn/gradle/node build-and-test before the
  version is baked into artifacts.
- guard_image_tags: refuse to push a main or supplemental version tag that
  already exists for a different commit (org.opencontainers.image.revision
  label, now set on every build); fail closed on registry errors.
- update_cd_repo: verify vX.Y.Z belongs to this commit before bumping the
  GitOps repo; record the source commit in the GitOps commit message.
- github_release: target CIRCLE_SHA1 instead of main and refuse when the tag
  points elsewhere.
- Generator: render cd_path only when cd.path is set, so manifests pinned to
  orb versions predating the parameter (website develop branches, Concilium,
  makers4) stop failing with "Unexpected argument(s): cd_path".

Tests: tests/test_allocate_version.sh (incl. 8 concurrent allocators against
an atomic fake refs API) and tests/test_release_guards.sh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:56:58 -05:00

135 lines
5.7 KiB
Bash

#!/usr/bin/env bash
# Tests for the release guards: immutable image tags and conditional cd_path emission.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
SRC_SCRIPTS="${PROJECT_DIR}/src/scripts"
PASS=0
FAIL=0
WORK=$(mktemp -d)
trap 'rm -rf "${WORK}"' EXIT
pass() { echo "PASS"; PASS=$((PASS + 1)); }
fail() { echo "FAIL${1:+ ($1)}"; FAIL=$((FAIL + 1)); }
# Fake docker: `docker buildx imagetools inspect REF --format ...` reads
# ${FAKE_REGISTRY}/<ref with / and : replaced by _> holding the revision label.
mkdir -p "${WORK}/bin"
cat > "${WORK}/bin/docker" <<'EOF'
#!/usr/bin/env bash
if [[ "$1" == "--version" ]]; then echo "Docker version 99"; exit 0; fi
if [[ "$1 $2 $3" == "buildx imagetools inspect" ]]; then
ref="$4"
if [[ -n "${FAKE_REGISTRY_ERROR:-}" ]]; then echo "unauthorized: authentication required" >&2; exit 1; fi
key=$(echo "${ref}" | tr '/:' '__')
f="${FAKE_REGISTRY}/${key}"
if [[ ! -f "${f}" ]]; then echo "ERROR: ${ref}: not found" >&2; exit 1; fi
rev=$(cat "${f}")
if [[ -z "${rev}" ]]; then echo '{"config":{"Labels":{}}}'; exit 0; fi
echo "{\"architecture\":\"amd64\",\"config\":{\"Labels\":{\"org.opencontainers.image.revision\":\"${rev}\"}}}"
exit 0
fi
echo "unexpected docker call: $*" >&2
exit 1
EOF
chmod +x "${WORK}/bin/docker"
put_image() { mkdir -p "${FAKE_REGISTRY}"; echo "$2" > "${FAKE_REGISTRY}/$(echo "$1" | tr '/:' '__')"; }
run_guard() {
PATH="${WORK}/bin:${PATH}" MUNITOR_SKIP_REGISTRY_LOGIN=1 FAKE_REGISTRY="${FAKE_REGISTRY}" \
IMAGE_NAME=website-backend REGISTRY=ghcr.io/koftwentytwo PROJECT_VERSION="$1" CIRCLE_SHA1="$2" \
SUPPLEMENTAL_IMAGES_JSON="${3:-[]}" bash "${SRC_SCRIPTS}/guard_image_tags.sh" >"${WORK}/guard.log" 2>&1
}
echo "=== guard_image_tags.sh Tests ==="
SUPP='[{"name":"migrations","dockerfile":"docker/Dockerfile.migrations"}]'
FAKE_REGISTRY="${WORK}/reg1"
echo -n " TEST: passes when the version tag does not exist... "
if run_guard 0.1.32 sha-new "${SUPP}"; then pass; else fail "$(cat "${WORK}/guard.log")"; fi
echo -n " TEST: passes on rerun when the tag was built from the same commit... "
put_image ghcr.io/koftwentytwo/website-backend:0.1.32 sha-new
put_image ghcr.io/koftwentytwo/website-backend-migrations:0.1.32 sha-new
if run_guard 0.1.32 sha-new "${SUPP}"; then pass; else fail "$(cat "${WORK}/guard.log")"; fi
echo -n " TEST: refuses to overwrite a version tag built from another commit (0.1.24 incident)... "
if run_guard 0.1.32 sha-other "${SUPP}"; then fail "guard allowed overwrite"; else pass; fi
echo -n " TEST: refuses when only a supplemental image collides... "
FAKE_REGISTRY="${WORK}/reg2"
put_image ghcr.io/koftwentytwo/website-backend-migrations:0.2.0 sha-old
if run_guard 0.2.0 sha-new "${SUPP}"; then fail "guard allowed supplemental overwrite"; else pass; fi
echo -n " TEST: refuses when an existing tag has no provenance label... "
FAKE_REGISTRY="${WORK}/reg3"
put_image ghcr.io/koftwentytwo/website-backend:0.3.0 ""
if run_guard 0.3.0 sha-new; then fail "unlabeled image overwritten"; else pass; fi
echo -n " TEST: registry errors fail closed... "
if FAKE_REGISTRY_ERROR=1 run_guard 0.4.0 sha-new; then fail "error treated as free"; else pass; fi
echo -n " TEST: docker_build labels images with the commit revision... "
if grep -q 'org.opencontainers.image.revision=${CIRCLE_SHA1}' "${SRC_SCRIPTS}/docker_build.sh" &&
grep -q 'org.opencontainers.image.revision=${CIRCLE_SHA1}' "${SRC_SCRIPTS}/build_push_supplemental.sh"; then
pass
else
fail "revision label missing"
fi
echo -n " TEST: docker_build_push guards tags before building and pushing... "
JOB="${PROJECT_DIR}/src/jobs/docker_build_push.yml"
g=$(grep -n 'guard_image_tags' "${JOB}" | head -1 | cut -d: -f1)
p=$(grep -n 'docker_push_ghcr' "${JOB}" | head -1 | cut -d: -f1)
if [[ -n "${g}" && -n "${p}" && ${g} -lt ${p} ]]; then pass; else fail "guard not before push"; fi
echo -n " TEST: build jobs reserve the version before persisting it... "
ok=true
for j in mvn gradle node; do
f="${PROJECT_DIR}/src/jobs/${j}_build_and_test.yml"
a=$(grep -n 'allocate_version' "${f}" | head -1 | cut -d: -f1)
e=$(grep -n 'export_version_vars' "${f}" | head -1 | cut -d: -f1)
m=$(grep -n 'persist_version_metadata' "${f}" | head -1 | cut -d: -f1)
[[ -n "${a}" && ${a} -gt ${e} && ${a} -lt ${m} ]] || ok=false
done
if ${ok}; then pass; else fail "allocate_version misplaced"; fi
echo -n " TEST: github_release targets the built commit, not the branch head... "
if grep -q 'TARGET="${CIRCLE_SHA1:-main}"' "${SRC_SCRIPTS}/github_release.sh" &&
grep -q 'Refusing to release' "${SRC_SCRIPTS}/github_release.sh"; then
pass
else
fail
fi
echo -n " TEST: update_cd_repo verifies the release tag belongs to this commit... "
if grep -q 'Refusing to bump the GitOps repo' "${SRC_SCRIPTS}/update_cd_repo.sh"; then pass; else fail; fi
echo ""
echo "=== cd_path emission (orb version compatibility) ==="
render() {
local fixture="$1" out="$2"
MUNITOR_CONFIG="${fixture}" MUNITOR_OUTPUT_FILE="${out}" \
bash "${SRC_SCRIPTS}/generate_config.sh" >/dev/null 2>&1 || true
}
echo -n " TEST: manifest without cd.path emits no cd_path (older orbs reject it)... "
render "${SCRIPT_DIR}/fixtures/java-webapp.munitor.yml" "${WORK}/java.yml"
if [[ -s "${WORK}/java.yml" ]] && grep -q 'munitor/update_cd_repo' "${WORK}/java.yml" && ! grep -q 'cd_path' "${WORK}/java.yml"; then
pass
else
fail "cd_path present or render failed"
fi
echo -n " TEST: manifest with cd.path still emits cd_path... "
render "${SCRIPT_DIR}/fixtures/node-webapp-consolidated.munitor.yml" "${WORK}/node.yml"
if grep -q 'cd_path: tenants/investinginchester/website/kustomization.yaml' "${WORK}/node.yml"; then pass; else fail; fi
echo ""
echo "Results: ${PASS} passed, ${FAIL} failed"
[[ ${FAIL} -eq 0 ]]