Make provenance-label readers multi-arch aware
Sync upstream and build images / sync-and-build (push) Successful in 25s
Sync upstream and build images / sync-and-build (push) Successful in 25s
Publishing -r2 exposed this: a buildx multi-arch push stores an OCI index at the tag, and both label readers assumed a single-arch manifest. Consequences were a release body with 'pnpm ?' and — much worse — CI's freshness check seeing the pinned image as permanently stale, which would have published a new -rN revision every six hours forever. Both readers now follow an index to a platform manifest (preferring amd64, the deployment's arch) before reading the config blob, and the publisher warns when an image carries no provenance labels at all. Verified against both a multi-arch tag (0.1.1-rc.2-r2) and a single-arch one (0.1.5-rc.2): the pinned build now reads as current, so no redundant rebuild. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
400797ecac
commit
2a0c808149
2 files changed
+38
-7
No files matched your search
@@ -36,10 +36,15 @@ const UPSTREAM = 'deepseek-ai/deepseek-harness';
|
||||
const UA = 'deepseek-harness-release-bot/1.0';
|
||||
|
||||
const MANIFEST_ACCEPT = [
|
||||
'application/vnd.oci.image.index.v1+json',
|
||||
'application/vnd.docker.distribution.manifest.list.v2+json',
|
||||
'application/vnd.docker.distribution.manifest.v2+json',
|
||||
'application/vnd.oci.image.manifest.v1+json',
|
||||
].join(',');
|
||||
|
||||
/** Platform preferred when a tag is a multi-arch index (the deployment's arch). */
|
||||
const PREFERRED_ARCH = 'amd64';
|
||||
|
||||
async function gitea(method, path, body) {
|
||||
const res = await fetch(`${GITEA}/api/v1${path}`, {
|
||||
method,
|
||||
@@ -134,14 +139,29 @@ function caveats(ver) {
|
||||
async function imageFacts(tag) {
|
||||
const res = await registry(`manifests/${tag}`, MANIFEST_ACCEPT);
|
||||
if (!res.ok) throw new Error(`manifest ${tag}: HTTP ${res.status}`);
|
||||
// The digest reported is the one `docker pull <tag>` pins — for a multi-arch
|
||||
// tag that is the index, not any single platform's manifest.
|
||||
const digest = res.headers.get('docker-content-digest') || '';
|
||||
const manifest = await res.json();
|
||||
let manifest = await res.json();
|
||||
// A multi-arch tag is an index: follow it to a platform manifest, because
|
||||
// only that one carries the config blob the labels live in.
|
||||
if (Array.isArray(manifest.manifests)) {
|
||||
const entries = manifest.manifests.filter((m) => m.platform?.os === 'linux');
|
||||
const chosen = entries.find((m) => m.platform?.architecture === PREFERRED_ARCH) ?? entries[0];
|
||||
if (!chosen) throw new Error(`manifest ${tag}: index has no linux platform`);
|
||||
const sub = await registry(`manifests/${chosen.digest}`, MANIFEST_ACCEPT);
|
||||
if (!sub.ok) throw new Error(`manifest ${tag} -> ${chosen.digest}: HTTP ${sub.status}`);
|
||||
manifest = await sub.json();
|
||||
}
|
||||
const cfgDigest = manifest.config?.digest;
|
||||
let labels = {};
|
||||
if (cfgDigest) {
|
||||
const blob = await registry(`blobs/${cfgDigest}`, '*/*');
|
||||
if (blob.ok) labels = (await blob.json()).config?.Labels ?? {};
|
||||
}
|
||||
if (!labels['dev.milesward.pnpm-version']) {
|
||||
console.error(`::warning::${tag}: no provenance labels found on the image config`);
|
||||
}
|
||||
return {
|
||||
digest,
|
||||
pnpm: labels['dev.milesward.pnpm-version'] || '',
|
||||
|
||||
@@ -87,13 +87,24 @@ jobs:
|
||||
[ "$(curl -s -o /dev/null -w '%{http_code}' -u "${{ github.repository_owner }}:${TOKEN}" \
|
||||
-H "Accept: $ACCEPT" "https://${REGISTRY}/v2/${IMAGE}/manifests/$1")" = 200 ]
|
||||
}
|
||||
# Labels of a single-arch manifest's config blob ("pnpm|base|recipe");
|
||||
# empty output (index manifests, missing labels) means "treat as stale".
|
||||
# Provenance labels from a tag's image config ("pnpm|base|recipe").
|
||||
# Empty output means "treat as stale". A multi-arch tag resolves to an
|
||||
# index, whose first child manifest is followed to reach the config —
|
||||
# without that, such a tag would look stale on every run and spam a
|
||||
# new revision build every six hours.
|
||||
fetch_manifest() {
|
||||
curl -s -u "${{ github.repository_owner }}:${TOKEN}" -H "Accept: $ACCEPT" \
|
||||
"https://${REGISTRY}/v2/${IMAGE}/manifests/$1"
|
||||
}
|
||||
first_digest() { printf '%s' "$1" | grep -o '"digest": *"sha256:[a-f0-9]*"' | head -1 | grep -o 'sha256:[a-f0-9]*'; }
|
||||
tag_toolchain() {
|
||||
MAN=$(curl -s -u "${{ github.repository_owner }}:${TOKEN}" \
|
||||
-H 'Accept: application/vnd.docker.distribution.manifest.v2+json,application/vnd.oci.image.manifest.v1+json' \
|
||||
"https://${REGISTRY}/v2/${IMAGE}/manifests/$1") || return 0
|
||||
CFG=$(printf '%s' "$MAN" | grep -o '"digest": *"sha256:[a-f0-9]*"' | head -1 | grep -o 'sha256:[a-f0-9]*') || return 0
|
||||
MAN=$(fetch_manifest "$1") || return 0
|
||||
if printf '%s' "$MAN" | grep -q '"manifests"'; then
|
||||
CHILD=$(first_digest "$MAN")
|
||||
[ -n "$CHILD" ] || return 0
|
||||
MAN=$(fetch_manifest "$CHILD") || return 0
|
||||
fi
|
||||
CFG=$(first_digest "$MAN") || return 0
|
||||
[ -n "$CFG" ] || return 0
|
||||
BLOB=$(curl -s -u "${{ github.repository_owner }}:${TOKEN}" "https://${REGISTRY}/v2/${IMAGE}/blobs/$CFG") || return 0
|
||||
P=$(printf '%s' "$BLOB" | grep -o '"dev.milesward.pnpm-version":"[^"]*"' | cut -d'"' -f4 || true)
|
||||
|
||||
Reference in new issue
Block a user