fix(ci): gate registry images on architecture completeness, not existence

Every check asked `docker manifest inspect` whether a tag resolved. A tag
serving only amd64 answers yes, so the arm64 half of the pool could be
absent from `latest` while the release, the CI-image wait and the cron
picker all reported the images present.

`architectures.sh` answers the narrower question instead: given a reference,
which architectures must it serve that it does not. It subtracts the
manifest index's platforms from `resolve_architectures()`, so the pool stays
declared in one place and an absent tag and a single-architecture tag give
the same answer to every caller.

`highest_release_tag.sh` checks `latest` before it walks the version tags.
The published install instructions name the image without a tag, so a reader
pulls `latest`, and only a release of the highest version republishes it: an
architecture missing from `latest` is therefore reported as that version
missing. Older tags stay an existence check, because their source predates
the architecture and no rebuild for it is available.

The CI-image wait is handed the run's own architecture list, the same
expression the build matrix and the manifest step already read, so it cannot
demand more than the run produced. The two release workflows pass no list and
therefore require the whole pool, which is what drives the rebuild.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-09-29 13:56:15 +02:00
1 parent 24d6a999dd
commit 964f3255f5
5 files changed
+73 -3

No files matched your search

@@ -251,6 +251,7 @@ jobs:
REPO_PREFIX: ${{ steps.owner.outputs.name }}
CI_TAG: ${{ needs.pools.outputs.image_tag }}
INFINITO_DISTROS: ${{ join(fromJson(needs.pools.outputs.matrix), ' ') }}
IMAGE_ARCHITECTURES: ${{ join(fromJson(needs.pools.outputs.architectures), ' ') }}
WAIT_ATTEMPTS: "90"
WAIT_SLEEP_SECONDS: "10"
run: ./scripts/meta/wait/ci_images.sh
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
#
# Output:
# The architectures the reference must serve but does not, space-separated;
# empty when it serves all of them.
#
# Env:
# IMAGE_ARCHITECTURES narrows what the reference must serve; empty asks for
# the whole pool utils/github/variant/pools.py declares
set -euo pipefail
ref="${1:?Missing image reference}"
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd "${script_dir}/../../../.." && pwd)"
if ! manifest="$(docker manifest inspect "${ref}" 2>/dev/null)"; then
manifest=""
fi
cd "${repo_root}"
MANIFEST="${manifest}" IMAGE_ARCHITECTURES="${IMAGE_ARCHITECTURES:-}" \
"${PYTHON:-python3}" -c '
import json
import os
from utils.github.variant.pools import resolve_architectures
raw = os.environ["MANIFEST"].strip()
index = json.loads(raw) if raw else {}
served = {
entry.get("platform", {}).get("architecture")
for entry in index.get("manifests", [])
}
required = resolve_architectures(os.environ["IMAGE_ARCHITECTURES"])
print(" ".join(name for name in required if name not in served))
'
+3 -2
View File
@@ -16,10 +16,11 @@ missing="false"
for distro in ${INFINITO_DISTROS}; do
img="${REGISTRY}/${OWNER}/${REPO_PREFIX}/${distro}:${CI_TAG}"
echo "Check: ${img}" >&2
if docker manifest inspect "${img}" >/dev/null 2>&1; then
absent="$("${script_dir}/architectures.sh" "${img}")"
if [[ -z "${absent}" ]]; then
echo " OK" >&2
else
echo " MISSING" >&2
echo " MISSING ${absent}" >&2
missing="true"
fi
done
@@ -10,12 +10,42 @@ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OWNER="$("${script_dir}/../repository/owner.sh")"
REPO_PREFIX="${REPO_PREFIX,,}"
serves_pool() {
local img="$1"
local absent
echo "Check: ${img}" >&2
absent="$("${script_dir}/architectures.sh" "${img}")"
if [[ -z "${absent}" ]]; then
echo " OK" >&2
return 0
fi
echo " MISSING ${absent}" >&2
return 1
}
mapfile -t tags < <("${script_dir}/../version_tags.sh")
if [[ ${#tags[@]} -eq 0 ]]; then
exit 0
fi
latest_incomplete="false"
for distro in ${INFINITO_DISTROS}; do
if ! serves_pool "${REGISTRY}/${OWNER}/${REPO_PREFIX}/${distro}:latest"; then
latest_incomplete="true"
fi
done
if ! serves_pool "${REGISTRY}/${OWNER}/${REPO_PREFIX}:latest"; then
latest_incomplete="true"
fi
if [[ "${latest_incomplete}" == "true" ]]; then
printf '%s\n' "${tags[$((${#tags[@]} - 1))]}"
exit 0
fi
for ((i = ${#tags[@]} - 1; i >= 0; i--)); do
tag="${tags[$i]}"
tag_missing="false"
+1 -1
View File
@@ -2,7 +2,7 @@
set -euo pipefail
# Env:
# REGISTRY, OWNER, REPO_PREFIX, CI_TAG, INFINITO_DISTROS
# REGISTRY, OWNER, REPO_PREFIX, CI_TAG, INFINITO_DISTROS, IMAGE_ARCHITECTURES
# image coordinates, as scripts/meta/resolve/missing/ci_images.sh reads them
# WAIT_ATTEMPTS polls before giving up (default 60)
# WAIT_SLEEP_SECONDS seconds between polls (default 10)