Merge pull request #1961 from pikasTech/fix/hwlab-pac-place-scripts-argmax
Pipelines as Code CI / hwlab-web-probe-sentinel-nc01- Success
Pipelines as Code CI / platform-infra-gitea-nc01- Success
Pipelines as Code CI / unidesk-host- Success

fix(pac): 收敛 HWLAB runtime GitOps 脚本体积
This commit is contained in:
Lyon
2026-07-14 04:16:27 +08:00
committed by GitHub
6 changed files with 102 additions and 13 deletions
+1
View File
@@ -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
@@ -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");
@@ -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"));
}
@@ -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"));
}
+2
View File
@@ -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),
};
}
@@ -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<string, any>;
const scripts = pipeline.spec.tasks.flatMap((task: Record<string, any>) => task.taskSpec.steps.map((step: Record<string, any>) => 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<string, any>;
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<string, any>;
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");