From 0e431e275ef4378deb72fc42e49ba23b63747940 Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 13 Jul 2026 22:09:36 +0200 Subject: [PATCH] =?UTF-8?q?fix(pac):=20=E6=94=B6=E6=95=9B=20HWLAB=20runtim?= =?UTF-8?q?e=20GitOps=20=E8=84=9A=E6=9C=AC=E4=BD=93=E7=A7=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/hwlab-node-lanes.yaml | 1 + .../hwlab/runtime-gitops-pipeline-guard.mjs | 21 +++-- .../hwlab/runtime-gitops-postprocess.mjs | 4 +- .../native/hwlab/runtime-gitops-verify.mjs | 4 +- scripts/src/hwlab-node-lanes.ts | 2 + ...-pipelines-as-code-source-artifact.test.ts | 83 +++++++++++++++++++ 6 files changed, 102 insertions(+), 13 deletions(-) diff --git a/config/hwlab-node-lanes.yaml b/config/hwlab-node-lanes.yaml index 0d5603d9..f5dc6d10 100644 --- a/config/hwlab-node-lanes.yaml +++ b/config/hwlab-node-lanes.yaml @@ -123,6 +123,7 @@ lanes: configRef: config/hwlab-node-lanes.yaml#lanes.v03.targets.NC01 renderer: hwlab-runtime-lane mode: remote-pipeline-annotation + maxInlineScriptBytes: 32768 git: url: git@github.com:pikasTech/HWLAB.git readUrl: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git diff --git a/scripts/native/hwlab/runtime-gitops-pipeline-guard.mjs b/scripts/native/hwlab/runtime-gitops-pipeline-guard.mjs index 77e0372e..9260d699 100644 --- a/scripts/native/hwlab/runtime-gitops-pipeline-guard.mjs +++ b/scripts/native/hwlab/runtime-gitops-pipeline-guard.mjs @@ -87,14 +87,12 @@ function hasNoBuildNoRolloutEarlyExit(script) { } function injectRuntimeGitopsCommands(script) { - if (hasPostprocess(script) && hasVerify(script)) return script; - const overlayEnv = `UNIDESK_RUNTIME_GITOPS_OVERLAY_B64=${shellSingle(Buffer.from(JSON.stringify({ - runtimePath: overlay.runtimePath, - observability: overlay.observability, - }), "utf8").toString("base64"))}`; + const overlayEnv = "UNIDESK_RUNTIME_GITOPS_OVERLAY_FILE=/etc/unidesk-cicd-runtime-gitops/runtime-gitops-overlay.json"; + const migratedScript = String(script).replace(/UNIDESK_RUNTIME_GITOPS_OVERLAY_B64='[^'\n]*'/gu, overlayEnv); + if (hasPostprocess(migratedScript) && hasVerify(migratedScript)) return migratedScript; 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( + return migratedScript.replace( /(node scripts\/run-bun\.mjs scripts\/gitops-render\.mjs[^\n]*--use-deploy-images[^\n]*)/g, (match) => { if (match.includes("--check")) return hasVerify(script) ? match : verify; @@ -127,7 +125,12 @@ function ensureRuntimeGitopsScriptsMount(taskSpec, step) { function writeScriptsConfigMap(targetPath, namespace) { if (scriptsDir === null) throw new Error("--scripts-dir is required with --scripts-configmap"); - const data = {}; + const data = { + "runtime-gitops-overlay.json": `${JSON.stringify({ + runtimePath: requiredOverlayString("runtimePath"), + observability: objectOr(overlay.observability), + })}\n`, + }; for (const name of ["runtime-gitops-observability.mjs", "runtime-gitops-postprocess.mjs", "runtime-gitops-verify.mjs"]) { data[name] = readFileSync(path.join(scriptsDir, name), "utf8"); } @@ -193,10 +196,6 @@ function objectOr(value) { return value && typeof value === "object" && !Array.isArray(value) ? value : {}; } -function shellSingle(value) { - return `'${String(value).replaceAll("'", "'\\''")}'`; -} - function requireYaml() { try { return requireFromScript("yaml"); diff --git a/scripts/native/hwlab/runtime-gitops-postprocess.mjs b/scripts/native/hwlab/runtime-gitops-postprocess.mjs index 5e183572..fda803ee 100644 --- a/scripts/native/hwlab/runtime-gitops-postprocess.mjs +++ b/scripts/native/hwlab/runtime-gitops-postprocess.mjs @@ -257,8 +257,10 @@ function listYamlFiles(root) { } function readOverlay() { + const file = process.env.UNIDESK_RUNTIME_GITOPS_OVERLAY_FILE; + if (file) return JSON.parse(readFileSync(file, "utf8")); const encoded = process.env.UNIDESK_RUNTIME_GITOPS_OVERLAY_B64; - if (!encoded) throw new Error("UNIDESK_RUNTIME_GITOPS_OVERLAY_B64 is required"); + if (!encoded) throw new Error("UNIDESK_RUNTIME_GITOPS_OVERLAY_FILE or UNIDESK_RUNTIME_GITOPS_OVERLAY_B64 is required"); return JSON.parse(Buffer.from(encoded, "base64").toString("utf8")); } diff --git a/scripts/native/hwlab/runtime-gitops-verify.mjs b/scripts/native/hwlab/runtime-gitops-verify.mjs index c79b4d91..97235a90 100644 --- a/scripts/native/hwlab/runtime-gitops-verify.mjs +++ b/scripts/native/hwlab/runtime-gitops-verify.mjs @@ -102,8 +102,10 @@ function listYamlFiles(root) { } function readOverlay() { + const file = process.env.UNIDESK_RUNTIME_GITOPS_OVERLAY_FILE; + if (file) return JSON.parse(readFileSync(file, "utf8")); const encoded = process.env.UNIDESK_RUNTIME_GITOPS_OVERLAY_B64; - if (!encoded) throw new Error("UNIDESK_RUNTIME_GITOPS_OVERLAY_B64 is required"); + if (!encoded) throw new Error("UNIDESK_RUNTIME_GITOPS_OVERLAY_FILE or UNIDESK_RUNTIME_GITOPS_OVERLAY_B64 is required"); return JSON.parse(Buffer.from(encoded, "base64").toString("utf8")); } diff --git a/scripts/src/hwlab-node-lanes.ts b/scripts/src/hwlab-node-lanes.ts index 2aaa75c0..5044acb2 100644 --- a/scripts/src/hwlab-node-lanes.ts +++ b/scripts/src/hwlab-node-lanes.ts @@ -651,6 +651,7 @@ export interface HwlabRuntimePipelineProvenanceSpec { readonly configRef: string; readonly renderer: "hwlab-runtime-lane"; readonly mode: "remote-pipeline-annotation"; + readonly maxInlineScriptBytes: number; } export interface HwlabRuntimeLaneSpec { @@ -1083,6 +1084,7 @@ export function parseHwlabRuntimePipelineProvenance( configRef, renderer: enumStringField(raw, "renderer", path, ["hwlab-runtime-lane"]), mode: enumStringField(raw, "mode", path, ["remote-pipeline-annotation"]), + maxInlineScriptBytes: boundedIntegerField(raw, "maxInlineScriptBytes", path, 1024, 131072), }; } diff --git a/scripts/src/platform-infra-pipelines-as-code-source-artifact.test.ts b/scripts/src/platform-infra-pipelines-as-code-source-artifact.test.ts index 42ac07e9..d8e40551 100644 --- a/scripts/src/platform-infra-pipelines-as-code-source-artifact.test.ts +++ b/scripts/src/platform-infra-pipelines-as-code-source-artifact.test.ts @@ -238,6 +238,80 @@ describe("HWLAB YAML-owned Pipeline provenance", () => { }, ownerPath, configRef)).toThrow("mode must be one of remote-pipeline-annotation"); }); + test("runtime GitOps guard keeps Tekton scripts below the owning YAML budget", () => { + const temporary = mkdtempSync(resolve(tmpdir(), "unidesk-hwlab-runtime-gitops-guard-")); + try { + const spec = hwlabRuntimeLaneSpecForNode("v03", "NC01"); + const budget = spec.pipelineProvenance?.maxInlineScriptBytes; + expect(budget).toBeNumber(); + const overlay = nodeRuntimeRenderOverlay(spec); + const pipelinePath = resolve(temporary, "pipeline.yaml"); + const configMapPath = resolve(temporary, "runtime-gitops-scripts.yaml"); + const scriptsDir = rootPath("scripts", "native", "hwlab"); + writeFileSync(pipelinePath, Bun.YAML.stringify({ + apiVersion: "tekton.dev/v1", + kind: "Pipeline", + metadata: { name: spec.pipeline, namespace: "hwlab-ci" }, + spec: { tasks: [{ + name: "gitops-promote", + taskSpec: { steps: [{ + name: "render", + script: "node scripts/run-bun.mjs scripts/gitops-render.mjs --use-deploy-images\nnode scripts/run-bun.mjs scripts/gitops-render.mjs --use-deploy-images --check\n", + }] }, + }] }, + })); + const result = spawnSync("node", [ + rootPath("scripts", "native", "hwlab", "runtime-gitops-pipeline-guard.mjs"), + "--pipeline", pipelinePath, + "--scripts-configmap", configMapPath, + "--namespace", "hwlab-ci", + "--scripts-dir", scriptsDir, + ], { + cwd: rootPath(), + env: { ...process.env, UNIDESK_RUNTIME_GITOPS_OVERLAY_B64: Buffer.from(JSON.stringify(overlay), "utf8").toString("base64") }, + encoding: "utf8", + timeout: 30_000, + }); + expect(result.status).toBe(0); + const pipeline = Bun.YAML.parse(readFileSync(pipelinePath, "utf8")) as Record; + const scripts = pipeline.spec.tasks.flatMap((task: Record) => task.taskSpec.steps.map((step: Record) => String(step.script ?? ""))); + expect(Math.max(...scripts.map((script: string) => Buffer.byteLength(script)))).toBeLessThanOrEqual(budget as number); + expect(scripts.join("\n")).toContain("UNIDESK_RUNTIME_GITOPS_OVERLAY_FILE=/etc/unidesk-cicd-runtime-gitops/runtime-gitops-overlay.json"); + expect(scripts.join("\n")).not.toContain(Buffer.from(JSON.stringify(overlay.observability), "utf8").toString("base64")); + const configMap = Bun.YAML.parse(readFileSync(configMapPath, "utf8")) as Record; + expect(JSON.parse(configMap.data["runtime-gitops-overlay.json"])).toEqual({ runtimePath: overlay.runtimePath, observability: overlay.observability }); + expect(Object.keys(configMap.data).sort()).toEqual([ + "runtime-gitops-observability.mjs", + "runtime-gitops-overlay.json", + "runtime-gitops-postprocess.mjs", + "runtime-gitops-verify.mjs", + ]); + const legacyPayload = Buffer.from(JSON.stringify({ runtimePath: overlay.runtimePath, observability: overlay.observability }), "utf8").toString("base64"); + const legacyPipeline = Bun.YAML.parse(readFileSync(pipelinePath, "utf8")) as Record; + const legacyStep = legacyPipeline.spec.tasks[0].taskSpec.steps[0]; + legacyStep.script = String(legacyStep.script).replaceAll( + "UNIDESK_RUNTIME_GITOPS_OVERLAY_FILE=/etc/unidesk-cicd-runtime-gitops/runtime-gitops-overlay.json", + `UNIDESK_RUNTIME_GITOPS_OVERLAY_B64='${legacyPayload}'`, + ); + writeFileSync(pipelinePath, Bun.YAML.stringify(legacyPipeline)); + const migrated = spawnSync("node", [ + rootPath("scripts", "native", "hwlab", "runtime-gitops-pipeline-guard.mjs"), + "--pipeline", pipelinePath, + ], { + cwd: rootPath(), + env: { ...process.env, UNIDESK_RUNTIME_GITOPS_OVERLAY_B64: Buffer.from(JSON.stringify(overlay), "utf8").toString("base64") }, + encoding: "utf8", + timeout: 30_000, + }); + expect(migrated.status).toBe(0); + const migratedText = readFileSync(pipelinePath, "utf8"); + expect(migratedText).toContain("UNIDESK_RUNTIME_GITOPS_OVERLAY_FILE=/etc/unidesk-cicd-runtime-gitops/runtime-gitops-overlay.json"); + expect(migratedText).not.toContain(legacyPayload); + } finally { + rmSync(temporary, { recursive: true, force: true }); + } + }); + test("normal control-plane postprocess writes exactly the shared four provenance fields", () => { const temporary = mkdtempSync(resolve(tmpdir(), "unidesk-hwlab-pipeline-provenance-")); try { @@ -491,7 +565,16 @@ describe("runtime source-commit selection", () => { configRef: "config/hwlab-node-lanes.yaml#lanes.v03.targets.NC01", renderer: "hwlab-runtime-lane", mode: "remote-pipeline-annotation", + maxInlineScriptBytes: 32768, }); + expect([ + spec.codeAgentRuntime?.kafkaEventBridge.features.directPublish, + spec.codeAgentRuntime?.kafkaEventBridge.features.liveKafkaSse, + spec.codeAgentRuntime?.kafkaEventBridge.features.kafkaRefreshReplay, + spec.codeAgentRuntime?.kafkaEventBridge.features.transactionalProjector, + spec.codeAgentRuntime?.kafkaEventBridge.features.projectionOutboxRelay, + spec.codeAgentRuntime?.kafkaEventBridge.features.projectionRealtime, + ]).toEqual([false, false, false, true, true, true]); expect(remoteAnnotations).toEqual(pipelineProvenanceAnnotations(remoteProvenance)); expect(Object.keys(remoteAnnotations).sort()).toEqual(Object.values(pipelineProvenanceAnnotationKeys).sort()); expect(readFileSync(rootPath("scripts", "src", "hwlab-node", "render.ts"), "utf8")).not.toContain("overlay.sourceArtifactProvenance");