fix: expose node git mirror flush partial success

This commit is contained in:
Codex
2026-06-16 06:19:52 +00:00
parent 6f8fe9e098
commit b9ae7b4798
6 changed files with 126 additions and 9 deletions
+67 -2
View File
@@ -495,6 +495,63 @@ function compactRuntimeCommand(result: CommandResult): Record<string, unknown> {
};
}
function parseLastJsonLineObject(text: string): Record<string, unknown> {
for (const line of text.split(/\r?\n/u).reverse()) {
const trimmed = line.trim();
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) continue;
try {
return record(JSON.parse(trimmed) as unknown);
} catch {
// Keep scanning: command tails can mix kubectl status, git output, and JSON payloads.
}
}
return {};
}
function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
}
function nodeRuntimeGitMirrorRefSources(scoped: ReturnType<typeof parseNodeScopedDelegatedOptions>, mirror: NodeRuntimeGitMirrorTargetSpec): Record<string, unknown> {
return {
localSource: `refs/heads/${mirror.sourceBranch}`,
githubSource: `refs/mirror-stage/heads/${mirror.sourceBranch}`,
localGitops: `refs/heads/${mirror.gitopsBranch}`,
githubGitops: `refs/mirror-stage/heads/${mirror.gitopsBranch}`,
githubFieldsAreMirrorStageCache: true,
cacheRefresh: `bun scripts/cli.ts hwlab nodes git-mirror sync --node ${scoped.node} --lane ${scoped.lane} --confirm --wait`,
};
}
function nodeRuntimeGitMirrorFlushPartialSuccess(scoped: ReturnType<typeof parseNodeScopedDelegatedOptions>, mirror: NodeRuntimeGitMirrorTargetSpec, result: CommandResult): Record<string, unknown> | null {
if (scoped.action !== "flush") return null;
const text = `${result.stdout}\n${result.stderr}`;
const payload = parseLastJsonLineObject(text);
const partialSuccess = typeof payload.partialSuccess === "string" && payload.partialSuccess.length > 0
? payload.partialSuccess
: null;
const payloadStatus = typeof payload.status === "string" && payload.status.length > 0 ? payload.status : null;
const branch = escapeRegExp(mirror.gitopsBranch);
const pushSucceededInGitOutput = new RegExp(`\\b${branch}\\s*->\\s*${branch}\\b`, "u").test(text);
const postPushFetchFailed = /kex_exchange_identification|Connection closed by remote host|Could not read from remote repository|fatal:.*fetch|fetch-pack|early EOF/iu.test(text);
if (partialSuccess !== "push-succeeded-fetch-failed" && !(result.exitCode !== 0 && pushSucceededInGitOutput && postPushFetchFailed)) {
return null;
}
return {
status: payloadStatus ?? "partial-success",
partialSuccess: "push-succeeded-fetch-failed",
degradedReason: "node-runtime-git-mirror-flush-post-push-fetch-failed",
retryable: true,
message: "GitOps push appears to have succeeded, but the post-push fetch/recheck failed. Refresh mirror-stage cache before deciding another flush is needed.",
payload: Object.keys(payload).length > 0 ? payload : null,
refSources: nodeRuntimeGitMirrorRefSources(scoped, mirror),
next: {
sync: `bun scripts/cli.ts hwlab nodes git-mirror sync --node ${scoped.node} --lane ${scoped.lane} --confirm --wait`,
status: `bun scripts/cli.ts hwlab nodes git-mirror status --node ${scoped.node} --lane ${scoped.lane}`,
},
};
}
function sshTcpPoolDiagnosticsFromCommand(spec: HwlabRuntimeLaneSpec, result: CommandResult): Record<string, unknown> | null {
if (isCommandSuccess(result)) return null;
const failureKind = classifySshTcpPoolFailure(`${result.stderr}\n${result.stdout}`);
@@ -1347,6 +1404,7 @@ function nodeRuntimeGitMirrorStatus(scoped: ReturnType<typeof parseNodeScopedDel
gitopsBranch: mirror.gitopsBranch,
resources,
summary,
refSources: nodeRuntimeGitMirrorRefSources(scoped, mirror),
result: compactRuntimeCommand(result),
degradedReason: ok ? undefined : "node-runtime-git-mirror-not-ready",
valuesPrinted: false,
@@ -1392,6 +1450,7 @@ function nodeRuntimeGitMirrorRun(scoped: ReturnType<typeof parseNodeScopedDelega
].join("\n");
const result = runNodeK3sScript(spec, script, scoped.timeoutSeconds);
const status = scoped.dryRun || !isCommandSuccess(result) ? undefined : nodeRuntimeGitMirrorStatus({ ...scoped, action: "status", dryRun: true, confirm: false });
const partialSuccess = nodeRuntimeGitMirrorFlushPartialSuccess(scoped, mirror, result);
return {
ok: isCommandSuccess(result) && (status === undefined || status.ok === true),
command: `hwlab nodes git-mirror ${scoped.action} --node ${scoped.node} --lane ${scoped.lane}`,
@@ -1404,8 +1463,14 @@ function nodeRuntimeGitMirrorRun(scoped: ReturnType<typeof parseNodeScopedDelega
manifest: scoped.dryRun ? manifest : undefined,
result: compactRuntimeCommand(result),
status,
degradedReason: isCommandSuccess(result) ? status?.ok === false ? "node-runtime-git-mirror-status-failed-after-action" : undefined : `node-runtime-git-mirror-${scoped.action}-failed`,
next: scoped.dryRun
partialSuccess: partialSuccess?.partialSuccess ?? undefined,
recovery: partialSuccess ?? undefined,
degradedReason: partialSuccess !== null
? "node-runtime-git-mirror-flush-post-push-fetch-failed"
: isCommandSuccess(result) ? status?.ok === false ? "node-runtime-git-mirror-status-failed-after-action" : undefined : `node-runtime-git-mirror-${scoped.action}-failed`,
next: partialSuccess !== null
? partialSuccess.next
: scoped.dryRun
? { run: `bun scripts/cli.ts hwlab nodes git-mirror ${scoped.action} --node ${scoped.node} --lane ${scoped.lane} --confirm` }
: { status: `bun scripts/cli.ts hwlab nodes git-mirror status --node ${scoped.node} --lane ${scoped.lane}` },
};