import { readFileSync } from "node:fs"; import { execFileSync } from "node:child_process"; import { existsSync } from "node:fs"; import https from "node:https"; const namespace = process.env.NAMESPACE || ""; const configMap = process.env.CONFIGMAP || ""; const followerIds = parseFollowerIds(process.env.FOLLOWERS_JSON || "[]"); const maxTimingStages = Number(process.env.MAX_TIMING_STAGES || "24"); const tokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"; const caPath = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"; const inCluster = Boolean(process.env.KUBERNETES_SERVICE_HOST && existsSync(tokenPath) && existsSync(caPath)); const host = process.env.KUBERNETES_SERVICE_HOST; const port = Number(process.env.KUBERNETES_SERVICE_PORT || "443"); const token = inCluster ? readFileSync(tokenPath, "utf8").trim() : ""; const ca = inCluster ? readFileSync(caPath) : null; function parseFollowerIds(text) { try { const parsed = JSON.parse(text); return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.length > 0) : []; } catch { return []; } } function request(method, path, body, contentType = "application/json") { return new Promise((resolve, reject) => { const headers = { authorization: `Bearer ${token}` }; const payload = body === undefined ? null : typeof body === "string" ? body : JSON.stringify(body); if (payload !== null) { headers["content-type"] = contentType; headers["content-length"] = Buffer.byteLength(payload); } const req = https.request({ host, port, path, method, ca, headers }, (res) => { let text = ""; res.setEncoding("utf8"); res.on("data", (chunk) => { text += chunk; }); res.on("end", () => resolve({ status: res.statusCode || 0, text })); }); req.on("error", reject); if (payload !== null) req.write(payload); req.end(); }); } async function readConfigMap() { if (!inCluster) return readConfigMapViaKubectl(); try { const result = await request("GET", `/api/v1/namespaces/${encodeURIComponent(namespace)}/configmaps/${encodeURIComponent(configMap)}`); if (result.status === 404) return { ok: true, present: false, object: null, error: result.text }; if (result.status < 200 || result.status >= 300) return { ok: false, present: false, object: null, error: result.text || `kube api GET configmap status ${result.status}` }; return { ok: true, present: true, object: JSON.parse(result.text), error: "" }; } catch (error) { return { ok: false, present: false, object: null, error: error?.message || String(error) }; } } function readConfigMapViaKubectl() { try { const stdout = execFileSync("kubectl", ["-n", namespace, "get", "configmap", configMap, "-o", "json"], { encoding: "utf8", maxBuffer: 16 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"], }); return { ok: true, present: true, object: JSON.parse(stdout), error: "" }; } catch (error) { const stderr = String(error?.stderr || error?.message || ""); if (/not found/i.test(stderr)) return { ok: true, present: false, object: null, error: stderr }; return { ok: false, present: false, object: null, error: stderr || "kubectl configmap read failed" }; } } function compactStateText(text) { if (typeof text !== "string" || text.length === 0) return null; let state; try { state = JSON.parse(text); } catch { return null; } return { id: stringOrNull(state.id), adapter: stringOrNull(state.adapter), enabled: state.enabled === true, phase: stringOrNull(state.phase), source: compactSource(state.source), target: compactTarget(state.target), lastTriggeredSha: stringOrNull(state.lastTriggeredSha), lastSucceededSha: stringOrNull(state.lastSucceededSha), pipelineRun: stringOrNull(state.pipelineRun), inFlightJob: stringOrNull(state.inFlightJob), controller: compactController(state.controller), decision: stringOrNull(state.decision), dryRun: state.dryRun === true, updatedAt: stringOrNull(state.updatedAt), timings: compactTimings(state.timings), warnings: arrayStrings(state.warnings).slice(0, 6), stateFormat: stringOrNull(state.stateFormat), command: compactCommand(state.command), }; } function compactCommand(command) { const value = recordOrNull(command); if (value === null) return null; return { mode: stringOrNull(value.mode) ?? stringOrNull(value.status), namespace: stringOrNull(value.namespace), pipelineRun: stringOrNull(value.pipelineRun), sourceCommit: stringOrNull(value.sourceCommit), sourceStageRef: stringOrNull(value.sourceStageRef), wait: value.wait === true ? true : null, pipelineRunCompleted: value.pipelineRunCompleted === true ? true : null, stillRunning: value.stillRunning === true ? true : null, closeout: compactCloseout(value.closeout), payload: compactNativePayload(value.payload), exitCode: numberOrNull(value.exitCode), timedOut: value.timedOut === true, statusAuthority: stringOrNull(value.statusAuthority), parsedDownstreamCliOutput: false, }; } function compactCloseout(closeout) { const value = recordOrNull(closeout); if (value === null) return null; return { ok: value.ok === true, completed: value.completed === true, timedOut: value.timedOut === true, polls: numberOrNull(value.polls), elapsedMs: numberOrNull(value.elapsedMs), summary: compactNativePayload(value.summary), statusAuthority: stringOrNull(value.statusAuthority), parsedDownstreamCliOutput: false, }; } function compactNativePayload(payload) { const value = recordOrNull(payload); if (value === null) return null; return { source: recordOrNull(value.source), sourceSync: recordOrNull(value.sourceSync), gitMirror: recordOrNull(value.gitMirror), tekton: recordOrNull(value.tekton), taskRuns: compactTaskRuns(value.taskRuns), planArtifacts: compactPlanArtifacts(value.planArtifacts), argo: recordOrNull(value.argo), runtime: recordOrNull(value.runtime), errors: arrayStrings(value.errors).slice(0, 5), statusAuthority: stringOrNull(value.statusAuthority), parsedDownstreamCliOutput: false, }; } function compactTaskRuns(taskRuns) { const value = recordOrNull(taskRuns); if (value === null) return null; return { ok: value.ok === true, count: numberOrNull(value.count), succeededCount: numberOrNull(value.succeededCount), failedCount: numberOrNull(value.failedCount), activeCount: numberOrNull(value.activeCount), performance: recordOrNull(value.performance), items: arrayRecords(value.items).slice(0, 16), }; } function compactPlanArtifacts(planArtifacts) { const value = recordOrNull(planArtifacts); if (value === null) return null; return { ok: value.ok === true, pipelineRun: stringOrNull(value.pipelineRun), eventFound: value.eventFound === true, degradedReason: stringOrNull(value.degradedReason), sourceCommitId: stringOrNull(value.sourceCommitId), affectedServices: arrayStrings(value.affectedServices).slice(0, 40), rolloutServices: arrayStrings(value.rolloutServices).slice(0, 40), buildServices: arrayStrings(value.buildServices).slice(0, 40), reusedServices: arrayStrings(value.reusedServices).slice(0, 40), buildSkippedCount: numberOrNull(value.buildSkippedCount), summary: stringOrNull(value.summary), disclosure: stringOrNull(value.disclosure), }; } function compactSource(source) { const value = recordOrNull(source); if (value === null) return null; return { repository: stringOrNull(value.repository), branch: stringOrNull(value.branch), branchRef: stringOrNull(value.branchRef), snapshotPrefix: stringOrNull(value.snapshotPrefix), observedSha: stringOrNull(value.observedSha), }; } function compactTarget(target) { const value = recordOrNull(target); if (value === null) return null; return { node: stringOrNull(value.node), lane: stringOrNull(value.lane), namespace: stringOrNull(value.namespace), sentinel: stringOrNull(value.sentinel), targetSha: stringOrNull(value.targetSha), }; } function compactController(controller) { const value = recordOrNull(controller); if (value === null) return null; return { mode: stringOrNull(value.mode), stateConfigMap: stringOrNull(value.stateConfigMap), leaseName: stringOrNull(value.leaseName), }; } function compactTimings(timings) { const value = recordOrNull(timings); if (value === null) return null; return { budgetSeconds: numberOrNull(value.budgetSeconds), totalSeconds: numberOrNull(value.totalSeconds), totalStatus: stringOrNull(value.totalStatus), totalSource: stringOrNull(value.totalSource), sourceCommit: stringOrNull(value.sourceCommit), startedAt: stringOrNull(value.startedAt), finishedAt: stringOrNull(value.finishedAt), overBudget: typeof value.overBudget === "boolean" ? value.overBudget : null, stages: arrayRecords(value.stages).slice(0, maxTimingStages).map(compactStageTiming), }; } function compactStageTiming(stage) { return { stage: stringOrNull(stage.stage), status: stringOrNull(stage.status), seconds: numberOrNull(stage.seconds), budgetSeconds: numberOrNull(stage.budgetSeconds), source: stringOrNull(stage.source), object: stringOrNull(stage.object), }; } function recordOrNull(value) { return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null; } function arrayRecords(value) { return Array.isArray(value) ? value.filter((item) => recordOrNull(item) !== null) : []; } function arrayStrings(value) { return Array.isArray(value) ? value.filter((item) => typeof item === "string") : []; } function stringOrNull(value) { return typeof value === "string" && value.length > 0 ? value : null; } function numberOrNull(value) { return typeof value === "number" && Number.isFinite(value) ? value : null; } const result = await readConfigMap(); const errors = []; const stateByFollower = {}; const valueBytes = {}; if (result.ok && result.present) { const data = recordOrNull(result.object?.data) || {}; for (const id of followerIds) { const text = typeof data[id] === "string" ? data[id] : ""; if (text.length === 0) continue; valueBytes[id] = Buffer.byteLength(text, "utf8"); const compact = compactStateText(text); if (compact === null) errors.push(`${id}: invalid state json`); else stateByFollower[id] = compact; } } if (!result.ok) errors.push(result.error); process.stdout.write(JSON.stringify({ ok: result.ok && errors.length === 0, present: result.present, metadata: result.object === null ? null : { name: stringOrNull(result.object?.metadata?.name), namespace: stringOrNull(result.object?.metadata?.namespace), resourceVersion: stringOrNull(result.object?.metadata?.resourceVersion), updatedAt: stringOrNull(result.object?.data?._updatedAt), keyCount: Object.keys(recordOrNull(result.object?.data) || {}).length, }, stateByFollower, valueBytes, errors, statusAuthority: "target-node-summary", parsedDownstreamCliOutput: false, }));