fix: postprocess D601 HWLAB runtime GitOps
This commit is contained in:
+170
-3
@@ -1352,14 +1352,130 @@ function nodeRuntimePipelinePostprocessScript(): string[] {
|
||||
"function writeYaml(file, doc) { fs.writeFileSync(file, YAML.stringify(doc).trimEnd() + '\\\\n'); }",
|
||||
"function listItems(doc) { return doc && doc.kind === 'List' && Array.isArray(doc.items) ? doc.items : [doc]; }",
|
||||
"function normalizeList(items) { return { apiVersion: 'v1', kind: 'List', items }; }",
|
||||
"function isObject(value) { return value && typeof value === 'object' && !Array.isArray(value); }",
|
||||
"function yamlFiles(dir) {",
|
||||
" const files = [];",
|
||||
" for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {",
|
||||
" const file = path.join(dir, entry.name);",
|
||||
" if (entry.isDirectory()) files.push(...yamlFiles(file));",
|
||||
" else if (entry.isFile() && /\\.ya?ml$/u.test(entry.name)) files.push(file);",
|
||||
" }",
|
||||
" return files;",
|
||||
"}",
|
||||
"function readYamlDocuments(file) { return YAML.parseAllDocuments(fs.readFileSync(file, 'utf8')).map((document) => document.toJS()).filter((doc) => doc !== null); }",
|
||||
"function writeYamlDocuments(file, docs) { fs.writeFileSync(file, docs.map((doc) => YAML.stringify(doc).trimEnd()).join('\\\\n---\\\\n') + '\\\\n'); }",
|
||||
"function podSpecFor(item) {",
|
||||
" if (!isObject(item) || !isObject(item.spec)) return null;",
|
||||
" if (item.kind === 'Pod') return item.spec;",
|
||||
" if (['Deployment', 'StatefulSet', 'DaemonSet', 'ReplicaSet', 'ReplicationController'].includes(item.kind)) return item.spec.template && item.spec.template.spec ? item.spec.template.spec : null;",
|
||||
" if (item.kind === 'Job') return item.spec.template && item.spec.template.spec ? item.spec.template.spec : null;",
|
||||
" if (item.kind === 'CronJob') return item.spec.jobTemplate && item.spec.jobTemplate.spec && item.spec.jobTemplate.spec.template ? item.spec.jobTemplate.spec.template.spec : null;",
|
||||
" return null;",
|
||||
"}",
|
||||
"function templateMetadataFor(item) {",
|
||||
" if (!isObject(item) || !isObject(item.spec)) return null;",
|
||||
" if (item.kind === 'Pod') return item.metadata || null;",
|
||||
" if (['Deployment', 'StatefulSet', 'DaemonSet', 'ReplicaSet', 'ReplicationController'].includes(item.kind)) return item.spec.template ? item.spec.template.metadata : null;",
|
||||
" if (item.kind === 'Job') return item.spec.template ? item.spec.template.metadata : null;",
|
||||
" if (item.kind === 'CronJob') return item.spec.jobTemplate && item.spec.jobTemplate.spec && item.spec.jobTemplate.spec.template ? item.spec.jobTemplate.spec.template.metadata : null;",
|
||||
" return null;",
|
||||
"}",
|
||||
"function stripMonitoringMetadata(metadata) {",
|
||||
" if (!isObject(metadata)) return false;",
|
||||
" let changed = false;",
|
||||
" if (isObject(metadata.labels) && metadata.labels['hwlab.pikastech.local/monitoring'] !== undefined && metadata.labels['hwlab.pikastech.local/monitoring'] !== 'disabled') {",
|
||||
" metadata.labels['hwlab.pikastech.local/monitoring'] = 'disabled';",
|
||||
" changed = true;",
|
||||
" }",
|
||||
" if (isObject(metadata.annotations) && metadata.annotations['hwlab.pikastech.local/metrics-sidecar-sha256'] !== undefined) {",
|
||||
" delete metadata.annotations['hwlab.pikastech.local/metrics-sidecar-sha256'];",
|
||||
" changed = true;",
|
||||
" }",
|
||||
" return changed;",
|
||||
"}",
|
||||
"function containerHasVolumeMount(container, name) { return isObject(container) && Array.isArray(container.volumeMounts) && container.volumeMounts.some((mount) => mount && mount.name === name); }",
|
||||
"function removeMetricsSidecar(podSpec) {",
|
||||
" if (!isObject(podSpec)) return false;",
|
||||
" let changed = false;",
|
||||
" if (Array.isArray(podSpec.containers)) {",
|
||||
" const next = podSpec.containers.filter((container) => !(isObject(container) && (container.name === 'hwlab-metrics' || (Array.isArray(container.command) && container.command.includes('/metrics/metrics-sidecar.mjs')))));",
|
||||
" if (next.length !== podSpec.containers.length) { podSpec.containers = next; changed = true; }",
|
||||
" }",
|
||||
" for (const group of ['containers', 'initContainers']) {",
|
||||
" for (const container of Array.isArray(podSpec[group]) ? podSpec[group] : []) {",
|
||||
" if (!isObject(container) || !Array.isArray(container.volumeMounts)) continue;",
|
||||
" const nextMounts = container.volumeMounts.filter((mount) => !(mount && mount.name === 'hwlab-metrics-sidecar'));",
|
||||
" if (nextMounts.length !== container.volumeMounts.length) { container.volumeMounts = nextMounts; changed = true; }",
|
||||
" }",
|
||||
" }",
|
||||
" if (Array.isArray(podSpec.volumes)) {",
|
||||
" const nextVolumes = podSpec.volumes.filter((volume) => !(volume && volume.name === 'hwlab-metrics-sidecar'));",
|
||||
" if (nextVolumes.length !== podSpec.volumes.length) { podSpec.volumes = nextVolumes; changed = true; }",
|
||||
" }",
|
||||
" return changed;",
|
||||
"}",
|
||||
"function envValue(container, name) {",
|
||||
" if (!isObject(container) || !Array.isArray(container.env)) return undefined;",
|
||||
" const item = container.env.find((env) => env && env.name === name);",
|
||||
" return item ? item.value : undefined;",
|
||||
"}",
|
||||
"function isEnvReuseContainer(container) { return envValue(container, 'HWLAB_RUNTIME_MODE') === 'env-reuse-git-mirror-checkout' || envValue(container, 'HWLAB_BOOT_SH') !== undefined || envValue(container, 'HWLAB_BOOT_COMMIT') !== undefined; }",
|
||||
"function startupProbeFrom(probe) {",
|
||||
" const next = JSON.parse(JSON.stringify(probe));",
|
||||
" next.periodSeconds = 10;",
|
||||
" next.timeoutSeconds = Math.max(Number(next.timeoutSeconds || 0), 2);",
|
||||
" next.failureThreshold = 30;",
|
||||
" next.successThreshold = 1;",
|
||||
" delete next.initialDelaySeconds;",
|
||||
" return next;",
|
||||
"}",
|
||||
"function addEnvReuseStartupProbe(podSpec) {",
|
||||
" if (!isObject(podSpec) || !Array.isArray(podSpec.containers)) return false;",
|
||||
" let changed = false;",
|
||||
" for (const container of podSpec.containers) {",
|
||||
" if (!isObject(container) || !isEnvReuseContainer(container) || container.startupProbe) continue;",
|
||||
" const sourceProbe = container.readinessProbe || container.livenessProbe;",
|
||||
" if (!sourceProbe) continue;",
|
||||
" container.startupProbe = startupProbeFrom(sourceProbe);",
|
||||
" changed = true;",
|
||||
" }",
|
||||
" return changed;",
|
||||
"}",
|
||||
"function patchRuntimeWorkloads() {",
|
||||
" let observabilityChanged = false;",
|
||||
" let startupProbeChanged = false;",
|
||||
" for (const file of yamlFiles(runtimePath)) {",
|
||||
" if (path.basename(file) === 'kustomization.yaml') continue;",
|
||||
" const docs = readYamlDocuments(file);",
|
||||
" let changed = false;",
|
||||
" for (const doc of docs) {",
|
||||
" for (const item of listItems(doc).filter(Boolean)) {",
|
||||
" if (!isObject(item)) continue;",
|
||||
" if (overlay.observability && overlay.observability.prometheusOperator === false) {",
|
||||
" const monitoringChanged = stripMonitoringMetadata(item.metadata) || stripMonitoringMetadata(templateMetadataFor(item)) || removeMetricsSidecar(podSpecFor(item));",
|
||||
" changed = monitoringChanged || changed;",
|
||||
" observabilityChanged = observabilityChanged || monitoringChanged;",
|
||||
" }",
|
||||
" const probeChanged = addEnvReuseStartupProbe(podSpecFor(item));",
|
||||
" changed = probeChanged || changed;",
|
||||
" startupProbeChanged = startupProbeChanged || probeChanged;",
|
||||
" }",
|
||||
" }",
|
||||
" if (changed) writeYamlDocuments(file, docs);",
|
||||
" }",
|
||||
" return { observabilityChanged, startupProbeChanged };",
|
||||
"}",
|
||||
"function patchKustomization() {",
|
||||
" const file = path.join(runtimePath, 'kustomization.yaml');",
|
||||
" if (!fs.existsSync(file)) return false;",
|
||||
" const doc = readYaml(file) || {};",
|
||||
" const resources = Array.isArray(doc.resources) ? doc.resources : [];",
|
||||
" const next = resources.filter((item) => !(overlay.observability && overlay.observability.prometheusOperator === false && item === 'observability.yaml'));",
|
||||
" if (next.length !== resources.length) { doc.resources = next; writeYaml(file, doc); return true; }",
|
||||
" return false;",
|
||||
" let changed = false;",
|
||||
" if (next.length !== resources.length) { doc.resources = next; writeYaml(file, doc); changed = true; }",
|
||||
" const observabilityFile = path.join(runtimePath, 'observability.yaml');",
|
||||
" if (overlay.observability && overlay.observability.prometheusOperator === false && fs.existsSync(observabilityFile)) { fs.rmSync(observabilityFile, { force: true }); changed = true; }",
|
||||
" return changed;",
|
||||
"}",
|
||||
"function patchExternalPostgres() {",
|
||||
" const pg = overlay.externalPostgres;",
|
||||
@@ -1396,8 +1512,9 @@ function nodeRuntimePipelinePostprocessScript(): string[] {
|
||||
" return changed;",
|
||||
"}",
|
||||
"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, externalPostgresChanged }));",
|
||||
"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 }));",
|
||||
"NODE_UNIDESK_RUNTIME_GITOPS_POSTPROCESS`;",
|
||||
"}",
|
||||
"function runtimeGitopsVerifyScript() {",
|
||||
@@ -1420,14 +1537,64 @@ function nodeRuntimePipelinePostprocessScript(): string[] {
|
||||
"if (!runtimePath || !fs.existsSync(runtimePath)) fail('runtime-path-missing');",
|
||||
"function readYaml(file) { return YAML.parse(fs.readFileSync(file, 'utf8')); }",
|
||||
"function listItems(doc) { return doc && doc.kind === 'List' && Array.isArray(doc.items) ? doc.items : [doc]; }",
|
||||
"function isObject(value) { return value && typeof value === 'object' && !Array.isArray(value); }",
|
||||
"function yamlFiles(dir) {",
|
||||
" const files = [];",
|
||||
" for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {",
|
||||
" const file = path.join(dir, entry.name);",
|
||||
" if (entry.isDirectory()) files.push(...yamlFiles(file));",
|
||||
" else if (entry.isFile() && /\\.ya?ml$/u.test(entry.name)) files.push(file);",
|
||||
" }",
|
||||
" return files;",
|
||||
"}",
|
||||
"function readYamlDocuments(file) { return YAML.parseAllDocuments(fs.readFileSync(file, 'utf8')).map((document) => document.toJS()).filter((doc) => doc !== null); }",
|
||||
"function podSpecFor(item) {",
|
||||
" if (!isObject(item) || !isObject(item.spec)) return null;",
|
||||
" if (item.kind === 'Pod') return item.spec;",
|
||||
" if (['Deployment', 'StatefulSet', 'DaemonSet', 'ReplicaSet', 'ReplicationController'].includes(item.kind)) return item.spec.template && item.spec.template.spec ? item.spec.template.spec : null;",
|
||||
" if (item.kind === 'Job') return item.spec.template && item.spec.template.spec ? item.spec.template.spec : null;",
|
||||
" if (item.kind === 'CronJob') return item.spec.jobTemplate && item.spec.jobTemplate.spec && item.spec.jobTemplate.spec.template ? item.spec.jobTemplate.spec.template.spec : null;",
|
||||
" return null;",
|
||||
"}",
|
||||
"function envValue(container, name) {",
|
||||
" if (!isObject(container) || !Array.isArray(container.env)) return undefined;",
|
||||
" const item = container.env.find((env) => env && env.name === name);",
|
||||
" return item ? item.value : undefined;",
|
||||
"}",
|
||||
"function isEnvReuseContainer(container) { return envValue(container, 'HWLAB_RUNTIME_MODE') === 'env-reuse-git-mirror-checkout' || envValue(container, 'HWLAB_BOOT_SH') !== undefined || envValue(container, 'HWLAB_BOOT_COMMIT') !== undefined; }",
|
||||
"function workloadRef(item, file, container) { return { file, kind: item && item.kind, name: item && item.metadata && item.metadata.name, container: container && container.name }; }",
|
||||
"function workloadChecks() {",
|
||||
" const metricsRefs = [];",
|
||||
" const missingStartupProbes = [];",
|
||||
" for (const file of yamlFiles(runtimePath)) {",
|
||||
" if (path.basename(file) === 'kustomization.yaml') continue;",
|
||||
" for (const doc of readYamlDocuments(file)) {",
|
||||
" for (const item of listItems(doc).filter(Boolean)) {",
|
||||
" const podSpec = podSpecFor(item);",
|
||||
" if (!isObject(podSpec)) continue;",
|
||||
" for (const container of Array.isArray(podSpec.containers) ? podSpec.containers : []) {",
|
||||
" 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 (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 };",
|
||||
"}",
|
||||
"const checks = [];",
|
||||
"const workloadCheck = workloadChecks();",
|
||||
"const kustomizationPath = path.join(runtimePath, 'kustomization.yaml');",
|
||||
"if (overlay.observability && overlay.observability.prometheusOperator === false) {",
|
||||
" if (!fs.existsSync(kustomizationPath)) fail('kustomization-missing');",
|
||||
" const resources = readYaml(kustomizationPath).resources || [];",
|
||||
" if (resources.includes('observability.yaml')) fail('observability-resource-still-rendered', { file: kustomizationPath });",
|
||||
" if (workloadCheck.metricsRefs.length > 0) fail('observability-sidecar-still-rendered', { refs: workloadCheck.metricsRefs.slice(0, 12), count: workloadCheck.metricsRefs.length });",
|
||||
" checks.push('observability-disabled');",
|
||||
"}",
|
||||
"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');",
|
||||
"const pg = overlay.externalPostgres;",
|
||||
"if (pg && pg.serviceName) {",
|
||||
" const file = path.join(runtimePath, 'external-postgres.yaml');",
|
||||
|
||||
Reference in New Issue
Block a user