ci: reuse candidates for production pull requests
Platform CI tests / QuickStack deploy action tests (push) Successful in 27s
Nuvisphere/Platform-CI: Immutable QuickStack OCI deployment / Build once and deploy exact digest (push) Successful in 28s

This commit is contained in:
2026-08-20 16:30:23 +02:00
parent a4247d2697
commit f4ffb28018
3 changed files with 91 additions and 6 deletions
+53 -4
View File
@@ -473,6 +473,36 @@ export function resolveDeploymentBranch({ eventName, environment = process.env,
);
}
export function resolvePullRequestHeadBranch({ environment = process.env, eventPayload } = {}) {
const direct = String(environment.GITHUB_HEAD_REF || environment.GITEA_HEAD_REF || "").trim();
if (direct) return direct;
let payload = eventPayload;
if (payload === undefined) {
const eventPath = String(environment.GITHUB_EVENT_PATH || environment.GITEA_EVENT_PATH || "").trim();
if (eventPath) {
payload = JSON.parse(fs.readFileSync(eventPath, "utf8"));
}
}
return requiredString(payload?.pull_request?.head?.ref, "Pull request head branch");
}
export function classifyVersion2Execution(pipeline, eventName) {
if (eventName === "pull_request") {
return pipeline.strategy === "promote" ? "validate-candidate" : "build-validation";
}
return pipeline.strategy === "candidate" ? "build-deploy" : "promote";
}
export function validatePromotionPullRequestSource(pipeline, headBranch) {
const sourceBranch = requiredString(pipeline.source?.branch, "Promotion source branch");
const actualHeadBranch = requiredString(headBranch, "Pull request head branch");
if (actualHeadBranch !== sourceBranch) {
throw new Error(`Production promotion pull requests must originate from ${sourceBranch}, not ${actualHeadBranch}.`);
}
return sourceBranch;
}
function appendSummary(text) {
const summary = process.env.GITHUB_STEP_SUMMARY ?? process.env.GITEA_STEP_SUMMARY;
if (summary) fs.appendFileSync(summary, `${text}\n`);
@@ -609,9 +639,10 @@ async function executeVersion2({ config, pipeline, eventName, sha, workspace })
const registry = requiredString(config.registry ?? "gitea.nuvisphere.de", "OCI registry").replace(/\/$/, "");
const dockerConfig = fs.mkdtempSync(path.join(os.tmpdir(), "quickstack-docker-"));
const dockerEnv = { ...process.env, DOCKER_CONFIG: dockerConfig };
const execution = classifyVersion2Execution(pipeline, eventName);
try {
if (!validationOnly) dockerLogin(registry, dockerEnv);
if (validationOnly || pipeline.strategy === "candidate") {
if (execution !== "build-validation") dockerLogin(registry, dockerEnv);
if (execution === "build-validation" || execution === "build-deploy") {
const artifacts = new Map();
for (const artifact of pipeline.artifacts) {
artifacts.set(artifact.name, buildArtifact({
@@ -620,10 +651,10 @@ async function executeVersion2({ config, pipeline, eventName, sha, workspace })
sha,
workspace,
dockerEnv,
validationOnly,
validationOnly: execution === "build-validation",
}));
}
if (validationOnly) {
if (execution === "build-validation") {
appendSummary(`Validated ${artifacts.size} immutable OCI artifact(s) for ${pipeline.branch}.`);
return;
}
@@ -635,6 +666,24 @@ async function executeVersion2({ config, pipeline, eventName, sha, workspace })
return;
}
if (execution === "validate-candidate") {
const sourceBranch = validatePromotionPullRequestSource(pipeline, resolvePullRequestHeadBranch());
const release = loadRelease(workspace, pipeline.release);
const artifacts = new Map();
for (const artifact of pipeline.artifacts) {
artifacts.set(artifact.name, pullCandidateArtifact({
artifact,
registry,
sourceSha: sha,
workspace,
dockerEnv,
}));
}
console.log(`Validated ${release.tag} against ${artifacts.size} tested candidate artifact(s) from ${sourceBranch} at ${sha}; no rebuild or deployment performed.`);
appendSummary(`Validated release ${release.tag} against tested candidate \`${sha}\` from \`${sourceBranch}\` without rebuilding.`);
return;
}
const sourceSha = resolvePromotionSource(workspace, pipeline.source);
const release = loadRelease(workspace, pipeline.release);
const artifacts = new Map();