Merge pull request #1502 from pikasTech/codex/cicd-visibility-followup-1501
fix(cicd): bound branch follower drill-down visibility
This commit is contained in:
@@ -36,9 +36,15 @@ async function main() {
|
||||
if (resolution.taskRun === null) {
|
||||
console.log(JSON.stringify({
|
||||
ok: false,
|
||||
degradedReason: "taskrun-not-found",
|
||||
degradedReason: resolution.degradedReason || "taskrun-not-found",
|
||||
message: resolution.message || null,
|
||||
query: { namespace, taskRun: query, pipelineRun: pipelineRunName || null, pipelineRunPrefix: pipelineRunPrefix || null },
|
||||
candidates: resolution.candidates,
|
||||
resolution: {
|
||||
mode: resolution.mode,
|
||||
bounded: true,
|
||||
namespaceTaskRunList: false,
|
||||
},
|
||||
statusAuthority: useServiceAccount ? "kubernetes-api-serviceaccount" : "target-node-kubectl-raw",
|
||||
parsedDownstreamCliOutput: false,
|
||||
}));
|
||||
@@ -121,10 +127,21 @@ async function main() {
|
||||
async function resolveTaskRun() {
|
||||
const direct = await getJson(`/apis/tekton.dev/v1/namespaces/${encodeURIComponent(namespace)}/taskruns/${encodeURIComponent(query)}`, false);
|
||||
if (direct !== null) return { mode: "direct-name", taskRun: direct, candidates: [] };
|
||||
if (!pipelineRunName) {
|
||||
const fullNameMiss = pipelineRunPrefix.length > 0 && query.startsWith(`${pipelineRunPrefix}-`);
|
||||
return {
|
||||
mode: fullNameMiss ? "direct-name-not-found" : "pipeline-run-required",
|
||||
degradedReason: fullNameMiss ? "taskrun-not-found" : "pipeline-run-required",
|
||||
message: fullNameMiss
|
||||
? `TaskRun ${query} was not found in namespace ${namespace}`
|
||||
: "--pipeline-run is required for pipeline-task alias lookup; namespace-wide TaskRun listing is disabled",
|
||||
taskRun: null,
|
||||
candidates: [],
|
||||
};
|
||||
}
|
||||
const selectors = [];
|
||||
if (pipelineRunName) selectors.push(`tekton.dev/pipelineRun=${pipelineRunName},tekton.dev/pipelineTask=${query}`);
|
||||
selectors.push(`tekton.dev/pipelineTask=${query}`);
|
||||
selectors.push(`tekton.dev/task=${query}`);
|
||||
selectors.push(`tekton.dev/pipelineRun=${pipelineRunName},tekton.dev/pipelineTask=${query}`);
|
||||
selectors.push(`tekton.dev/pipelineRun=${pipelineRunName},tekton.dev/task=${query}`);
|
||||
for (const selector of selectors) {
|
||||
const list = await getJson(`/apis/tekton.dev/v1/namespaces/${encodeURIComponent(namespace)}/taskruns?labelSelector=${encodeURIComponent(selector)}`, false);
|
||||
const candidates = taskRunCandidates(list);
|
||||
|
||||
@@ -366,7 +366,7 @@ function parseFollower(root: Record<string, unknown>, index: number): FollowerSp
|
||||
const budgets = recordField(root, "budgets", label);
|
||||
const commands = recordField(root, "commands", label);
|
||||
const nativeStatus = recordField(root, "nativeStatus", label);
|
||||
const drillDown = recordField(root, "drillDown", label);
|
||||
const drillDown = asOptionalRecord(root.drillDown);
|
||||
const closeout = recordField(root, "closeout", label);
|
||||
const configRefs = stringMap(recordField(target, "configRefs", `${label}.target`), `${label}.target.configRefs`);
|
||||
return {
|
||||
@@ -406,7 +406,7 @@ function parseFollower(root: Record<string, unknown>, index: number): FollowerSp
|
||||
logs: parseCommand(recordField(commands, "logs", `${label}.commands`), `${label}.commands.logs`),
|
||||
},
|
||||
nativeStatus: parseNativeStatus(nativeStatus, `${label}.nativeStatus`),
|
||||
drillDown: parseDrillDown(drillDown, `${label}.drillDown`),
|
||||
drillDown: drillDown === null ? null : parseDrillDown(drillDown, `${label}.drillDown`),
|
||||
closeoutChecks: stringArrayField(closeout, "checks", `${label}.closeout`),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ function renderTaskRunHuman(payload: Record<string, unknown>): string {
|
||||
const timing = asOptionalRecord(result?.nodeCicdTiming);
|
||||
const query = asOptionalRecord(payload.query);
|
||||
const command = asOptionalRecord(payload.command);
|
||||
const identity = asOptionalRecord(command?.identity);
|
||||
return [
|
||||
`CI/CD BRANCH-FOLLOWER TASKRUN (${payload.ok === false ? "failed" : "ok"})`,
|
||||
"",
|
||||
@@ -59,9 +60,11 @@ function renderTaskRunHuman(payload: Record<string, unknown>): string {
|
||||
logs.length === 0 ? "" : `\nLOG TAILS\n${table(["POD", "CONTAINER", "STATUS", "REASON", "LINES", "BYTES", "TIMING", "MESSAGE"], logs.map(logRow))}`,
|
||||
errors.length === 0 ? "" : `\nERRORS\n${table(["POD", "CONTAINER", "REASON", "MESSAGE"], errors.map((item) => [item.pod, item.container, item.degradedReason, item.message]))}`,
|
||||
timing === null ? "" : `\nNODE_CICD_TIMING\n${JSON.stringify(timing, null, 2)}`,
|
||||
command === null ? "" : `\nTARGET COMMAND\n${table(["ROUTE", "SCRIPT", "NAMESPACE", "TASKRUN", "PIPELINERUN", "EXIT", "PARSE_ERROR"], [[identity?.route ?? "-", identity?.script ?? "-", identity?.namespace ?? "-", identity?.taskRun ?? "-", identity?.pipelineRun ?? "-", command.exitCode ?? "-", command.parseError ?? "-"]])}`,
|
||||
command?.stdoutTail ? `\nSTDOUT_TAIL\n${command.stdoutTail}` : "",
|
||||
command?.stderrTail ? `\nSTDERR_TAIL\n${command.stderrTail}` : "",
|
||||
"",
|
||||
`policy: tailLines=${policy?.logsTailLines ?? "-"} maxLogBytes=${policy?.maxLogBytes ?? "-"} timeoutSeconds=${policy?.taskRunTimeoutSeconds ?? "-"} maxContainers=${policy?.maxContainers ?? "-"}`,
|
||||
command?.stderrTail ? `stderr: ${command.stderrTail}` : "",
|
||||
"",
|
||||
].filter((line) => line !== "").join("\n");
|
||||
}
|
||||
|
||||
@@ -16,6 +16,30 @@ export async function runBranchFollowerTaskRunDrillDown(
|
||||
if (follower.nativeStatus.tekton === null) throw new Error(`follower ${follower.id} has no Tekton native status config`);
|
||||
const taskRun = options.taskRunName;
|
||||
if (taskRun === null) throw new Error("taskrun drill-down requires --taskrun <taskrun-name|pipeline-task>");
|
||||
if (follower.drillDown === null) {
|
||||
return {
|
||||
ok: false,
|
||||
action: "taskrun",
|
||||
follower: follower.id,
|
||||
adapter: follower.adapter,
|
||||
degradedReason: "drilldown-policy-missing",
|
||||
message: `follower ${follower.id} registry is missing drillDown policy; apply the current config before TaskRun drill-down`,
|
||||
query: {
|
||||
taskRun,
|
||||
pipelineRun: options.pipelineRunName,
|
||||
tektonNamespace: follower.nativeStatus.tekton.namespace,
|
||||
pipelineRunPrefix: follower.nativeStatus.tekton.pipelineRunPrefix,
|
||||
},
|
||||
policy: null,
|
||||
result: null,
|
||||
statusAuthority: options.inCluster ? "kubernetes-api-serviceaccount" : "target-node-kubectl-raw",
|
||||
parsedDownstreamCliOutput: false,
|
||||
next: {
|
||||
apply: "bun scripts/cli.ts cicd branch-follower apply --confirm --wait",
|
||||
status: `bun scripts/cli.ts cicd branch-follower status --follower ${follower.id}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
const policy = {
|
||||
taskRunTimeoutSeconds: options.timeoutSeconds ?? follower.drillDown.taskRunTimeoutSeconds,
|
||||
logsTailLines: options.logsTailLines ?? follower.drillDown.logsTailLines,
|
||||
@@ -42,7 +66,9 @@ export async function runBranchFollowerTaskRunDrillDown(
|
||||
].join("\n");
|
||||
const startedAt = Date.now();
|
||||
const result = runKubeScript(registry, options, script, "", policy.taskRunTimeoutSeconds * 1000);
|
||||
const parsed = result.exitCode === 0 ? parseJsonObject(result.stdout) : null;
|
||||
const parsedResult = result.exitCode === 0 ? parseJsonObject(result.stdout) : { value: null, error: "target-command-failed" };
|
||||
const parsed = parsedResult.value;
|
||||
const includeTails = result.exitCode !== 0 || parsed === null || parsed.ok === false;
|
||||
return {
|
||||
ok: result.exitCode === 0 && parsed !== null && parsed.ok !== false,
|
||||
action: "taskrun",
|
||||
@@ -59,10 +85,19 @@ export async function runBranchFollowerTaskRunDrillDown(
|
||||
policy,
|
||||
result: parsed,
|
||||
command: {
|
||||
identity: {
|
||||
route: options.inCluster ? "in-cluster" : registry.controller.kubeRoute,
|
||||
script: "scripts/native/cicd/taskrun-drilldown.mjs",
|
||||
namespace: follower.nativeStatus.tekton.namespace,
|
||||
taskRun,
|
||||
pipelineRun: options.pipelineRunName,
|
||||
},
|
||||
exitCode: result.exitCode,
|
||||
timedOut: result.timedOut,
|
||||
elapsedMs: Date.now() - startedAt,
|
||||
stderrTail: result.exitCode === 0 ? "" : redactText(tailText(result.stderr || result.stdout, 1200)),
|
||||
parseError: parsedResult.error,
|
||||
stdoutTail: includeTails ? redactText(tailText(result.stdout, 1600)) : "",
|
||||
stderrTail: includeTails ? redactText(tailText(result.stderr, 1200)) : "",
|
||||
},
|
||||
next: {
|
||||
status: `bun scripts/cli.ts cicd branch-follower status --follower ${follower.id}`,
|
||||
@@ -71,14 +106,16 @@ export async function runBranchFollowerTaskRunDrillDown(
|
||||
};
|
||||
}
|
||||
|
||||
function parseJsonObject(text: string): Record<string, unknown> | null {
|
||||
function parseJsonObject(text: string): { value: Record<string, unknown> | null; error: string | null } {
|
||||
const trimmed = text.trim();
|
||||
if (trimmed.length === 0) return null;
|
||||
if (trimmed.length === 0) return { value: null, error: "empty-stdout" };
|
||||
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;
|
||||
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
|
||||
? { value: parsed as Record<string, unknown>, error: null }
|
||||
: { value: null, error: "stdout-json-not-object" };
|
||||
} catch (error) {
|
||||
return { value: null, error: `stdout-json-parse-failed: ${error instanceof Error ? error.message : String(error)}` };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ export interface FollowerSpec {
|
||||
maxLogBytes: number;
|
||||
maxMessageBytes: number;
|
||||
maxContainers: number;
|
||||
};
|
||||
} | null;
|
||||
closeoutChecks: string[];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user