diff --git a/scripts/src/ci.ts b/scripts/src/ci.ts index a77aaeca..d56b3f28 100644 --- a/scripts/src/ci.ts +++ b/scripts/src/ci.ts @@ -931,6 +931,39 @@ function parseArtifactSummaryFromOutput(output: string, context: ArtifactSummary return parseArtifactSummaryFromFields(fields, context); } +async function completeArtifactSummaryFromRegistry(artifact: ArtifactSummary, context: ArtifactSummaryContext): Promise { + if (artifact.digest !== null && artifact.digest.length > 0 && artifact.digestRef !== null && artifact.digestRef.length > 0) return artifact; + const planned = artifactSummaryDefaults(context); + const registry = artifact.registry.length > 0 ? artifact.registry : planned.registry; + const repository = artifact.repository.length > 0 ? artifact.repository : planned.repository; + const tag = artifact.tag.length > 0 ? artifact.tag : planned.tag; + const imageRef = artifact.imageRef.length > 0 ? artifact.imageRef : `${repository}:${tag}`; + if (registry !== "127.0.0.1:5000" || !repository.startsWith(`${registry}/`)) return artifact; + const repositoryPath = repository.slice(`${registry}/`.length); + if (repositoryPath.length === 0 || repositoryPath.includes("..") || tag.length === 0) return artifact; + const result = await dispatchSsh([ + "set -euo pipefail", + `manifest_url=${shellQuote(`http://127.0.0.1:5000/v2/${repositoryPath}/manifests/${tag}`)}`, + "headers=$(mktemp /tmp/unidesk-artifact-summary.XXXXXX.headers)", + "trap 'rm -f \"$headers\"' EXIT", + "curl -fsSI -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -D \"$headers\" -o /dev/null \"$manifest_url\"", + "manifest_digest=$(awk 'BEGIN{IGNORECASE=1} /^Docker-Content-Digest:/ {gsub(/\\r/, \"\", $2); print $2; exit}' \"$headers\")", + "test -n \"$manifest_digest\"", + "printf 'artifact_registry_manifest_digest=%s\\n' \"$manifest_digest\"", + ].join("\n"), 60_000, 45_000); + const digest = /^artifact_registry_manifest_digest=(sha256:[0-9a-f]{64})$/mu.exec(result.stdout)?.[1] ?? null; + if (!result.ok || digest === null) return { ...artifact, registry, repository, tag, imageRef }; + return { + ...artifact, + registry, + repository, + tag, + imageRef, + digest, + digestRef: `${repository}@${digest}`, + }; +} + function missingArtifactSummaryFields(artifact: ArtifactSummary): string[] { const missing: string[] = []; if (artifact.serviceId.length === 0) missing.push("serviceId"); @@ -978,12 +1011,14 @@ async function readArtifactSummaryFromPipelineRun(name: string, context: Artifac if (fields.size > 0) { const fromResults = parseArtifactSummaryFromFields(fields, context); if (missingArtifactSummaryFields(fromResults).length === 0) return fromResults; + const completedFromResults = await completeArtifactSummaryFromRegistry(fromResults, context); + if (missingArtifactSummaryFields(completedFromResults).length === 0) return completedFromResults; } } catch { // Fall back to pod logs below; a malformed diagnostic line must not mask a succeeded PipelineRun. } } - return parseArtifactSummaryFromOutput(await readPipelineRunLogText(name), context); + return completeArtifactSummaryFromRegistry(parseArtifactSummaryFromOutput(await readPipelineRunLogText(name), context), context); } async function readPipelineRunLogText(name: string): Promise {