Files
computer-playbook/scripts/meta/wait/images.sh
kevinveenbirkenbachandClaude Opus 5.5 7c7b895ae9 fix(ci): match the privileged run of a fork PR by its number as well
The wait job picks the newest privileged run on the head commit of the
PR. Two fork PRs that share a head repository, branch and commit but
target different bases each start such a run, and the commit alone cannot
tell them apart. A failed run of the other PR would then end the wait of
this one while its images are still being built.

The run name of the workflow carries the PR number, so the match now
requires it next to the head commit. A run without that name matches
nothing, and the wait does what it does whenever no run is found: it
keeps polling for the images until they exist or the wait times out.

Raised by the CodeRabbit review of this PR.

Refs #784

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 12:07:14 +02:00

180 lines
5.4 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
: "${GITHUB_REPOSITORY:?Missing GITHUB_REPOSITORY}"
: "${GH_TOKEN:?Missing GH_TOKEN}"
: "${WORKFLOW_FILE:?Missing WORKFLOW_FILE}"
: "${TARGET_EVENT:?Missing TARGET_EVENT}"
: "${PR_NUMBER:?Missing PR_NUMBER}"
: "${IMAGE_TAG:?Missing IMAGE_TAG}"
: "${INFINITO_DISTROS:?Missing INFINITO_DISTROS}"
: "${WAIT_ATTEMPTS:?Missing WAIT_ATTEMPTS}"
: "${WAIT_SLEEP_SECONDS:?Missing WAIT_SLEEP_SECONDS}"
for bin in docker gh jq; do
if ! command -v "${bin}" >/dev/null 2>&1; then
echo "ERROR: ${bin} not found." >&2
exit 1
fi
done
repo_owner="$(scripts/meta/resolve/repository/owner.sh)"
repo_name="$(scripts/meta/resolve/repository/name.sh)"
pr_head_sha="${PR_HEAD_SHA:-}"
pr_updated_at="${PR_UPDATED_AT:-}"
pr_updated_at_floor=""
last_run_state=""
last_run_id=""
lookup_failure_limit=30
lookup_failures=0
if [[ -n "${pr_updated_at}" ]]; then
if ! pr_updated_at_floor="$(date -u -d "${pr_updated_at} - 300 seconds" '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null)"; then
echo "WARN: Could not relax PR_UPDATED_AT=${pr_updated_at}; using the raw timestamp." >&2
pr_updated_at_floor="${pr_updated_at}"
fi
fi
read -r -a distros_raw <<<"${INFINITO_DISTROS}"
distros=()
for distro in "${distros_raw[@]}"; do
[[ -n "${distro}" ]] || continue
distros+=("${distro}")
done
if [[ "${#distros[@]}" -eq 0 ]]; then
echo "ERROR: INFINITO_DISTROS did not contain any entries." >&2
exit 1
fi
build_image_refs() {
local distro
for distro in "${distros[@]}"; do
printf 'ghcr.io/%s/%s/%s:%s\n' "${repo_owner}" "${repo_name}" "${distro}" "${IMAGE_TAG}"
done
}
mapfile -t image_refs < <(build_image_refs)
find_matching_run() {
local query="event=${TARGET_EVENT}&per_page=100"
if [[ -n "${pr_head_sha}" ]]; then
query+="&head_sha=${pr_head_sha}"
fi
gh api --paginate \
-H "Accept: application/vnd.github+json" \
"/repos/${GITHUB_REPOSITORY}/actions/workflows/${WORKFLOW_FILE}/runs?${query}" |
jq -sc \
--argjson pr_number "${PR_NUMBER}" \
--arg pr_head_sha "${pr_head_sha}" \
--arg pr_updated_at_floor "${pr_updated_at_floor}" '
[
.[]
| .workflow_runs[]?
| select(
if $pr_head_sha != "" then
(.head_sha // "") == $pr_head_sha
and ((.display_title // "") | test("PR #\($pr_number)( |$)"))
else
any(.pull_requests[]?; (.number // -1) == $pr_number)
end
)
| select($pr_updated_at_floor == "" or .created_at >= $pr_updated_at_floor)
]
| sort_by(.created_at)
| last // empty
'
}
find_missing_images() {
local ref
missing_images=()
for ref in "${image_refs[@]}"; do
if ! docker manifest inspect "${ref}" >/dev/null 2>&1; then
missing_images+=("${ref}")
fi
done
}
echo "Waiting for fork CI images for PR #${PR_NUMBER}"
echo "Expecting ${#image_refs[@]} image refs with tag ${IMAGE_TAG}"
if [[ -n "${pr_head_sha}" ]]; then
echo "Matching privileged runs on PR head.sha=${pr_head_sha}"
fi
if [[ -n "${pr_updated_at_floor}" ]]; then
echo "Ignoring privileged runs created before ${pr_updated_at_floor} (relaxed from PR_UPDATED_AT=${pr_updated_at})"
fi
for attempt in $(seq 1 "${WAIT_ATTEMPTS}"); do
find_missing_images
if [[ "${#missing_images[@]}" -eq 0 ]]; then
echo "All required CI images are available."
exit 0
fi
if run_json="$(find_matching_run)"; then
lookup_failures=0
else
lookup_failures=$((lookup_failures + 1))
if [[ "${lookup_failures}" -ge "${lookup_failure_limit}" ]]; then
echo "The lookup of the privileged workflow run failed ${lookup_failures} times in a row." >&2
exit 1
fi
echo "[${attempt}/${WAIT_ATTEMPTS}] Lookup of the privileged workflow run failed (${lookup_failures}/${lookup_failure_limit}). Waiting ${WAIT_SLEEP_SECONDS}s..."
sleep "${WAIT_SLEEP_SECONDS}"
continue
fi
if [[ -n "${run_json}" && "${run_json}" != "null" ]]; then
run_id="$(jq -r '.id' <<<"${run_json}")"
status="$(jq -r '.status' <<<"${run_json}")"
conclusion="$(jq -r '.conclusion // ""' <<<"${run_json}")"
url="$(jq -r '.html_url // ""' <<<"${run_json}")"
created_at="$(jq -r '.created_at // ""' <<<"${run_json}")"
state="${run_id}:${status}:${conclusion}"
if [[ "${state}" != "${last_run_state}" || "${run_id}" != "${last_run_id}" ]]; then
echo "[${attempt}/${WAIT_ATTEMPTS}] Privileged run ${run_id} created_at=${created_at} status=${status} conclusion=${conclusion:-<pending>}"
if [[ -n "${url}" ]]; then
echo "Run URL: ${url}"
fi
last_run_state="${state}"
last_run_id="${run_id}"
fi
if [[ "${status}" == "completed" && "${conclusion}" != "success" ]]; then
echo "Privileged run ${run_id} finished with conclusion=${conclusion} before all CI images became available." >&2
if [[ -n "${url}" ]]; then
echo "Run URL: ${url}" >&2
fi
echo "Still missing (first 10):" >&2
for ref in "${missing_images[@]:0:10}"; do
echo " - ${ref}" >&2
done
exit 1
fi
else
if [[ "${last_run_state}" != "missing" ]]; then
echo "[${attempt}/${WAIT_ATTEMPTS}] No matching privileged workflow run found yet."
last_run_state="missing"
last_run_id=""
fi
fi
echo "[${attempt}/${WAIT_ATTEMPTS}] Missing ${#missing_images[@]}/${#image_refs[@]} CI images. Waiting ${WAIT_SLEEP_SECONDS}s..."
echo "Example missing: ${missing_images[0]}"
sleep "${WAIT_SLEEP_SECONDS}"
done
echo "Timed out waiting for CI images tagged ${IMAGE_TAG}." >&2
echo "Still missing (first 10):" >&2
for ref in "${missing_images[@]:0:10}"; do
echo " - ${ref}" >&2
done
exit 1