diff --git a/apps/api/internal/store/acceptance.go b/apps/api/internal/store/acceptance.go index 8b097c3..3345a11 100644 --- a/apps/api/internal/store/acceptance.go +++ b/apps/api/internal/store/acceptance.go @@ -266,6 +266,11 @@ WHERE ( id = $1::uuid AND status = 'running' ) + OR ( + id = $1::uuid + AND $2::text = 'failed' + AND status = 'pending' + ) OR ( id = $1::uuid AND $2::text = 'failed' diff --git a/apps/api/internal/store/acceptance_integration_test.go b/apps/api/internal/store/acceptance_integration_test.go index 5d1b263..bdaf6c8 100644 --- a/apps/api/internal/store/acceptance_integration_test.go +++ b/apps/api/internal/store/acceptance_integration_test.go @@ -56,6 +56,11 @@ WHERE setting_key = $1`, SystemSettingGatewayTrafficMode) if strings.Contains(string(encoded), token) { t.Fatal("acceptance run response exposed the raw token") } + if failed, err := db.FinishAcceptanceRun(ctx, FinishAcceptanceRunInput{ + RunID: run.ID, Passed: false, FailureReason: "pre-activation gate failed", + }); err != nil || failed.Status != "failed" { + t.Fatalf("finish pending acceptance run after pre-activation failure=%+v err=%v", failed, err) + } mode, err := db.ActivateAcceptanceRun(ctx, run.ID) if err != nil { t.Fatalf("activate acceptance run: %v", err) diff --git a/scripts/cluster/run-production-acceptance.sh b/scripts/cluster/run-production-acceptance.sh index edef071..39d23dc 100755 --- a/scripts/cluster/run-production-acceptance.sh +++ b/scripts/cluster/run-production-acceptance.sh @@ -190,7 +190,6 @@ fi node "$cluster_root/scripts/release-manifest.mjs" validate "$release_manifest" >/dev/null release_sha=$(node "$cluster_root/scripts/release-manifest.mjs" get "$release_manifest" sourceSha) -base_sha=$(node "$cluster_root/scripts/release-manifest.mjs" get "$release_manifest" baseReleaseSha) api_image=$(node "$cluster_root/scripts/release-manifest.mjs" get "$release_manifest" images.api) [[ $release_sha =~ ^[0-9a-f]{40}$ && $api_image =~ @sha256:[0-9a-f]{64}$ ]] || { echo 'release manifest is not full-SHA and digest pinned' >&2 @@ -1315,6 +1314,13 @@ deploy_protocol_emulator() { -n "$namespace" --timeout=300s } +is_release_ancestor() { + local ancestor=$1 + local descendant=$2 + [[ $ancestor =~ ^[0-9a-f]{40}$ && $descendant =~ ^[0-9a-f]{40}$ ]] && + git -C "$cluster_root" merge-base --is-ancestor "$ancestor" "$descendant" +} + create_and_activate_run() { local create_response=$temporary_root/create-run.json local activate_response=$temporary_root/activate-run.json @@ -1393,8 +1399,8 @@ create_and_activate_run() { echo 'current-release validation mode has unexpected image digests' >&2 return 1 } - elif [[ -z $base_sha || $traffic_release != "$base_sha" ]]; then - echo 'refusing to replace validation mode outside the direct release base' >&2 + elif ! is_release_ancestor "$traffic_release" "$release_sha"; then + echo 'refusing to replace validation mode owned by a non-ancestor release' >&2 return 1 fi body=$(jq -cn \ diff --git a/tests/release/manual-release-test.sh b/tests/release/manual-release-test.sh index 84d2078..b5a7e13 100755 --- a/tests/release/manual-release-test.sh +++ b/tests/release/manual-release-test.sh @@ -307,5 +307,6 @@ if grep -Fq 'archive_timeout: 60s' "$root/deploy/kubernetes/production/database. fi "$root/tests/release/cluster-release-helper-test.sh" >/dev/null +"$root/tests/release/production-acceptance-script-test.sh" >/dev/null echo 'manual_release_tests=PASS' diff --git a/tests/release/production-acceptance-script-test.sh b/tests/release/production-acceptance-script-test.sh new file mode 100755 index 0000000..f816a87 --- /dev/null +++ b/tests/release/production-acceptance-script-test.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT + +script=$root/scripts/cluster/run-production-acceptance.sh +awk ' + /^is_release_ancestor\(\)/ { capture = 1 } + capture && /^create_and_activate_run\(\)/ { exit } + capture { print } +' "$script" >"$tmp/is-release-ancestor.sh" + +# shellcheck source=/dev/null +source "$tmp/is-release-ancestor.sh" + +cluster_root=$tmp/repository +git init -q "$cluster_root" +git -C "$cluster_root" config user.name 'Acceptance Script Test' +git -C "$cluster_root" config user.email 'acceptance-script-test@example.invalid' +git -C "$cluster_root" commit --allow-empty -qm first +first=$(git -C "$cluster_root" rev-parse HEAD) +git -C "$cluster_root" commit --allow-empty -qm second +git -C "$cluster_root" commit --allow-empty -qm third +third=$(git -C "$cluster_root" rev-parse HEAD) + +is_release_ancestor "$first" "$third" +if is_release_ancestor "$third" "$first"; then + echo 'descendant release was accepted as an ancestor' >&2 + exit 1 +fi +if is_release_ancestor invalid "$third"; then + echo 'invalid release identifier was accepted as an ancestor' >&2 + exit 1 +fi + +echo 'production_acceptance_script_tests=PASS'