import { readFileSync } from "node:fs"; import https from "node:https"; const namespace = process.argv[2] || ""; const pipelineRun = process.argv[3] || ""; const host = process.env.KUBERNETES_SERVICE_HOST; const port = Number(process.env.KUBERNETES_SERVICE_PORT || "443"); const token = readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/token", "utf8").trim(); const ca = readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"); function request(path) { return new Promise((resolve) => { const req = https.request({ host, port, path, method: "GET", ca, headers: { authorization: `Bearer ${token}` } }, (res) => { let body = ""; res.setEncoding("utf8"); res.on("data", (chunk) => { body += chunk; }); res.on("end", () => resolve({ status: res.statusCode || 0, body })); }); req.on("error", (error) => resolve({ status: 599, body: error?.message || String(error) })); req.end(); }); } function parse(text) { try { return text ? JSON.parse(text) : null; } catch { return null; } } function strings(value) { return Array.isArray(value) ? value.filter((item) => typeof item === "string") : []; } const out = { ok: false, pipelineRun, pods: [], eventFound: false, degradedReason: "plan-artifacts-log-query-skipped", statusAuthority: "kubernetes-api-serviceaccount", }; if (!namespace || !pipelineRun) { process.stdout.write(JSON.stringify(out)); process.exit(0); } const selector = encodeURIComponent(`tekton.dev/pipelineRun=${pipelineRun}`); const podsResult = await request(`/api/v1/namespaces/${encodeURIComponent(namespace)}/pods?labelSelector=${selector}`); const pods = parse(podsResult.body); const items = Array.isArray(pods?.items) ? pods.items : []; const planPods = items.filter((pod) => { const labels = pod?.metadata?.labels || {}; const name = pod?.metadata?.name || ""; return labels["tekton.dev/pipelineTask"] === "plan-artifacts" || labels["tekton.dev/task"] === "plan-artifacts" || /plan-artifacts/u.test(name); }); out.pods = planPods.map((pod) => pod?.metadata?.name).filter(Boolean); const events = []; for (const podName of out.pods.slice(-4)) { const log = await request(`/api/v1/namespaces/${encodeURIComponent(namespace)}/pods/${encodeURIComponent(podName)}/log?tailLines=240`); if (log.status < 200 || log.status >= 300) continue; for (const raw of log.body.split(/\r?\n/u)) { const line = raw.trim(); if (!line.startsWith("{")) continue; const event = parse(line); if (event?.event === "g14-ci-plan") events.push(event); } } const latest = events.at(-1) || null; if (latest) { const audit = latest.artifactProvenanceAudit && typeof latest.artifactProvenanceAudit === "object" ? latest.artifactProvenanceAudit : null; const unsafeReuseServices = strings(audit?.unsafeReuseServices); const provenanceRebuildServices = strings(audit?.provenanceRebuildServices); Object.assign(out, { ok: true, eventFound: true, degradedReason: null, sourceCommitId: typeof latest.sourceCommitId === "string" ? latest.sourceCommitId : null, affectedServices: strings(latest.affectedServices), rolloutServices: strings(latest.rolloutServices), buildServices: strings(latest.buildServices), reusedServices: strings(latest.reusedServices), buildSkippedCount: typeof latest.buildSkippedCount === "number" ? latest.buildSkippedCount : null, artifactProvenanceAudit: audit, summary: `build=${strings(latest.buildServices).length} reuse=${strings(latest.reusedServices).length} unsafeReuse=${unsafeReuseServices.length} provenanceRebuild=${provenanceRebuildServices.length}`, disclosure: "parsed from plan-artifacts g14-ci-plan log event via Kubernetes pod logs", }); } else { out.degradedReason = podsResult.status >= 200 && podsResult.status < 300 ? "g14-ci-plan-event-not-found" : "plan-artifacts-pod-list-failed"; } process.stdout.write(JSON.stringify(out));