diff --git a/scripts/native/hwlab/runtime-gitops-observability.mjs b/scripts/native/hwlab/runtime-gitops-observability.mjs new file mode 100644 index 00000000..fce3644a --- /dev/null +++ b/scripts/native/hwlab/runtime-gitops-observability.mjs @@ -0,0 +1,44 @@ +// Native helper injected into HWLAB runtime GitOps postprocess/verify scripts. +// It intentionally avoids imports so it can also run inside `node -` heredocs. +(function installRuntimeGitopsObservability(globalObject) { + const prometheusOperatorKinds = new Set(["ServiceMonitor", "PrometheusRule", "PodMonitor", "Probe"]); + + function isObject(value) { + return value !== null && typeof value === "object" && !Array.isArray(value); + } + + function isPrometheusOperatorResource(item) { + return isObject(item) + && typeof item.apiVersion === "string" + && item.apiVersion.startsWith("monitoring.coreos.com/") + && prometheusOperatorKinds.has(String(item.kind)); + } + + function prometheusOperatorDisabled(overlay) { + return isObject(overlay?.observability) && overlay.observability.prometheusOperator === false; + } + + function stripPrometheusOperatorResources(doc, overlay) { + if (!prometheusOperatorDisabled(overlay)) return { docs: [doc], changed: false }; + if (isObject(doc) && doc.kind === "List" && Array.isArray(doc.items)) { + const items = doc.items.filter((item) => !isPrometheusOperatorResource(item)); + return { docs: items.length > 0 ? [{ ...doc, items }] : [], changed: items.length !== doc.items.length }; + } + return isPrometheusOperatorResource(doc) ? { docs: [], changed: true } : { docs: [doc], changed: false }; + } + + function prometheusOperatorResourceRef(item, file) { + return { + file, + kind: item && item.kind, + name: item && item.metadata && item.metadata.name, + container: null, + }; + } + + globalObject.unideskRuntimeGitopsObservability = { + isPrometheusOperatorResource, + stripPrometheusOperatorResources, + prometheusOperatorResourceRef, + }; +})(globalThis); diff --git a/scripts/src/hwlab-node/render.ts b/scripts/src/hwlab-node/render.ts index fe924059..836ec0bb 100644 --- a/scripts/src/hwlab-node/render.ts +++ b/scripts/src/hwlab-node/render.ts @@ -40,6 +40,8 @@ import { externalPostgresBridgeStatus, externalPostgresSecretStatus, getNodeRunt import { webObserveShort, webObserveText } from "./web-probe-observe"; import { hwlabRuntimeActiveExternalPostgres } from "../hwlab-node-lanes"; +const runtimeGitopsObservabilityNativeScript = readFileSync(rootPath("scripts/native/hwlab/runtime-gitops-observability.mjs"), "utf8").trimEnd(); + export function nodeRuntimeGitMirrorJobName(mirror: NodeRuntimeGitMirrorTargetSpec, action: "sync" | "flush"): string { const prefix = action === "sync" ? mirror.syncJobPrefix : mirror.flushJobPrefix; return `${prefix}-${Date.now().toString(36)}`.slice(0, 63); @@ -1408,6 +1410,7 @@ export function nodeRuntimePipelinePostprocessScript(): string[] { "const vm = require('node:vm');", "const renderDir = process.argv[2];", "const overlay = JSON.parse(Buffer.from(process.argv[3], 'base64').toString('utf8'));", + `const runtimeGitopsObservabilityNativeScript = ${JSON.stringify(runtimeGitopsObservabilityNativeScript)};`, "const pipelinePath = path.join(renderDir, overlay.tektonDir, 'pipeline.yaml');", "let text = fs.readFileSync(pipelinePath, 'utf8');", "let YAML = null;", @@ -1572,6 +1575,8 @@ export function nodeRuntimePipelinePostprocessScript(): string[] { "const crypto = require('crypto');", "const YAML = require('yaml');", "const overlay = ${runtimeOverlay};", + "${runtimeGitopsObservabilityNativeScript}", + "const observabilityNative = globalThis.unideskRuntimeGitopsObservability;", "const runtimePath = String(overlay.runtimePath || '');", "const renderDir = String(overlay.runtimeRenderDir || '');", "const legacyRuntimePath = runtimePath ? path.posix.join(path.posix.dirname(path.posix.dirname(runtimePath)), path.posix.basename(runtimePath)) : '';", @@ -1640,15 +1645,6 @@ export function nodeRuntimePipelinePostprocessScript(): string[] { " }", " return changed;", "}", - "function isPrometheusOperatorResource(item) { return item && item.apiVersion && String(item.apiVersion).startsWith('monitoring.coreos.com/') && ['ServiceMonitor', 'PrometheusRule', 'PodMonitor', 'Probe'].includes(item.kind); }", - "function stripPrometheusOperatorResources(doc) {", - " if (!(overlay.observability && overlay.observability.prometheusOperator === false)) return { docs: [doc], changed: false };", - " if (doc && doc.kind === 'List' && Array.isArray(doc.items)) {", - " const items = doc.items.filter((item) => !isPrometheusOperatorResource(item));", - " return { docs: items.length > 0 ? [{ ...doc, items }] : [], changed: items.length !== doc.items.length };", - " }", - " return isPrometheusOperatorResource(doc) ? { docs: [], changed: true } : { docs: [doc], changed: false };", - "}", "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;", @@ -1805,7 +1801,7 @@ export function nodeRuntimePipelinePostprocessScript(): string[] { " let changed = false;", " const nextDocs = [];", " for (const doc of docs) {", - " const stripped = stripPrometheusOperatorResources(doc);", + " const stripped = observabilityNative.stripPrometheusOperatorResources(doc, overlay);", " changed = stripped.changed || changed;", " observabilityChanged = observabilityChanged || stripped.changed;", " for (const nextDoc of stripped.docs) {", @@ -2047,6 +2043,8 @@ export function nodeRuntimePipelinePostprocessScript(): string[] { "const crypto = require('crypto');", "const YAML = require('yaml');", "const overlay = ${runtimeOverlay};", + "${runtimeGitopsObservabilityNativeScript}", + "const observabilityNative = globalThis.unideskRuntimeGitopsObservability;", "const runtimePath = String(overlay.runtimePath || '');", "function fail(reason, extra = {}) {", " console.error(JSON.stringify({ event: 'unidesk-runtime-gitops-verify', ok: false, reason, runtimePath, ...extra }));", @@ -2101,7 +2099,6 @@ export function nodeRuntimePipelinePostprocessScript(): string[] { " if (!exposure || !Array.isArray(exposure.extraProxies)) return [];", " return exposure.extraProxies.filter((proxy) => proxy && proxy.cloudWebEnvName && proxy.publicBaseUrl).map((proxy) => ({ name: String(proxy.cloudWebEnvName), value: String(proxy.publicBaseUrl) }));", "}", - "function isPrometheusOperatorResource(item) { return item && item.apiVersion && String(item.apiVersion).startsWith('monitoring.coreos.com/') && ['ServiceMonitor', 'PrometheusRule', 'PodMonitor', 'Probe'].includes(item.kind); }", "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 monitoringResources = [];", @@ -2132,7 +2129,7 @@ export function nodeRuntimePipelinePostprocessScript(): string[] { " if (path.basename(file) === 'kustomization.yaml') continue;", " for (const doc of readYamlDocuments(file)) {", " for (const item of listItems(doc).filter(Boolean)) {", - " if (isPrometheusOperatorResource(item)) monitoringResources.push(workloadRef(item, file, null));", + " if (observabilityNative.isPrometheusOperatorResource(item)) monitoringResources.push(observabilityNative.prometheusOperatorResourceRef(item, file));", " const podSpec = podSpecFor(item);", " if (!isObject(podSpec)) continue;", " for (const container of Array.isArray(podSpec.containers) ? podSpec.containers : []) {",