#!/usr/bin/env bash ## image-label-amd64.sh REF LABEL [LABEL...] ## ## Prints the value of each LABEL (one per line, empty line if the label is ## absent) from the *linux/amd64* image behind REF, whether REF is a plain ## single-platform manifest, an OCI image index (attestation children are ## ignored) or a Docker manifest list. ## ## Why: `imagetools inspect REF --format '{{ index .Image.Config.Labels ... }}'` ## is only meaningful for a single-platform image; on a multi-arch index `.Image` ## is platform-keyed / unavailable depending on the buildx version, and the old ## callers swallowed the error (fail-open). This resolves the amd64 child ## digest from the raw index, then inspects that child directly. ## ## Exit: 0 labels printed; 3 REF does not exist in the registry (not found); ## 1 anything else (network/auth error, malformed manifest, no amd64 ## image, config is not amd64, ...) with a clear message on stderr. ## Env: IMAGETOOLS overrides "docker buildx imagetools" (tests use a stub). set -euo pipefail IMAGETOOLS=${IMAGETOOLS:-docker buildx imagetools} die() { echo "image-label-amd64: $*" >&2; exit 1; } [ $# -ge 2 ] || die "usage: image-label-amd64.sh REF LABEL [LABEL...]" ref=$1; shift # Run an imagetools command; map "tag/manifest does not exist" to exit 3. it() { local out err rc=0 err=$(mktemp); trap 'rm -f "$err"' RETURN # shellcheck disable=SC2086 out=$($IMAGETOOLS "$@" 2>"$err") || rc=$? if [ "$rc" -ne 0 ]; then if grep -qiE 'not found|manifest unknown|name unknown|: 404' "$err"; then echo "image-label-amd64: $ref: not found ($(head -c 300 "$err" | tr '\n' ' '))" >&2 return 3 fi echo "image-label-amd64: imagetools $1 failed (rc=$rc): $(head -c 300 "$err" | tr '\n' ' ')" >&2 return 1 fi printf '%s' "$out" } rc=0; raw=$(it inspect --raw "$ref") || rc=$? [ "$rc" -eq 0 ] || exit "$rc" # Classify and, for an index, pick the single linux/amd64 child digest. rc=0 verdict=$(printf '%s' "$raw" | python3 -I -c ' import json, sys try: d = json.load(sys.stdin) except Exception as e: print("ERR not JSON: %s" % e); sys.exit(0) if not isinstance(d, dict): print("ERR manifest is not a JSON object"); sys.exit(0) mt = d.get("mediaType", "") if isinstance(d.get("manifests"), list): kids = [m for m in d["manifests"] if isinstance(m, dict) and isinstance(m.get("platform"), dict) and m["platform"].get("os") == "linux" and m["platform"].get("architecture") == "amd64" and "vnd.docker.reference.type" not in (m.get("annotations") or {})] digs = [m.get("digest", "") for m in kids] if len(digs) != 1: print("ERR index has %d linux/amd64 images (need exactly 1)" % len(digs)); sys.exit(0) if not digs[0].startswith("sha256:") or len(digs[0]) != 71: print("ERR malformed amd64 child digest %r" % digs[0]); sys.exit(0) print("INDEX " + digs[0]) elif isinstance(d.get("config"), dict) and isinstance(d.get("layers"), list): print("SINGLE") else: print("ERR neither an image index nor an image manifest (mediaType=%r)" % mt) ') || rc=$? [ "$rc" -eq 0 ] || die "could not parse manifest of $ref" case "$verdict" in ERR*) die "$ref: ${verdict#ERR }" ;; SINGLE) target=$ref ;; INDEX\ *) base=${ref%%@*} last=${base##*/} [[ $last == *:* ]] && base=${base%:"${last#*:}"} target="$base@${verdict#INDEX }" ;; *) die "$ref: unexpected parser output" ;; esac rc=0; cfg=$(it inspect "$target" --format '{{json .Image}}') || rc=$? [ "$rc" -eq 0 ] || { [ "$rc" -eq 3 ] && die "$target: not found although listed by the index"; exit "$rc"; } printf '%s' "$cfg" | python3 -I -c ' import json, sys try: c = json.load(sys.stdin) except Exception as e: sys.stderr.write("image-label-amd64: image config is not JSON: %s\n" % e); sys.exit(1) if not isinstance(c, dict) or c.get("architecture") != "amd64": sys.stderr.write("image-label-amd64: image config is not linux/amd64 (architecture=%r)\n" % (c.get("architecture") if isinstance(c, dict) else None)); sys.exit(1) cfg = c.get("config") or {} labels = cfg.get("Labels") or {} if not isinstance(labels, dict): sys.stderr.write("image-label-amd64: Labels is not an object\n"); sys.exit(1) for name in sys.argv[1:]: v = labels.get(name, "") print(v if isinstance(v, str) else "") ' "$@"