perf(ci): reuse verified pull request candidates
Platform CI tests / QuickStack deploy action tests (pull_request) Successful in 25s
Nuvisphere/Platform-CI: Immutable QuickStack OCI deployment / Build once and deploy exact digest (pull_request) Successful in 28s
Platform CI tests / QuickStack deploy action tests (push) Successful in 21s
Nuvisphere/Platform-CI: Immutable QuickStack OCI deployment / Build once and deploy exact digest (push) Successful in 28s

This commit was merged in pull request #6.
This commit is contained in:
2026-08-26 12:22:27 +02:00
parent f39d803d7a
commit 0cc4f072e7
3 changed files with 79 additions and 6 deletions
+53 -5
View File
@@ -621,6 +621,13 @@ export function isSameRepositoryPullRequest({ eventName, environment = process.e
return Boolean(baseRepository && headRepository && baseRepository === headRepository);
}
export function isValidationOnlyCandidateBuild({
eventName,
sameRepositoryPullRequest,
} = {}) {
return eventName === "pull_request" && !sameRepositoryPullRequest;
}
export function classifyVersion2Execution(pipeline, eventName) {
if (eventName === "pull_request") {
return pipeline.strategy === "promote" ? "validate-candidate" : "build-validation";
@@ -704,8 +711,26 @@ function buildArtifact({
dockerEnv,
validationOnly,
useRegistryCache = !validationOnly,
reuseExistingCandidate = false,
}) {
const taggedImage = `${registry}/${artifact.image}:sha-${sha}`;
if (reuseExistingCandidate) {
try {
const existing = pullCandidateArtifact({
artifact,
registry,
sourceSha: sha,
workspace,
dockerEnv,
});
console.log(`Reusing verified candidate ${existing.exactImage}; no Docker build required.`);
return existing;
} catch (error) {
console.log(
`No reusable candidate found for ${taggedImage}; building it now (${error instanceof Error ? error.message : String(error)}).`,
);
}
}
const cacheImage = `${registry}/${artifact.image}:buildcache`;
const builder = ensureBuildxBuilder(dockerEnv);
const buildArgs = [
@@ -744,6 +769,14 @@ function pullCandidateArtifact({ artifact, registry, sourceSha, workspace, docke
const taggedImage = `${registry}/${artifact.image}:sha-${sourceSha}`;
run("docker", ["pull", taggedImage], { cwd: workspace, env: dockerEnv });
verifyRequiredContainerFiles(taggedImage, artifact.requiredFiles, { cwd: workspace, env: dockerEnv });
const revision = run(
"docker",
["image", "inspect", "--format", '{{ index .Config.Labels "org.opencontainers.image.revision" }}', taggedImage],
{ cwd: workspace, env: dockerEnv, capture: true },
).trim();
if (revision !== sourceSha) {
throw new Error(`Candidate ${taggedImage} has revision ${revision || "<missing>"}, expected ${sourceSha}.`);
}
const repoDigests = run(
"docker",
["image", "inspect", "--format", "{{range .RepoDigests}}{{println .}}{{end}}", taggedImage],
@@ -857,7 +890,14 @@ async function executeVersion2({
artifactIndex = 0,
}) {
const commitMessage = run("git", ["log", "-1", "--pretty=%B"], { cwd: workspace, capture: true });
const validationOnly = eventName === "pull_request";
const sameRepositoryPullRequest = isSameRepositoryPullRequest({
eventName,
environment: process.env,
});
const validationOnly = isValidationOnlyCandidateBuild({
eventName,
sameRepositoryPullRequest,
});
const skipMarker = pipeline.strategy === "promote"
? String(pipeline.release?.skipMarker ?? "[skip prod-release]")
: "[skip quickstack-deploy]";
@@ -894,12 +934,15 @@ async function executeVersion2({
sha,
workspace,
dockerEnv,
validationOnly: execution === "build-validation",
validationOnly,
useRegistryCache,
reuseExistingCandidate: !validationOnly,
});
appendSummary(
execution === "build-validation"
validationOnly
? `Validated OCI artifact \`${artifact.name}\` for \`${pipeline.branch}\`.`
: execution === "build-validation"
? `Built reusable pull-request candidate \`${artifact.name}\`: \`${built.exactImage}\`.`
: `Built OCI artifact \`${artifact.name}\`: \`${built.exactImage}\`.`,
);
return;
@@ -938,12 +981,17 @@ async function executeVersion2({
sha,
workspace,
dockerEnv,
validationOnly: execution === "build-validation",
validationOnly,
useRegistryCache,
reuseExistingCandidate: !validationOnly,
}));
}
if (execution === "build-validation") {
appendSummary(`Validated ${artifacts.size} immutable OCI artifact(s) for ${pipeline.branch}.`);
appendSummary(
validationOnly
? `Validated ${artifacts.size} immutable OCI artifact(s) for ${pipeline.branch}.`
: `Published ${artifacts.size} reusable pull-request candidate artifact(s) for ${pipeline.branch}.`,
);
return;
}
const client = createQuickStackClient({
@@ -7,6 +7,7 @@ import {
deployExactImage,
expandTokens,
mergeEnvironment,
isValidationOnlyCandidateBuild,
normalizeVolumeMountPaths,
parseEnvironment,
resolveActionMode,
@@ -105,6 +106,30 @@ test("production pull requests validate tested candidates without rebuilding", (
);
});
test("same-repository pull requests publish reusable candidates while forks stay validation-only", () => {
assert.equal(
isValidationOnlyCandidateBuild({
eventName: "pull_request",
sameRepositoryPullRequest: true,
}),
false,
);
assert.equal(
isValidationOnlyCandidateBuild({
eventName: "pull_request",
sameRepositoryPullRequest: false,
}),
true,
);
assert.equal(
isValidationOnlyCandidateBuild({
eventName: "push",
sameRepositoryPullRequest: false,
}),
false,
);
});
test("validates split action modes and artifact build slots", () => {
assert.equal(resolveActionMode(undefined), "all");
assert.equal(resolveActionMode("BUILD"), "build");