From 9ce41f9e7e2ac813a0c14b0d3ccd2da28d6111a1 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 12 Jun 2026 22:05:05 +0000 Subject: [PATCH] fix: rewrite D601 runtime GitOps images --- config/hwlab-node-lanes.yaml | 3 ++ scripts/src/hwlab-node-lanes.ts | 21 +++++++++++ scripts/src/hwlab-node.ts | 67 +++++++++++++++++++++++++++++++-- 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/config/hwlab-node-lanes.yaml b/config/hwlab-node-lanes.yaml index 7ddb86e9..920bbef1 100644 --- a/config/hwlab-node-lanes.yaml +++ b/config/hwlab-node-lanes.yaml @@ -129,6 +129,9 @@ lanes: XDG_CONFIG_HOME: /tekton/home/.config observability: prometheusOperator: false + runtimeImageRewrites: + - source: fatedier/frpc:v0.68.1 + target: 127.0.0.1:5000/hwlab/frpc:v0.68.1 public: webUrl: https://v03.hwpod.com apiUrl: https://v03.hwpod.com diff --git a/scripts/src/hwlab-node-lanes.ts b/scripts/src/hwlab-node-lanes.ts index ef2f9584..dc9080fd 100644 --- a/scripts/src/hwlab-node-lanes.ts +++ b/scripts/src/hwlab-node-lanes.ts @@ -85,6 +85,11 @@ export interface HwlabRuntimeObservabilitySpec { readonly prometheusOperator: boolean; } +export interface HwlabRuntimeImageRewriteSpec { + readonly source: string; + readonly target: string; +} + export interface HwlabRuntimeLaneSpec { readonly lane: HwlabRuntimeLane; readonly nodeId: string; @@ -123,6 +128,7 @@ export interface HwlabRuntimeLaneSpec { readonly buildkit?: HwlabRuntimeBuildkitSpec; readonly externalPostgres?: HwlabRuntimeExternalPostgresSpec; readonly observability: HwlabRuntimeObservabilitySpec; + readonly runtimeImageRewrites: readonly HwlabRuntimeImageRewriteSpec[]; readonly networkProfileId: string; readonly downloadProfileId: string; readonly networkProfile: HwlabNetworkProfileSpec; @@ -161,6 +167,7 @@ interface HwlabLaneConfig { readonly buildkit?: HwlabRuntimeBuildkitSpec; readonly externalPostgres?: HwlabRuntimeExternalPostgresSpec; readonly observability: HwlabRuntimeObservabilitySpec; + readonly runtimeImageRewrites: readonly HwlabRuntimeImageRewriteSpec[]; } interface HwlabNodeLaneConfig { @@ -359,6 +366,7 @@ function laneConfig(id: HwlabRuntimeLane, raw: Record): HwlabLa buildkit: buildkitConfig(raw.buildkit, `lanes.${id}.buildkit`), externalPostgres: externalPostgresConfig(raw.externalPostgres, `lanes.${id}.externalPostgres`), observability: observabilityConfig(raw.observability, `lanes.${id}.observability`), + runtimeImageRewrites: runtimeImageRewritesConfig(raw.runtimeImageRewrites, `lanes.${id}.runtimeImageRewrites`), }; } @@ -426,6 +434,18 @@ function observabilityConfig(value: unknown, path: string): HwlabRuntimeObservab }; } +function runtimeImageRewritesConfig(value: unknown, path: string): HwlabRuntimeImageRewriteSpec[] { + if (value === undefined) return []; + if (!Array.isArray(value)) throw new Error(`${path} must be an array`); + return value.map((item, index) => { + const raw = asRecord(item, `${path}[${index}]`); + return { + source: stringField(raw, "source", `${path}[${index}]`), + target: stringField(raw, "target", `${path}[${index}]`), + }; + }); +} + function readHwlabNodeLaneConfig(): HwlabNodeLaneConfig { const path = rootPath(HWLAB_NODE_LANE_CONFIG_PATH); const raw = readFileSync(path, "utf8"); @@ -508,6 +528,7 @@ function buildRuntimeLaneSpec(config: HwlabLaneConfig): HwlabRuntimeLaneSpec { ...(config.buildkit === undefined ? {} : { buildkit: config.buildkit }), ...(config.externalPostgres === undefined ? {} : { externalPostgres: config.externalPostgres }), observability: config.observability, + runtimeImageRewrites: config.runtimeImageRewrites, networkProfileId: networkProfile.id, downloadProfileId: downloadProfile.id, networkProfile, diff --git a/scripts/src/hwlab-node.ts b/scripts/src/hwlab-node.ts index 0da2c41f..2f53439c 100644 --- a/scripts/src/hwlab-node.ts +++ b/scripts/src/hwlab-node.ts @@ -264,6 +264,7 @@ function nodeRuntimeExpected(spec: HwlabRuntimeLaneSpec): Record item && item.source === image);", + " return match ? match.target : image;", + "}", + "function patchRuntimeImages(podSpec) {", + " if (!isObject(podSpec)) return false;", + " let changed = false;", + " for (const group of ['containers', 'initContainers']) {", + " for (const container of Array.isArray(podSpec[group]) ? podSpec[group] : []) {", + " if (!isObject(container) || typeof container.image !== 'string') continue;", + " const nextImage = rewriteRuntimeImage(container.image);", + " if (nextImage !== container.image) { container.image = nextImage; changed = true; }", + " }", + " }", + " return changed;", + "}", + "function rewriteEnvValue(value) {", + " if (typeof value !== 'string') return value;", + " return value.replaceAll('http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git', overlay.gitReadUrl);", + "}", + "function patchGitReadUrlEnv(podSpec) {", + " if (!isObject(podSpec)) return false;", + " let changed = false;", + " for (const group of ['containers', 'initContainers']) {", + " for (const container of Array.isArray(podSpec[group]) ? podSpec[group] : []) {", + " if (!isObject(container) || !Array.isArray(container.env)) continue;", + " for (const env of container.env) {", + " if (!isObject(env) || typeof env.value !== 'string') continue;", + " const nextValue = rewriteEnvValue(env.value);", + " if (nextValue !== env.value) { env.value = nextValue; changed = true; }", + " }", + " }", + " }", + " return changed;", + "}", "function patchRuntimeWorkloads() {", " let observabilityChanged = false;", " let startupProbeChanged = false;", + " let imageRewriteChanged = false;", + " let gitReadUrlChanged = false;", " for (const file of yamlFiles(runtimePath)) {", " if (path.basename(file) === 'kustomization.yaml') continue;", " const docs = readYamlDocuments(file);", @@ -1462,11 +1505,17 @@ function nodeRuntimePipelinePostprocessScript(): string[] { " const probeChanged = addEnvReuseStartupProbe(podSpecFor(item));", " changed = probeChanged || changed;", " startupProbeChanged = startupProbeChanged || probeChanged;", + " const imageChanged = patchRuntimeImages(podSpecFor(item));", + " changed = imageChanged || changed;", + " imageRewriteChanged = imageRewriteChanged || imageChanged;", + " const gitUrlChanged = patchGitReadUrlEnv(podSpecFor(item));", + " changed = gitUrlChanged || changed;", + " gitReadUrlChanged = gitReadUrlChanged || gitUrlChanged;", " }", " }", " if (changed) writeYamlDocuments(file, docs);", " }", - " return { observabilityChanged, startupProbeChanged };", + " return { observabilityChanged, startupProbeChanged, imageRewriteChanged, gitReadUrlChanged };", "}", "function patchKustomization() {", " const file = path.join(runtimePath, 'kustomization.yaml');", @@ -1517,7 +1566,7 @@ function nodeRuntimePipelinePostprocessScript(): string[] { "const kustomizationChanged = patchKustomization();", "const runtimeWorkloadsChanged = patchRuntimeWorkloads();", "const externalPostgresChanged = patchExternalPostgres();", - "console.error(JSON.stringify({ event: 'unidesk-runtime-gitops-postprocess', ok: true, runtimePath, sourcePath, pathRelocated: sourcePath !== runtimePath, observabilityPrometheusOperator: overlay.observability ? overlay.observability.prometheusOperator : null, kustomizationChanged, observabilityWorkloadsChanged: runtimeWorkloadsChanged.observabilityChanged, startupProbeChanged: runtimeWorkloadsChanged.startupProbeChanged, externalPostgresChanged }));", + "console.error(JSON.stringify({ event: 'unidesk-runtime-gitops-postprocess', ok: true, runtimePath, sourcePath, pathRelocated: sourcePath !== runtimePath, observabilityPrometheusOperator: overlay.observability ? overlay.observability.prometheusOperator : null, runtimeImageRewriteCount: (overlay.runtimeImageRewrites || []).length, kustomizationChanged, observabilityWorkloadsChanged: runtimeWorkloadsChanged.observabilityChanged, startupProbeChanged: runtimeWorkloadsChanged.startupProbeChanged, runtimeImageRewriteChanged: runtimeWorkloadsChanged.imageRewriteChanged, gitReadUrlChanged: runtimeWorkloadsChanged.gitReadUrlChanged, externalPostgresChanged }));", "NODE_UNIDESK_RUNTIME_GITOPS_POSTPROCESS`;", "}", "function runtimeGitopsVerifyScript() {", @@ -1526,6 +1575,8 @@ function nodeRuntimePipelinePostprocessScript(): string[] { " runtimeNamespace: overlay.runtimeNamespace,", " externalPostgres: overlay.externalPostgres,", " observability: overlay.observability,", + " runtimeImageRewrites: overlay.runtimeImageRewrites,", + " gitReadUrl: overlay.gitReadUrl,", " });", " return `node - <<'NODE_UNIDESK_RUNTIME_GITOPS_VERIFY'", "const fs = require('fs');", @@ -1569,6 +1620,9 @@ function nodeRuntimePipelinePostprocessScript(): string[] { "function workloadChecks() {", " const metricsRefs = [];", " const missingStartupProbes = [];", + " const publicRuntimeImages = [];", + " const staleGitReadUrls = [];", + " const rewriteSources = new Set((overlay.runtimeImageRewrites || []).map((item) => item && item.source).filter(Boolean));", " for (const file of yamlFiles(runtimePath)) {", " if (path.basename(file) === 'kustomization.yaml') continue;", " for (const doc of readYamlDocuments(file)) {", @@ -1579,12 +1633,14 @@ function nodeRuntimePipelinePostprocessScript(): string[] { " if (!isObject(container)) continue;", " if (container.name === 'hwlab-metrics' || (Array.isArray(container.volumeMounts) && container.volumeMounts.some((mount) => mount && mount.name === 'hwlab-metrics-sidecar'))) metricsRefs.push(workloadRef(item, file, container));", " if (isEnvReuseContainer(container) && (container.readinessProbe || container.livenessProbe) && !container.startupProbe) missingStartupProbes.push(workloadRef(item, file, container));", + " if (typeof container.image === 'string' && rewriteSources.has(container.image)) publicRuntimeImages.push({ ...workloadRef(item, file, container), image: container.image });", + " if (Array.isArray(container.env) && container.env.some((env) => env && typeof env.value === 'string' && env.value.includes('git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git') && env.value !== overlay.gitReadUrl)) staleGitReadUrls.push(workloadRef(item, file, container));", " }", " if (Array.isArray(podSpec.volumes) && podSpec.volumes.some((volume) => volume && volume.name === 'hwlab-metrics-sidecar')) metricsRefs.push(workloadRef(item, file, { name: 'volume/hwlab-metrics-sidecar' }));", " }", " }", " }", - " return { metricsRefs, missingStartupProbes };", + " return { metricsRefs, missingStartupProbes, publicRuntimeImages, staleGitReadUrls };", "}", "const checks = [];", "const workloadCheck = workloadChecks();", @@ -1598,6 +1654,10 @@ function nodeRuntimePipelinePostprocessScript(): string[] { "}", "if (workloadCheck.missingStartupProbes.length > 0) fail('env-reuse-startup-probe-missing', { refs: workloadCheck.missingStartupProbes.slice(0, 12), count: workloadCheck.missingStartupProbes.length });", "checks.push('env-reuse-startup-probes');", + "if (workloadCheck.publicRuntimeImages.length > 0) fail('runtime-image-rewrite-missing', { refs: workloadCheck.publicRuntimeImages.slice(0, 12), count: workloadCheck.publicRuntimeImages.length });", + "if ((overlay.runtimeImageRewrites || []).length > 0) checks.push('runtime-image-rewrites');", + "if (workloadCheck.staleGitReadUrls.length > 0) fail('runtime-git-read-url-stale', { refs: workloadCheck.staleGitReadUrls.slice(0, 12), count: workloadCheck.staleGitReadUrls.length, expected: overlay.gitReadUrl });", + "checks.push('runtime-git-read-url');", "const pg = overlay.externalPostgres;", "if (pg && pg.serviceName) {", " const file = path.join(runtimePath, 'external-postgres.yaml');", @@ -1829,6 +1889,7 @@ function nodeRuntimeRenderOverlay(spec: HwlabRuntimeLaneSpec): Record