Build/apply candidate images once, require digests on promote, and keep pinned env-service from rebuilding divergent images. Co-authored-by: Cursor <cursoragent@cursor.com>
206 lines
6.3 KiB
Bash
Executable File
206 lines
6.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Validate that a commit has a promotion-eligible receipt (PASS)
|
|
# or a non-expired audited waiver binding a successful receipt.
|
|
#
|
|
# Usage:
|
|
# ./scripts/promotion-receipt-check.sh [--commit SHA] [--require-digests]
|
|
#
|
|
# Exit 0: eligible to promote
|
|
# Exit 1: blocked (missing / product_fail / infra_fail / stale waiver / mismatch / missing digests)
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
RECEIPT_DIR="${ROOT}/.doc/test-reports/assets/promotion-receipts"
|
|
COMMIT=""
|
|
REQUIRE_DIGESTS=0
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--commit) COMMIT="${2:-}"; shift 2 ;;
|
|
--require-digests) REQUIRE_DIGESTS=1; shift ;;
|
|
-h|--help)
|
|
sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'
|
|
exit 0
|
|
;;
|
|
*) echo "unknown arg: $1" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "${COMMIT}" ]]; then
|
|
COMMIT="$(git -C "${ROOT}" rev-parse HEAD)"
|
|
fi
|
|
COMMIT="$(echo "${COMMIT}" | tr -d '[:space:]' | tr 'A-F' 'a-f')"
|
|
|
|
# Resolve full sha when possible; receipts may use full or abbreviated form.
|
|
FULL_COMMIT="$(git -C "${ROOT}" rev-parse --verify "${COMMIT}^{commit}" 2>/dev/null || true)"
|
|
if [[ -n "${FULL_COMMIT}" ]]; then
|
|
COMMIT="${FULL_COMMIT}"
|
|
fi
|
|
|
|
find_receipt() {
|
|
local sha="$1"
|
|
local candidate
|
|
if [[ -f "${RECEIPT_DIR}/${sha}.json" ]]; then
|
|
echo "${RECEIPT_DIR}/${sha}.json"
|
|
return 0
|
|
fi
|
|
# Allow abbreviated filename match against full sha prefix.
|
|
shopt -s nullglob
|
|
for candidate in "${RECEIPT_DIR}"/*.json; do
|
|
local base
|
|
base="$(basename "${candidate}" .json)"
|
|
[[ "${base}" == *.waiver ]] && continue
|
|
[[ "${base}" == *.images ]] && continue
|
|
if [[ "${sha}" == "${base}"* || "${base}" == "${sha}"* ]]; then
|
|
echo "${candidate}"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
find_waiver() {
|
|
local sha="$1"
|
|
if [[ -f "${RECEIPT_DIR}/${sha}.waiver.json" ]]; then
|
|
echo "${RECEIPT_DIR}/${sha}.waiver.json"
|
|
return 0
|
|
fi
|
|
shopt -s nullglob
|
|
local candidate base
|
|
for candidate in "${RECEIPT_DIR}"/*.waiver.json; do
|
|
base="$(basename "${candidate}" .waiver.json)"
|
|
if [[ "${sha}" == "${base}"* || "${base}" == "${sha}"* ]]; then
|
|
echo "${candidate}"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
RECEIPT_PATH=""
|
|
if RECEIPT_PATH="$(find_receipt "${COMMIT}")"; then
|
|
:
|
|
else
|
|
RECEIPT_PATH=""
|
|
fi
|
|
|
|
WAIVER_PATH=""
|
|
if WAIVER_PATH="$(find_waiver "${COMMIT}")"; then
|
|
:
|
|
else
|
|
WAIVER_PATH=""
|
|
fi
|
|
|
|
export RECEIPT_PATH WAIVER_PATH COMMIT RECEIPT_DIR ROOT REQUIRE_DIGESTS
|
|
|
|
python3 <<'PY'
|
|
import json, os, sys
|
|
from datetime import datetime, timezone
|
|
|
|
commit = os.environ["COMMIT"]
|
|
receipt_path = os.environ.get("RECEIPT_PATH") or ""
|
|
waiver_path = os.environ.get("WAIVER_PATH") or ""
|
|
receipt_dir = os.environ["RECEIPT_DIR"]
|
|
root = os.environ.get("ROOT") or "."
|
|
require_digests = os.environ.get("REQUIRE_DIGESTS") == "1"
|
|
|
|
def load(path):
|
|
with open(path, encoding="utf-8") as f:
|
|
return json.load(f)
|
|
|
|
def fail(msg, code=1):
|
|
print(f"promotion-receipt-check: BLOCKED — {msg}", file=sys.stderr)
|
|
sys.exit(code)
|
|
|
|
def ok(msg):
|
|
print(f"promotion-receipt-check: OK — {msg}")
|
|
sys.exit(0)
|
|
|
|
def require_image_digests(r, label):
|
|
if not require_digests:
|
|
return
|
|
digests = r.get("image_digests") or {}
|
|
missing = [k for k in ("app", "events", "pi-agent") if not digests.get(k)]
|
|
if missing:
|
|
fail(f"{label} missing image_digests for same-image promote: {', '.join(missing)}")
|
|
|
|
def resolve_bound_receipt(binds):
|
|
if not binds:
|
|
return None
|
|
candidates = []
|
|
if os.path.isabs(binds):
|
|
candidates.append(binds)
|
|
elif binds.startswith(".doc/"):
|
|
candidates.append(os.path.join(root, binds))
|
|
candidates.append(os.path.join(receipt_dir, binds))
|
|
candidates.append(os.path.join(receipt_dir, f"{binds}.json"))
|
|
for c in candidates:
|
|
if os.path.isfile(c) and not c.endswith(".images.json") and not c.endswith(".waiver.json"):
|
|
return c
|
|
for name in os.listdir(receipt_dir):
|
|
if name.endswith(".waiver.json") or name.endswith(".images.json") or not name.endswith(".json"):
|
|
continue
|
|
stem = name[:-5]
|
|
if binds.startswith(stem) or stem.startswith(binds):
|
|
return os.path.join(receipt_dir, name)
|
|
return None
|
|
|
|
now = datetime.now(timezone.utc)
|
|
|
|
if receipt_path:
|
|
r = load(receipt_path)
|
|
verdict = r.get("verdict")
|
|
r_commit = (r.get("commit") or "").lower()
|
|
if r_commit and not (commit.startswith(r_commit) or r_commit.startswith(commit)):
|
|
fail(f"receipt commit mismatch: receipt={r_commit} target={commit}")
|
|
if verdict == "pass":
|
|
require_image_digests(r, "PASS receipt")
|
|
ok(f"PASS receipt for {commit[:12]} ({receipt_path})")
|
|
elif verdict == "product_fail":
|
|
fail(f"product_fail receipt rejects promotion ({receipt_path}); failure_class={r.get('failure_class')}")
|
|
elif verdict == "infra_fail":
|
|
if not waiver_path:
|
|
fail(f"infra_fail receipt freezes promotion ({receipt_path}); provide timed waiver or re-run nightly")
|
|
else:
|
|
fail(f"unknown verdict {verdict!r} in {receipt_path}")
|
|
|
|
if not waiver_path:
|
|
fail(f"missing promotion receipt for {commit[:12]} under {receipt_dir}")
|
|
|
|
w = load(waiver_path)
|
|
w_commit = (w.get("commit") or "").lower()
|
|
if w_commit and not (commit.startswith(w_commit) or w_commit.startswith(commit)):
|
|
fail(f"waiver commit mismatch: waiver={w_commit} target={commit}")
|
|
|
|
expires = w.get("expires_at")
|
|
if not expires:
|
|
fail(f"waiver missing expires_at ({waiver_path})")
|
|
try:
|
|
exp = datetime.fromisoformat(expires.replace("Z", "+00:00"))
|
|
except Exception as e:
|
|
fail(f"waiver expires_at unparseable: {expires} ({e})")
|
|
if exp.tzinfo is None:
|
|
exp = exp.replace(tzinfo=timezone.utc)
|
|
if now > exp:
|
|
fail(f"waiver expired at {expires} ({waiver_path})")
|
|
|
|
owner = w.get("owner") or ""
|
|
reason = w.get("reason") or ""
|
|
if not owner or not reason:
|
|
fail(f"waiver requires owner and reason ({waiver_path})")
|
|
|
|
binds = w.get("binds_receipt") or w.get("binds_recent_pass_receipt") or ""
|
|
bound_path = resolve_bound_receipt(binds)
|
|
if not bound_path:
|
|
fail(f"waiver binds_receipt not found: {binds!r}")
|
|
br = load(bound_path)
|
|
if br.get("verdict") != "pass":
|
|
fail(f"waiver bound receipt is not PASS: {bound_path}")
|
|
require_image_digests(br, "waiver-bound PASS receipt")
|
|
|
|
ok(
|
|
f"audited waiver for {commit[:12]} by {owner} until {expires}; "
|
|
f"bound PASS={bound_path}"
|
|
)
|
|
PY
|