mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 11:46:35 +00:00
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:
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
@@ -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))
|
||||
'
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user