90 lines
3.8 KiB
TypeScript
90 lines
3.8 KiB
TypeScript
// SPEC: PJ2026-01060703 CI/CD branch follower native Kubernetes helpers.
|
|
// Responsibility: submit/probe Kubernetes Jobs and Tekton PipelineRuns via file-backed native scripts.
|
|
import { repoRoot, rootPath } from "./config";
|
|
import { runCommand, type CommandResult } from "./command";
|
|
import type { NativeK8sJobResult } from "./cicd-types";
|
|
|
|
const NATIVE_SCRIPT_DIR = "scripts/native/cicd";
|
|
|
|
interface NativeTimingOptions {
|
|
nativeTransportGraceSeconds: number;
|
|
nativePollIntervalSeconds: number;
|
|
}
|
|
|
|
export function runNativeTektonPipelineRun(namespace: string, pipelineRun: string, manifest: Record<string, unknown>, wait: boolean, timeoutSeconds: number, timing: NativeTimingOptions): CommandResult {
|
|
return runCommand(["node", rootPath(NATIVE_SCRIPT_DIR, "submit-pipelinerun.mjs")], repoRoot, {
|
|
input: Buffer.from(JSON.stringify(manifest), "utf8").toString("base64"),
|
|
timeoutMs: (timeoutSeconds + timing.nativeTransportGraceSeconds) * 1000,
|
|
env: {
|
|
...process.env,
|
|
NAMESPACE: namespace,
|
|
PIPELINERUN: pipelineRun,
|
|
WAIT: wait ? "true" : "false",
|
|
TIMEOUT_SECONDS: String(timeoutSeconds),
|
|
POLL_INTERVAL_SECONDS: String(timing.nativePollIntervalSeconds),
|
|
},
|
|
});
|
|
}
|
|
|
|
export function runNativeK8sJob(namespace: string, jobName: string, manifest: Record<string, unknown>, timeoutSeconds: number, logContainer: string, timing: NativeTimingOptions): NativeK8sJobResult {
|
|
const result = runCommand(["node", rootPath(NATIVE_SCRIPT_DIR, "native-job.mjs")], repoRoot, {
|
|
input: Buffer.from(JSON.stringify(manifest), "utf8").toString("base64"),
|
|
timeoutMs: (timeoutSeconds + timing.nativeTransportGraceSeconds) * 1000,
|
|
env: {
|
|
...process.env,
|
|
NAMESPACE: namespace,
|
|
JOB_NAME: jobName,
|
|
LOG_CONTAINER: logContainer,
|
|
TIMEOUT_SECONDS: String(timeoutSeconds),
|
|
POLL_INTERVAL_SECONDS: String(timing.nativePollIntervalSeconds),
|
|
},
|
|
});
|
|
const parsed = parseJsonObject(result.stdout);
|
|
const nativeWaitTimedOut = parsed?.timedOut === true && parsed?.failed !== true;
|
|
return {
|
|
ok: result.exitCode === 0 && parsed?.ok === true,
|
|
completed: parsed?.completed === true,
|
|
failed: parsed?.failed === true || (result.exitCode !== 0 && !nativeWaitTimedOut),
|
|
timedOut: nativeWaitTimedOut || result.timedOut,
|
|
created: parsed?.created === true,
|
|
reused: parsed?.reused === true,
|
|
jobName,
|
|
namespace,
|
|
polls: numberOrNull(parsed?.polls) ?? 0,
|
|
elapsedMs: numberOrNull(parsed?.elapsedMs) ?? 0,
|
|
logsTail: stringOrNull(parsed?.logsTail),
|
|
summary: asOptionalRecord(parsed?.summary),
|
|
conditionReason: stringOrNull(parsed?.conditionReason),
|
|
conditionMessage: stringOrNull(parsed?.conditionMessage) ?? (result.exitCode === 0 || nativeWaitTimedOut ? null : tailText(result.stderr || result.stdout, 500)),
|
|
statusAuthority: "kubernetes-api-serviceaccount",
|
|
parsedDownstreamCliOutput: false,
|
|
};
|
|
}
|
|
|
|
function parseJsonObject(text: string): Record<string, unknown> | null {
|
|
const trimmed = text.trim();
|
|
if (trimmed.length === 0) return null;
|
|
try {
|
|
const parsed = JSON.parse(trimmed) as unknown;
|
|
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed as Record<string, unknown> : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function asOptionalRecord(value: unknown): Record<string, unknown> | null {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
|
}
|
|
|
|
function stringOrNull(value: unknown): string | null {
|
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
}
|
|
|
|
function numberOrNull(value: unknown): number | null {
|
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
}
|
|
|
|
function tailText(text: string, maxChars: number): string {
|
|
return text.length <= maxChars ? text : text.slice(text.length - maxChars);
|
|
}
|