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, includeCommand) { if (typeof text !== "string" || text.length === 0) return null; let state; try { state = JSON.parse(text); } catch { return null; } const compact = { 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), rawStateDiagnostic: rawStateDiagnostic(state, text), }; if (includeCommand) compact.command = compactCommand(state.command); return compact; } 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), reconcileTimeline: compactReconcileTimeline(value.reconcileTimeline), parsedDownstreamCliOutput: false, }; } function compactReconcileTimeline(reconcileTimeline) { const value = recordOrNull(reconcileTimeline); if (value === null) return null; const steps = arrayRecords(value.steps).slice(-16).map((step) => ({ follower: stringOrNull(step.follower), step: stringOrNull(step.step), status: stringOrNull(step.status), startedAt: stringOrNull(step.startedAt), finishedAt: stringOrNull(step.finishedAt), elapsedMs: numberOrNull(step.elapsedMs), observedSha: stringOrNull(step.observedSha), targetSha: stringOrNull(step.targetSha), phase: stringOrNull(step.phase), pipelineRun: stringOrNull(step.pipelineRun), object: stringOrNull(step.object), message: stringOrNull(step.message), reason: stringOrNull(step.reason), exitCode: numberOrNull(step.exitCode), })); return { startedAt: stringOrNull(value.startedAt), finishedAt: stringOrNull(value.finishedAt), elapsedMs: numberOrNull(value.elapsedMs), controller: value.controller === true, dryRun: value.dryRun === true, confirm: value.confirm === true, wait: value.wait === true, followerCount: numberOrNull(value.followerCount), followers: arrayStrings(value.followers).slice(0, 8), bounded: true, omittedStepCount: Math.max(0, arrayRecords(value.steps).length - steps.length), steps, }; } function rawStateDiagnostic(state, text) { const command = recordOrNull(state.command); const reconcileTimeline = recordOrNull(command?.reconcileTimeline); return { bounded: true, valueBytes: Buffer.byteLength(text, "utf8"), hasCommand: command !== null, commandBytes: jsonBytes(command), hasReconcileTimeline: reconcileTimeline !== null, reconcileTimelineBytes: jsonBytes(reconcileTimeline), reconcileTimelineStepCount: reconcileTimeline === null ? 0 : arrayRecords(reconcileTimeline.steps).length, reconcileTimelineStartedAt: stringOrNull(reconcileTimeline?.startedAt), reconcileTimelineFinishedAt: stringOrNull(reconcileTimeline?.finishedAt), reconcileTimelineElapsedMs: numberOrNull(reconcileTimeline?.elapsedMs), missingReason: reconcileTimeline === null ? command === null ? "command missing" : "command.reconcileTimeline missing" : null, }; } function jsonBytes(value) { if (value === null) return null; try { return Buffer.byteLength(JSON.stringify(value), "utf8"); } catch { return null; } } 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; const refreshEvidence = compactRefreshEvidence(recordOrNull(recordOrNull(value.nativeCapabilities)?.controlPlaneRefresh)); return { gitMirror: compactGitMirror(value.gitMirror), reuseConfig: compactReuseConfig(value.reuseConfig), tekton: compactTekton(value.tekton), pipeline: compactPipeline(value.pipeline), taskRuns: compactTaskRuns(value.taskRuns), planArtifacts: compactPlanArtifacts(value.planArtifacts), argo: compactArgo(value.argo), runtime: compactRuntime(value.runtime), refreshEvidence, refreshEvidenceReason: refreshEvidence === null ? "no stored control-plane-refresh capability evidence; status refresh job is reported separately at top-level refresh" : null, errors: arrayStrings(value.errors).slice(0, 5), statusAuthority: stringOrNull(value.statusAuthority), parsedDownstreamCliOutput: false, }; } function compactReuseConfig(reuseConfig) { const value = recordOrNull(reuseConfig); if (value === null) return null; return { ok: value.ok === true, present: value.present === true, path: stringOrNull(value.path), sourceCommit: stringOrNull(value.sourceCommit), stageRef: stringOrNull(value.stageRef), sha256: stringOrNull(value.sha256), serviceCount: numberOrNull(value.serviceCount), serviceIds: compactReuseServiceIds(value), errors: arrayStrings(value.errors).slice(0, 5), valuesRedacted: true, }; } function compactReuseServiceIds(value) { const explicit = arrayStrings(value.serviceIds); if (explicit.length > 0) return explicit.slice(0, 16); return arrayRecords(value.services).map((service) => stringOrNull(service.id)).filter(Boolean).slice(0, 16); } function compactGitMirror(gitMirror) { const value = recordOrNull(gitMirror); if (value === null) return null; return { ok: value.ok === true, sourceSnapshotReady: value.sourceSnapshotReady === true, pendingFlush: value.pendingFlush === true, githubInSync: value.githubInSync === true, sourceBranch: stringOrNull(value.sourceBranch), gitopsBranch: stringOrNull(value.gitopsBranch), localSource: stringOrNull(value.localSource), githubSource: stringOrNull(value.githubSource), localGitops: stringOrNull(value.localGitops), githubGitops: stringOrNull(value.githubGitops), }; } function compactTekton(tekton) { const value = recordOrNull(tekton); if (value === null) return null; return { name: stringOrNull(value.name), pipelineRefName: stringOrNull(value.pipelineRefName), succeeded: value.succeeded === true ? true : value.succeeded === false ? false : null, reason: stringOrNull(value.reason), startTime: stringOrNull(value.startTime), completionTime: stringOrNull(value.completionTime), durationSeconds: numberOrNull(value.durationSeconds), }; } function compactPipeline(pipeline) { const value = recordOrNull(pipeline); if (value === null) return null; return { metadata: recordOrNull(value.metadata), spec: recordOrNull(value.spec), }; } function compactRefreshEvidence(refresh) { const value = recordOrNull(refresh); const summary = recordOrNull(value?.summary); if (value === null || summary === null) return null; return { jobName: stringOrNull(value.jobName) ?? stringOrNull(summary.jobName), namespace: stringOrNull(value.namespace) ?? stringOrNull(summary.namespace), status: stringOrNull(summary.status), pipeline: stringOrNull(summary.pipeline) || stringOrNull(recordOrNull(summary.apply)?.pipelineName) || stringOrNull(recordOrNull(summary.render)?.pipelineName), sourceCommit: stringOrNull(summary.sourceCommit), sourceStageRef: stringOrNull(summary.sourceStageRef), elapsedMs: numberOrNull(summary.elapsedMs), render: compactRefreshRender(recordOrNull(summary.render)), apply: compactRefreshApply(recordOrNull(summary.apply)), sourceAuthority: stringOrNull(summary.sourceAuthority), statusAuthority: stringOrNull(summary.statusAuthority), parsedDownstreamCliOutput: false, }; } function compactRefreshRender(value) { if (value === null) return null; return { pipelineName: stringOrNull(value.pipelineName), taskCount: numberOrNull(value.taskCount), runtimeReadyTask: compactRefreshRuntimeReady(recordOrNull(value.runtimeReadyTask)), }; } function compactRefreshApply(value) { if (value === null) return null; return { pipelineName: stringOrNull(value.pipelineName), namespace: stringOrNull(value.namespace), resourceVersion: stringOrNull(value.resourceVersion), annotations: compactStringMap(recordOrNull(value.annotations)), labels: compactStringMap(recordOrNull(value.labels)), degradedReason: stringOrNull(value.degradedReason), }; } function compactRefreshRuntimeReady(value) { if (value === null) return null; return { present: booleanOrNull(value.present), name: stringOrNull(value.name), runAfter: compactStringArray(value.runAfter, 4), when: compactWhenList(value.when, 4), }; } function compactWhenList(value, limit) { return Array.isArray(value) ? value.map((item) => recordOrNull(item)).filter(Boolean).slice(0, limit).map((item) => ({ input: stringOrNull(item.input), operator: stringOrNull(item.operator), values: compactStringArray(item.values, 4), })) : []; } function compactStringMap(value) { if (value === null) return null; const output = {}; for (const [key, item] of Object.entries(value).slice(0, 8)) { const text = stringOrNull(item); if (text !== null) output[key] = text; } return Object.keys(output).length === 0 ? null : output; } function compactArgo(argo) { const value = recordOrNull(argo); if (value === null) return null; return { name: stringOrNull(value.name), syncStatus: stringOrNull(value.syncStatus), healthStatus: stringOrNull(value.healthStatus), healthMessage: stringOrNull(value.healthMessage), revision: stringOrNull(value.revision), operationPhase: stringOrNull(value.operationPhase), operationMessage: stringOrNull(value.operationMessage), operationStartedAt: stringOrNull(value.operationStartedAt), operationFinishedAt: stringOrNull(value.operationFinishedAt), operationDurationSeconds: numberOrNull(value.operationDurationSeconds), conditions: arrayRecords(value.conditions).slice(0, 5), nonReadyResources: arrayRecords(value.nonReadyResources).slice(0, 5), ready: value.ready === true, }; } function compactRuntime(runtime) { const value = recordOrNull(runtime); if (value === null) return null; return { namespace: stringOrNull(value.namespace), ready: value.ready === true, targetSha: stringOrNull(value.targetSha), expectedSha: stringOrNull(value.expectedSha), aligned: value.aligned === true ? true : value.aligned === false ? false : null, }; } function compactTaskRuns(taskRuns) { const value = recordOrNull(taskRuns); if (value === null) return null; const items = arrayRecords(value.items); 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), failedItems: items.filter((item) => item.status === "False").slice(0, 5).map(compactTaskRunItem), activeItems: items.filter((item) => item.status !== "True" && item.status !== "False").slice(0, 5).map(compactTaskRunItem), slowItems: arrayRecords(recordOrNull(value.performance)?.slowTaskRuns).slice(0, 5).map(compactTaskRunItem), }; } function compactTaskRunItem(item) { return { name: stringOrNull(item.name), pipelineTask: stringOrNull(item.pipelineTask), status: stringOrNull(item.status), reason: stringOrNull(item.reason), durationSeconds: numberOrNull(item.durationSeconds), }; } 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), affectedServicesCount: arrayStrings(value.affectedServices).length, rolloutServicesCount: arrayStrings(value.rolloutServices).length, buildServicesCount: arrayStrings(value.buildServices).length, reusedServicesCount: arrayStrings(value.reusedServices).length, 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: prioritizedStageTimings(arrayRecords(value.stages)).slice(0, maxTimingStages).map(compactStageTiming), }; } function prioritizedStageTimings(stages) { const priority = []; const rest = []; for (const stage of stages) { if (isPriorityTaskStage(stage)) priority.push(stage); else rest.push(stage); } const seen = new Set(); const out = []; for (const stage of [...priority, ...rest]) { const key = [ stringOrNull(stage.stage), stringOrNull(stage.status), stringOrNull(stage.source), stringOrNull(stage.object), ].filter((item) => item !== null).join("|"); if (seen.has(key)) continue; seen.add(key); out.push(stage); } return out; } function isPriorityTaskStage(stage) { const name = stringOrNull(stage.stage) || ""; if (!name.startsWith("task:")) return false; const status = stringOrNull(stage.status) || ""; const seconds = numberOrNull(stage.seconds); return status.startsWith("failed") || status === "running" || (seconds !== null && seconds > 60); } 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; } function booleanOrNull(value) { return value === true ? true : value === false ? false : null; } function compactStringArray(value, limit) { return Array.isArray(value) ? value.map((item) => stringOrNull(item)).filter(Boolean).slice(0, limit) : []; } const result = await readConfigMap(); const errors = []; const stateByFollower = {}; const valueBytes = {}; if (result.ok && result.present) { const data = recordOrNull(result.object?.data) || {}; const includeCommand = followerIds.length === 1; 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, includeCommand); 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, }));