diff --git a/.gitea/scripts/publish-releases.js b/.gitea/scripts/publish-releases.js index 3674de44bf..96b156ed36 100644 --- a/.gitea/scripts/publish-releases.js +++ b/.gitea/scripts/publish-releases.js @@ -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 ` 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'] || '', diff --git a/.gitea/workflows/sync-upstream.yml b/.gitea/workflows/sync-upstream.yml index 34b41688f9..bb539c3a12 100644 --- a/.gitea/workflows/sync-upstream.yml +++ b/.gitea/workflows/sync-upstream.yml @@ -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)