fix: harden monitor migration snapshots
This commit is contained in:
@@ -251,7 +251,7 @@ function runSentinelMigrationJob(spec: HwlabRuntimeLaneSpec, options: Extract<We
|
||||
const payload = sentinelPayloadFromLogs(String(probe.logsTail ?? ""));
|
||||
const ok = probe.succeeded === true && payload.ok === true;
|
||||
const failure = probe.failed === true ? migrationJobFailureSummary(probe, payload) : null;
|
||||
return rendered(ok, command, `ok=${ok}`, { ...plan, ok, mutation: true, phase: probe.succeeded === true ? "succeeded" : "failed", result: payload, failure, next: failure === null ? undefined : migrationJobFailureNext(state, namespace, jobName), valuesRedacted: true });
|
||||
return rendered(ok, command, `ok=${ok}`, { ...plan, ok, mutation: true, phase: probe.succeeded === true ? "succeeded" : "failed", result: probe.succeeded === true ? payload : undefined, failure, next: failure === null ? undefined : migrationJobFailureNext(state, namespace, jobName), valuesRedacted: true });
|
||||
}
|
||||
runCommand(["sleep", "2"], repoRoot, { timeoutMs: 3_000 });
|
||||
}
|
||||
@@ -262,19 +262,28 @@ export function migrationJobFailureSummary(probe: Record<string, unknown>, paylo
|
||||
const containers = Array.isArray(probe.containers) ? probe.containers.filter(isRecord).map((item) => ({ name: stringAtNullable(item, "name"), phase: stringAtNullable(item, "phase"), exitCode: finiteNumberOrNull(item.exitCode), reason: stringAtNullable(item, "reason"), message: short(stringAtNullable(item, "message") ?? "") })).filter((item) => item.phase === "terminated" || item.exitCode !== null || item.reason !== null) : [];
|
||||
const failed = containers.find((item) => item.exitCode !== null && item.exitCode !== 0) ?? containers[0] ?? null;
|
||||
const logsTail = redactMigrationJobLogTail(short(String(probe.logsTail ?? "")));
|
||||
return { phase: stringAtNullable(probe, "podPhase") ?? "Failed", container: failed?.name ?? null, exitCode: failed?.exitCode ?? null, status: failed?.reason ?? stringAtNullable(probe, "reason") ?? "JobFailed", message: redactMigrationJobLogTail(short(stringAtNullable(payload, "error") ?? stringAtNullable(payload, "message") ?? failed?.message ?? "migration Job failed before a structured command result was emitted")), logsTail, valuesRedacted: true };
|
||||
const nestedError = isRecord(payload.error) ? payload.error : null;
|
||||
return { phase: stringAtNullable(probe, "podPhase") ?? "Failed", container: failed?.name ?? null, exitCode: failed?.exitCode ?? null, status: failed?.reason ?? stringAtNullable(nestedError, "code") ?? stringAtNullable(probe, "reason") ?? "JobFailed", message: redactMigrationJobLogTail(short(stringAtNullable(nestedError, "message") ?? stringAtNullable(payload, "message") ?? failed?.message ?? "migration Job failed before a structured command result was emitted")), logsTail, valuesRedacted: true };
|
||||
}
|
||||
|
||||
function redactMigrationJobLogTail(value: string): string {
|
||||
return value.replace(/(DATABASE_URL|password|token|secret)\s*[=:]\s*[^\s]+/giu, "$1=<redacted>");
|
||||
return value
|
||||
.replace(/\b(?:postgres(?:ql)?|mysql):\/\/[^\s"']+/giu, "<redacted-url>")
|
||||
.replace(/\bBearer\s+[^\s"']+/giu, "Bearer <redacted>")
|
||||
.replace(/(["']?(?:DATABASE_URL|password|token|secret)["']?\s*[=:]\s*["']?)[^\s,"'}\]]+/giu, "$1<redacted>")
|
||||
.replace(/([?&](?:password|token|secret)=[^&\s"']+)/giu, "<redacted-query>");
|
||||
}
|
||||
|
||||
function migrationJobFailureNext(state: SentinelCicdState, namespace: string, jobName: string): Record<string, string> {
|
||||
return { logs: `trans ${stringAt(state.controlPlaneNode, "kubeRoute")} kubectl -n ${namespace} logs job/${jobName} --all-containers=true --tail=120`, describe: `trans ${stringAt(state.controlPlaneNode, "kubeRoute")} kubectl -n ${namespace} describe job/${jobName}` };
|
||||
}
|
||||
|
||||
function sameMigrationSource(left: MonitorSqliteSource, right: MonitorSqliteSource): boolean {
|
||||
return left.sentinelId === right.sentinelId && left.node === right.node && left.lane === right.lane && left.path === right.path && left.runtimeConfigRef === right.runtimeConfigRef && left.stateRoot === right.stateRoot && left.ownerPvc === right.ownerPvc && left.artifactSources.length === right.artifactSources.length && left.artifactSources.every((source, index) => source.ownerPvc === right.artifactSources[index]?.ownerPvc && source.mountPath === right.artifactSources[index]?.mountPath && source.hostPath === right.artifactSources[index]?.hostPath);
|
||||
export function sameMigrationSource(left: MonitorSqliteSource, right: MonitorSqliteSource): boolean {
|
||||
return left.sentinelId === right.sentinelId && left.node === right.node && left.lane === right.lane && left.path === right.path && left.runtimeConfigRef === right.runtimeConfigRef && left.stateRoot === right.stateRoot && left.ownerPvc === right.ownerPvc && left.artifactSources.length === right.artifactSources.length && left.artifactSources.every((source, index) => source.ownerPvc === right.artifactSources[index]?.ownerPvc && source.mountPath === right.artifactSources[index]?.mountPath && source.hostPath === right.artifactSources[index]?.hostPath) && monitorGapSet(left.legacyArtifactGaps) === monitorGapSet(right.legacyArtifactGaps);
|
||||
}
|
||||
|
||||
function monitorGapSet(gaps: readonly { readonly runId: string; readonly artifactCount: number; readonly stateDir: string; readonly reportSha256: string }[] | undefined): string {
|
||||
return JSON.stringify([...(gaps ?? [])].sort((left, right) => left.runId.localeCompare(right.runId)));
|
||||
}
|
||||
|
||||
function resolvedMigrationSources(spec: HwlabRuntimeLaneSpec, sentinelId: string | null): readonly MonitorSqliteSource[] {
|
||||
|
||||
Reference in New Issue
Block a user