feat: migrate jd01 hwlab ci to gitea pac

This commit is contained in:
Codex
2026-07-05 23:55:43 +00:00
parent c20227b2cf
commit 344be2cd53
13 changed files with 365 additions and 46 deletions
+5 -5
View File
@@ -59,16 +59,16 @@ controller:
followers:
- id: hwlab-jd01-v03
enabled: true
enabled: false
adapter: hwlab-node-runtime
description: Follow HWLAB v0.3 into the JD01 HWLAB node runtime.
description: Retired for GH-1557; HWLAB v0.3 JD01 runtime CI is driven by Gitea mirror -> Pipelines-as-Code -> Tekton -> ArgoCD.
source:
repository: pikasTech/HWLAB
branch: v0.3
branchRef: config/hwlab-node-lanes.yaml#lanes.v03.sourceBranch
authorityRef: config/hwlab-node-lanes.yaml#lanes.v03.targets.JD01.git.readUrl
snapshotPrefix: refs/unidesk/snapshots/hwlab-node-runtime/v0.3
snapshotRef: config/hwlab-node-lanes.yaml#lanes.v03.targets.JD01.git.readUrl
authorityRef: config/platform-infra/gitea.yaml#sourceAuthority.repositories.hwlab-jd01-v03
snapshotPrefix: refs/unidesk/snapshots/gitea-actions/hwlab-node-runtime/v0.3
snapshotRef: config/platform-infra/gitea.yaml#sourceAuthority.repositories.hwlab-jd01-v03.snapshot.prefix
target:
node: JD01
lane: v03
+5 -4
View File
@@ -481,15 +481,16 @@ targets:
repository: pikasTech/HWLAB
branch: v0.3
sourceAuthority:
mode: gitMirrorSnapshot
resolver: k8s-git-mirror
mode: giteaSnapshot
resolver: gitea-mirror
giteaMirrorRepoKey: hwlab-jd01-v03
allowHostGit: false
allowHostWorkspace: false
allowGithubDirectInPipeline: false
sourceSnapshot:
stageRefPrefix: refs/unidesk/snapshots/hwlab-node-runtime/{branch}
stageRefPrefix: refs/unidesk/snapshots/gitea-actions/hwlab-node-runtime/{branch}
missingObjectPolicy: fail-fast
refreshPolicy: sync-before-snapshot
refreshPolicy: gitea-controlled-snapshot
gitops:
branch: v0.3-gitops
path: deploy/gitops/node/jd01/runtime-v03
+12 -1
View File
@@ -880,6 +880,17 @@ lanes:
JD01:
node: JD01
workspace: /root/workspace/hwlab-v03
sourceAuthority:
mode: giteaSnapshot
resolver: gitea-mirror
giteaMirrorRepoKey: hwlab-jd01-v03
allowHostGit: false
allowHostWorkspace: false
allowGithubDirectInPipeline: false
sourceSnapshot:
stageRefPrefix: refs/unidesk/snapshots/gitea-actions/hwlab-node-runtime/{branch}
missingObjectPolicy: fail-fast
refreshPolicy: gitea-controlled-snapshot
sourceWorkspace:
git:
remoteName: github
@@ -956,7 +967,7 @@ lanes:
controlPlaneFieldManager: unidesk-hwlab-jd01-v03-control-plane
git:
url: git@github.com:pikasTech/HWLAB.git
readUrl: http://git-mirror-http.devops-infra.svc.cluster.local:8080/pikasTech/HWLAB.git
readUrl: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git
writeUrl: http://git-mirror-write.devops-infra.svc.cluster.local:8080/pikasTech/HWLAB.git
argo:
repoURL: http://git-mirror-http.devops-infra.svc.cluster.local:8080/pikasTech/HWLAB.git
+22
View File
@@ -9,6 +9,7 @@ metadata:
- 1548
- 1549
- 1550
- 1557
defaults:
targetId: JD01
@@ -116,6 +117,27 @@ sourceAuthority:
readUrl: http://git-mirror-http.devops-infra.svc.cluster.local:8080/pikasTech/unidesk.git
configRef: config/cicd-branch-followers.yaml#controller.source.gitMirrorReadUrl
disposition: replaced-by-gitea
- key: hwlab-jd01-v03
targetId: JD01
upstream:
repository: pikasTech/HWLAB
cloneUrl: https://github.com/pikasTech/HWLAB.git
branch: v0.3
gitea:
owner: mirrors
name: pikasTech-HWLAB
mirrorMode: controlled-push
publicRead: true
readUrl: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git
gitops:
branch: v0.3-gitops
flushDisposition: retained-for-gitops-flush
snapshot:
prefix: refs/unidesk/snapshots/gitea-actions/hwlab-node-runtime/v0.3
legacyGitMirror:
readUrl: http://git-mirror-http.devops-infra.svc.cluster.local:8080/pikasTech/HWLAB.git
configRef: config/hwlab-node-lanes.yaml#lanes.v03.targets.JD01.git.readUrl
disposition: replaced-by-gitea
targets:
- id: JD01
+35 -1
View File
@@ -8,6 +8,7 @@ metadata:
relatedIssues:
- 1552
- 1555
- 1557
defaults:
targetId: JD01
@@ -43,7 +44,7 @@ gitea:
secretSourceRef: .env/pipelines-as-code.webhook
events:
- push
branch: v0.2
branch: params.source_branch
repositories:
- id: agentrun-jd01-v02
@@ -89,6 +90,30 @@ repositories:
sentinel_id: jd01-web-probe-sentinel
pipeline_name: hwlab-web-probe-sentinel-jd01-pac
pipeline_run_prefix: hwlab-web-probe-sentinel-jd01
- id: hwlab-jd01-v03
name: hwlab-jd01-v03
namespace: hwlab-ci
providerType: gitea
url: https://gitea.pikapython.com/mirrors/pikasTech-HWLAB
cloneUrl: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git
owner: mirrors
repo: pikasTech-HWLAB
secretName: pac-gitea-hwlab-jd01-v03
tokenKey: token
webhookSecretKey: webhook.secret
concurrencyLimit: 1
params:
git_read_url: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git
git_write_url: http://git-mirror-write.devops-infra.svc.cluster.local:8080/pikasTech/HWLAB.git
source_branch: v0.3
gitops_branch: v0.3-gitops
source_snapshot_prefix: refs/unidesk/snapshots/gitea-actions/hwlab-node-runtime/v0.3
pipeline_name: hwlab-jd01-v03-ci-image-publish
pipeline_run_prefix: hwlab-jd01-v03-ci-poll
service_account: hwlab-jd01-v03-tekton-runner
node: JD01
lane: v03
runtime_namespace: hwlab-v03
consumers:
- id: agentrun-jd01-v02
@@ -109,3 +134,12 @@ consumers:
pipelineRunPrefix: hwlab-web-probe-sentinel-jd01
argoNamespace: argocd
argoApplication: hwlab-web-probe-sentinel-jd01
- id: hwlab-jd01-v03
repositoryRef: hwlab-jd01-v03
node: JD01
lane: v03
namespace: hwlab-ci
pipeline: hwlab-jd01-v03-ci-image-publish
pipelineRunPrefix: hwlab-jd01-v03-ci-poll
argoNamespace: argocd
argoApplication: hwlab-node-v03
@@ -298,8 +298,9 @@ export interface ControlPlaneTargetSpec {
repository: string;
branch: string;
sourceAuthority: {
mode: "gitMirrorSnapshot";
resolver: "k8s-git-mirror";
mode: "gitMirrorSnapshot" | "giteaSnapshot";
resolver: "k8s-git-mirror" | "gitea-mirror";
giteaMirrorRepoKey?: string;
allowHostGit: false;
allowHostWorkspace: false;
allowGithubDirectInPipeline: false;
@@ -307,7 +308,7 @@ export interface ControlPlaneTargetSpec {
sourceSnapshot: {
stageRefPrefix: string;
missingObjectPolicy: "fail-fast";
refreshPolicy: "sync-before-snapshot";
refreshPolicy: "sync-before-snapshot" | "gitea-controlled-snapshot";
};
};
gitops: { branch: string; path: string };
+10 -4
View File
@@ -1954,11 +1954,17 @@ function targetSpec(raw: Record<string, unknown>, index: number): ControlPlaneTa
function controlPlaneSourceAuthoritySpec(raw: Record<string, unknown>, path: string): ControlPlaneTargetSpec["source"]["sourceAuthority"] {
const mode = stringField(raw, "mode", path);
const resolver = stringField(raw, "resolver", path);
if (mode !== "gitMirrorSnapshot") throw new Error(`${path}.mode must be gitMirrorSnapshot`);
if (resolver !== "k8s-git-mirror") throw new Error(`${path}.resolver must be k8s-git-mirror`);
if (mode !== "gitMirrorSnapshot" && mode !== "giteaSnapshot") throw new Error(`${path}.mode must be gitMirrorSnapshot or giteaSnapshot`);
if (resolver !== "k8s-git-mirror" && resolver !== "gitea-mirror") throw new Error(`${path}.resolver must be k8s-git-mirror or gitea-mirror`);
if (mode === "gitMirrorSnapshot" && resolver !== "k8s-git-mirror") throw new Error(`${path}.resolver must be k8s-git-mirror when mode is gitMirrorSnapshot`);
if (mode === "giteaSnapshot" && resolver !== "gitea-mirror") throw new Error(`${path}.resolver must be gitea-mirror when mode is giteaSnapshot`);
const giteaMirrorRepoKey = optionalStringField(raw, "giteaMirrorRepoKey", path);
if (mode === "giteaSnapshot" && giteaMirrorRepoKey === undefined) throw new Error(`${path}.giteaMirrorRepoKey is required when mode is giteaSnapshot`);
if (giteaMirrorRepoKey !== undefined && !/^[A-Za-z0-9._-]+$/u.test(giteaMirrorRepoKey)) throw new Error(`${path}.giteaMirrorRepoKey must be a simple Gitea mirror repo key`);
return {
mode,
resolver,
...(giteaMirrorRepoKey === undefined ? {} : { giteaMirrorRepoKey }),
allowHostGit: falseBooleanField(raw, "allowHostGit", path),
allowHostWorkspace: falseBooleanField(raw, "allowHostWorkspace", path),
allowGithubDirectInPipeline: falseBooleanField(raw, "allowGithubDirectInPipeline", path),
@@ -1973,7 +1979,7 @@ function controlPlaneSourceSnapshotSpec(raw: Record<string, unknown>, path: stri
const missingObjectPolicy = stringField(raw, "missingObjectPolicy", path);
const refreshPolicy = stringField(raw, "refreshPolicy", path);
if (missingObjectPolicy !== "fail-fast") throw new Error(`${path}.missingObjectPolicy must be fail-fast`);
if (refreshPolicy !== "sync-before-snapshot") throw new Error(`${path}.refreshPolicy must be sync-before-snapshot`);
if (refreshPolicy !== "sync-before-snapshot" && refreshPolicy !== "gitea-controlled-snapshot") throw new Error(`${path}.refreshPolicy must be sync-before-snapshot or gitea-controlled-snapshot`);
return { stageRefPrefix, missingObjectPolicy, refreshPolicy };
}
@@ -2126,7 +2132,7 @@ function tektonArgoObserverRole(target: ControlPlaneTargetSpec, labels: Record<s
{
apiGroups: ["argoproj.io"],
resources: ["applications"],
verbs: ["get", "list", "watch"],
verbs: ["get", "list", "watch", "patch"],
},
],
};
+16 -7
View File
@@ -331,8 +331,9 @@ export interface HwlabRuntimeImageBuildSpec {
}
export interface HwlabRuntimeSourceAuthoritySpec {
readonly mode: "gitMirrorSnapshot";
readonly resolver: "k8s-git-mirror";
readonly mode: "gitMirrorSnapshot" | "giteaSnapshot";
readonly resolver: "k8s-git-mirror" | "gitea-mirror";
readonly giteaMirrorRepoKey?: string;
readonly allowHostGit: false;
readonly allowHostWorkspace: false;
readonly allowGithubDirectInPipeline: false;
@@ -341,7 +342,7 @@ export interface HwlabRuntimeSourceAuthoritySpec {
export interface HwlabRuntimeSourceSnapshotSpec {
readonly stageRefPrefix: string;
readonly missingObjectPolicy: "fail-fast";
readonly refreshPolicy: "sync-before-snapshot";
readonly refreshPolicy: "sync-before-snapshot" | "gitea-controlled-snapshot";
}
export interface HwlabRuntimePublicExposureFrpcProxySpec {
@@ -522,7 +523,7 @@ export function hwlabRuntimeActiveExternalPostgres(spec: HwlabRuntimeLaneSpec):
}
export function hwlabRuntimeSourceSnapshotStageRefPrefix(spec: HwlabRuntimeLaneSpec): string {
if (spec.sourceSnapshot === undefined) throw new Error(`${HWLAB_NODE_LANE_CONFIG_PATH} lanes.${spec.lane}.sourceSnapshot is required for k8s git-mirror snapshot source authority`);
if (spec.sourceSnapshot === undefined) throw new Error(`${HWLAB_NODE_LANE_CONFIG_PATH} lanes.${spec.lane}.sourceSnapshot is required for runtime source snapshot authority`);
return spec.sourceSnapshot.stageRefPrefix.replaceAll("{branch}", spec.sourceBranch);
}
@@ -861,9 +862,17 @@ function laneTargetConfig(id: HwlabRuntimeLane, nodeId: string, baseRaw: Record<
function sourceAuthorityConfig(value: unknown, path: string): HwlabRuntimeSourceAuthoritySpec | undefined {
if (value === undefined) return undefined;
const raw = asRecord(value, path);
const mode = enumStringField(raw, "mode", path, ["gitMirrorSnapshot", "giteaSnapshot"]);
const resolver = enumStringField(raw, "resolver", path, ["k8s-git-mirror", "gitea-mirror"]);
if (mode === "gitMirrorSnapshot" && resolver !== "k8s-git-mirror") throw new Error(`${path}.resolver must be k8s-git-mirror when mode is gitMirrorSnapshot`);
if (mode === "giteaSnapshot" && resolver !== "gitea-mirror") throw new Error(`${path}.resolver must be gitea-mirror when mode is giteaSnapshot`);
const giteaMirrorRepoKey = optionalStringField(raw, "giteaMirrorRepoKey", path);
if (mode === "giteaSnapshot" && giteaMirrorRepoKey === undefined) throw new Error(`${path}.giteaMirrorRepoKey is required when mode is giteaSnapshot`);
if (giteaMirrorRepoKey !== undefined && !/^[A-Za-z0-9._-]+$/u.test(giteaMirrorRepoKey)) throw new Error(`${path}.giteaMirrorRepoKey must be a simple Gitea mirror repo key`);
return {
mode: enumStringField(raw, "mode", path, ["gitMirrorSnapshot"]),
resolver: enumStringField(raw, "resolver", path, ["k8s-git-mirror"]),
mode,
resolver,
...(giteaMirrorRepoKey === undefined ? {} : { giteaMirrorRepoKey }),
allowHostGit: falseBooleanField(raw, "allowHostGit", path),
allowHostWorkspace: falseBooleanField(raw, "allowHostWorkspace", path),
allowGithubDirectInPipeline: falseBooleanField(raw, "allowGithubDirectInPipeline", path),
@@ -880,7 +889,7 @@ function sourceSnapshotConfig(value: unknown, path: string): HwlabRuntimeSourceS
return {
stageRefPrefix,
missingObjectPolicy: enumStringField(raw, "missingObjectPolicy", path, ["fail-fast"]),
refreshPolicy: enumStringField(raw, "refreshPolicy", path, ["sync-before-snapshot"]),
refreshPolicy: enumStringField(raw, "refreshPolicy", path, ["sync-before-snapshot", "gitea-controlled-snapshot"]),
};
}
+47
View File
@@ -95,6 +95,7 @@ export function nodeRuntimeGitopsRoot(spec: HwlabRuntimeLaneSpec): string {
}
export function resolveNodeRuntimeLaneHead(spec: HwlabRuntimeLaneSpec): { sourceCommit: string | null; result: CommandResult; stdoutRecovery?: Record<string, unknown> } {
if (spec.sourceAuthority?.mode === "giteaSnapshot") return resolveNodeRuntimeGiteaLaneHead(spec);
const mirror = nodeRuntimeSourceMirrorTarget(spec);
const script = [
"set +e",
@@ -134,6 +135,52 @@ export function resolveNodeRuntimeLaneHead(spec: HwlabRuntimeLaneSpec): { source
return { sourceCommit: result.exitCode === 0 ? match : null, result, stdoutRecovery: payloadResolution.diagnostics };
}
function resolveNodeRuntimeGiteaLaneHead(spec: HwlabRuntimeLaneSpec): { sourceCommit: string | null; result: CommandResult; stdoutRecovery?: Record<string, unknown> } {
const repoKey = nodeRuntimeGiteaRepoKey(spec);
const result = runCommand([
"bun",
"scripts/cli.ts",
"platform-infra",
"gitea",
"mirror",
"status",
"--target",
spec.nodeId,
"--repo",
repoKey,
"--raw",
], repoRoot, { timeoutMs: 60_000 });
const payloadResolution = resolveRuntimeCleanupJson(result, "platform-infra gitea mirror status JSON");
const payload = runtimeRecord(payloadResolution.parsed?.data ?? payloadResolution.parsed);
const repositories = Array.isArray(payload.repositories)
? payload.repositories.filter((item): item is Record<string, unknown> => typeof item === "object" && item !== null && !Array.isArray(item))
: [];
const repo = repositories[0] ?? {};
const branchCommit = typeof repo.branchCommit === "string" && /^[0-9a-f]{40}$/iu.test(repo.branchCommit) ? repo.branchCommit.toLowerCase() : null;
const snapshotCommit = typeof repo.snapshotCommit === "string" && /^[0-9a-f]{40}$/iu.test(repo.snapshotCommit) ? repo.snapshotCommit.toLowerCase() : null;
const snapshotRef = typeof repo.snapshotRef === "string" ? repo.snapshotRef : null;
const sourceCommit = branchCommit !== null && snapshotCommit === branchCommit ? branchCommit : null;
return {
sourceCommit: result.exitCode === 0 ? sourceCommit : null,
result,
stdoutRecovery: {
...payloadResolution.diagnostics,
sourceAuthority: "gitea-snapshot",
repoKey,
branchCommit,
snapshotCommit,
snapshotRef,
snapshotPresent: sourceCommit !== null,
},
};
}
function nodeRuntimeGiteaRepoKey(spec: HwlabRuntimeLaneSpec): string {
const repoKey = spec.sourceAuthority?.giteaMirrorRepoKey;
if (typeof repoKey !== "string" || repoKey.length === 0) throw new Error(`giteaMirrorRepoKey is required for node=${spec.nodeId} lane=${spec.lane} gitea source authority`);
return repoKey;
}
function resolveRuntimeCleanupJson(result: CommandResult, requestedStdoutType: string): { parsed: Record<string, unknown> | null; diagnostics: Record<string, unknown> } {
const resolved = resolveCliChildJsonCommandResult({
result,
+91 -16
View File
@@ -35,7 +35,7 @@ import { parseNodeScopedDelegatedOptions } from "./plan";
import { compactNodeRuntimeTaskRunDiagnostic, nodeRuntimeControlPlaneStatus, nodeRuntimePipelineFailureSummary } from "./render";
import { compactRuntimeCommand } from "./runtime-common";
import { compactNodeRuntimeGitMirrorObservation, compactNodeRuntimeGitMirrorRun, nodeRuntimeEnsureGitMirrorFlushed, nodeRuntimeEnsureGitMirrorSourceCurrent, nodeRuntimeExternalPostgresSecretRows, nodeRuntimeGitMirrorRun, nodeRuntimeGitMirrorStatus, nodeRuntimeOpportunisticGitMirrorFlush, nodeRuntimeOpportunisticGitMirrorSync, nodeScopedFullOutput, type NodeRuntimeGitMirrorRunOptions } from "./status";
import { record } from "./utils";
import { parseJsonObject, record, statusText } from "./utils";
import { webObserveTable } from "./web-observe-render";
import { createNodeRuntimePipelineRun, getNodeRuntimePipelineRun, nodeRuntimePipelineRunManifest, printNodeRuntimeTriggerProgress, waitForNodeRuntimePipelineRunTerminal } from "./web-probe";
import { webObserveShort, webObserveText } from "./web-probe-observe";
@@ -53,20 +53,7 @@ export function nodeRuntimeTriggerCurrent(scoped: ReturnType<typeof parseNodeSco
const triggerDeadlineMs = triggerStartedAt + (nodeRuntimeCicdWaitSeconds(scoped) * 1000);
const remainingTriggerSeconds = () => Math.max(0, Math.floor((triggerDeadlineMs - Date.now()) / 1000));
const triggerGitMirrorOptions = nodeRuntimeTriggerGitMirrorOptions(triggerDeadlineMs);
const sourceSyncScoped = nodeRuntimeScopedForTriggerDeadline(scoped, triggerDeadlineMs);
const sourceSnapshotSync = scoped.dryRun
? null
: sourceSyncScoped === null
? nodeRuntimeTriggerBudgetExhausted(scoped, "source-snapshot-sync", "-", "-")
: nodeRuntimeGitMirrorRun({
...sourceSyncScoped,
domain: "git-mirror",
action: "sync",
confirm: true,
dryRun: false,
wait: true,
discardStaleGitops: scoped.discardStaleGitops === true || scoped.rerun === true,
}, triggerGitMirrorOptions);
const sourceSnapshotSync = scoped.dryRun ? null : nodeRuntimeSourceSnapshotSync(scoped, triggerDeadlineMs, triggerGitMirrorOptions);
if (sourceSnapshotSync !== null && sourceSnapshotSync.ok !== true) {
return {
ok: false,
@@ -184,7 +171,9 @@ export function nodeRuntimeTriggerCurrent(scoped: ReturnType<typeof parseNodeSco
};
}
printNodeRuntimeTriggerProgress(spec, { stage: "git-mirror-pre-sync", status: "started", sourceCommit, pipelineRun });
const gitMirror = nodeRuntimeEnsureGitMirrorSourceCurrent(scoped, sourceCommit, pipelineRun, triggerGitMirrorOptions);
const gitMirror = spec.sourceAuthority?.mode === "giteaSnapshot"
? nodeRuntimeEnsureGiteaSourceCurrent(scoped, sourceCommit, pipelineRun, triggerGitMirrorOptions)
: nodeRuntimeEnsureGitMirrorSourceCurrent(scoped, sourceCommit, pipelineRun, triggerGitMirrorOptions);
if (gitMirror.ok !== true) {
printNodeRuntimeTriggerProgress(spec, { stage: "git-mirror-pre-sync", status: "failed", sourceCommit, pipelineRun, reason: gitMirror.degradedReason ?? null });
return {
@@ -320,6 +309,92 @@ function nodeRuntimeTriggerGitMirrorOptions(deadlineMs: number): NodeRuntimeGitM
return { deadlineMs, maxAttempts: 1 };
}
function nodeRuntimeSourceSnapshotSync(
scoped: ReturnType<typeof parseNodeScopedDelegatedOptions>,
deadlineMs: number,
gitMirrorOptions: NodeRuntimeGitMirrorRunOptions,
): Record<string, unknown> {
const syncScoped = nodeRuntimeScopedForTriggerDeadline(scoped, deadlineMs);
if (syncScoped === null) return nodeRuntimeTriggerBudgetExhausted(scoped, "source-snapshot-sync", "-", "-");
if (scoped.spec.sourceAuthority?.mode === "giteaSnapshot") return nodeRuntimeGiteaMirrorSync(scoped, deadlineMs);
return nodeRuntimeGitMirrorRun({
...syncScoped,
domain: "git-mirror",
action: "sync",
confirm: true,
dryRun: false,
wait: true,
discardStaleGitops: scoped.discardStaleGitops === true || scoped.rerun === true,
}, gitMirrorOptions);
}
function nodeRuntimeGiteaMirrorSync(scoped: ReturnType<typeof parseNodeScopedDelegatedOptions>, deadlineMs: number): Record<string, unknown> {
const repoKey = scoped.spec.sourceAuthority?.giteaMirrorRepoKey;
if (typeof repoKey !== "string" || repoKey.length === 0) {
return {
ok: false,
action: "platform-infra-gitea-mirror-sync",
mode: "gitea-controlled-snapshot-sync",
mutation: false,
degradedReason: "node-runtime-gitea-repo-key-missing",
};
}
const timeoutMs = Math.max(1_000, Math.min(120_000, deadlineMs - Date.now()));
const result = runCommand([
"bun",
"scripts/cli.ts",
"platform-infra",
"gitea",
"mirror",
"sync",
"--target",
scoped.node,
"--repo",
repoKey,
"--confirm",
"--raw",
], repoRoot, { timeoutMs });
const parsedRoot = record(parseJsonObject(statusText(result)));
const parsed = record(parsedRoot.data ?? parsedRoot);
const ok = result.exitCode === 0 && (parsed.ok === true || parsedRoot.ok === true);
return {
ok,
action: "platform-infra-gitea-mirror-sync",
mode: "gitea-controlled-snapshot-sync",
mutation: ok,
repoKey,
sourceAuthority: "gitea-snapshot",
sourceBundles: parsed.sourceBundles ?? null,
repositories: parsed.repositories ?? null,
result: compactRuntimeCommand(result),
remote: parsed,
degradedReason: ok ? undefined : "node-runtime-gitea-source-snapshot-sync-failed",
next: ok ? undefined : {
status: `bun scripts/cli.ts platform-infra gitea mirror status --target ${scoped.node} --repo ${repoKey}`,
retry: `bun scripts/cli.ts platform-infra gitea mirror sync --target ${scoped.node} --repo ${repoKey} --confirm`,
},
};
}
function nodeRuntimeEnsureGiteaSourceCurrent(
scoped: ReturnType<typeof parseNodeScopedDelegatedOptions>,
sourceCommit: string,
pipelineRun: string,
options: NodeRuntimeGitMirrorRunOptions,
): Record<string, unknown> {
const flush = nodeRuntimeEnsureGitMirrorFlushed(scoped, "pre", sourceCommit, pipelineRun, null, options);
return {
ok: flush.ok === true,
mode: "gitea-source-authority",
sourceAuthority: "gitea-snapshot",
sourceCommit,
sourceCurrent: true,
legacyGitMirrorRole: "gitops-flush-only",
flush,
degradedReason: flush.ok === true ? undefined : "node-runtime-git-mirror-pre-flush-failed",
};
}
function nodeRuntimeScopedForTriggerDeadline(
scoped: ReturnType<typeof parseNodeScopedDelegatedOptions>,
deadlineMs: number,
+106
View File
@@ -61,6 +61,111 @@ export function nodeRuntimeExternalPostgresSecretRows(secrets: Record<string, un
}
export function nodeRuntimeGitMirrorStatus(scoped: ReturnType<typeof parseNodeScopedDelegatedOptions>): Record<string, unknown> {
if (scoped.spec.sourceAuthority?.mode === "giteaSnapshot") return nodeRuntimeGiteaSourceStatus(scoped);
return nodeRuntimeLegacyGitMirrorStatus(scoped);
}
function nodeRuntimeGiteaSourceStatus(scoped: ReturnType<typeof parseNodeScopedDelegatedOptions>): Record<string, unknown> {
const repoKey = scoped.spec.sourceAuthority?.giteaMirrorRepoKey;
if (typeof repoKey !== "string" || repoKey.length === 0) {
return {
ok: false,
command: `hwlab nodes git-mirror status --node ${scoped.node} --lane ${scoped.lane}`,
node: scoped.node,
lane: scoped.lane,
mode: "gitea-source+legacy-gitops-flush",
mutation: false,
degradedReason: "node-runtime-gitea-repo-key-missing",
};
}
const legacy = nodeRuntimeLegacyGitMirrorStatus(scoped);
const result = runCommand([
"bun",
"scripts/cli.ts",
"platform-infra",
"gitea",
"mirror",
"status",
"--target",
scoped.node,
"--repo",
repoKey,
"--raw",
], repoRoot, { timeoutMs: scoped.timeoutSeconds * 1000 });
const parsedRoot = record(parseJsonObject(statusText(result)));
const parsed = record(parsedRoot.data ?? parsedRoot);
const repositories = Array.isArray(parsed.repositories)
? parsed.repositories.filter((item): item is Record<string, unknown> => typeof item === "object" && item !== null && !Array.isArray(item))
: [];
const repo = repositories[0] ?? {};
const branchCommit = typeof repo.branchCommit === "string" && /^[0-9a-f]{40}$/iu.test(repo.branchCommit) ? repo.branchCommit.toLowerCase() : null;
const snapshotCommit = typeof repo.snapshotCommit === "string" && /^[0-9a-f]{40}$/iu.test(repo.snapshotCommit) ? repo.snapshotCommit.toLowerCase() : null;
const snapshotRef = typeof repo.snapshotRef === "string" ? repo.snapshotRef : null;
const sourceReady = branchCommit !== null && snapshotCommit === branchCommit;
const legacySummary = compactNodeRuntimeGitMirrorStatus(legacy);
const localGitops = typeof legacySummary.localGitops === "string" ? legacySummary.localGitops : null;
const githubGitops = typeof legacySummary.githubGitops === "string" ? legacySummary.githubGitops : null;
const pendingFlush = legacySummary.pendingFlush === true || (localGitops !== null && githubGitops !== null && localGitops !== githubGitops);
const summary = {
localSource: branchCommit,
githubSource: branchCommit,
sourceAuthority: "gitea-snapshot",
sourceStageRef: snapshotRef,
sourceSnapshot: snapshotCommit,
localGitops,
githubGitops,
pendingFlush,
flushNeeded: pendingFlush,
githubInSync: sourceReady && localGitops !== null && githubGitops !== null && localGitops === githubGitops,
refSources: {
localSource: `gitea:${repoKey}:refs/heads/${scoped.spec.sourceBranch}`,
githubSource: `gitea:${repoKey}:refs/heads/${scoped.spec.sourceBranch}`,
sourceSnapshot: snapshotRef,
localGitops: "legacy-git-mirror:refs/heads/" + scoped.spec.gitopsBranch,
githubGitops: "legacy-git-mirror:refs/mirror-stage/heads/" + scoped.spec.gitopsBranch,
githubFieldsAreMirrorStageCache: false,
},
};
const ok = result.exitCode === 0 && (parsed.ok === true || parsedRoot.ok === true) && sourceReady;
return {
ok,
command: `hwlab nodes git-mirror status --node ${scoped.node} --lane ${scoped.lane}`,
node: scoped.node,
lane: scoped.lane,
mode: "gitea-source+legacy-gitops-flush",
mutation: false,
namespace: record(legacy).namespace ?? null,
readUrl: scoped.spec.gitReadUrl,
writeUrl: scoped.spec.gitWriteUrl,
sourceBranch: scoped.spec.sourceBranch,
gitopsBranch: scoped.spec.gitopsBranch,
resources: record(legacy.resources),
githubTransport: record(legacy.githubTransport),
summary,
gitea: {
repoKey,
repository: repo,
status: parsed,
result: compactRuntimeCommand(result),
},
legacyGitMirror: {
role: "gitops-flush-only",
ok: legacy.ok === true,
summary: legacySummary,
degradedReason: legacy.degradedReason ?? null,
},
refSources: summary.refSources,
result: compactRuntimeCommand(result),
degradedReason: ok ? undefined : "node-runtime-gitea-source-snapshot-not-ready",
valuesPrinted: false,
next: {
sync: `bun scripts/cli.ts platform-infra gitea mirror sync --target ${scoped.node} --repo ${repoKey} --confirm`,
flush: `bun scripts/cli.ts hwlab nodes git-mirror flush --node ${scoped.node} --lane ${scoped.lane} --confirm`,
},
};
}
function nodeRuntimeLegacyGitMirrorStatus(scoped: ReturnType<typeof parseNodeScopedDelegatedOptions>): Record<string, unknown> {
const spec = scoped.spec;
const mirror = nodeRuntimeGitMirrorTarget(spec);
const script = [
@@ -703,6 +808,7 @@ export function nodeRuntimeOpportunisticGitMirrorSync(
pipelineRun: string,
options: NodeRuntimeGitMirrorRunOptions = {},
): Record<string, unknown> | null {
if (scoped.spec.sourceAuthority?.mode === "giteaSnapshot") return null;
const stage = "git-mirror-parallel-sync";
const syncScoped = nodeRuntimeScopedWithinDeadline(scoped, options.deadlineMs);
if (syncScoped === null) return null;
+5 -4
View File
@@ -648,7 +648,7 @@ async function mirrorCommand(config: UniDeskConfig, args: string[]): Promise<Rec
return options.full || options.raw ? result : renderMirrorSync(result);
}
if (action === "status") {
const options = parseCommonOptions(args.slice(1));
const options = parseMirrorOptions(args.slice(1));
const result = await mirrorStatus(config, options);
return options.full || options.raw ? result : renderMirrorStatus(result);
}
@@ -683,14 +683,15 @@ function mirrorPlan(options: CommonOptions): Record<string, unknown> {
};
}
async function mirrorStatus(config: UniDeskConfig, options: CommonOptions): Promise<Record<string, unknown>> {
async function mirrorStatus(config: UniDeskConfig, options: CommonOptions & { repoKey?: string | null }): Promise<Record<string, unknown>> {
const gitea = readGiteaConfig();
const target = resolveTarget(gitea, options.targetId);
const health = await validate(config, options);
const healthValidation = record(health.validation);
const credentials = credentialSummaries(gitea);
const secrets = ensureMirrorSecrets(gitea, false);
const result = await capture(config, target.route, ["sh"], remoteScript("mirror-status", gitea, target, "", { ...options, confirm: false, dryRun: true, wait: false }, { repos: repositoriesForTarget(gitea, target), secrets }));
const selectedRepos = selectedRepositories(gitea, target, options.repoKey ?? null);
const result = await capture(config, target.route, ["sh"], remoteScript("mirror-status", gitea, target, "", { ...options, confirm: false, dryRun: true, wait: false }, { repos: selectedRepos, secrets }));
const parsed = parseJsonOutput(result.stdout);
const repositories = arrayRecords(record(parsed).repositories);
return {
@@ -711,7 +712,7 @@ async function mirrorStatus(config: UniDeskConfig, options: CommonOptions): Prom
readinessDetail: "Gitea source-authority is ready when every configured repo has branch and snapshot refs in Gitea.",
},
responsibilities: gitea.sourceAuthority.responsibilities.map(responsibilitySummary),
repositories: repositories.length > 0 ? repositories : repositoriesForTarget(gitea, target).map((repo) => repositorySummary(gitea, repo)),
repositories: repositories.length > 0 ? repositories : selectedRepos.map((repo) => repositorySummary(gitea, repo)),
credentials,
remote: parsed ?? compactCapture(result, { full: options.full || result.exitCode !== 0 }),
next: mirrorNextCommands(target.id),
@@ -451,7 +451,7 @@ function remoteScript(action: "apply" | "status" | "webhook-test", pac: PacConfi
UNIDESK_PAC_PIPELINE_RUN_PREFIX: consumer.pipelineRunPrefix,
UNIDESK_PAC_ARGO_NAMESPACE: consumer.argoNamespace,
UNIDESK_PAC_ARGO_APPLICATION: consumer.argoApplication,
UNIDESK_PAC_PART_OF: consumer.id.startsWith("sentinel") ? "hwlab-web-probe-sentinel" : "agentrun",
UNIDESK_PAC_PART_OF: pacPartOf(consumer.id),
UNIDESK_PAC_SPEC: kubernetesLabelValue(pac.metadata.relatedIssues.includes(1555) ? "GH-1552-GH-1555" : pac.metadata.spec),
};
const exports = Object.entries(env).map(([key, value]) => `export ${key}=${shQuote(value)}`).join("\n");
@@ -462,6 +462,12 @@ function credentialPath(root: string, sourceRef: string): string {
return sourceRef.startsWith("/") ? sourceRef : join(root, sourceRef);
}
function pacPartOf(consumerId: string): string {
if (consumerId.startsWith("sentinel")) return "hwlab-web-probe-sentinel";
if (consumerId.startsWith("hwlab-")) return "hwlab";
return "agentrun";
}
function parseLinePair(path: string, usernameLine: number, passwordLine: number): { username: string; password: string } {
const lines = readFileSync(path, "utf8").split(/\r?\n/u);
const username = lines[usernameLine - 1]?.trim() ?? "";