ci: wait for concurrent candidate publication
Nuvisphere/Platform-CI: Immutable QuickStack OCI deployment / Build once and deploy exact digest (push) Successful in 23s

This commit is contained in:
2026-08-24 09:40:35 +02:00
parent b6a550e9a3
commit 8f8110fb67
4 changed files with 89 additions and 6 deletions
+42 -5
View File
@@ -577,11 +577,46 @@ function buildArtifact({ artifact, registry, sha, workspace, dockerEnv, validati
return { artifact, taggedImage, digest, exactImage: `${registry}/${artifact.image}@${digest}` };
}
function pullCandidateArtifact({ artifact, registry, sourceSha, workspace, dockerEnv }) {
const PROMOTION_PR_CANDIDATE_PULL_ATTEMPTS = 40;
const PROMOTION_PR_CANDIDATE_PULL_RETRY_MS = 10_000;
export async function pullCandidateArtifact({
artifact,
registry,
sourceSha,
workspace,
dockerEnv,
pullAttempts = 1,
pullRetryMs = 0,
runCommand = run,
sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
}) {
const taggedImage = `${registry}/${artifact.image}:sha-${sourceSha}`;
run("docker", ["pull", taggedImage], { cwd: workspace, env: dockerEnv });
if (!Number.isInteger(pullAttempts) || pullAttempts < 1) {
throw new Error("Candidate pull attempts must be a positive integer.");
}
if (!Number.isFinite(pullRetryMs) || pullRetryMs < 0) {
throw new Error("Candidate pull retry delay must be a non-negative number.");
}
for (let attempt = 1; attempt <= pullAttempts; attempt += 1) {
try {
runCommand("docker", ["pull", taggedImage], { cwd: workspace, env: dockerEnv });
break;
} catch (error) {
if (attempt === pullAttempts) {
throw new Error(
`Candidate ${taggedImage} did not become available after ${pullAttempts} attempt(s).`,
{ cause: error },
);
}
console.log(
`Candidate ${taggedImage} is not available yet (${attempt}/${pullAttempts}); retrying in ${pullRetryMs}ms.`,
);
await sleep(pullRetryMs);
}
}
verifyRequiredContainerFiles(taggedImage, artifact.requiredFiles, { cwd: workspace, env: dockerEnv });
const repoDigests = run(
const repoDigests = runCommand(
"docker",
["image", "inspect", "--format", "{{range .RepoDigests}}{{println .}}{{end}}", taggedImage],
{ cwd: workspace, env: dockerEnv, capture: true },
@@ -701,12 +736,14 @@ async function executeVersion2({ config, pipeline, eventName, sha, workspace })
const release = loadRelease(workspace, pipeline.release);
const artifacts = new Map();
for (const artifact of pipeline.artifacts) {
artifacts.set(artifact.name, pullCandidateArtifact({
artifacts.set(artifact.name, await pullCandidateArtifact({
artifact,
registry,
sourceSha: sha,
workspace,
dockerEnv,
pullAttempts: PROMOTION_PR_CANDIDATE_PULL_ATTEMPTS,
pullRetryMs: PROMOTION_PR_CANDIDATE_PULL_RETRY_MS,
}));
}
console.log(`Validated ${release.tag} against ${artifacts.size} tested candidate artifact(s) from ${sourceBranch} at ${sha}; no rebuild or deployment performed.`);
@@ -718,7 +755,7 @@ async function executeVersion2({ config, pipeline, eventName, sha, workspace })
const release = loadRelease(workspace, pipeline.release);
const artifacts = new Map();
for (const artifact of pipeline.artifacts) {
artifacts.set(artifact.name, pullCandidateArtifact({
artifacts.set(artifact.name, await pullCandidateArtifact({
artifact,
registry,
sourceSha,