feat: retire legacy HWLAB G14 runtime
This commit is contained in:
+405
-14
@@ -3,7 +3,7 @@ import { dirname, join } from "node:path";
|
||||
import { createHash, randomBytes } from "node:crypto";
|
||||
import { repoRoot, rootPath, type Config } from "./config";
|
||||
import { runCommand } from "./command";
|
||||
import { readJob, startJob } from "./jobs";
|
||||
import { cancelJob, listJobs, readJob, startJob } from "./jobs";
|
||||
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "./hwlab-node-lanes";
|
||||
|
||||
const HWLAB_REPO = "pikasTech/HWLAB";
|
||||
@@ -15,9 +15,11 @@ const V02_SOURCE_BRANCH = V02_LANE_SPEC.sourceBranch;
|
||||
const V02_WORKSPACE = V02_LANE_SPEC.workspace;
|
||||
const V02_CICD_REPO = V02_LANE_SPEC.cicdRepo;
|
||||
const DEV_NAMESPACE = "hwlab-dev";
|
||||
const PROD_NAMESPACE = "hwlab-prod";
|
||||
const CI_NAMESPACE = "hwlab-ci";
|
||||
const ARGO_NAMESPACE = "argocd";
|
||||
const DEV_APP = "hwlab-g14-dev";
|
||||
const PROD_APP = "hwlab-g14-prod";
|
||||
const V02_APP = V02_LANE_SPEC.app;
|
||||
const V02_PIPELINE = V02_LANE_SPEC.pipeline;
|
||||
const V02_POLLER = "hwlab-v02-branch-poller";
|
||||
@@ -136,6 +138,15 @@ interface G14ControlPlaneOptions {
|
||||
history: boolean;
|
||||
}
|
||||
|
||||
interface G14LegacyRetirementOptions {
|
||||
action: "status" | "plan" | "execute";
|
||||
dryRun: boolean;
|
||||
confirm: boolean;
|
||||
wait: boolean;
|
||||
timeoutSeconds: number;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
type V02StatusTargetMode = "latest-source-head" | "source-commit" | "pipeline-run";
|
||||
|
||||
interface V02ControlPlaneStatusTarget {
|
||||
@@ -414,6 +425,28 @@ function parseControlPlaneOptions(args: string[]): G14ControlPlaneOptions {
|
||||
};
|
||||
}
|
||||
|
||||
function parseLegacyRetirementOptions(args: string[]): G14LegacyRetirementOptions {
|
||||
const [actionRaw = "status"] = args;
|
||||
if (actionRaw !== "status" && actionRaw !== "plan" && actionRaw !== "execute") {
|
||||
throw new Error("retirement usage: status|plan|execute [--dry-run|--confirm] [--wait] [--reason text] [--timeout-seconds N]");
|
||||
}
|
||||
const confirm = args.includes("--confirm");
|
||||
const explicitDryRun = args.includes("--dry-run");
|
||||
if (confirm && explicitDryRun) throw new Error("retirement accepts only one of --confirm or --dry-run");
|
||||
if (confirm && actionRaw !== "execute") throw new Error("--confirm is only valid for retirement execute");
|
||||
if (args.includes("--wait") && actionRaw !== "execute") throw new Error("--wait is only valid for retirement execute");
|
||||
const reason = optionValue(args, "--reason") ?? "user requested immediate retirement of legacy G14 DEV/PROD";
|
||||
if (reason.length > 240) throw new Error("--reason is limited to 240 characters");
|
||||
return {
|
||||
action: actionRaw,
|
||||
confirm,
|
||||
wait: args.includes("--wait"),
|
||||
dryRun: actionRaw !== "execute" || explicitDryRun || !confirm,
|
||||
timeoutSeconds: positiveIntegerOption(args, "--timeout-seconds", actionRaw === "execute" ? 60 : 30, 300),
|
||||
reason,
|
||||
};
|
||||
}
|
||||
|
||||
function parseToolsImageOptions(args: string[]): G14ToolsImageOptions {
|
||||
const [actionRaw] = args;
|
||||
if (actionRaw !== "status" && actionRaw !== "build") {
|
||||
@@ -5045,6 +5078,327 @@ function runG14Secret(options: G14SecretOptions): Record<string, unknown> {
|
||||
};
|
||||
}
|
||||
|
||||
function legacyG14RetirementStatePath(): string {
|
||||
return rootPath(".state", "hwlab-g14", "legacy-g14-retirement.json");
|
||||
}
|
||||
|
||||
function legacyG14RetirementApplications(): string[] {
|
||||
return [DEV_APP, PROD_APP];
|
||||
}
|
||||
|
||||
function legacyG14RetirementNamespaces(): string[] {
|
||||
return [DEV_NAMESPACE, PROD_NAMESPACE];
|
||||
}
|
||||
|
||||
function protectedG14RuntimeApplications(): string[] {
|
||||
return [V02_APP, hwlabRuntimeLaneSpec("v03").app];
|
||||
}
|
||||
|
||||
function protectedG14RuntimeNamespaces(): string[] {
|
||||
return [V02_RUNTIME_NAMESPACE, hwlabRuntimeLaneSpec("v03").runtimeNamespace];
|
||||
}
|
||||
|
||||
function readLegacyG14RetirementMarker(): Record<string, unknown> | null {
|
||||
const path = legacyG14RetirementStatePath();
|
||||
if (!existsSync(path)) return null;
|
||||
try {
|
||||
return record(JSON.parse(readFileSync(path, "utf8")) as unknown);
|
||||
} catch (error) {
|
||||
return { status: "unreadable", path, error: error instanceof Error ? error.message : String(error) };
|
||||
}
|
||||
}
|
||||
|
||||
function writeLegacyG14RetirementMarker(status: string, reason: string, details: Record<string, unknown> = {}): Record<string, unknown> {
|
||||
const path = legacyG14RetirementStatePath();
|
||||
mkdirSync(dirname(path), { recursive: true });
|
||||
const previous = readLegacyG14RetirementMarker();
|
||||
const marker = {
|
||||
status,
|
||||
reason,
|
||||
requestedAt: typeof previous?.requestedAt === "string" ? previous.requestedAt : new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
legacyApplications: legacyG14RetirementApplications(),
|
||||
legacyNamespaces: legacyG14RetirementNamespaces(),
|
||||
protectedApplications: protectedG14RuntimeApplications(),
|
||||
protectedNamespaces: protectedG14RuntimeNamespaces(),
|
||||
...details,
|
||||
};
|
||||
writeFileSync(path, `${JSON.stringify(marker, null, 2)}\n`, "utf8");
|
||||
return { ...marker, path };
|
||||
}
|
||||
|
||||
function legacyG14RetirementBlocksMonitor(): Record<string, unknown> | null {
|
||||
const marker = readLegacyG14RetirementMarker();
|
||||
if (marker === null) {
|
||||
return {
|
||||
status: "retired-by-contract",
|
||||
path: legacyG14RetirementStatePath(),
|
||||
reason: "legacy G14 DEV/PROD monitor is retired; inspect retirement status for live cluster evidence",
|
||||
next: { status: "bun scripts/cli.ts hwlab g14 retirement status", v02Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v02" },
|
||||
};
|
||||
}
|
||||
const status = String(marker.status ?? "");
|
||||
return status === "retiring" || status === "retired" || status === "failed" || status === "unreadable" ? marker : null;
|
||||
}
|
||||
|
||||
function k8sMetadataNamespace(item: Record<string, unknown>): string | null {
|
||||
const namespace = record(item.metadata).namespace;
|
||||
return typeof namespace === "string" && namespace.length > 0 ? namespace : null;
|
||||
}
|
||||
|
||||
function applicationSummary(item: Record<string, unknown>): Record<string, unknown> {
|
||||
return {
|
||||
name: k8sMetadataName(item),
|
||||
namespace: k8sMetadataNamespace(item) ?? ARGO_NAMESPACE,
|
||||
deletionTimestamp: record(item.metadata).deletionTimestamp ?? null,
|
||||
sync: nested(item, ["status", "sync", "status"]) ?? null,
|
||||
health: nested(item, ["status", "health", "status"]) ?? null,
|
||||
revision: nested(item, ["status", "sync", "revision"]) ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
function namespaceSummary(item: Record<string, unknown>): Record<string, unknown> {
|
||||
return {
|
||||
name: k8sMetadataName(item),
|
||||
phase: nested(item, ["status", "phase"]) ?? null,
|
||||
deletionTimestamp: record(item.metadata).deletionTimestamp ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
function parseNamespacedResourcePreview(text: string): Record<string, unknown>[] {
|
||||
const groups: Record<string, string[]> = {};
|
||||
let current = "";
|
||||
for (const line of text.split(/\r?\n/u)) {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed.length === 0) continue;
|
||||
const namespaceMatch = /^__namespace\t(.+)$/u.exec(trimmed);
|
||||
if (namespaceMatch !== null) {
|
||||
current = namespaceMatch[1] ?? "";
|
||||
if (current.length > 0) groups[current] = [];
|
||||
continue;
|
||||
}
|
||||
if (current.length > 0) groups[current]?.push(trimmed);
|
||||
}
|
||||
return Object.entries(groups).map(([namespace, items]) => ({
|
||||
namespace,
|
||||
count: items.length,
|
||||
preview: items.slice(0, 40),
|
||||
truncated: items.length > 40,
|
||||
}));
|
||||
}
|
||||
|
||||
function legacyG14RetirementStatusScript(): string {
|
||||
const legacyApps = legacyG14RetirementApplications().map(shellQuote).join(" ");
|
||||
const legacyNamespaces = legacyG14RetirementNamespaces().map(shellQuote).join(" ");
|
||||
const protectedApps = protectedG14RuntimeApplications().map(shellQuote).join(" ");
|
||||
const protectedNamespaces = protectedG14RuntimeNamespaces().map(shellQuote).join(" ");
|
||||
return [
|
||||
"set +e",
|
||||
"section() {",
|
||||
" name=\"$1\"",
|
||||
" shift",
|
||||
" printf '__UNIDESK_SECTION_BEGIN__ %s\\n' \"$name\"",
|
||||
" \"$@\"",
|
||||
" code=$?",
|
||||
" printf '\\n__UNIDESK_SECTION_END__ %s exit=%s\\n' \"$name\" \"$code\"",
|
||||
"}",
|
||||
`section legacyApplications kubectl get application -n ${shellQuote(ARGO_NAMESPACE)} ${legacyApps} --ignore-not-found=true -o json`,
|
||||
`section legacyNamespaces kubectl get namespace ${legacyNamespaces} --ignore-not-found=true -o json`,
|
||||
`section protectedApplications kubectl get application -n ${shellQuote(ARGO_NAMESPACE)} ${protectedApps} --ignore-not-found=true -o json`,
|
||||
`section protectedNamespaces kubectl get namespace ${protectedNamespaces} --ignore-not-found=true -o json`,
|
||||
"section legacyResources sh -lc " + shellQuote([
|
||||
`for ns in ${legacyNamespaces}; do`,
|
||||
" printf '__namespace\\t%s\\n' \"$ns\"",
|
||||
" kubectl get deploy,statefulset,svc,pod,pvc,ingress,configmap,secret -n \"$ns\" -o name --ignore-not-found=true 2>/dev/null | sed -n '1,160p'",
|
||||
"done",
|
||||
].join("\n")),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function legacyG14RetirementStatus(): Record<string, unknown> {
|
||||
const result = g14K3s(["script", "--", legacyG14RetirementStatusScript()], 60_000);
|
||||
const sections = parseShellSections(statusText(result));
|
||||
const legacyApplications = parseSectionJsonArray(sections.legacyApplications).map(applicationSummary);
|
||||
const legacyNamespaces = parseSectionJsonArray(sections.legacyNamespaces).map(namespaceSummary);
|
||||
const protectedApplications = parseSectionJsonArray(sections.protectedApplications).map(applicationSummary);
|
||||
const protectedNamespaces = parseSectionJsonArray(sections.protectedNamespaces).map(namespaceSummary);
|
||||
const terminating = legacyApplications.some((item) => item.deletionTimestamp !== null)
|
||||
|| legacyNamespaces.some((item) => item.deletionTimestamp !== null || item.phase === "Terminating");
|
||||
const retired = legacyApplications.length === 0 && legacyNamespaces.length === 0;
|
||||
const retirementState = retired ? "retired" : terminating ? "terminating" : "active";
|
||||
const protectedAppNames = new Set(protectedApplications.map((item) => item.name).filter((name): name is string => typeof name === "string"));
|
||||
const protectedNamespaceNames = new Set(protectedNamespaces.map((item) => item.name).filter((name): name is string => typeof name === "string"));
|
||||
const missingProtectedApplications = protectedG14RuntimeApplications().filter((name) => !protectedAppNames.has(name));
|
||||
const missingProtectedNamespaces = protectedG14RuntimeNamespaces().filter((name) => !protectedNamespaceNames.has(name));
|
||||
const sectionsOk =
|
||||
shellSectionOk(sections.legacyApplications) &&
|
||||
shellSectionOk(sections.legacyNamespaces) &&
|
||||
shellSectionOk(sections.protectedApplications) &&
|
||||
shellSectionOk(sections.protectedNamespaces) &&
|
||||
shellSectionOk(sections.legacyResources);
|
||||
return {
|
||||
ok: isCommandSuccess(result) && sectionsOk,
|
||||
command: "hwlab g14 retirement status",
|
||||
provider: G14_PROVIDER,
|
||||
argoNamespace: ARGO_NAMESPACE,
|
||||
retirementState,
|
||||
retired,
|
||||
legacy: {
|
||||
applications: legacyApplications,
|
||||
namespaces: legacyNamespaces,
|
||||
resources: parseNamespacedResourcePreview(String(sections.legacyResources?.stdout ?? "")),
|
||||
},
|
||||
protected: {
|
||||
applications: protectedApplications,
|
||||
namespaces: protectedNamespaces,
|
||||
missingApplications: missingProtectedApplications,
|
||||
missingNamespaces: missingProtectedNamespaces,
|
||||
ok: missingProtectedApplications.length === 0 && missingProtectedNamespaces.length === 0,
|
||||
},
|
||||
marker: readLegacyG14RetirementMarker(),
|
||||
result: compactCommandResult(result),
|
||||
next: retired
|
||||
? { verifyV02: "bun scripts/cli.ts hwlab g14 control-plane status --lane v02", verifyV03: "bun scripts/cli.ts hwlab nodes control-plane status --node G14 --lane v03" }
|
||||
: { plan: "bun scripts/cli.ts hwlab g14 retirement plan", execute: "bun scripts/cli.ts hwlab g14 retirement execute --confirm" },
|
||||
};
|
||||
}
|
||||
|
||||
function legacyG14RetirementPlan(status = legacyG14RetirementStatus()): Record<string, unknown> {
|
||||
return {
|
||||
ok: status.ok === true,
|
||||
command: "hwlab g14 retirement plan",
|
||||
mode: "dry-run",
|
||||
mutation: false,
|
||||
reason: "retire legacy G14 DEV/PROD runtime and support surface without touching v0.2/v0.3",
|
||||
destructiveTargets: {
|
||||
applications: legacyG14RetirementApplications().map((name) => ({ apiVersion: "argoproj.io/v1alpha1", kind: "Application", namespace: ARGO_NAMESPACE, name })),
|
||||
namespaces: legacyG14RetirementNamespaces().map((name) => ({ apiVersion: "v1", kind: "Namespace", name })),
|
||||
},
|
||||
protectedTargets: {
|
||||
applications: protectedG14RuntimeApplications().map((name) => ({ apiVersion: "argoproj.io/v1alpha1", kind: "Application", namespace: ARGO_NAMESPACE, name })),
|
||||
namespaces: protectedG14RuntimeNamespaces().map((name) => ({ apiVersion: "v1", kind: "Namespace", name })),
|
||||
},
|
||||
localSupportActions: [
|
||||
{ action: "write-retirement-marker", path: legacyG14RetirementStatePath() },
|
||||
{ action: "cancel-active-local-job", jobName: "hwlab_g14_pr_monitor" },
|
||||
{ action: "block-new-monitor-starts", command: "bun scripts/cli.ts hwlab g14 monitor-prs --lane g14" },
|
||||
],
|
||||
status,
|
||||
next: { execute: "bun scripts/cli.ts hwlab g14 retirement execute --confirm" },
|
||||
};
|
||||
}
|
||||
|
||||
function legacyG14RecordRolloutRetired(): Record<string, unknown> {
|
||||
return {
|
||||
ok: false,
|
||||
command: "hwlab g14 record-rollout",
|
||||
phase: "retired",
|
||||
degradedReason: "legacy-g14-dev-prod-retired",
|
||||
message: "Legacy G14 DEV rollout recording is retired. Use active runtime lane closeout/status evidence instead.",
|
||||
retirement: readLegacyG14RetirementMarker(),
|
||||
next: {
|
||||
retirementStatus: "bun scripts/cli.ts hwlab g14 retirement status",
|
||||
v02Status: "bun scripts/cli.ts hwlab g14 control-plane status --lane v02",
|
||||
v03Status: "bun scripts/cli.ts hwlab nodes control-plane status --node G14 --lane v03",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function cancelLegacyG14MonitorJobs(dryRun: boolean): Record<string, unknown> {
|
||||
const candidates = listJobs()
|
||||
.filter((job) => job.name === "hwlab_g14_pr_monitor" && (job.status === "queued" || job.status === "running"))
|
||||
.map((job) => ({ id: job.id, name: job.name, status: job.status, createdAt: job.createdAt, runnerPid: job.runnerPid ?? null }));
|
||||
if (dryRun) return { ok: true, dryRun: true, candidates, actions: candidates.map((job) => ({ action: "would-cancel", ...job })) };
|
||||
const actions = candidates.map((job) => cancelJob(job.id));
|
||||
return { ok: actions.every((action) => record(action).ok !== false), dryRun: false, candidates, actions };
|
||||
}
|
||||
|
||||
function legacyG14RetirementDeleteScript(): string {
|
||||
const legacyApps = legacyG14RetirementApplications();
|
||||
const legacyNamespaces = legacyG14RetirementNamespaces();
|
||||
return [
|
||||
"set +e",
|
||||
"overall=0",
|
||||
"printf '__UNIDESK_SECTION_BEGIN__ deleteApplications\\n'",
|
||||
...legacyApps.map((app) => [
|
||||
`kubectl delete application -n ${shellQuote(ARGO_NAMESPACE)} ${shellQuote(app)} --ignore-not-found=true --wait=false`,
|
||||
"code=$?",
|
||||
"[ \"$code\" -eq 0 ] || overall=1",
|
||||
`printf 'application\\t%s\\texit=%s\\n' ${shellQuote(app)} "$code"`,
|
||||
].join("\n")),
|
||||
"printf '__UNIDESK_SECTION_END__ deleteApplications exit=0\\n'",
|
||||
"printf '__UNIDESK_SECTION_BEGIN__ deleteNamespaces\\n'",
|
||||
...legacyNamespaces.map((namespace) => [
|
||||
`kubectl delete namespace ${shellQuote(namespace)} --ignore-not-found=true --wait=false`,
|
||||
"code=$?",
|
||||
"[ \"$code\" -eq 0 ] || overall=1",
|
||||
`printf 'namespace\\t%s\\texit=%s\\n' ${shellQuote(namespace)} "$code"`,
|
||||
].join("\n")),
|
||||
"printf '__UNIDESK_SECTION_END__ deleteNamespaces exit=0\\n'",
|
||||
"exit \"$overall\"",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function waitForLegacyG14Retirement(timeoutSeconds: number): Record<string, unknown> {
|
||||
const startedAtMs = Date.now();
|
||||
let lastStatus: Record<string, unknown> = {};
|
||||
let attempts = 0;
|
||||
while (Date.now() - startedAtMs <= timeoutSeconds * 1000) {
|
||||
attempts += 1;
|
||||
lastStatus = legacyG14RetirementStatus();
|
||||
if (lastStatus.retired === true) {
|
||||
return { ok: true, attempts, elapsedMs: Date.now() - startedAtMs, status: lastStatus };
|
||||
}
|
||||
const wait = runCommand(["sleep", "5"], repoRoot, { timeoutMs: 7_000 });
|
||||
if (wait.exitCode !== 0) break;
|
||||
}
|
||||
return { ok: false, attempts, elapsedMs: Date.now() - startedAtMs, status: lastStatus, degradedReason: "legacy-g14-retirement-not-complete-before-timeout" };
|
||||
}
|
||||
|
||||
function runLegacyG14Retirement(options: G14LegacyRetirementOptions): Record<string, unknown> {
|
||||
const before = legacyG14RetirementStatus();
|
||||
if (options.action === "status") return before;
|
||||
if (options.action === "plan" || options.dryRun) return { ...legacyG14RetirementPlan(before), command: `hwlab g14 retirement ${options.action}`, reason: options.reason };
|
||||
|
||||
const markerBeforeDelete = writeLegacyG14RetirementMarker("retiring", options.reason, { command: "hwlab g14 retirement execute --confirm" });
|
||||
const localJobs = cancelLegacyG14MonitorJobs(false);
|
||||
const deleteResult = g14K3s(["script", "--", legacyG14RetirementDeleteScript()], Math.max(60_000, options.timeoutSeconds * 1000));
|
||||
const deleteSections = parseShellSections(statusText(deleteResult));
|
||||
const afterDelete = legacyG14RetirementStatus();
|
||||
const wait = options.wait ? waitForLegacyG14Retirement(options.timeoutSeconds) : null;
|
||||
const finalStatus = wait === null ? afterDelete : record(wait.status);
|
||||
const liveMutationOk = isCommandSuccess(deleteResult);
|
||||
const finalRetired = finalStatus.retired === true;
|
||||
const marker = writeLegacyG14RetirementMarker(finalRetired ? "retired" : liveMutationOk ? "retiring" : "failed", options.reason, {
|
||||
command: "hwlab g14 retirement execute --confirm",
|
||||
deleteExitCode: deleteResult.exitCode,
|
||||
finalRetirementState: finalStatus.retirementState ?? null,
|
||||
});
|
||||
return {
|
||||
ok: liveMutationOk && (options.wait ? finalRetired : finalStatus.retirementState !== "active"),
|
||||
command: "hwlab g14 retirement execute",
|
||||
mode: "confirmed-retirement",
|
||||
mutation: true,
|
||||
reason: options.reason,
|
||||
before,
|
||||
markerBeforeDelete,
|
||||
localJobs,
|
||||
delete: {
|
||||
ok: liveMutationOk,
|
||||
applications: String(deleteSections.deleteApplications?.stdout ?? "").split(/\r?\n/u).filter(Boolean),
|
||||
namespaces: String(deleteSections.deleteNamespaces?.stdout ?? "").split(/\r?\n/u).filter(Boolean),
|
||||
result: compactCommandResult(deleteResult),
|
||||
},
|
||||
afterDelete,
|
||||
wait,
|
||||
finalStatus,
|
||||
marker,
|
||||
next: finalRetired
|
||||
? { verifyV02: "bun scripts/cli.ts hwlab g14 control-plane status --lane v02", verifyV03: "bun scripts/cli.ts hwlab nodes control-plane status --node G14 --lane v03" }
|
||||
: { status: "bun scripts/cli.ts hwlab g14 retirement status" },
|
||||
};
|
||||
}
|
||||
|
||||
function deleteLegacyGitMirrorCronJob(dryRun: boolean): CommandJsonResult {
|
||||
return g14K3s([
|
||||
"kubectl",
|
||||
@@ -7991,14 +8345,14 @@ function newDailyBriefBody(date: string): string {
|
||||
"",
|
||||
"## 常驻观察与长期建议",
|
||||
"",
|
||||
"- G14 HWLAB source truth 固定为 G14 `/root/hwlab` 与 `origin/G14`;DEV rollout 只接受 G14 Tekton/GitOps/Argo 和公网 health 证据。",
|
||||
"- Legacy G14 DEV rollout 已退役;当前 HWLAB runtime 证据必须来自 active runtime lane。",
|
||||
"- GitHub issue/PR 写操作必须走 UniDesk `gh` 子命令;G14 k3s 操作必须走 UniDesk `trans G14:k3s`。",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function insertBriefIndexRow(indexBody: string, date: string, brief: { number: number; url: string }): string {
|
||||
if (parseBriefIssueFromIndex(indexBody, date) !== null) return indexBody;
|
||||
const row = `| ${date} | [#${brief.number}](${brief.url}) | 已创建当日 G14 DEV rollout 自动简报入口;后续每次 DEV 上线记录 CI/CD 耗时和上线 changelog。 | 继续监控 HWLAB base=G14 PR,ready 后自动合并并滚动到 G14 DEV。 |`;
|
||||
const row = `| ${date} | [#${brief.number}](${brief.url}) | legacy G14 DEV rollout 自动简报入口已退役;当前证据写入 active runtime lane closeout。 | 使用 active runtime lane 的受控 CI/CD 入口和 issue 评论记录。 |`;
|
||||
const marker = "### 指挥简报索引";
|
||||
const markerIndex = indexBody.indexOf(marker);
|
||||
if (markerIndex === -1) return `${indexBody.trimEnd()}\n\n${marker}\n\n| 日期(北京时间) | 指挥简报 issue | 当日推进 | 下一步计划 |\n| --- | --- | --- | --- |\n${row}\n`;
|
||||
@@ -8131,7 +8485,7 @@ export function rolloutRecordBody(input: {
|
||||
"",
|
||||
`## 更新 ${now.date} ${now.time.slice(0, 5)} 北京时间`,
|
||||
"",
|
||||
`### G14 DEV rollout:PR #${input.pr.number}`,
|
||||
`### Retired legacy G14 DEV rollout record:PR #${input.pr.number}`,
|
||||
"",
|
||||
`- PR: [#${input.pr.number} ${title}](${url})`,
|
||||
`- 合并 commit: \`${input.sourceCommit}\``,
|
||||
@@ -8827,6 +9181,17 @@ async function monitorV02Cycle(options: G14MonitorOptions, cycle: number): Promi
|
||||
|
||||
async function monitorCycle(options: G14MonitorOptions, cycle: number): Promise<Record<string, unknown>> {
|
||||
if (options.lane === "v02") return monitorV02Cycle(options, cycle);
|
||||
const retirement = legacyG14RetirementBlocksMonitor();
|
||||
if (retirement !== null) {
|
||||
return {
|
||||
ok: false,
|
||||
cycle,
|
||||
phase: "retired",
|
||||
degradedReason: "legacy-g14-dev-prod-retired",
|
||||
retirement,
|
||||
next: { status: "bun scripts/cli.ts hwlab g14 retirement status", v02Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v02" },
|
||||
};
|
||||
}
|
||||
printEvent("g14.monitor.cycle.start", { cycle, dryRun: options.dryRun });
|
||||
const precheck = precheckWorkspace();
|
||||
if (!isCommandSuccess(precheck)) return { ok: false, cycle, phase: "workspace-precheck", precheck };
|
||||
@@ -8881,12 +9246,12 @@ export function hwlabG14Help(): Record<string, unknown> {
|
||||
command: "hwlab g14",
|
||||
output: "json",
|
||||
usage: [
|
||||
"bun scripts/cli.ts hwlab g14 monitor-prs",
|
||||
"bun scripts/cli.ts hwlab g14 monitor-prs --lane v02",
|
||||
"bun scripts/cli.ts hwlab g14 monitor-prs --lane v02 --status",
|
||||
"bun scripts/cli.ts hwlab g14 monitor-prs --once --dry-run",
|
||||
"bun scripts/cli.ts hwlab g14 retirement status",
|
||||
"bun scripts/cli.ts hwlab g14 retirement plan",
|
||||
"bun scripts/cli.ts hwlab g14 retirement execute --confirm",
|
||||
"bun scripts/cli.ts hwlab g14 monitor-prs --lane v02 --once --dry-run",
|
||||
"bun scripts/cli.ts hwlab g14 record-rollout --pr <number> [--source-commit sha]",
|
||||
"bun scripts/cli.ts hwlab g14 control-plane status --lane v02",
|
||||
"bun scripts/cli.ts hwlab g14 control-plane status --lane v02 --history",
|
||||
"bun scripts/cli.ts hwlab g14 control-plane status --lane v02 --pipeline-run hwlab-v02-ci-poll-<short-sha>",
|
||||
@@ -8955,20 +9320,22 @@ export function hwlabG14Help(): Record<string, unknown> {
|
||||
"bun scripts/cli.ts hwlab g14 upstream-image ensure --name openfga --tag v1.17.0 --confirm",
|
||||
"bun scripts/cli.ts job status <jobId> --tail-bytes 30000",
|
||||
],
|
||||
description: "G14 HWLAB PR monitor, DEV rollout command, bounded v0.2 control-plane bootstrap/cleanup/runtime-migration helper, node-scoped runtime lane v03 control-plane apply/status/refresh/trigger entry, runtime lane SecretRef bootstrap, devops-infra git mirror and observability maintenance, controlled CI tools image build/status entry, and allowlisted upstream image mirroring. The public monitor starts a fire-and-forget job. Default monitor lane is base=G14; --lane v02 monitors base=v0.2 PRs, waits for GitHub preflight/CI readiness, automatically merges ready PRs without waiting for other active v0.2 PipelineRuns, triggers v0.2 CD with latest-only GitOps writeback, flushes the git mirror when needed, and posts deduplicated PR comments for pending, blocked/conflict, success, superseded, failure, or timeout states. confirmed control-plane trigger-current and git-mirror sync/flush also return async jobs by default, with --wait reserved for explicit synchronous debugging. control-plane v02 keeps the full closeout/cleanup/runtime-migration verdict path; v03+ is advertised through `hwlab nodes ... --node <node-id> --lane vNN` so node identity remains configuration data instead of a command family. secret status/ensure is the standard runtime lane SecretRef bootstrap path for OpenFGA and master admin API key; it never reads or prints secret values. upstream-image status/ensure only mirrors allowlisted upstream runtime images into the G14 local registry. git-mirror status/apply/sync/flush is the manual devops-infra mirror/relay control path and does not install a CronJob. observability status/apply/query/targets/boundary/closeout owns the shared Prometheus Operator and Prometheus instance in devops-infra, adds bounded PromQL assertions and semantic closeout summaries, while HWLAB lane manifests own only ServiceMonitor and PrometheusRule objects.",
|
||||
description: "G14 HWLAB PR monitor, legacy DEV/PROD retirement status/plan/execute command, bounded v0.2 control-plane bootstrap/cleanup/runtime-migration helper, node-scoped runtime lane v03 control-plane apply/status/refresh/trigger entry, runtime lane SecretRef bootstrap, devops-infra git mirror and observability maintenance, controlled CI tools image build/status entry, and allowlisted upstream image mirroring. The legacy base=G14 monitor is blocked by the retirement contract; the local retirement marker records live execution evidence. `--lane v02` monitors base=v0.2 PRs, waits for GitHub preflight/CI readiness, automatically merges ready PRs without waiting for other active v0.2 PipelineRuns, triggers v0.2 CD with latest-only GitOps writeback, flushes the git mirror when needed, and posts deduplicated PR comments for pending, blocked/conflict, success, superseded, failure, or timeout states. confirmed control-plane trigger-current and git-mirror sync/flush also return async jobs by default, with --wait reserved for explicit synchronous debugging. control-plane v02 keeps the full closeout/cleanup/runtime-migration verdict path; v03+ is advertised through `hwlab nodes ... --node <node-id> --lane vNN` so node identity remains configuration data instead of a command family. secret status/ensure is the standard runtime lane SecretRef bootstrap path for OpenFGA and master admin API key; it never reads or prints secret values. upstream-image status/ensure only mirrors allowlisted upstream runtime images into the G14 local registry. git-mirror status/apply/sync/flush is the manual devops-infra mirror/relay control path and does not install a CronJob. observability status/apply/query/targets/boundary/closeout owns the shared Prometheus Operator and Prometheus instance in devops-infra, adds bounded PromQL assertions and semantic closeout summaries, while HWLAB lane manifests own only ServiceMonitor and PrometheusRule objects.",
|
||||
defaults: {
|
||||
repo: HWLAB_REPO,
|
||||
base: G14_SOURCE_BRANCH,
|
||||
legacyBase: G14_SOURCE_BRANCH,
|
||||
v02Base: V02_SOURCE_BRANCH,
|
||||
runtimeLaneConfig: hwlabRuntimeLaneConfigPath(),
|
||||
runtimeLanes: hwlabRuntimeLaneIds(),
|
||||
provider: G14_PROVIDER,
|
||||
workspace: G14_WORKSPACE,
|
||||
legacyWorkspace: G14_WORKSPACE,
|
||||
v02Workspace: V02_WORKSPACE,
|
||||
v03Workspace: hwlabRuntimeLaneSpec("v03").workspace,
|
||||
ciToolsImageRepo: G14_CI_TOOLS_IMAGE_REPO,
|
||||
intervalSeconds: DEFAULT_INTERVAL_SECONDS,
|
||||
devApplication: DEV_APP,
|
||||
prodApplication: PROD_APP,
|
||||
legacyRetirementState: legacyG14RetirementStatePath(),
|
||||
v02Application: V02_APP,
|
||||
v03Application: hwlabRuntimeLaneSpec("v03").app,
|
||||
v03Node: hwlabRuntimeLaneSpec("v03").nodeId,
|
||||
@@ -8999,6 +9366,7 @@ function monitorStatus(options: G14MonitorOptions): Record<string, unknown> {
|
||||
const stateFileName = hwlabG14MonitorStateFileName(options);
|
||||
const stateFileRole = hwlabG14MonitorStateRole(options);
|
||||
const latestPath = join(stateDir, stateFileName);
|
||||
const retirement = options.lane === "g14" ? legacyG14RetirementBlocksMonitor() : null;
|
||||
const exists = existsSync(latestPath);
|
||||
let latest: Record<string, unknown> | null = null;
|
||||
let job: Record<string, unknown> | null = null;
|
||||
@@ -9047,7 +9415,11 @@ function monitorStatus(options: G14MonitorOptions): Record<string, unknown> {
|
||||
job,
|
||||
statusCommand,
|
||||
degradedReason,
|
||||
next: statusCommand === null ? {
|
||||
retirement,
|
||||
next: retirement !== null ? {
|
||||
retirementStatus: "bun scripts/cli.ts hwlab g14 retirement status",
|
||||
v02Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v02",
|
||||
} : statusCommand === null ? {
|
||||
start: `bun scripts/cli.ts hwlab g14 monitor-prs --lane ${options.lane}`,
|
||||
} : {
|
||||
status: statusCommand,
|
||||
@@ -9059,8 +9431,7 @@ export async function runHwlabG14Command(_config: Config, args: string[]): Promi
|
||||
if (args.length === 0 || args.includes("--help") || args.includes("-h")) return { ok: true, ...hwlabG14Help() };
|
||||
const [action] = args;
|
||||
if (action === "record-rollout") {
|
||||
const options = parseRecordRolloutOptions(args.slice(1));
|
||||
return appendRolloutBrief(options);
|
||||
return legacyG14RecordRolloutRetired();
|
||||
}
|
||||
if (action === "control-plane") {
|
||||
const options = parseControlPlaneOptions(args.slice(1));
|
||||
@@ -9072,6 +9443,10 @@ export async function runHwlabG14Command(_config: Config, args: string[]): Promi
|
||||
}
|
||||
return runV02ControlPlane(options);
|
||||
}
|
||||
if (action === "retirement") {
|
||||
const options = parseLegacyRetirementOptions(args.slice(1));
|
||||
return runLegacyG14Retirement(options);
|
||||
}
|
||||
if (action === "secret") {
|
||||
const options = parseSecretOptions(args.slice(1));
|
||||
return runG14Secret(options);
|
||||
@@ -9096,11 +9471,27 @@ export async function runHwlabG14Command(_config: Config, args: string[]): Promi
|
||||
return runG14Observability(options);
|
||||
}
|
||||
if (action !== "monitor-prs") {
|
||||
return { ok: false, command: `hwlab g14 ${action ?? ""}`.trim(), degradedReason: "unsupported-command", message: "supported commands: hwlab g14 monitor-prs, hwlab g14 record-rollout, hwlab g14 control-plane, hwlab g14 secret, hwlab g14 git-mirror, hwlab g14 observability, hwlab g14 tools-image, hwlab g14 upstream-image" };
|
||||
return { ok: false, command: `hwlab g14 ${action ?? ""}`.trim(), degradedReason: "unsupported-command", message: "supported commands: hwlab g14 monitor-prs, hwlab g14 retirement, hwlab g14 control-plane, hwlab g14 secret, hwlab g14 git-mirror, hwlab g14 observability, hwlab g14 tools-image, hwlab g14 upstream-image" };
|
||||
}
|
||||
const options = parseOptions(args.slice(1));
|
||||
if (options.worker) return runMonitorWorker(options);
|
||||
if (args.includes("--status")) return monitorStatus(options);
|
||||
const retirement = options.lane === "g14" ? legacyG14RetirementBlocksMonitor() : null;
|
||||
if (retirement !== null) {
|
||||
return {
|
||||
ok: false,
|
||||
command: "hwlab g14 monitor-prs",
|
||||
lane: options.lane,
|
||||
baseBranch: monitorBaseBranch(options.lane),
|
||||
degradedReason: "legacy-g14-dev-prod-retired",
|
||||
message: "Legacy G14 DEV/PROD has a retirement marker; start v0.2 monitor with --lane v02 or inspect retirement status.",
|
||||
retirement,
|
||||
next: {
|
||||
retirementStatus: "bun scripts/cli.ts hwlab g14 retirement status",
|
||||
v02Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v02",
|
||||
},
|
||||
};
|
||||
}
|
||||
const command = ["bun", "scripts/cli.ts", "hwlab", "g14", "monitor-prs", "--worker", "--lane", options.lane, "--interval-seconds", String(options.intervalSeconds), "--timeout-seconds", String(options.timeoutSeconds), ...(options.once ? ["--once"] : []), ...(options.dryRun ? ["--dry-run"] : []), ...(options.maxCycles > 0 ? ["--max-cycles", String(options.maxCycles)] : [])];
|
||||
const jobName = options.lane === "v02" ? "hwlab_g14_v02_pr_monitor" : "hwlab_g14_pr_monitor";
|
||||
const jobNote = options.lane === "v02"
|
||||
|
||||
Reference in New Issue
Block a user