From 89ef75a12e4aa5037b7488cdf96fb906814995ec Mon Sep 17 00:00:00 2001 From: castrojo Date: Tue, 8 Sep 2026 14:12:38 +0000 Subject: [PATCH] fix(images): hide unpublished Utah testing image commands and attestations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Utah's product spec only has a single 'testing' stream tag. When the ghcr.io/projectbluefin/utah:testing tag has never been pushed, the registry tag list is empty, but buildTopStreams' fallback path still fabricated a bootc switch command for it, and buildSecurityInfo still rendered cosign verify/verify-attestation commands with hasAttestation: true — even though skopeo inspect reports 'manifest unknown' for that tag. Add isImagePublished(spec, tagSet) to check whether any of a product's streamOrder tags actually exist in the registry before building switch commands or security/attestation commands. When a product's primary tag isn't published yet, streams/testingStreams are left empty and security is cleared, and the images.json product gains an explicit imagePublished: false flag. ImagesCatalog.tsx now renders an 'awaiting initial release' message in both the Streams and Signing/SBOM sections instead of empty tag lists or unpublished commands when imagePublished is false. Regenerated the Utah entry in static/data/images.json to reflect the new awaiting-initial-release state until the next scheduled fetch job runs. Fixes #1081 Signed-off-by: castrojo --- scripts/fetch-github-images.js | 41 ++++-- scripts/fetch-github-images.test.js | 9 ++ src/components/ImagesCatalog.tsx | 204 ++++++++++++++++------------ static/data/images.json | 31 ++--- 4 files changed, 162 insertions(+), 123 deletions(-) diff --git a/scripts/fetch-github-images.js b/scripts/fetch-github-images.js index 9749f60d..c2bd9f16 100644 --- a/scripts/fetch-github-images.js +++ b/scripts/fetch-github-images.js @@ -341,6 +341,14 @@ async function inspectImage(imageRef, tag) { return JSON.parse(stdout); } +// A product's primary stream tag (e.g. Utah's `testing`) is only considered +// published once it actually shows up in the registry's tag list. This keeps +// buildTopStreams' "no exact match" fallback from fabricating a switch +// command for a tag that has never been pushed at all. +function isImagePublished(spec, tagSet) { + return spec.streamOrder.some((tag) => tagSet.has(tag)); +} + function buildTopStreams(spec, tagSet) { const top = []; for (const tag of spec.streamOrder) { @@ -597,6 +605,7 @@ async function buildProduct(spec, feeds, cachedById, ageHours, sbomCache) { tags = existing?.allTags || []; } const tagSet = new Set(tags); + const imagePublished = isImagePublished(spec, tagSet); let nvidiaTagSet = null; if (spec.nvidiaPackage) { @@ -610,16 +619,16 @@ async function buildProduct(spec, feeds, cachedById, ageHours, sbomCache) { } } - const streams = attachNvidiaCommands( - buildTopStreams(spec, tagSet), - spec, - nvidiaTagSet, - ); - const testingStreams = attachNvidiaTestingCommands( - buildTestingStreams(spec, tags), - spec, - nvidiaTagSet, - ); + const streams = imagePublished + ? attachNvidiaCommands(buildTopStreams(spec, tagSet), spec, nvidiaTagSet) + : []; + const testingStreams = imagePublished + ? attachNvidiaTestingCommands( + buildTestingStreams(spec, tags), + spec, + nvidiaTagSet, + ) + : []; for (const stream of streams) { stream.versions = await buildStreamVersionInfo( @@ -717,8 +726,17 @@ async function buildProduct(spec, feeds, cachedById, ageHours, sbomCache) { metadata, metadataSource, versions, - security: buildSecurityInfo(spec, inspectTag), + security: imagePublished + ? buildSecurityInfo(spec, inspectTag) + : { + cosignKeyUrl: null, + verifyCommand: null, + attestCommand: null, + hasAttestation: false, + sbomCommand: null, + }, inspectTag, + imagePublished, lastPublishedAt: lastPublishedAt, stale, keepEvenIfStale: Boolean(spec.keepEvenIfStale), @@ -866,6 +884,7 @@ module.exports = { handleUnavailableCache, hasUsableSbomData, isCurrentImageCatalog, + isImagePublished, main, normalizeTestingTag, reportMainError, diff --git a/scripts/fetch-github-images.test.js b/scripts/fetch-github-images.test.js index 0acbbcf3..b2445ad0 100644 --- a/scripts/fetch-github-images.test.js +++ b/scripts/fetch-github-images.test.js @@ -13,6 +13,7 @@ const { cacheAgeHours, handleUnavailableCache, isCurrentImageCatalog, + isImagePublished, main, normalizeTestingTag, reportMainError, @@ -358,6 +359,14 @@ test("buildSecurityInfo returns keyless verification commands for keyless repos" assert.match(info.attestCommand, /https:\/\/slsa\.dev\/provenance\/v1/); }); +test("isImagePublished is false when the registry has no matching stream tag", () => { + const spec = { streamOrder: ["testing"] }; + + assert.equal(isImagePublished(spec, new Set()), false); + assert.equal(isImagePublished(spec, new Set(["unstable", "latest"])), false); + assert.equal(isImagePublished(spec, new Set(["testing"])), true); +}); + test("buildSecurityInfo returns keyless verification commands for Utah", () => { const info = buildSecurityInfo( { diff --git a/src/components/ImagesCatalog.tsx b/src/components/ImagesCatalog.tsx index 66337092..a01f8c80 100644 --- a/src/components/ImagesCatalog.tsx +++ b/src/components/ImagesCatalog.tsx @@ -58,6 +58,7 @@ interface Product { summary: string; artwork: "bluefin" | "achillobator" | "dakotaraptor"; supportedArches?: string[] | null; + imageRef?: string; downloads?: { display: string; source: "live" | "cache" | "unavailable"; @@ -94,6 +95,7 @@ interface Product { sbomCommand?: string | null; } | null; lastPublishedAt?: string | null; + imagePublished?: boolean; } interface ImagesCatalog { @@ -341,6 +343,7 @@ export default function ImagesCatalogComponent({ const releaseUrl = assetsLink(product.versions?.release?.url); const lastValidated = formatDate(catalog.generatedAt || null); const lastPublished = formatDate(product.lastPublishedAt || null); + const isAwaitingInitialRelease = product.imagePublished === false; const hasNvidiaVariant = product.streams.some((entry) => Boolean(entry.nvidiaCommand)) || product.testingStreams.some((entry) => Boolean(entry.nvidiaCommand)); @@ -484,7 +487,13 @@ export default function ImagesCatalogComponent({ )} - {product.streams.length > 0 ? ( + {isAwaitingInitialRelease ? ( +

+ Awaiting initial release: no image has been published to{" "} + {product.imageRef} yet. Switch commands will + appear here once the first build ships. +

+ ) : product.streams.length > 0 ? ( ({ @@ -532,15 +541,17 @@ export default function ImagesCatalogComponent({

No active tags.

)} -
- - Testing Branches ({product.testingStreams.length}) - - -
+ {!isAwaitingInitialRelease && ( +
+ + Testing Branches ({product.testingStreams.length}) + + +
+ )}
Signing and SBOM - {product.security?.cosignKeyUrl ? ( + {isAwaitingInitialRelease ? (

- Key: {product.security.cosignKeyUrl} + Signing, provenance, and SBOM commands will appear here once{" "} + {product.imageRef} has a published image to + verify.

) : ( -

- No published cosign key URL in this catalog. -

- )} - - - -

- Signature verification confirms this image was signed by the - expected maintainers and helps detect tampering before - deployment.{" "} - - Learn more - - . -

- {product.security?.verifyCommand && ( - - {product.security.verifyCommand} - - )} -
- -

- Provenance attestation lets you validate how the image was - built in CI so you can make trust decisions from evidence.{" "} - - Learn more - - . -

- {product.security?.attestCommand && ( - - {product.security.attestCommand} - + <> + {product.security?.cosignKeyUrl ? ( +

+ Key: {product.security.cosignKeyUrl} +

+ ) : ( +

+ No published cosign key URL in this catalog. +

)} - {product.security?.attestCommand && - product.security.hasAttestation === false && ( + + +

- Note: attestations are not yet published for this image. - The command is provided for when they are. + Signature verification confirms this image was signed by + the expected maintainers and helps detect tampering + before deployment.{" "} + + Learn more + + .

- )} -
- -

- SBOMs are published alongside each image as OCI referrers. - Use oras to inspect attached artifacts and pull the SBOM for - audits, policy checks, and vulnerability triage.{" "} - - Learn more - - . -

- {product.security?.sbomCommand && ( - - {product.security.sbomCommand} - - )} -
-
+ {product.security?.verifyCommand && ( + + {product.security.verifyCommand} + + )} +
+ +

+ Provenance attestation lets you validate how the image + was built in CI so you can make trust decisions from + evidence.{" "} + + Learn more + + . +

+ {product.security?.attestCommand && ( + + {product.security.attestCommand} + + )} + {product.security?.attestCommand && + product.security.hasAttestation === false && ( +

+ Note: attestations are not yet published for this + image. The command is provided for when they are. +

+ )} +
+ +

+ SBOMs are published alongside each image as OCI + referrers. Use oras to inspect attached artifacts and + pull the SBOM for audits, policy checks, and + vulnerability triage.{" "} + + Learn more + + . +

+ {product.security?.sbomCommand && ( + + {product.security.sbomCommand} + + )} +
+
+ + )}
); diff --git a/static/data/images.json b/static/data/images.json index 9758489c..c2207c81 100644 --- a/static/data/images.json +++ b/static/data/images.json @@ -274,23 +274,7 @@ "amd", "intel" ], - "streams": [ - { - "label": "TESTING", - "tag": "testing", - "command": "sudo bootc switch ghcr.io/projectbluefin/utah:testing --enforce-container-sigpolicy", - "versions": { - "gnome": null, - "kernel": null, - "nvidia": null, - "fedora": null, - "flatpak": null, - "mesa": null, - "podman": null - }, - "nvidiaCommand": null - } - ], + "streams": [], "testingStreams": [], "metadata": null, "metadataSource": "unavailable", @@ -307,15 +291,16 @@ }, "security": { "cosignKeyUrl": null, - "verifyCommand": "cosign verify --certificate-oidc-issuer https://token.actions.githubusercontent.com --certificate-identity-regexp '^https://github.com/projectbluefin/utah/.github/workflows/' ghcr.io/projectbluefin/utah:testing", - "attestCommand": "cosign verify-attestation --type https://slsa.dev/provenance/v1 --certificate-oidc-issuer https://token.actions.githubusercontent.com --certificate-identity-regexp '^https://github.com/projectbluefin/utah/.github/workflows/' ghcr.io/projectbluefin/utah:testing", - "hasAttestation": true, - "sbomCommand": "oras discover ghcr.io/projectbluefin/utah:testing" + "verifyCommand": null, + "attestCommand": null, + "hasAttestation": false, + "sbomCommand": null }, "inspectTag": "testing", "lastPublishedAt": null, "stale": false, - "keepEvenIfStale": true + "keepEvenIfStale": true, + "imagePublished": false } ] -} \ No newline at end of file +}