fix(cicd): add NC01 PaC status closeout

This commit is contained in:
Codex
2026-07-08 19:55:43 +02:00
parent 1ae53ffc4d
commit e3951035f3
16 changed files with 644 additions and 37 deletions
@@ -130,6 +130,13 @@ interface CommonOptions {
raw: boolean;
}
export interface PipelinesAsCodeNodeStatusOptions {
targetId: string | null;
nodeId: string | null;
full: boolean;
raw: boolean;
}
interface HistoryOptions extends CommonOptions {
limit: number;
detailId: string | null;
@@ -446,6 +453,75 @@ async function status(config: UniDeskConfig, options: CommonOptions): Promise<Re
};
}
export async function getPlatformInfraPipelinesAsCodeNodeStatus(config: UniDeskConfig, options: PipelinesAsCodeNodeStatusOptions): Promise<Record<string, unknown>> {
const pac = readPacConfig();
const target = resolveTarget(pac, options.targetId ?? options.nodeId);
const nodeId = options.nodeId ?? target.id;
const consumers = pac.consumers.filter((consumer) => consumer.node.toLowerCase() === nodeId.toLowerCase());
if (consumers.length === 0) {
throw new Error(`no Pipelines-as-Code consumers are configured for node ${nodeId}; known nodes: ${Array.from(new Set(pac.consumers.map((item) => item.node))).sort().join(", ")}`);
}
const startedAt = Date.now();
const statuses = await Promise.all(consumers.map(async (consumer) => {
const repository = resolveRepository(pac, consumer.repositoryRef);
try {
const current = await status(config, { targetId: target.id, consumerId: consumer.id, full: false, raw: false });
return nodeStatusRow(target.id, consumer, repository, current);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return {
consumer: consumer.id,
node: consumer.node,
lane: consumer.lane,
repository: `${repository.owner}/${repository.repo}`,
namespace: consumer.namespace,
pipeline: consumer.pipeline,
argoApplication: consumer.argoApplication,
ready: false,
ciReady: false,
argoReady: false,
runtimeReady: false,
diagnosticsReady: false,
reason: "status-read-failed",
hint: message,
error: message,
valuesPrinted: false,
};
}
}));
const ready = statuses.every((row) => row.ready === true);
return {
ok: ready,
action: "cicd-node-status",
mutation: false,
node: nodeId,
target: targetSummary(target),
config: {
path: configLabel,
source: "Gitea Repository CR + Tekton PipelineRun/TaskRun + Argo + runtime live objects.",
displayTimeZone: pac.display.timeZone,
valuesPrinted: false,
},
consumers: statuses,
summary: {
ready,
total: statuses.length,
readyCount: statuses.filter((row) => row.ready === true).length,
blockedCount: statuses.filter((row) => row.ready !== true).length,
elapsedMs: Date.now() - startedAt,
valuesPrinted: false,
},
next: {
status: `bun scripts/cli.ts cicd status --node ${nodeId}`,
history: `bun scripts/cli.ts platform-infra pipelines-as-code history --target ${target.id} --limit 10`,
closeout: `bun scripts/cli.ts platform-infra pipelines-as-code closeout --target ${target.id} --wait`,
valuesPrinted: false,
},
details: options.full || options.raw ? statuses : undefined,
valuesPrinted: false,
};
}
async function closeout(config: UniDeskConfig, options: CloseoutOptions): Promise<Record<string, unknown>> {
const pac = readPacConfig();
const target = resolveTarget(pac, options.targetId);
@@ -723,6 +799,75 @@ function statusSummary(payload: Record<string, unknown>): Record<string, unknown
};
}
function nodeStatusRow(targetId: string, consumer: PacConsumer, repository: PacRepository, current: Record<string, unknown>): Record<string, unknown> {
const summary = record(current.summary);
const latest = record(summary.latestPipelineRun);
const artifact = record(summary.artifact);
const argo = record(summary.argo);
const runtime = record(summary.runtime);
const diagnostics = record(summary.diagnostics);
const pipelineGate = record(summary.pipelineRunGate);
const desired = numericValue(runtime.replicas);
const readyReplicas = numericValue(runtime.readyReplicas);
const runtimeReady = desired !== null && desired > 0 && readyReplicas === desired;
const argoReady = stringValue(argo.sync) === "Synced" && stringValue(argo.health) === "Healthy";
const ciReady = pipelineGate.ok === true;
const diagnosticsReady = diagnostics.ok !== false;
const ready = current.ok === true && ciReady && argoReady && runtimeReady && diagnosticsReady;
return {
consumer: consumer.id,
node: consumer.node,
lane: consumer.lane,
repository: `${repository.owner}/${repository.repo}`,
namespace: consumer.namespace,
pipeline: consumer.pipeline,
argoApplication: consumer.argoApplication,
ready,
ciReady,
argoReady,
runtimeReady,
diagnosticsReady,
latestPipelineRun: stringValue(latest.name),
pipelineStatus: statusText(latest),
durationSeconds: latest.durationSeconds ?? null,
sourceCommit: stringValue(latest.sourceCommit),
imageStatus: stringValue(artifact.imageStatus),
envReuse: envReuseText(record(artifact.envReuse)),
digest: stringValue(artifact.digest),
gitopsCommit: stringValue(artifact.gitopsCommit),
argo: {
sync: stringValue(argo.sync),
health: stringValue(argo.health),
revision: stringValue(argo.revision),
},
runtime: {
deployment: stringValue(runtime.deployment),
readyReplicas: runtime.readyReplicas ?? null,
replicas: runtime.replicas ?? null,
digest: stringValue(runtime.digest),
image: stringValue(runtime.image),
},
diagnostics: {
ok: diagnostics.ok !== false,
code: stringValue(diagnostics.code),
phase: stringValue(diagnostics.phase),
hint: compactLine(stringValue(diagnostics.hint)),
},
reason: ready ? "ready" : nodeStatusReason({ ciReady, argoReady, runtimeReady, diagnosticsReady, pipelineStatus: statusText(latest), diagnostics }),
statusCommand: `bun scripts/cli.ts platform-infra pipelines-as-code status --target ${targetId} --consumer ${consumer.id}`,
historyCommand: `bun scripts/cli.ts platform-infra pipelines-as-code history --target ${targetId} --consumer ${consumer.id} --limit 10`,
valuesPrinted: false,
};
}
function nodeStatusReason(input: { ciReady: boolean; argoReady: boolean; runtimeReady: boolean; diagnosticsReady: boolean; pipelineStatus: string; diagnostics: Record<string, unknown> }): string {
if (!input.ciReady) return `ci:${input.pipelineStatus}`;
if (!input.argoReady) return "argo-not-ready";
if (!input.runtimeReady) return "runtime-not-ready";
if (!input.diagnosticsReady) return stringValue(input.diagnostics.code, "diagnostics-not-ready");
return "not-ready";
}
function policyChecks(repository: PacRepository): Array<Record<string, unknown>> {
return [
{ name: "single-path", ok: true, detail: "Gitea webhook -> Pipelines-as-Code -> Tekton -> Argo/k8s runtime; no Gitea Actions/act_runner/branch-follower fallback." },