fix: pre-sync hwlab v02 git mirror
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { gitMirrorFlushJobManifest, gitMirrorSyncJobManifest, hwlabG14MonitorStateFileName, parsePipelineTaskRunMetrics, rolloutRecordBody, semanticChangelogBullets } from "./src/hwlab-g14";
|
||||
import { gitMirrorFlushJobManifest, gitMirrorSyncJobManifest, gitMirrorV02SyncRequirement, hwlabG14MonitorStateFileName, parseGitMirrorStatusRefs, parsePipelineTaskRunMetrics, rolloutRecordBody, semanticChangelogBullets, v02PipelineServiceIds } from "./src/hwlab-g14";
|
||||
|
||||
function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
|
||||
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
|
||||
@@ -40,6 +40,33 @@ const flushPodSpec = record(flushTemplate.spec);
|
||||
const flushContainer = record(Array.isArray(flushPodSpec.containers) ? flushPodSpec.containers[0] : null);
|
||||
assertCondition(JSON.stringify(flushContainer.command) === JSON.stringify(["/script/flush.sh"]), "git mirror flush Job must run the flush script", gitMirrorFlushJob);
|
||||
|
||||
const gitMirrorStatusRaw = [
|
||||
'lastSync={"ok":true}',
|
||||
"lastWrite=",
|
||||
"lastFlush=",
|
||||
'refs={"refs":{"localV02":"0cf79ecc9d8a784b7712b0b3a58d5e39025ba0dc","localG14":"1111111111111111111111111111111111111111","localGitops":"2222222222222222222222222222222222222222","githubGitops":"3333333333333333333333333333333333333333"},"pendingFlush":true}',
|
||||
].join("\n");
|
||||
const parsedGitMirrorRefs = parseGitMirrorStatusRefs(gitMirrorStatusRaw);
|
||||
assertCondition(
|
||||
parsedGitMirrorRefs.refs.localV02 === "0cf79ecc9d8a784b7712b0b3a58d5e39025ba0dc",
|
||||
"git mirror status parser must expose local v0.2 ref for trigger pre-sync",
|
||||
parsedGitMirrorRefs,
|
||||
);
|
||||
assertCondition(parsedGitMirrorRefs.pendingFlush === true, "git mirror status parser must preserve pending flush signal", parsedGitMirrorRefs);
|
||||
assertCondition(
|
||||
gitMirrorV02SyncRequirement("0cf79ecc9d8a784b7712b0b3a58d5e39025ba0dc", gitMirrorStatusRaw).required === false,
|
||||
"trigger-current must not sync mirror when local v0.2 already matches source commit",
|
||||
);
|
||||
assertCondition(
|
||||
gitMirrorV02SyncRequirement("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", gitMirrorStatusRaw).required === true,
|
||||
"trigger-current must sync mirror before creating PipelineRun when local v0.2 is stale",
|
||||
);
|
||||
assertCondition(
|
||||
!v02PipelineServiceIds().includes("hwlab-cli"),
|
||||
"v0.2 PipelineRun service matrix must not build hwlab-cli because cli is short-connection source tool",
|
||||
v02PipelineServiceIds(),
|
||||
);
|
||||
|
||||
const prBody = [
|
||||
"## 背景",
|
||||
"",
|
||||
@@ -145,6 +172,8 @@ console.log(JSON.stringify({
|
||||
"once dry-run jobs have a distinct diagnostic state file",
|
||||
"git mirror sync is a manual devops-infra Job, not a CronJob",
|
||||
"git mirror flush is a manual devops-infra Job, not a CronJob",
|
||||
"trigger-current can decide whether v0.2 git mirror pre-sync is required",
|
||||
"v0.2 PipelineRun service matrix excludes hwlab-cli",
|
||||
"rollout brief includes natural-language changelog before automatic diff summary",
|
||||
"semantic changelog extracts Chinese summary sections",
|
||||
"rollout brief includes lazy-build reused/rebuild metrics and service durations",
|
||||
|
||||
+141
-1
@@ -40,9 +40,13 @@ const V02_SERVICE_IDS = [
|
||||
"hwlab-device-pod",
|
||||
"hwlab-gateway",
|
||||
"hwlab-edge-proxy",
|
||||
"hwlab-cli",
|
||||
"hwlab-agent-skills",
|
||||
];
|
||||
|
||||
export function v02PipelineServiceIds(): string[] {
|
||||
return [...V02_SERVICE_IDS];
|
||||
}
|
||||
|
||||
const G14_CI_TOOLS_IMAGE_REPO = "127.0.0.1:5000/hwlab/hwlab-ci-node-tools";
|
||||
const G14_CI_TOOLS_BASE_TAG = "node22-alpine-v1";
|
||||
const DEFAULT_INTERVAL_SECONDS = 600;
|
||||
@@ -313,6 +317,10 @@ function record(value: unknown): Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
||||
}
|
||||
|
||||
function stringOrNull(value: unknown): string | null {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
||||
}
|
||||
|
||||
function nested(value: unknown, keys: string[]): unknown {
|
||||
let current = value;
|
||||
for (const key of keys) current = record(current)[key];
|
||||
@@ -438,6 +446,11 @@ function commandErrorSummary(result: CommandJsonResult): string {
|
||||
return text.slice(0, 4000);
|
||||
}
|
||||
|
||||
function tailText(value: unknown, maxBytes = 2000): string {
|
||||
const text = String(value ?? "").trim();
|
||||
return text.length <= maxBytes ? text : text.slice(-maxBytes);
|
||||
}
|
||||
|
||||
function listCleanupPipelineRuns(options: G14ControlPlaneOptions): Record<string, unknown>[] {
|
||||
const result = g14K3s([
|
||||
"kubectl",
|
||||
@@ -932,6 +945,7 @@ function runV02ControlPlane(options: G14ControlPlaneOptions): Record<string, unk
|
||||
}
|
||||
const before = getPipelineRunCompact(v02PipelineRunName(sourceCommit));
|
||||
if (options.dryRun) {
|
||||
const gitMirrorPreSync = preSyncV02GitMirror(sourceCommit, options);
|
||||
return {
|
||||
ok: true,
|
||||
command: "hwlab g14 control-plane trigger-current --lane v02",
|
||||
@@ -940,6 +954,7 @@ function runV02ControlPlane(options: G14ControlPlaneOptions): Record<string, unk
|
||||
sourceCommit,
|
||||
pipelineRun: v02PipelineRunName(sourceCommit),
|
||||
before,
|
||||
gitMirrorPreSync,
|
||||
manifest: v02PipelineRunManifest(sourceCommit),
|
||||
next: { triggerCurrent: "bun scripts/cli.ts hwlab g14 control-plane trigger-current --lane v02 --confirm" },
|
||||
};
|
||||
@@ -956,6 +971,21 @@ function runV02ControlPlane(options: G14ControlPlaneOptions): Record<string, unk
|
||||
degradedReason: "refuse-active-or-successful-pipelinerun",
|
||||
};
|
||||
}
|
||||
const gitMirrorPreSync = preSyncV02GitMirror(sourceCommit, options);
|
||||
if (gitMirrorPreSync.ok !== true) {
|
||||
return {
|
||||
ok: false,
|
||||
command: "hwlab g14 control-plane trigger-current --lane v02",
|
||||
lane: "v02",
|
||||
mode: "confirmed-trigger",
|
||||
phase: "git-mirror-pre-sync",
|
||||
sourceCommit,
|
||||
pipelineRun: v02PipelineRunName(sourceCommit),
|
||||
before,
|
||||
gitMirrorPreSync,
|
||||
degradedReason: "git-mirror-pre-sync-failed",
|
||||
};
|
||||
}
|
||||
const deletePipelineRun = deleteV02PipelineRun(v02PipelineRunName(sourceCommit));
|
||||
const createPipelineRun = isCommandSuccess(deletePipelineRun)
|
||||
? createV02PipelineRun(sourceCommit, options.timeoutSeconds)
|
||||
@@ -968,6 +998,7 @@ function runV02ControlPlane(options: G14ControlPlaneOptions): Record<string, unk
|
||||
sourceCommit,
|
||||
pipelineRun: v02PipelineRunName(sourceCommit),
|
||||
before,
|
||||
gitMirrorPreSync,
|
||||
deletePipelineRun,
|
||||
createPipelineRun,
|
||||
after: getPipelineRunCompact(v02PipelineRunName(sourceCommit)),
|
||||
@@ -1076,6 +1107,115 @@ export function gitMirrorFlushJobManifest(name: string): Record<string, unknown>
|
||||
return manifest;
|
||||
}
|
||||
|
||||
export function parseGitMirrorStatusRefs(raw: string): { refs: Record<string, string | null>; pendingFlush: boolean | null } {
|
||||
const line = raw.split(/\r?\n/u).find((item) => item.startsWith("refs="));
|
||||
if (line === undefined) return { refs: {}, pendingFlush: null };
|
||||
try {
|
||||
const parsed = record(JSON.parse(line.slice("refs=".length)) as unknown);
|
||||
const refs = record(parsed.refs);
|
||||
return {
|
||||
refs: {
|
||||
localV02: stringOrNull(refs.localV02),
|
||||
localG14: stringOrNull(refs.localG14),
|
||||
localGitops: stringOrNull(refs.localGitops),
|
||||
githubGitops: stringOrNull(refs.githubGitops),
|
||||
},
|
||||
pendingFlush: typeof parsed.pendingFlush === "boolean" ? parsed.pendingFlush : null,
|
||||
};
|
||||
} catch {
|
||||
return { refs: {}, pendingFlush: null };
|
||||
}
|
||||
}
|
||||
|
||||
export function gitMirrorV02SyncRequirement(sourceCommit: string, rawStatus: string): Record<string, unknown> {
|
||||
const parsed = parseGitMirrorStatusRefs(rawStatus);
|
||||
const localV02 = parsed.refs.localV02 ?? null;
|
||||
return {
|
||||
required: localV02 !== sourceCommit,
|
||||
sourceCommit,
|
||||
localV02,
|
||||
pendingFlush: parsed.pendingFlush,
|
||||
refs: parsed.refs,
|
||||
reason: localV02 === sourceCommit ? "local-v02-current" : localV02 === null ? "local-v02-unresolved" : "local-v02-stale",
|
||||
};
|
||||
}
|
||||
|
||||
function gitMirrorStatusCacheRaw(status: Record<string, unknown>): string {
|
||||
return String(nested(status, ["cache", "raw"]) ?? "");
|
||||
}
|
||||
|
||||
function compactGitMirrorStatus(status: Record<string, unknown>, sourceCommit: string): Record<string, unknown> {
|
||||
const requirement = gitMirrorV02SyncRequirement(sourceCommit, gitMirrorStatusCacheRaw(status));
|
||||
return {
|
||||
ok: status.ok === true,
|
||||
readUrl: status.readUrl ?? V02_GIT_READ_URL,
|
||||
writeUrl: status.writeUrl ?? V02_GIT_WRITE_URL,
|
||||
legacyCronJobExists: nested(status, ["legacyCronJob", "exists"]) === true,
|
||||
required: requirement.required,
|
||||
sourceCommit,
|
||||
localV02: requirement.localV02 ?? null,
|
||||
pendingFlush: requirement.pendingFlush ?? null,
|
||||
reason: requirement.reason,
|
||||
cacheOk: nested(status, ["cache", "ok"]) === true,
|
||||
cacheStderr: tailText(nested(status, ["cache", "stderr"]), 1000),
|
||||
resourcesOk: nested(status, ["resources", "ok"]) === true,
|
||||
};
|
||||
}
|
||||
|
||||
function compactGitMirrorSync(sync: Record<string, unknown>): Record<string, unknown> {
|
||||
return {
|
||||
ok: sync.ok === true,
|
||||
command: sync.command ?? "hwlab g14 git-mirror sync",
|
||||
mode: sync.mode ?? null,
|
||||
namespace: sync.namespace ?? GIT_MIRROR_NAMESPACE,
|
||||
jobName: sync.jobName ?? null,
|
||||
exitCode: nested(sync, ["result", "exitCode"]) ?? null,
|
||||
stdoutTail: tailText(nested(sync, ["result", "stdout"]), 3000),
|
||||
stderrTail: tailText(nested(sync, ["result", "stderr"]), 3000),
|
||||
};
|
||||
}
|
||||
|
||||
function preSyncV02GitMirror(sourceCommit: string, options: Pick<G14ControlPlaneOptions, "dryRun" | "timeoutSeconds">): Record<string, unknown> {
|
||||
const before = runGitMirrorStatus();
|
||||
const beforeSummary = compactGitMirrorStatus(before, sourceCommit);
|
||||
if (options.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
mode: "dry-run",
|
||||
sourceCommit,
|
||||
before: beforeSummary,
|
||||
action: beforeSummary.required === true ? "would-sync-before-trigger" : "already-current",
|
||||
next: beforeSummary.required === true ? { syncAndTrigger: "bun scripts/cli.ts hwlab g14 control-plane trigger-current --lane v02 --confirm" } : undefined,
|
||||
};
|
||||
}
|
||||
if (beforeSummary.required !== true) {
|
||||
return {
|
||||
ok: true,
|
||||
mode: "already-current",
|
||||
sourceCommit,
|
||||
before: beforeSummary,
|
||||
};
|
||||
}
|
||||
const sync = runGitMirrorSync({
|
||||
action: "sync",
|
||||
confirm: true,
|
||||
dryRun: false,
|
||||
timeoutSeconds: options.timeoutSeconds,
|
||||
});
|
||||
const after = runGitMirrorStatus();
|
||||
const afterSummary = compactGitMirrorStatus(after, sourceCommit);
|
||||
const ok = sync.ok === true && afterSummary.required === false;
|
||||
return {
|
||||
ok,
|
||||
mode: "auto-sync-before-trigger",
|
||||
sourceCommit,
|
||||
before: beforeSummary,
|
||||
sync: compactGitMirrorSync(sync),
|
||||
after: afterSummary,
|
||||
degradedReason: ok ? undefined : "git-mirror-local-v02-not-current-after-sync",
|
||||
};
|
||||
}
|
||||
|
||||
function runGitMirrorStatus(): Record<string, unknown> {
|
||||
const resources = g14K3s([
|
||||
"kubectl",
|
||||
|
||||
Reference in New Issue
Block a user