fix: 收敛 PaC 自动交付提示
This commit is contained in:
+3
-119
@@ -5,9 +5,8 @@ import { existsSync, readFileSync } from "node:fs";
|
||||
import { runCommand, type CommandResult } from "./command";
|
||||
import { repoRoot, rootPath } from "./config";
|
||||
import type { AdapterSummary, BranchFollowerDebugStep, BranchFollowerRegistry, FollowerSpec, FollowerState, K8sStateRead, ParsedOptions } from "./cicd-types";
|
||||
import { renderControllerDebugJob, waitForJobShell } from "./cicd-controller-render";
|
||||
import { taskRunItems } from "./cicd-taskruns";
|
||||
import { redactText, shQuote } from "./platform-infra-ops-library";
|
||||
import { redactText } from "./platform-infra-ops-library";
|
||||
|
||||
type KubeScriptRunner = (registry: BranchFollowerRegistry, options: ParsedOptions, script: string, input: string, timeoutMs: number) => CommandResult;
|
||||
|
||||
@@ -23,7 +22,6 @@ export interface CicdDebugDeps {
|
||||
export async function buildDebugStep(registry: BranchFollowerRegistry, options: ParsedOptions, deps: CicdDebugDeps): Promise<Record<string, unknown>> {
|
||||
const step = options.debugStep ?? "state-read";
|
||||
if (options.followerId === null) throw new Error("debug-step requires --follower <id>");
|
||||
if (!options.inCluster) return runTargetDebugStepJob(registry, options, step, deps);
|
||||
|
||||
const selected = deps.selectFollowers(registry, options, { includeDisabled: true });
|
||||
if (selected.length !== 1) throw new Error("debug-step operates on exactly one follower");
|
||||
@@ -53,7 +51,7 @@ export async function buildDebugStep(registry: BranchFollowerRegistry, options:
|
||||
action: "debug-step",
|
||||
step,
|
||||
follower: follower.id,
|
||||
execution: "k8s-native-in-cluster",
|
||||
execution: options.inCluster ? "k8s-native-in-cluster" : "operator-readonly",
|
||||
dryRun: !options.confirm,
|
||||
stateBefore: stateSnapshot(before, follower.id),
|
||||
stateWrite: { ok: false, skipped: true, reason: "stored-state-missing" },
|
||||
@@ -74,7 +72,7 @@ export async function buildDebugStep(registry: BranchFollowerRegistry, options:
|
||||
action: "debug-step",
|
||||
step,
|
||||
follower: follower.id,
|
||||
execution: "k8s-native-in-cluster",
|
||||
execution: options.inCluster ? "k8s-native-in-cluster" : "operator-readonly",
|
||||
dryRun: !options.confirm,
|
||||
stateBefore: stateSnapshot(before, follower.id),
|
||||
controllerSource,
|
||||
@@ -128,123 +126,10 @@ export function renderDebugStepHuman(payload: Record<string, unknown>): string {
|
||||
`controller-source: ${next?.controllerSource ?? "-"}`,
|
||||
`status-read: ${next?.statusRead ?? "-"}`,
|
||||
`decide: ${next?.decide ?? "-"}`,
|
||||
`state-write: ${next?.stateWrite ?? "-"}`,
|
||||
"",
|
||||
].filter((line) => line !== "").join("\n");
|
||||
}
|
||||
|
||||
function runTargetDebugStepJob(registry: BranchFollowerRegistry, options: ParsedOptions, step: BranchFollowerDebugStep, deps: CicdDebugDeps): Record<string, unknown> {
|
||||
const timeoutSeconds = options.timeoutSeconds ?? registry.controller.budgets.runOnceSeconds;
|
||||
const jobName = `${registry.controller.deploymentName}-debug-${step}-${Date.now().toString(36)}`.replace(/[^a-z0-9-]+/gu, "-").slice(0, 63);
|
||||
const manifest = renderControllerDebugJob(registry, options, jobName, step, timeoutSeconds);
|
||||
const manifestYaml = `${Bun.YAML.stringify(manifest).trim()}\n`;
|
||||
const script = [
|
||||
"set -eu",
|
||||
"tmp=$(mktemp)",
|
||||
"base64 -d >\"$tmp\" <<'UNIDESK_CICD_DEBUG_JOB_B64'",
|
||||
Buffer.from(manifestYaml, "utf8").toString("base64"),
|
||||
"UNIDESK_CICD_DEBUG_JOB_B64",
|
||||
`kubectl -n ${shQuote(registry.controller.namespace)} delete job ${shQuote(jobName)} --ignore-not-found=true >/dev/null 2>&1 || true`,
|
||||
`kubectl apply --server-side --force-conflicts --field-manager=${shQuote(registry.controller.fieldManager)} -f "$tmp" >/dev/null`,
|
||||
waitForJobShell(registry.controller.namespace, jobName, timeoutSeconds),
|
||||
].join("\n");
|
||||
const result = deps.runKubeScript(registry, options, script, "", (timeoutSeconds + registry.controller.budgets.reconcileTransportGraceSeconds) * 1000);
|
||||
const parsed = parseLastJsonObject(result.stdout);
|
||||
const state = deps.readK8sState(registry, options);
|
||||
const followerId = options.followerId ?? "";
|
||||
const compact = compactTargetDebugResult(parsed);
|
||||
const ok = result.exitCode === 0 && parsed?.ok !== false;
|
||||
const includeTargetTail = !ok || parsed === null;
|
||||
const fallbackStateAfter = stateSnapshot(state, followerId);
|
||||
return {
|
||||
ok,
|
||||
action: "debug-step",
|
||||
step,
|
||||
follower: followerId,
|
||||
execution: "k8s-native-debug-job",
|
||||
dryRun: !options.confirm,
|
||||
stateBefore: compact?.stateBefore ?? compactStateLike(asOptionalRecord(parsed?.stateBefore)),
|
||||
controllerSource: compact?.controllerSource ?? asOptionalRecord(parsed?.controllerSource),
|
||||
status: compact?.status ?? null,
|
||||
decision: compact?.decision ?? null,
|
||||
stateWrite: compact?.stateWrite ?? null,
|
||||
target: {
|
||||
name: jobName,
|
||||
namespace: registry.controller.namespace,
|
||||
exitCode: result.exitCode,
|
||||
timedOut: result.timedOut,
|
||||
parsed: parsed !== null,
|
||||
stdoutTail: includeTargetTail ? redactText(tailText(result.stdout, 1000)) : "",
|
||||
stderrTail: includeTargetTail ? redactText(tailText(result.stderr, 800)) : "",
|
||||
},
|
||||
stateAfter: compact?.stateAfter ?? compactStateLike(asOptionalRecord(parsed?.stateAfter) ?? fallbackStateAfter),
|
||||
parsedDownstreamCliOutput: false,
|
||||
next: debugNext(followerId),
|
||||
};
|
||||
}
|
||||
|
||||
function compactTargetDebugResult(parsed: Record<string, unknown> | null): Record<string, unknown> | null {
|
||||
if (parsed === null) return null;
|
||||
const stateBefore = asOptionalRecord(parsed.stateBefore);
|
||||
const controllerSource = asOptionalRecord(parsed.controllerSource);
|
||||
const status = asOptionalRecord(parsed.status);
|
||||
const decision = asOptionalRecord(parsed.decision);
|
||||
const stateWrite = asOptionalRecord(parsed.stateWrite);
|
||||
const stateAfter = asOptionalRecord(parsed.stateAfter);
|
||||
return {
|
||||
ok: parsed.ok === true,
|
||||
action: stringOrNull(parsed.action),
|
||||
step: stringOrNull(parsed.step),
|
||||
follower: stringOrNull(parsed.follower),
|
||||
stateBefore: compactStateLike(stateBefore),
|
||||
controllerSource: controllerSource === null ? null : {
|
||||
ok: controllerSource.ok === true,
|
||||
repository: stringOrNull(controllerSource.repository),
|
||||
branch: stringOrNull(controllerSource.branch),
|
||||
head: stringOrNull(controllerSource.head),
|
||||
registrySha256: stringOrNull(controllerSource.registrySha256),
|
||||
closeoutRereadMarker: controllerSource.closeoutRereadMarker === true,
|
||||
branchFollowerFile: asOptionalRecord(controllerSource.branchFollowerFile),
|
||||
errors: Array.isArray(controllerSource.errors) ? controllerSource.errors.map(String).slice(0, 5) : [],
|
||||
},
|
||||
status: status === null ? null : {
|
||||
ok: status.ok === true,
|
||||
phase: stringOrNull(status.phase),
|
||||
observedSha: stringOrNull(status.observedSha),
|
||||
targetSha: stringOrNull(status.targetSha),
|
||||
aligned: status.aligned === true ? true : status.aligned === false ? false : null,
|
||||
pipelineRun: stringOrNull(status.pipelineRun),
|
||||
message: stringOrNull(status.message),
|
||||
gates: asOptionalRecord(status.gates),
|
||||
},
|
||||
decision: decision === null ? null : {
|
||||
phase: stringOrNull(decision.phase),
|
||||
observedSha: stringOrNull(decision.observedSha),
|
||||
targetSha: stringOrNull(decision.targetSha),
|
||||
decision: stringOrNull(decision.decision),
|
||||
totalSeconds: numberOrNull(asOptionalRecord(decision.timings)?.totalSeconds),
|
||||
totalStatus: stringOrNull(asOptionalRecord(decision.timings)?.totalStatus),
|
||||
},
|
||||
stateWrite,
|
||||
stateAfter: compactStateLike(stateAfter),
|
||||
};
|
||||
}
|
||||
|
||||
function compactStateLike(value: Record<string, unknown> | null): Record<string, unknown> | null {
|
||||
if (value === null) return null;
|
||||
const metadata = asOptionalRecord(value.metadata);
|
||||
return {
|
||||
ok: value.ok === true,
|
||||
phase: stringOrNull(value.phase),
|
||||
observedSha: stringOrNull(value.observedSha),
|
||||
targetSha: stringOrNull(value.targetSha),
|
||||
totalSeconds: numberOrNull(value.totalSeconds),
|
||||
timingStatus: stringOrNull(value.timingStatus),
|
||||
resourceVersion: stringOrNull(metadata?.resourceVersion),
|
||||
rawStateDiagnostic: asOptionalRecord(value.rawStateDiagnostic),
|
||||
};
|
||||
}
|
||||
|
||||
function controllerSourceSnapshot(registry: BranchFollowerRegistry): Record<string, unknown> {
|
||||
const head = commandText(["git", "rev-parse", "HEAD"]);
|
||||
const branch = commandText(["git", "symbolic-ref", "--short", "HEAD"]);
|
||||
@@ -573,7 +458,6 @@ function debugNext(followerId: string): Record<string, string> {
|
||||
controllerSource: `bun scripts/cli.ts cicd branch-follower debug-step --follower ${followerId} --step controller-source`,
|
||||
statusRead: `bun scripts/cli.ts cicd branch-follower debug-step --follower ${followerId} --step status-read`,
|
||||
decide: `bun scripts/cli.ts cicd branch-follower debug-step --follower ${followerId} --step decide`,
|
||||
stateWrite: `bun scripts/cli.ts cicd branch-follower debug-step --follower ${followerId} --step state-write --confirm`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user