fix(cicd): inject runtime gitops guard in native refresh

This commit is contained in:
Codex
2026-07-04 11:05:21 +00:00
parent bdd7fcfc7b
commit 7cc6f24293
5 changed files with 290 additions and 2 deletions
@@ -15,6 +15,7 @@ const gitReadUrl = requiredEnv("GIT_READ_URL");
const fieldManager = requiredEnv("FIELD_MANAGER");
const tektonNamespace = requiredEnv("TEKTON_NAMESPACE");
const kubeRequestTimeoutSeconds = requiredEnvPositiveNumber("KUBE_REQUEST_TIMEOUT_SECONDS");
const runtimeGitopsConfigMapName = requiredEnv("RUNTIME_GITOPS_CONFIGMAP_NAME");
const overlay = JSON.parse(Buffer.from(requiredEnv("HWLAB_RENDER_OVERLAY_B64"), "base64").toString("utf8"));
const workDir = mkdtempSync(path.join(tmpdir(), `hwlab-control-plane-${sourceCommit.slice(0, 12)}-`));
const repoDir = path.join(workDir, "repo");
@@ -143,7 +144,8 @@ async function applyPipeline() {
if (typeof renderedPipelineName !== "string" || renderedPipelineName.length === 0) {
throw new Error(`rendered Pipeline metadata.name missing: ${pipelinePath}`);
}
const render = summarizeRenderedPipeline(pipeline, renderedPipelineName);
const runtimeGitopsGuard = injectRuntimeGitopsGuard(pipeline);
const render = summarizeRenderedPipeline(pipeline, renderedPipelineName, runtimeGitopsGuard);
const pipelineName = requiredOverlayString("pipelineName");
pipeline.metadata = pipeline.metadata && typeof pipeline.metadata === "object" ? pipeline.metadata : {};
pipeline.metadata.name = pipelineName;
@@ -286,13 +288,82 @@ function requiredNonNegativeNumber(name) {
return Math.floor(value);
}
function summarizeRenderedPipeline(pipeline, pipelineName) {
function injectRuntimeGitopsGuard(pipeline) {
const tasks = Array.isArray(pipeline?.spec?.tasks) ? pipeline.spec.tasks : [];
const task = tasks.find((item) => recordOrNull(item)?.name === "gitops-promote");
if (!task) throw new Error("runtime GitOps guard injection failed: gitops-promote task missing");
const taskSpec = recordOrNull(task.taskSpec);
if (taskSpec === null) throw new Error("runtime GitOps guard injection failed: gitops-promote taskSpec missing");
const steps = Array.isArray(taskSpec.steps) ? taskSpec.steps : [];
const step = steps.find((item) => typeof recordOrNull(item)?.script === "string" && item.script.includes("scripts/gitops-render.mjs"));
if (!step) throw new Error("runtime GitOps guard injection failed: gitops-promote render step missing");
const originalScript = String(step.script);
let script = patchNoBuildNoRolloutSkip(originalScript);
const noBuildSkipPatched = script !== originalScript;
const beforeInjection = script;
script = injectRuntimeGitopsCommands(script);
const postprocessInjected = !beforeInjection.includes("runtime-gitops-postprocess.mjs") && script.includes("runtime-gitops-postprocess.mjs");
const verifyInjected = !beforeInjection.includes("runtime-gitops-verify.mjs") && script.includes("runtime-gitops-verify.mjs");
if (!script.includes("runtime-gitops-postprocess.mjs") || !script.includes("runtime-gitops-verify.mjs")) {
throw new Error("runtime GitOps guard injection failed: postprocess/verify commands missing after patch");
}
step.script = script;
ensureRuntimeGitopsScriptsMount(taskSpec, step);
return {
present: true,
configMapName: runtimeGitopsConfigMapName,
stepName: stringOrNull(step.name),
noBuildSkipPatched,
postprocessInjected,
verifyInjected,
};
}
function patchNoBuildNoRolloutSkip(script) {
return String(script).replace(
/(^|\n)([ \t]*)echo '\{"event":"gitops-promote","status":"skipped","reason":"no-build-no-rollout-plan"\}'\n[ \t]*exit 0(?=\n|$)/u,
(_match, prefix, indent) => `${prefix}${indent}echo '{"event":"gitops-promote","status":"continuing","reason":"no-build-no-rollout-plan-gitops-verify"}'`,
);
}
function injectRuntimeGitopsCommands(script) {
if (script.includes("runtime-gitops-postprocess.mjs") && script.includes("runtime-gitops-verify.mjs")) return script;
const overlayEnv = `UNIDESK_RUNTIME_GITOPS_OVERLAY_B64=${shellSingle(Buffer.from(JSON.stringify(overlay), "utf8").toString("base64"))}`;
const postprocess = `${overlayEnv} node /etc/unidesk-cicd-runtime-gitops/runtime-gitops-postprocess.mjs`;
const verify = `${overlayEnv} node /etc/unidesk-cicd-runtime-gitops/runtime-gitops-verify.mjs`;
return String(script).replace(
/(node scripts\/run-bun\.mjs scripts\/gitops-render\.mjs[^\n]*--use-deploy-images[^\n]*)/g,
(match) => {
if (match.includes("--check")) return `${match}\n${verify}`;
return `${match}\n${postprocess}`;
},
);
}
function ensureRuntimeGitopsScriptsMount(taskSpec, step) {
const volumeName = "unidesk-runtime-gitops-scripts";
taskSpec.volumes = Array.isArray(taskSpec.volumes) ? taskSpec.volumes : [];
if (!taskSpec.volumes.some((item) => recordOrNull(item)?.name === volumeName)) {
taskSpec.volumes.push({ name: volumeName, configMap: { name: runtimeGitopsConfigMapName, defaultMode: 0o755 } });
}
step.volumeMounts = Array.isArray(step.volumeMounts) ? step.volumeMounts : [];
if (!step.volumeMounts.some((item) => recordOrNull(item)?.name === volumeName)) {
step.volumeMounts.push({ name: volumeName, mountPath: "/etc/unidesk-cicd-runtime-gitops", readOnly: true });
}
}
function shellSingle(value) {
return `'${String(value).replaceAll("'", "'\\''")}'`;
}
function summarizeRenderedPipeline(pipeline, pipelineName, runtimeGitopsGuard) {
const tasks = Array.isArray(pipeline?.spec?.tasks) ? pipeline.spec.tasks : [];
const runtimeReady = tasks.find((task) => recordOrNull(task)?.name === "runtime-ready");
return {
pipelineName,
taskCount: tasks.length,
runtimeReadyTask: summarizeRuntimeReadyTask(runtimeReady),
runtimeGitopsGuard,
};
}