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
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:
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user