import { createHash } from "node:crypto"; import { readFileSync } from "node:fs"; import { rootPath } from "./config"; import { runCommand, type CommandResult } from "./command"; import { egressBenchmarkCompactResult, egressBenchmarkDryRun, egressBenchmarkStartScript, egressBenchmarkStatusScript, type EgressBenchmarkSpec } from "./egress-proxy-benchmark"; import { resolveEgressProxySourceRef } from "./egress-proxy-sources"; import { hwlabRuntimeLaneSpecForNode, isHwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "./hwlab-node-lanes"; import type { RenderedCliResult } from "./output"; import { fingerprintSecretValues, readEnvSourceFile, requiredEnvValue } from "./secrets"; export const HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH = "config/hwlab-node-control-plane.yaml"; type InfraAction = "plan" | "status" | "apply"; type ToolsImageAction = "status" | "build" | "logs"; type ArgoAction = "status" | "apply" | "logs"; type EgressBenchmarkAction = "benchmark" | "status" | "logs"; type CiBuildBenchmarkAction = "benchmark" | "status" | "logs"; interface InfraOptions { action: InfraAction; node: string; lane: string; dryRun: boolean; confirm: boolean; timeoutSeconds: number; } interface ToolsImageOptions { action: ToolsImageAction; node: string; lane: string; dryRun: boolean; confirm: boolean; timeoutSeconds: number; tailLines: number; } interface ArgoOptions { action: ArgoAction; node: string; lane: string; dryRun: boolean; confirm: boolean; timeoutSeconds: number; tailLines: number; } interface EgressBenchmarkOptions { action: EgressBenchmarkAction; node: string; lane: string; profile: "no-mirror"; dryRun: boolean; confirm: boolean; samples: number; sampleTimeoutSeconds: number; timeoutSeconds: number; tailLines: number; } interface CiBuildBenchmarkOptions { action: CiBuildBenchmarkAction; node: string; lane: string; profile: string; dryRun: boolean; confirm: boolean; timeoutSeconds: number; tailLines: number; } interface CiBuildBenchmarkCachePolicy { noPipelineRunReuse: boolean; forceFullBuild: boolean; forbidGitopsCatalogReuse: boolean; forbidDependencyCache: boolean; forbidBuildkitCache: boolean; forbidRegistryMirror: boolean; forbidLocalPreheatedImages: boolean; } interface CiBuildBenchmarkProfileSpec { profile: string; runtimeLaneConfigRef: string; pipelineRunPrefix: string; catalogPathTemplate: string; imageTagMode: "full"; pipelineTimeoutSeconds: number; cachePolicy: CiBuildBenchmarkCachePolicy; requiredTimings: readonly string[]; failureFamilies: readonly string[]; } interface ControlPlaneEgressProxySpec { mode: "k8s-service-cluster-ip"; clientName: string; namespace: string; serviceName: string; port: number; sourceConfigRef: string | null; sourceFingerprint: string | null; sourceRef: string; sourceKey: string; sourceType: "subscription-url" | "master-shadowsocks"; preferredOutbound: "vless-reality" | "hysteria2" | null; noProxy: readonly string[]; } interface ControlPlaneGitMirrorEgressProxySpec { mode: "node-global" | "direct"; required: boolean; } type ControlPlaneGitMirrorGithubTransportSpec = | { mode: "ssh"; privateKeySecretKey: string; privateKeySourceRef: string; privateKeySourceKey: string; privateKeySourceEncoding: "plain" | "base64"; knownHostsSecretKey: string | null; knownHostsSourceRef: string | null; knownHostsSourceKey: string | null; knownHostsSourceEncoding: "plain" | "base64" | null; } | { mode: "https"; username: string; tokenSecretName: string; tokenSecretKey: string; tokenSourceRef: string; tokenSourceKey: string; }; interface ControlPlaneTektonGitWorkspaceSecretSpec { name: string; namespace: string; sourceRefFrom: "gitMirror.githubTransport"; privateKeySecretKey: string; knownHostsSecretKey: string; } interface ControlPlaneTektonRuntimeObserverRbacSpec { namespace: string; roleName: string; roleBindingName: string; } interface ControlPlaneTektonArgoObserverRbacSpec { namespace: string; roleName: string; roleBindingName: string; } interface ControlPlaneNodeSpec { id: string; route: string; kubeRoute: string; k3s: ControlPlaneK3sNodeSpec | null; registry: { endpoint: string }; egressProxy: ControlPlaneEgressProxySpec | null; } interface ControlPlaneK3sNodeSpec { serviceName: string; dropInPath: string; nodeStatusName: string; execStartPre: readonly (readonly string[])[]; serverArgs: readonly string[]; kubelet: { maxPods: number }; } interface DockerfileInlineSpec { filename: string; lines: readonly string[]; } interface ImageRewriteSpec { source: string; pullImage: string; target: string; } interface ControlPlaneTargetSpec { id: string; node: string; lane: string; enabled: boolean; ciNamespace: string; runtimeNamespace: string; source: { repository: string; branch: string }; gitops: { branch: string; path: string }; gitMirror: { namespace: string; serviceReadName: string; serviceWriteName: string; cachePvcName: string; cachePvcStorage: string; cacheHostPath: string | null; servicePort: number; deploymentReplicas: number; secretName: string; syncConfigMapName: string; syncJobPrefix: string; flushJobPrefix: string; readUrl: string; writeUrl: string; egressProxy: ControlPlaneGitMirrorEgressProxySpec | null; githubTransport: ControlPlaneGitMirrorGithubTransportSpec; }; tekton: { pipelineName: string; serviceAccountName: string; pipelineRunPrefix: string; gitWorkspaceSecret: ControlPlaneTektonGitWorkspaceSecretSpec; runtimeObserverRbac: ControlPlaneTektonRuntimeObserverRbacSpec; argoObserverRbac: ControlPlaneTektonArgoObserverRbacSpec; toolsImage: { output: string; imagePullPolicy: "Always" | "IfNotPresent" | "Never"; sourceKind: "dockerfile" | "docker-compose"; context: string; dockerfile?: string; dockerfileInline?: DockerfileInlineSpec; composeFile?: string; buildArgs: Readonly>; buildNetwork: string | null; publicBaseImages: readonly string[]; buildOwner: string; buildMode: string; }; }; ciBuildBenchmarks: readonly CiBuildBenchmarkProfileSpec[]; argo: { namespace: string; projectName: string; applicationName: string; applicationFile: string; install: { enabled: boolean; sourceKind: "url"; version: string; manifestUrl: string; fieldManager: string; imagePullPolicy: "Always" | "IfNotPresent" | "Never"; preloadImages: readonly string[]; imageRewrites: readonly ImageRewriteSpec[]; requiredCrds: readonly string[]; expectedDeployments: readonly string[]; expectedStatefulSets: readonly string[]; readinessTimeoutSeconds: number; }; }; } interface ControlPlaneImagePolicy { requireReproducibleBuildSource: boolean; forbidPrivateOrNodeLocalImagesAsInputs: boolean; allowNodeLocalRegistryAsBuildOutput: boolean; requiredSourceKinds: readonly ("dockerfile" | "docker-compose")[]; } interface ControlPlaneConfig { version: number; kind: string; metadata: { owner: string; relatedIssues: readonly number[] }; imagePolicy: ControlPlaneImagePolicy; nodes: Record; targets: readonly ControlPlaneTargetSpec[]; } export interface HwlabNodeControlPlaneSourceWorkspaceBootstrapSpec { readonly configPath: string; readonly targetId: string; readonly node: string; readonly lane: string; readonly ciNamespace: string; readonly serviceAccountName: string; readonly toolsImage: string; readonly imagePullPolicy: "Always" | "IfNotPresent" | "Never"; readonly gitReadUrl: string; readonly gitWriteUrl: string; readonly gitMirrorNamespace: string; } export function runHwlabNodeControlPlaneInfra(args: string[]): Record | RenderedCliResult { if (args[0] === "tools-image") { const options = parseToolsImageOptions(args.slice(1)); const { config, node, target } = controlPlaneContext(options.node, options.lane); return runToolsImageCommand(config, node, target, options); } if (args[0] === "argo") { const options = parseArgoOptions(args.slice(1)); const { config, node, target } = controlPlaneContext(options.node, options.lane); return runArgoCommand(config, node, target, options); } if (args[0] === "egress-benchmark") { const options = parseEgressBenchmarkOptions(args.slice(1)); const { config, node, target } = controlPlaneContext(options.node, options.lane); return runEgressBenchmarkCommand(config, node, target, options); } if (args[0] === "ci-build-benchmark") { const options = parseCiBuildBenchmarkOptions(args.slice(1)); const { config, node, target } = controlPlaneContext(options.node, options.lane); return runCiBuildBenchmarkCommand(config, node, target, options); } const options = parseInfraOptions(args); const { config, node, target } = controlPlaneContext(options.node, options.lane); if (options.action === "plan") return infraPlan(config, node, target, options); if (options.action === "status") return infraStatus(config, node, target, options); return infraApply(config, node, target, options); } function controlPlaneContext(nodeId: string, lane: string): { config: ControlPlaneConfig; node: ControlPlaneNodeSpec; target: ControlPlaneTargetSpec } { const config = readControlPlaneConfig(); const node = config.nodes[nodeId]; if (node === undefined) throw new Error(`unknown node ${nodeId}; known nodes: ${Object.keys(config.nodes).join(", ")}`); const target = config.targets.find((item) => item.node === nodeId && item.lane === lane); if (target === undefined) throw new Error(`no control-plane target for node=${nodeId} lane=${lane} in ${HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH}`); if (!target.enabled) throw new Error(`control-plane target ${target.id} is disabled in ${HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH}`); return { config, node, target }; } export function hwlabNodeControlPlaneCiGitWorkspaceSecret(nodeId: string, lane: string): { name: string; namespace: string } { const { target } = controlPlaneContext(nodeId, lane); return { name: target.tekton.gitWorkspaceSecret.name, namespace: target.tekton.gitWorkspaceSecret.namespace, }; } export function hwlabNodeControlPlaneSourceWorkspaceBootstrap(nodeId: string, lane: string): HwlabNodeControlPlaneSourceWorkspaceBootstrapSpec { const { target } = controlPlaneContext(nodeId, lane); return { configPath: HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, targetId: target.id, node: target.node, lane: target.lane, ciNamespace: target.ciNamespace, serviceAccountName: target.tekton.serviceAccountName, toolsImage: target.tekton.toolsImage.output, imagePullPolicy: target.tekton.toolsImage.imagePullPolicy, gitReadUrl: target.gitMirror.readUrl, gitWriteUrl: target.gitMirror.writeUrl, gitMirrorNamespace: target.gitMirror.namespace, }; } export function hwlabNodeControlPlaneInfraHelp(): Record { return { ok: true, command: "hwlab nodes control-plane infra", configPath: HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, description: "Plan/status/apply YAML-controlled HWLAB node-local k3s, CI/CD and git-mirror control-plane prerequisites. Cross-node PK01/Caddy/FRP/runtime rollout remains explicit semi-automatic CLI work.", usage: [ "bun scripts/cli.ts hwlab nodes control-plane infra plan --node D601 --lane v03", "bun scripts/cli.ts hwlab nodes control-plane infra status --node D601 --lane v03", "bun scripts/cli.ts hwlab nodes control-plane infra apply --node D601 --lane v03 --dry-run", "bun scripts/cli.ts hwlab nodes control-plane infra apply --node D601 --lane v03 --confirm", "bun scripts/cli.ts hwlab nodes control-plane infra tools-image status --node D601 --lane v03", "bun scripts/cli.ts hwlab nodes control-plane infra tools-image build --node D601 --lane v03 --dry-run", "bun scripts/cli.ts hwlab nodes control-plane infra tools-image build --node D601 --lane v03 --confirm", "bun scripts/cli.ts hwlab nodes control-plane infra tools-image logs --node D601 --lane v03", "bun scripts/cli.ts hwlab nodes control-plane infra argo status --node D601 --lane v03", "bun scripts/cli.ts hwlab nodes control-plane infra argo apply --node D601 --lane v03 --dry-run", "bun scripts/cli.ts hwlab nodes control-plane infra argo apply --node D601 --lane v03 --confirm", "bun scripts/cli.ts hwlab nodes control-plane infra argo logs --node D601 --lane v03", "bun scripts/cli.ts hwlab nodes control-plane infra egress-benchmark --node D601 --lane v03 --profile no-mirror --confirm", "bun scripts/cli.ts hwlab nodes control-plane infra egress-benchmark status --node D601 --lane v03 --profile no-mirror", "bun scripts/cli.ts hwlab nodes control-plane infra ci-build-benchmark --node D601 --lane v03 --profile no-mirror-full --confirm", "bun scripts/cli.ts hwlab nodes control-plane infra ci-build-benchmark status --node D601 --lane v03 --profile no-mirror-full", "bun scripts/cli.ts hwlab nodes control-plane infra ci-build-benchmark logs --node D601 --lane v03 --profile no-mirror-full", ], g14Consistency: "D601 target fields mirror the existing G14 runtime lane control-plane vocabulary: source branch, gitops branch/path, Pipeline, PipelineRun prefix, ServiceAccount, Argo Application, and git-mirror read/write/sync/flush status concepts.", }; } function infraPlan(_config: ControlPlaneConfig, node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, options: InfraOptions): Record { return { ok: true, command: "hwlab nodes control-plane infra plan", configPath: HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, node: node.id, lane: target.lane, mode: "plan", mutation: false, target: planSummary(node, target), expected: expectedSummary(node, target), hostConfig: k3sNodeConfigPlan(node), imagePolicy: _config.imagePolicy, g14Consistency: { laneVocabulary: ["sourceBranch", "gitopsBranch", "catalogPath", "runtime.path", "runtime.namespace", "tekton.pipeline", "pipelineRunPrefix", "argo.application"], gitMirrorStatusVocabulary: ["localSource", "githubSource", "localGitops", "githubGitops", "pendingFlush", "flushNeeded", "githubInSync"], note: "D601 values differ only through YAML target fields; the control-plane model is intentionally aligned with G14 runtime lane semantics.", }, resources: manifestObjectSummary(renderInfraManifest(node, target)), next: { status: `bun scripts/cli.ts hwlab nodes control-plane infra status --node ${node.id} --lane ${target.lane}`, dryRun: `bun scripts/cli.ts hwlab nodes control-plane infra apply --node ${node.id} --lane ${target.lane} --dry-run`, apply: `bun scripts/cli.ts hwlab nodes control-plane infra apply --node ${node.id} --lane ${target.lane} --confirm`, toolsImageBuild: `bun scripts/cli.ts hwlab nodes control-plane infra tools-image build --node ${node.id} --lane ${target.lane} --confirm`, argoApply: `bun scripts/cli.ts hwlab nodes control-plane infra argo apply --node ${node.id} --lane ${target.lane} --confirm`, }, options: { timeoutSeconds: options.timeoutSeconds }, }; } function infraStatus(_config: ControlPlaneConfig, node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, options: InfraOptions): Record { const script = statusScript(node, target); const result = runTransK3s(node.kubeRoute, script, options.timeoutSeconds); const parsed = parseRemoteJson(result.stdout); const status = typeof parsed === "object" && parsed !== null ? parsed as Record : { parseError: "remote status did not return a JSON object", stdoutPreview: result.stdout.slice(0, 1000) }; const components = record(status.components); const argo = record(components.argo); const argoInstall = record(argo.install); const argoObserverRbac = record(argo.argoObserverRbac); const gitMirror = record(components.gitMirror); const gitMirrorGithubTransport = record(gitMirror.githubTransport); const tekton = record(components.tekton); const ciNamespace = record(components.ciNamespace); const ciGitWorkspaceSecret = record(ciNamespace.gitWorkspaceSecret); const runtimeNamespace = record(components.runtimeNamespace); const runtimeObserverRbac = record(runtimeNamespace.runtimeObserverRbac); const registry = record(components.registry); const k3sNodeConfig = record(components.k3sNodeConfig); const k3sNodeConfigReady = node.k3s === null || (boolField(k3sNodeConfig, "dropInMatches") && numberValue(k3sNodeConfig.liveCapacityPods) === node.k3s.kubelet.maxPods && numberValue(k3sNodeConfig.liveAllocatablePods) === node.k3s.kubelet.maxPods); const ok = result.exitCode === 0 && k3sNodeConfigReady && boolField(tekton, "installed") && boolField(ciNamespace, "exists") && boolField(ciGitWorkspaceSecret, "ready") && boolField(runtimeNamespace, "exists") && boolField(runtimeObserverRbac, "ready") && boolField(gitMirror, "namespaceExists") && boolField(gitMirror, "readServiceExists") && boolField(gitMirror, "writeServiceExists") && (gitMirrorGithubTransport.required !== true || boolField(gitMirrorGithubTransport, "ready")) && (boolField(gitMirror, "cachePvcExists") || boolField(gitMirror, "cacheHostPathReady")) && boolField(registry, "ready") && boolField(registry, "toolsImageReady") && boolField(argo, "installed") && boolField(argo, "projectExists") && boolField(argo, "applicationExists") && boolField(argoObserverRbac, "ready") && boolField(argoInstall, "crdsReady") && boolField(argoInstall, "deploymentsReady") && boolField(argoInstall, "statefulSetsReady"); return { ok, command: "hwlab nodes control-plane infra status", configPath: HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, node: node.id, lane: target.lane, mode: "status", mutation: false, expected: expectedSummary(node, target), status, readiness: { ok, k3sNodeConfigReady, tektonInstalled: boolField(tekton, "installed"), ciNamespaceExists: boolField(ciNamespace, "exists"), ciGitWorkspaceSecretReady: boolField(ciGitWorkspaceSecret, "ready"), runtimeNamespaceExists: boolField(runtimeNamespace, "exists"), runtimeObserverRbacReady: boolField(runtimeObserverRbac, "ready"), gitMirrorNamespaceExists: boolField(gitMirror, "namespaceExists"), gitMirrorReadServiceExists: boolField(gitMirror, "readServiceExists"), gitMirrorWriteServiceExists: boolField(gitMirror, "writeServiceExists"), gitMirrorGithubTransportReady: gitMirrorGithubTransport.required !== true || boolField(gitMirrorGithubTransport, "ready"), gitMirrorCachePvcExists: boolField(gitMirror, "cachePvcExists"), gitMirrorCacheHostPathReady: boolField(gitMirror, "cacheHostPathReady"), gitMirrorReadReady: boolField(gitMirror, "readDeploymentReady"), gitMirrorWriteReady: boolField(gitMirror, "writeDeploymentReady"), argoInstalled: boolField(argo, "installed"), argoProjectExists: boolField(argo, "projectExists"), argoApplicationExists: boolField(argo, "applicationExists"), argoObserverRbacReady: boolField(argoObserverRbac, "ready"), argoCrdsReady: boolField(argoInstall, "crdsReady"), argoDeploymentsReady: boolField(argoInstall, "deploymentsReady"), argoStatefulSetsReady: boolField(argoInstall, "statefulSetsReady"), registryReady: boolField(registry, "ready"), toolsImageReady: boolField(registry, "toolsImageReady"), }, result: compactCommandResult(result), next: ok ? { runtimePreparation: `bun scripts/cli.ts hwlab nodes control-plane plan --node ${node.id} --lane ${target.lane}` } : statusNext(node, target, registry, gitMirror, argo, ciNamespace, runtimeNamespace, k3sNodeConfig), }; } function infraApply(_config: ControlPlaneConfig, node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, options: InfraOptions): Record { if (options.confirm && options.dryRun) throw new Error("infra apply accepts only one of --dry-run or --confirm"); const dryRun = options.dryRun || !options.confirm; const manifest = renderInfraManifest(node, target); const yaml = `${manifest.map((item) => Bun.YAML.stringify(item).trim()).join("\n---\n")}\n`; const imageStatus = toolsImageStatus(node, target, options.timeoutSeconds); if (dryRun) { return { ok: true, command: "hwlab nodes control-plane infra apply", configPath: HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, node: node.id, lane: target.lane, mode: "dry-run", mutation: false, expected: expectedSummary(node, target), hostConfig: k3sNodeConfigPlan(node), preflight: { registryReady: imageStatus.registryReady, toolsImageReady: imageStatus.toolsImageReady, toolsImage: target.tekton.toolsImage.output, result: imageStatus.result, }, resources: manifestObjectSummary(manifest), manifest: { objects: manifest.length, bytes: Buffer.byteLength(yaml), sha256: sha256Short(yaml) }, note: "dry-run renders D601 node-local control-plane bootstrap resources only; it does not trigger HWLAB runtime rollout and does not touch PK01/Caddy/FRP.", next: applyNext(node, target, imageStatus), }; } const script = applyScript(yaml, node, target); const result = runTransK3s(node.kubeRoute, script, options.timeoutSeconds); const parsed = parseRemoteJson(result.stdout); return { ok: result.exitCode === 0, command: "hwlab nodes control-plane infra apply", configPath: HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, node: node.id, lane: target.lane, mode: "confirmed-apply", mutation: result.exitCode === 0, expected: expectedSummary(node, target), preflight: { registryReady: imageStatus.registryReady, toolsImageReady: imageStatus.toolsImageReady, toolsImage: target.tekton.toolsImage.output, warning: imageStatus.toolsImageReady ? null : "tools-image-missing; bootstrap objects were applied but readiness still requires a controlled image build/publish stage", result: imageStatus.result, }, resources: manifestObjectSummary(manifest), apply: typeof parsed === "object" && parsed !== null ? parsed : { stdoutPreview: result.stdout.slice(0, 2000) }, result: compactCommandResult(result), next: { status: `bun scripts/cli.ts hwlab nodes control-plane infra status --node ${node.id} --lane ${target.lane}` }, }; } function runToolsImageCommand(_config: ControlPlaneConfig, node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, options: ToolsImageOptions): Record { if (options.action === "status") return toolsImageCommandStatus(node, target, options); if (options.action === "logs") return remoteJobLogs(node, target, "tools-image", options); return toolsImageBuild(node, target, options); } function runArgoCommand(_config: ControlPlaneConfig, node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, options: ArgoOptions): Record { if (options.action === "status") return argoCommandStatus(node, target, options); if (options.action === "logs") return remoteJobLogs(node, target, "argo", options); return argoApply(node, target, options); } function runEgressBenchmarkCommand(_config: ControlPlaneConfig, node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, options: EgressBenchmarkOptions): Record | RenderedCliResult { const spec = controlPlaneEgressBenchmarkSpec(node, target, options); if (options.action === "status" || options.action === "logs") { const result = runTransK3s(node.kubeRoute, egressBenchmarkStatusScript(spec, options.tailLines), options.timeoutSeconds); const parsed = parseRemoteJson(result.stdout); const status = record(record(parsed).status); const state = renderCell(status.state, "unknown"); return renderControlPlaneBenchmarkResult({ ok: result.exitCode === 0 && (options.action === "logs" || state !== "failed"), command: `hwlab nodes control-plane infra egress-benchmark ${options.action}`, configPath: HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, node: node.id, lane: target.lane, mutation: false, job: typeof parsed === "object" && parsed !== null ? parsed : { stdoutPreview: result.stdout.slice(0, 2000) }, result: egressBenchmarkCompactResult(result), }); } if (options.dryRun) { return renderControlPlaneBenchmarkResult({ ok: true, command: "hwlab nodes control-plane infra egress-benchmark", configPath: HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, node: node.id, lane: target.lane, mode: "dry-run", mutation: false, plan: egressBenchmarkDryRun(spec), next: { confirm: `bun scripts/cli.ts hwlab nodes control-plane infra egress-benchmark --node ${node.id} --lane ${target.lane} --profile ${options.profile} --confirm` }, }); } const result = runTransK3s(node.kubeRoute, egressBenchmarkStartScript(spec), options.timeoutSeconds); const parsed = parseRemoteJson(result.stdout); return renderControlPlaneBenchmarkResult({ ok: result.exitCode === 0, command: "hwlab nodes control-plane infra egress-benchmark", configPath: HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, node: node.id, lane: target.lane, mode: "async-job", mutation: result.exitCode === 0, start: typeof parsed === "object" && parsed !== null ? parsed : { stdoutPreview: result.stdout.slice(0, 2000) }, result: egressBenchmarkCompactResult(result), }); } function controlPlaneEgressBenchmarkSpec(node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, options: EgressBenchmarkOptions): EgressBenchmarkSpec { const proxy = node.egressProxy; if (proxy === null) throw new Error(`nodes.${node.id}.egressProxy is required for egress-benchmark`); return { scope: "hwlab-control-plane", targetId: `${node.id}/${target.lane}`, route: node.kubeRoute, namespace: proxy.namespace, serviceName: proxy.serviceName, port: proxy.port, noProxy: proxy.noProxy, sourceType: proxy.sourceType, sourceRef: proxy.sourceRef, sourceConfigRef: proxy.sourceConfigRef, sourceFingerprint: proxy.sourceFingerprint, profile: options.profile, samples: options.samples, sampleTimeoutSeconds: options.sampleTimeoutSeconds, }; } function renderControlPlaneBenchmarkResult(result: Record): RenderedCliResult { const start = record(result.start); const job = record(result.job); const status = record(job.status); const plan = record(result.plan); const next = record(result.next); const rows = Array.isArray(status.rows) ? status.rows.map(record) : []; const statusText = renderCell(status.state, result.ok === false ? "failed" : "ok"); const logTail = typeof job.logTail === "string" ? job.logTail.trimEnd() : ""; const node = renderCell(result.node); const lane = renderCell(result.lane); const target = `${node}/${lane}`; const profile = renderCell(result.profile ?? plan.profile ?? status.profile, "no-mirror"); const statusCommand = renderCell(start.statusCommand, `bun scripts/cli.ts hwlab nodes control-plane infra egress-benchmark status --node ${node} --lane ${lane} --profile ${profile}`); const logsCommand = renderCell(start.logsCommand, `bun scripts/cli.ts hwlab nodes control-plane infra egress-benchmark logs --node ${node} --lane ${lane} --profile ${profile}`); const lines = [ "HWLAB CONTROL-PLANE EGRESS BENCHMARK", "", ...renderTable(["TARGET", "PROFILE", "MODE", "STATUS"], [[target, profile, renderCell(result.mode ?? optionsModeFromCommand(result.command)), statusText]]), "", rows.length === 0 ? "RESULTS\n-" : [ "RESULTS", ...renderTable(["TEST", "SUCCESS", "SAMPLES", "P50", "P95", "FAILURES"], rows.map((row) => [ renderCell(row.test), renderCell(row.success), renderCell(row.samples), renderCell(row.p50Ms), renderCell(row.p95Ms), JSON.stringify(row.failureFamilies ?? {}), ])), ].join("\n"), ...(logTail.length === 0 ? [] : ["", "LOG TAIL", logTail]), "", "NEXT", ` ${renderCell(next.confirm, "")}`, ` ${statusCommand}`, ` ${logsCommand}`, "", "Disclosure: default output is bounded; Secret/proxy source values are not printed.", ].filter((line) => line !== " "); return { ok: result.ok !== false, command: renderCell(result.command, "hwlab nodes control-plane infra egress-benchmark"), renderedText: lines.join("\n"), contentType: "text/plain" }; } function runCiBuildBenchmarkCommand(_config: ControlPlaneConfig, node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, options: CiBuildBenchmarkOptions): RenderedCliResult { const profile = ciBuildBenchmarkProfileForTarget(target, options.profile); const runtime = ciBuildBenchmarkRuntimeSpec(node, target, profile); if (options.action === "status" || options.action === "logs") { const result = runTransK3s(node.kubeRoute, ciBuildBenchmarkStatusScript(target, profile, options.tailLines, options.action === "logs"), options.timeoutSeconds); const parsed = parseRemoteJson(result.stdout); const job = typeof parsed === "object" && parsed !== null ? parsed as Record : { stdoutPreview: result.stdout.slice(0, 2000) }; return renderCiBuildBenchmarkResult({ ok: result.exitCode === 0 && ciBuildBenchmarkLiveOk(job, runtime.serviceIds, profile), command: `hwlab nodes control-plane infra ci-build-benchmark ${options.action}`, configPath: HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, node: node.id, lane: target.lane, profile: profile.profile, mode: options.action, mutation: false, benchmark: ciBuildBenchmarkDefinitionSummary(runtime, target, profile), job, result: compactCommandResult(result), }); } const head = resolveCiBuildBenchmarkSourceHead(runtime); if (head.sourceCommit === null) { throw new Error(`failed to resolve ${runtime.gitUrl} refs/heads/${runtime.sourceBranch}: ${head.result.stderr || head.result.stdout || `exit ${head.result.exitCode}`}`); } const pipelineRun = ciBuildBenchmarkPipelineRunName(profile.pipelineRunPrefix, head.sourceCommit); const catalogPath = ciBuildBenchmarkCatalogPath(profile, pipelineRun); const manifest = ciBuildBenchmarkPipelineRunManifest(runtime, target, profile, head.sourceCommit, pipelineRun, catalogPath); const plan = { ...ciBuildBenchmarkDefinitionSummary(runtime, target, profile), pipelineRun, sourceCommit: head.sourceCommit, catalogPath, manifest: manifestObjectSummary([manifest]), manifestSha256: sha256Short(JSON.stringify(manifest)), sourceHead: compactCommandResult(head.result), }; if (options.dryRun) { return renderCiBuildBenchmarkResult({ ok: true, command: "hwlab nodes control-plane infra ci-build-benchmark", configPath: HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, node: node.id, lane: target.lane, profile: profile.profile, mode: "dry-run", mutation: false, plan, next: { confirm: `bun scripts/cli.ts hwlab nodes control-plane infra ci-build-benchmark --node ${node.id} --lane ${target.lane} --profile ${profile.profile} --confirm` }, }); } const result = runTransK3s(node.kubeRoute, ciBuildBenchmarkStartScript(target, profile, manifest, runtime.pipeline, pipelineRun, head.sourceCommit, catalogPath), options.timeoutSeconds); const parsed = parseRemoteJson(result.stdout); return renderCiBuildBenchmarkResult({ ok: result.exitCode === 0, command: "hwlab nodes control-plane infra ci-build-benchmark", configPath: HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, node: node.id, lane: target.lane, profile: profile.profile, mode: "confirmed-start", mutation: result.exitCode === 0, benchmark: ciBuildBenchmarkDefinitionSummary(runtime, target, profile), start: typeof parsed === "object" && parsed !== null ? parsed : { stdoutPreview: result.stdout.slice(0, 2000) }, result: compactCommandResult(result), }); } function ciBuildBenchmarkProfileForTarget(target: ControlPlaneTargetSpec, profileName: string): CiBuildBenchmarkProfileSpec { const profile = target.ciBuildBenchmarks.find((item) => item.profile === profileName); if (profile === undefined) { const known = target.ciBuildBenchmarks.map((item) => item.profile).join(", ") || ""; throw new Error(`ci-build-benchmark profile ${profileName} is not declared for target ${target.id}; known profiles: ${known}`); } return profile; } function ciBuildBenchmarkRuntimeSpec(node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, profile: CiBuildBenchmarkProfileSpec): HwlabRuntimeLaneSpec { if (!isHwlabRuntimeLane(target.lane)) throw new Error(`target ${target.id}.lane=${target.lane} is not a runtime lane in config/hwlab-node-lanes.yaml`); const runtime = hwlabRuntimeLaneSpecForNode(target.lane, node.id); if (runtime.nodeId !== node.id || runtime.lane !== target.lane) throw new Error(`runtime lane mismatch for ${node.id}/${target.lane}`); if (!profile.runtimeLaneConfigRef.startsWith("config/hwlab-node-lanes.yaml#")) { throw new Error(`targets.${target.id}.ciBuildBenchmarks.${profile.profile}.runtimeLaneConfigRef must point at config/hwlab-node-lanes.yaml`); } return runtime; } function resolveCiBuildBenchmarkSourceHead(spec: HwlabRuntimeLaneSpec): { sourceCommit: string | null; result: CommandResult } { const result = runCommand(["git", "ls-remote", spec.gitUrl, `refs/heads/${spec.sourceBranch}`], rootPath(), { timeoutMs: 45_000 }); if (result.exitCode !== 0 || result.timedOut) return { sourceCommit: null, result }; const match = /[0-9a-f]{40}/iu.exec(`${result.stdout}\n${result.stderr}`); return { sourceCommit: match?.[0].toLowerCase() ?? null, result }; } function ciBuildBenchmarkPipelineRunName(prefix: string, sourceCommit: string): string { const suffix = `${sourceCommit.slice(0, 12)}-${Date.now().toString(36)}`; return `${prefix}-${suffix}`.slice(0, 63).replace(/-+$/u, ""); } function ciBuildBenchmarkCatalogPath(profile: CiBuildBenchmarkProfileSpec, pipelineRun: string): string { return profile.catalogPathTemplate.replace(/\{profile\}/gu, profile.profile).replace(/\{pipelineRun\}/gu, pipelineRun); } function ciBuildBenchmarkBuildCacheMode(profile: CiBuildBenchmarkProfileSpec): "disabled" | "registry" { return profile.cachePolicy.forbidBuildkitCache ? "disabled" : "registry"; } function ciBuildBenchmarkDefinitionSummary(runtime: HwlabRuntimeLaneSpec, target: ControlPlaneTargetSpec, profile: CiBuildBenchmarkProfileSpec): Record { return { targetId: target.id, profile: profile.profile, runtimeLaneConfigRef: profile.runtimeLaneConfigRef, namespace: target.ciNamespace, pipeline: runtime.pipeline, serviceAccountName: runtime.serviceAccountName, sourceBranch: runtime.sourceBranch, gitReadUrl: runtime.gitReadUrl, gitWriteUrl: runtime.gitWriteUrl, gitopsBranch: runtime.gitopsBranch, runtimePath: runtime.runtimePath, registryPrefix: runtime.registryPrefix, baseImage: runtime.baseImage, services: runtime.serviceIds, imageTagMode: profile.imageTagMode, buildCacheMode: ciBuildBenchmarkBuildCacheMode(profile), cachePolicy: profile.cachePolicy, requiredTimings: profile.requiredTimings, failureFamilies: profile.failureFamilies, }; } function ciBuildBenchmarkPipelineRunManifest( runtime: HwlabRuntimeLaneSpec, target: ControlPlaneTargetSpec, profile: CiBuildBenchmarkProfileSpec, sourceCommit: string, pipelineRun: string, catalogPath: string, ): Record { return { apiVersion: "tekton.dev/v1", kind: "PipelineRun", metadata: { name: pipelineRun, namespace: target.ciNamespace, labels: { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/gitops-target": runtime.lane, "hwlab.pikastech.local/source-commit": sourceCommit, "hwlab.pikastech.local/trigger": "unidesk-ci-build-benchmark", "unidesk.ai/benchmark": "ci-build", "unidesk.ai/benchmark-profile": profile.profile, }, annotations: { "hwlab.pikastech.local/node": runtime.nodeId, "hwlab.pikastech.local/source-branch": runtime.sourceBranch, "hwlab.pikastech.local/gitops-branch": runtime.gitopsBranch, "hwlab.pikastech.local/runtime-path": runtime.runtimePath, "hwlab.pikastech.local/network-profile": runtime.networkProfileId, "hwlab.pikastech.local/download-profile": runtime.downloadProfileId, "unidesk.ai/issue": "pikasTech/unidesk#1010", "unidesk.ai/cache-policy": JSON.stringify(profile.cachePolicy), "unidesk.ai/build-cache-mode": ciBuildBenchmarkBuildCacheMode(profile), "unidesk.ai/catalog-path": catalogPath, "unidesk.ai/runtime-lane-config-ref": profile.runtimeLaneConfigRef, "unidesk.ai/required-timings": profile.requiredTimings.join(","), }, }, spec: { pipelineRef: { name: runtime.pipeline }, timeouts: { pipeline: `${profile.pipelineTimeoutSeconds}s` }, taskRunTemplate: { serviceAccountName: runtime.serviceAccountName, podTemplate: { hostNetwork: true, dnsPolicy: "ClusterFirstWithHostNet", securityContext: { fsGroup: 1000 }, }, }, params: [ { name: "git-url", value: runtime.gitUrl }, { name: "git-read-url", value: runtime.gitReadUrl }, { name: "git-write-url", value: runtime.gitWriteUrl }, { name: "source-branch", value: runtime.sourceBranch }, { name: "gitops-branch", value: runtime.gitopsBranch }, { name: "lane", value: runtime.lane }, { name: "catalog-path", value: catalogPath }, { name: "image-tag-mode", value: profile.imageTagMode }, { name: "runtime-path", value: runtime.runtimePath }, { name: "revision", value: sourceCommit }, { name: "registry-prefix", value: runtime.registryPrefix }, { name: "services", value: runtime.serviceIds.join(",") }, { name: "base-image", value: runtime.baseImage }, { name: "build-cache-mode", value: ciBuildBenchmarkBuildCacheMode(profile) }, ], workspaces: [ { name: "source", volumeClaimTemplate: { spec: { accessModes: ["ReadWriteOnce"], resources: { requests: { storage: "8Gi" } } } } }, { name: "git-ssh", secret: { secretName: target.tekton.gitWorkspaceSecret.name } }, ], }, }; } function renderCiBuildBenchmarkResult(result: Record): RenderedCliResult { const start = record(result.start); const job = record(result.job); const plan = record(result.plan); const benchmark = record(result.benchmark ?? plan); const next = record(result.next); const pipelineRun = record(job.pipelineRun); const taskRows = ciBuildBenchmarkTaskRows(job); const serviceRows = ciBuildBenchmarkServiceRows(job, benchmark.services); const failures = ciBuildBenchmarkFailureRows(job, serviceRows, benchmark); const profile = renderCell(result.profile ?? benchmark.profile ?? plan.profile, "unknown"); const node = renderCell(result.node); const lane = renderCell(result.lane); const target = `${node}/${lane}`; const statusText = renderCell(job.state ?? pipelineRun.status ?? start.state, result.ok === false ? "failed" : "ok"); const pipelineRunName = renderCell(job.pipelineRunName ?? pipelineRun.name ?? start.pipelineRun ?? plan.pipelineRun); const sourceCommit = renderCell(pipelineRun.sourceCommit ?? start.sourceCommit ?? plan.sourceCommit); const statusCommand = renderCell(start.statusCommand, `bun scripts/cli.ts hwlab nodes control-plane infra ci-build-benchmark status --node ${node} --lane ${lane} --profile ${profile}`); const logsCommand = renderCell(start.logsCommand, `bun scripts/cli.ts hwlab nodes control-plane infra ci-build-benchmark logs --node ${node} --lane ${lane} --profile ${profile}`); const logTail = typeof job.logTail === "string" ? job.logTail.trimEnd() : ""; const lines = [ "HWLAB K3S CI BUILD BENCHMARK", "", ...renderTable(["TARGET", "PROFILE", "MODE", "STATUS", "PIPELINERUN", "SOURCE"], [[target, profile, renderCell(result.mode ?? optionsModeFromCommand(result.command)), statusText, pipelineRunName, shortDisplay(sourceCommit)]]), "", "POLICY", ...renderTable(["FIELD", "VALUE"], [ ["pipeline", renderCell(benchmark.pipeline)], ["catalogPath", renderCell(start.catalogPath ?? plan.catalogPath ?? pipelineRun.catalogPath)], ["services", String((Array.isArray(benchmark.services) ? benchmark.services : []).length)], ["buildCacheMode", renderCell(benchmark.buildCacheMode)], ["cachePolicy", JSON.stringify(benchmark.cachePolicy ?? {})], ["requiredTimings", Array.isArray(benchmark.requiredTimings) ? benchmark.requiredTimings.join(",") : "-"], ]), "", serviceRows.length === 0 ? "SERVICES\n-" : [ "SERVICES", ...renderTable(["SERVICE", "TASK", "STATUS", "DURATION", "FAILURE"], serviceRows.map((row) => [ renderCell(row.service), renderCell(row.task), renderCell(row.status), renderCell(row.duration), renderCell(row.failure), ])), ].join("\n"), "", taskRows.length === 0 ? "TIMINGS\n-" : [ "TIMINGS", ...renderTable(["TASK", "STATUS", "DURATION", "START", "END"], taskRows.map((row) => [ renderCell(row.task), renderCell(row.status), renderCell(row.duration), renderCell(row.start), renderCell(row.end), ])), ].join("\n"), ...(failures.length === 0 ? [] : ["", "FAILURE FAMILIES", ...renderTable(["FAMILY", "COUNT", "SCOPE"], failures.map((row) => [renderCell(row.family), renderCell(row.count), renderCell(row.scope)]))]), ...(logTail.length === 0 ? [] : ["", "LOG TAIL", logTail]), "", "NEXT", ` ${renderCell(next.confirm, "")}`, ` ${statusCommand}`, ` ${logsCommand}`, "", "Disclosure: output is bounded; Git/proxy/Secret values are not expanded. A succeeded PipelineRun with missing build- TaskRuns is reported as cache-hit-forbidden.", ].filter((line) => line !== " "); return { ok: result.ok !== false, command: renderCell(result.command, "hwlab nodes control-plane infra ci-build-benchmark"), renderedText: lines.join("\n"), contentType: "text/plain" }; } function toolsImageCommandStatus(node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, options: ToolsImageOptions): Record { const registry = toolsImageStatus(node, target, options.timeoutSeconds); const jobResult = runTransK3s(node.kubeRoute, remoteJobStatusScript(target, "tools-image", options.tailLines), options.timeoutSeconds); const jobStatus = parseRemoteJson(jobResult.stdout); const ok = registry.registryReady && registry.toolsImageReady; return { ok, command: "hwlab nodes control-plane infra tools-image status", configPath: HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, node: node.id, lane: target.lane, mutation: false, image: target.tekton.toolsImage.output, imageSource: target.tekton.toolsImage, registry, job: typeof jobStatus === "object" && jobStatus !== null ? jobStatus : { parseError: "remote job status did not return JSON", stdoutPreview: jobResult.stdout.slice(0, 1000) }, result: compactCommandResult(jobResult), next: ok ? { infraStatus: `bun scripts/cli.ts hwlab nodes control-plane infra status --node ${node.id} --lane ${target.lane}` } : { build: `bun scripts/cli.ts hwlab nodes control-plane infra tools-image build --node ${node.id} --lane ${target.lane} --confirm`, logs: `bun scripts/cli.ts hwlab nodes control-plane infra tools-image logs --node ${node.id} --lane ${target.lane}` }, }; } function toolsImageBuild(node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, options: ToolsImageOptions): Record { if (options.confirm && options.dryRun) throw new Error("tools-image build accepts only one of --dry-run or --confirm"); const dryRun = options.dryRun || !options.confirm; const dockerfile = toolsImageDockerfile(target); const buildPlan = { outputImage: target.tekton.toolsImage.output, sourceKind: target.tekton.toolsImage.sourceKind, dockerfileInline: target.tekton.toolsImage.dockerfileInline, buildArgs: target.tekton.toolsImage.buildArgs, buildNetwork: target.tekton.toolsImage.buildNetwork, publicBaseImages: target.tekton.toolsImage.publicBaseImages, nodeLocalRegistryOutputOnly: true, egressProxy: controlPlaneEgressProxySummary(node.egressProxy), stateDir: remoteJobStateDir(target, "tools-image"), }; if (dryRun) { return { ok: true, command: "hwlab nodes control-plane infra tools-image build", configPath: HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, node: node.id, lane: target.lane, mode: "dry-run", mutation: false, buildPlan, dockerfile: { bytes: Buffer.byteLength(dockerfile), sha256: sha256Short(dockerfile), preview: dockerfile }, next: { confirm: `bun scripts/cli.ts hwlab nodes control-plane infra tools-image build --node ${node.id} --lane ${target.lane} --confirm` }, }; } const result = runTransK3s(node.kubeRoute, toolsImageBuildStartScript(node, target, dockerfile), options.timeoutSeconds); const parsed = parseRemoteJson(result.stdout); return { ok: result.exitCode === 0, command: "hwlab nodes control-plane infra tools-image build", configPath: HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, node: node.id, lane: target.lane, mode: "confirmed-start", mutation: result.exitCode === 0, buildPlan, start: typeof parsed === "object" && parsed !== null ? parsed : { stdoutPreview: result.stdout.slice(0, 2000) }, result: compactCommandResult(result), next: { status: `bun scripts/cli.ts hwlab nodes control-plane infra tools-image status --node ${node.id} --lane ${target.lane}`, logs: `bun scripts/cli.ts hwlab nodes control-plane infra tools-image logs --node ${node.id} --lane ${target.lane}`, infraStatus: `bun scripts/cli.ts hwlab nodes control-plane infra status --node ${node.id} --lane ${target.lane}`, }, }; } function argoCommandStatus(node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, options: ArgoOptions): Record { const result = runTransK3s(node.kubeRoute, statusScript(node, target), options.timeoutSeconds); const parsed = parseRemoteJson(result.stdout); const status = typeof parsed === "object" && parsed !== null ? parsed as Record : {}; const argo = record(record(status.components).argo); const argoInstall = record(argo.install); const jobResult = runTransK3s(node.kubeRoute, remoteJobStatusScript(target, "argo", options.tailLines), options.timeoutSeconds); const jobStatus = parseRemoteJson(jobResult.stdout); const ok = boolField(argo, "installed") && boolField(argo, "projectExists") && boolField(argo, "applicationExists") && boolField(argoInstall, "crdsReady") && boolField(argoInstall, "deploymentsReady") && boolField(argoInstall, "statefulSetsReady"); return { ok, command: "hwlab nodes control-plane infra argo status", configPath: HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, node: node.id, lane: target.lane, mutation: false, expected: { namespace: target.argo.namespace, project: target.argo.projectName, application: target.argo.applicationName, install: target.argo.install, }, readiness: { installed: boolField(argo, "installed"), projectExists: boolField(argo, "projectExists"), applicationExists: boolField(argo, "applicationExists"), crdsReady: boolField(argoInstall, "crdsReady"), deploymentsReady: boolField(argoInstall, "deploymentsReady"), statefulSetsReady: boolField(argoInstall, "statefulSetsReady"), }, argo, job: typeof jobStatus === "object" && jobStatus !== null ? jobStatus : { parseError: "remote job status did not return JSON", stdoutPreview: jobResult.stdout.slice(0, 1000) }, result: { k3s: compactCommandResult(result), job: compactCommandResult(jobResult) }, next: ok ? { infraStatus: `bun scripts/cli.ts hwlab nodes control-plane infra status --node ${node.id} --lane ${target.lane}` } : { apply: `bun scripts/cli.ts hwlab nodes control-plane infra argo apply --node ${node.id} --lane ${target.lane} --confirm`, logs: `bun scripts/cli.ts hwlab nodes control-plane infra argo logs --node ${node.id} --lane ${target.lane}` }, }; } function argoApply(node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, options: ArgoOptions): Record { if (options.confirm && options.dryRun) throw new Error("argo apply accepts only one of --dry-run or --confirm"); if (!target.argo.install.enabled) throw new Error(`targets.${target.id}.argo.install.enabled=false`); const dryRun = options.dryRun || !options.confirm; const desired = argoDesiredManifest(target); const desiredYaml = `${desired.map((item) => Bun.YAML.stringify(item).trim()).join("\n---\n")}\n`; const applyPlan = { namespace: target.argo.namespace, version: target.argo.install.version, manifestUrl: target.argo.install.manifestUrl, fieldManager: target.argo.install.fieldManager, imageRewrites: target.argo.install.imageRewrites, preloadImages: target.argo.install.preloadImages, requiredCrds: target.argo.install.requiredCrds, desired: manifestObjectSummary(desired), desiredSha256: sha256Short(desiredYaml), stateDir: remoteJobStateDir(target, "argo"), egressProxy: controlPlaneEgressProxySummary(node.egressProxy), }; if (dryRun) { return { ok: true, command: "hwlab nodes control-plane infra argo apply", configPath: HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, node: node.id, lane: target.lane, mode: "dry-run", mutation: false, applyPlan, desiredYaml: { objects: desired.length, bytes: Buffer.byteLength(desiredYaml), sha256: sha256Short(desiredYaml), preview: desiredYaml }, next: { confirm: `bun scripts/cli.ts hwlab nodes control-plane infra argo apply --node ${node.id} --lane ${target.lane} --confirm` }, }; } const result = runTransK3s(node.kubeRoute, argoApplyStartScript(node, target, desiredYaml), options.timeoutSeconds); const parsed = parseRemoteJson(result.stdout); return { ok: result.exitCode === 0, command: "hwlab nodes control-plane infra argo apply", configPath: HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, node: node.id, lane: target.lane, mode: "confirmed-start", mutation: result.exitCode === 0, applyPlan, start: typeof parsed === "object" && parsed !== null ? parsed : { stdoutPreview: result.stdout.slice(0, 2000) }, result: compactCommandResult(result), next: { status: `bun scripts/cli.ts hwlab nodes control-plane infra argo status --node ${node.id} --lane ${target.lane}`, logs: `bun scripts/cli.ts hwlab nodes control-plane infra argo logs --node ${node.id} --lane ${target.lane}`, infraStatus: `bun scripts/cli.ts hwlab nodes control-plane infra status --node ${node.id} --lane ${target.lane}`, }, }; } function parseInfraOptions(args: string[]): InfraOptions { const [actionRaw] = args; if (actionRaw === undefined || actionRaw === "--help" || actionRaw === "-h" || actionRaw === "help") throw new Error("infra usage: infra plan|status|apply --node NODE --lane vNN [--dry-run|--confirm]"); if (actionRaw !== "plan" && actionRaw !== "status" && actionRaw !== "apply") throw new Error(`unsupported infra action ${actionRaw}; expected plan|status|apply`); const node = requiredOption(args, "--node"); const lane = requiredOption(args, "--lane"); const confirm = args.includes("--confirm"); const explicitDryRun = args.includes("--dry-run"); if (confirm && explicitDryRun) throw new Error("infra accepts only one of --confirm or --dry-run"); return { action: actionRaw, node, lane, confirm, dryRun: actionRaw === "apply" ? explicitDryRun || !confirm : true, timeoutSeconds: positiveIntegerOption(args, "--timeout-seconds", 60, 60), }; } function parseToolsImageOptions(args: string[]): ToolsImageOptions { const [actionRaw] = args; if (actionRaw === undefined || actionRaw === "--help" || actionRaw === "-h" || actionRaw === "help") throw new Error("infra tools-image usage: tools-image status|build|logs --node NODE --lane vNN [--dry-run|--confirm]"); if (actionRaw !== "status" && actionRaw !== "build" && actionRaw !== "logs") throw new Error(`unsupported tools-image action ${actionRaw}; expected status|build|logs`); const confirm = args.includes("--confirm"); const explicitDryRun = args.includes("--dry-run"); if (confirm && explicitDryRun) throw new Error("tools-image accepts only one of --confirm or --dry-run"); return { action: actionRaw, node: requiredOption(args, "--node"), lane: requiredOption(args, "--lane"), confirm, dryRun: actionRaw === "build" ? explicitDryRun || !confirm : true, timeoutSeconds: positiveIntegerOption(args, "--timeout-seconds", 60, 60), tailLines: positiveIntegerOption(args, "--tail-lines", 120, 1000), }; } function parseArgoOptions(args: string[]): ArgoOptions { const [actionRaw] = args; if (actionRaw === undefined || actionRaw === "--help" || actionRaw === "-h" || actionRaw === "help") throw new Error("infra argo usage: argo status|apply|logs --node NODE --lane vNN [--dry-run|--confirm]"); if (actionRaw !== "status" && actionRaw !== "apply" && actionRaw !== "logs") throw new Error(`unsupported argo action ${actionRaw}; expected status|apply|logs`); const confirm = args.includes("--confirm"); const explicitDryRun = args.includes("--dry-run"); if (confirm && explicitDryRun) throw new Error("argo accepts only one of --confirm or --dry-run"); return { action: actionRaw, node: requiredOption(args, "--node"), lane: requiredOption(args, "--lane"), confirm, dryRun: actionRaw === "apply" ? explicitDryRun || !confirm : true, timeoutSeconds: positiveIntegerOption(args, "--timeout-seconds", 60, 60), tailLines: positiveIntegerOption(args, "--tail-lines", 120, 1000), }; } function parseEgressBenchmarkOptions(args: string[]): EgressBenchmarkOptions { const first = args[0]; const action: EgressBenchmarkAction = first === "status" || first === "logs" ? first : "benchmark"; const effectiveArgs = action === "benchmark" ? args : args.slice(1); if (first === "--help" || first === "-h" || first === "help") { throw new Error("infra egress-benchmark usage: egress-benchmark [status|logs] --node NODE --lane vNN --profile no-mirror [--dry-run|--confirm]"); } const profileRaw = optionValue(effectiveArgs, "--profile") ?? "no-mirror"; if (profileRaw !== "no-mirror") throw new Error("egress-benchmark --profile currently supports no-mirror"); const confirm = effectiveArgs.includes("--confirm"); const explicitDryRun = effectiveArgs.includes("--dry-run"); if (confirm && explicitDryRun) throw new Error("egress-benchmark accepts only one of --confirm or --dry-run"); return { action, node: requiredOption(effectiveArgs, "--node"), lane: requiredOption(effectiveArgs, "--lane"), profile: profileRaw, confirm, dryRun: action === "benchmark" ? explicitDryRun || !confirm : false, samples: positiveIntegerOption(effectiveArgs, "--samples", 5, 20), sampleTimeoutSeconds: positiveIntegerOption(effectiveArgs, "--sample-timeout-seconds", 240, 900), timeoutSeconds: positiveIntegerOption(effectiveArgs, "--timeout-seconds", 60, 60), tailLines: positiveIntegerOption(effectiveArgs, "--tail-lines", 80, 1000), }; } function parseCiBuildBenchmarkOptions(args: string[]): CiBuildBenchmarkOptions { const first = args[0]; const action: CiBuildBenchmarkAction = first === "status" || first === "logs" ? first : "benchmark"; const effectiveArgs = action === "benchmark" ? args : args.slice(1); if (first === "--help" || first === "-h" || first === "help") { throw new Error("infra ci-build-benchmark usage: ci-build-benchmark [status|logs] --node NODE --lane vNN --profile PROFILE [--dry-run|--confirm]"); } const confirm = effectiveArgs.includes("--confirm"); const explicitDryRun = effectiveArgs.includes("--dry-run"); if (confirm && explicitDryRun) throw new Error("ci-build-benchmark accepts only one of --confirm or --dry-run"); return { action, node: requiredOption(effectiveArgs, "--node"), lane: requiredOption(effectiveArgs, "--lane"), profile: requiredOption(effectiveArgs, "--profile"), confirm, dryRun: action === "benchmark" ? explicitDryRun || !confirm : false, timeoutSeconds: positiveIntegerOption(effectiveArgs, "--timeout-seconds", 60, 60), tailLines: positiveIntegerOption(effectiveArgs, "--tail-lines", 120, 1000), }; } function readControlPlaneConfig(): ControlPlaneConfig { const parsed = asRecord(Bun.YAML.parse(readFileSync(rootPath(HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH), "utf8")) as unknown, HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH); const version = numberField(parsed, "version", HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH); const kind = stringField(parsed, "kind", HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH); if (kind !== "hwlab-node-control-plane") throw new Error(`${HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH}.kind must be hwlab-node-control-plane`); const metadataRaw = asRecord(parsed.metadata, "metadata"); const imagePolicy = imagePolicySpec(asRecord(parsed.imagePolicy, "imagePolicy")); const nodes = Object.fromEntries(Object.entries(asRecord(parsed.nodes, "nodes")).map(([id, raw]) => [id, nodeSpec(id, asRecord(raw, `nodes.${id}`))])); const targetsRaw = parsed.targets; if (!Array.isArray(targetsRaw)) throw new Error(`${HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH}.targets must be an array`); const targets = targetsRaw.map((raw, index) => targetSpec(asRecord(raw, `targets[${index}]`), index)); for (const target of targets) { if (nodes[target.node] === undefined) throw new Error(`targets.${target.id}.node references missing node ${target.node}`); validateTargetImagePolicy(target, imagePolicy); } return { version, kind, metadata: { owner: stringField(metadataRaw, "owner", "metadata"), relatedIssues: numberArrayField(metadataRaw, "relatedIssues", "metadata"), }, imagePolicy, nodes, targets, }; } function imagePolicySpec(raw: Record): ControlPlaneImagePolicy { const requiredSourceKinds = stringArrayField(raw, "requiredSourceKinds", "imagePolicy").map((item) => { if (item !== "dockerfile" && item !== "docker-compose") throw new Error("imagePolicy.requiredSourceKinds must contain only dockerfile or docker-compose"); return item; }); return { requireReproducibleBuildSource: booleanField(raw, "requireReproducibleBuildSource", "imagePolicy"), forbidPrivateOrNodeLocalImagesAsInputs: booleanField(raw, "forbidPrivateOrNodeLocalImagesAsInputs", "imagePolicy"), allowNodeLocalRegistryAsBuildOutput: booleanField(raw, "allowNodeLocalRegistryAsBuildOutput", "imagePolicy"), requiredSourceKinds, }; } function toolsImageSpec(raw: Record, path: string): ControlPlaneTargetSpec["tekton"]["toolsImage"] { const sourceKind = stringField(raw, "sourceKind", path); if (sourceKind !== "dockerfile" && sourceKind !== "docker-compose") throw new Error(`${path}.sourceKind must be dockerfile or docker-compose`); const imagePullPolicy = optionalStringField(raw, "imagePullPolicy", path) ?? "IfNotPresent"; if (imagePullPolicy !== "Always" && imagePullPolicy !== "IfNotPresent" && imagePullPolicy !== "Never") throw new Error(`${path}.imagePullPolicy must be Always, IfNotPresent, or Never`); const publicBaseImages = stringArrayField(raw, "publicBaseImages", path); if (publicBaseImages.length === 0) throw new Error(`${path}.publicBaseImages must list at least one public base image`); for (const image of publicBaseImages) validatePublicBaseImage(image, `${path}.publicBaseImages`); const dockerfile = optionalStringField(raw, "dockerfile", path); const dockerfileInline = raw.dockerfileInline === undefined ? undefined : dockerfileInlineSpec(asRecord(raw.dockerfileInline, `${path}.dockerfileInline`), `${path}.dockerfileInline`); const composeFile = optionalStringField(raw, "composeFile", path); if (sourceKind === "dockerfile" && dockerfile === undefined && dockerfileInline === undefined) throw new Error(`${path}.dockerfile or ${path}.dockerfileInline is required when sourceKind=dockerfile`); if (dockerfile !== undefined && dockerfileInline !== undefined) throw new Error(`${path} must use only one of dockerfile or dockerfileInline`); if (sourceKind === "docker-compose" && composeFile === undefined) throw new Error(`${path}.composeFile is required when sourceKind=docker-compose`); const buildArgsRaw = raw.buildArgs === undefined ? {} : asRecord(raw.buildArgs, `${path}.buildArgs`); const buildArgs = stringRecordField(buildArgsRaw, `${path}.buildArgs`); for (const image of Object.values(buildArgs)) { if (looksLikeImageReference(image)) validatePublicBaseImage(image, `${path}.buildArgs`); } return { output: stringField(raw, "output", path), imagePullPolicy, sourceKind, context: stringField(raw, "context", path), dockerfile, dockerfileInline, composeFile, buildArgs, buildNetwork: optionalStringField(raw, "buildNetwork", path) ?? null, publicBaseImages, buildOwner: stringField(raw, "buildOwner", path), buildMode: stringField(raw, "buildMode", path), }; } function dockerfileInlineSpec(raw: Record, path: string): DockerfileInlineSpec { const filename = stringField(raw, "filename", path); if (!/^[A-Za-z0-9._/-]+$/u.test(filename) || filename.includes("..")) throw new Error(`${path}.filename has an unsupported format`); const lines = stringArrayField(raw, "lines", path); if (lines.length === 0) throw new Error(`${path}.lines must not be empty`); return { filename, lines }; } function argoInstallSpec(raw: Record, path: string): ControlPlaneTargetSpec["argo"]["install"] { const sourceKind = stringField(raw, "sourceKind", path); if (sourceKind !== "url") throw new Error(`${path}.sourceKind must be url`); const imagePullPolicy = optionalStringField(raw, "imagePullPolicy", path) ?? "IfNotPresent"; if (imagePullPolicy !== "Always" && imagePullPolicy !== "IfNotPresent" && imagePullPolicy !== "Never") throw new Error(`${path}.imagePullPolicy must be Always, IfNotPresent, or Never`); const imageRewritesRaw = raw.imageRewrites === undefined ? [] : raw.imageRewrites; if (!Array.isArray(imageRewritesRaw)) throw new Error(`${path}.imageRewrites must be an array`); const imageRewrites = imageRewritesRaw.map((item, index) => imageRewriteSpec(asRecord(item, `${path}.imageRewrites[${index}]`), `${path}.imageRewrites[${index}]`)); const manifestUrl = stringField(raw, "manifestUrl", path); validateHttpsUrl(manifestUrl, `${path}.manifestUrl`); return { enabled: booleanField(raw, "enabled", path), sourceKind, version: stringField(raw, "version", path), manifestUrl, fieldManager: stringField(raw, "fieldManager", path), imagePullPolicy, preloadImages: stringArrayField(raw, "preloadImages", path), imageRewrites, requiredCrds: stringArrayField(raw, "requiredCrds", path), expectedDeployments: stringArrayField(raw, "expectedDeployments", path), expectedStatefulSets: stringArrayField(raw, "expectedStatefulSets", path), readinessTimeoutSeconds: positiveConfigIntegerField(raw, "readinessTimeoutSeconds", path), }; } function ciBuildBenchmarkProfileSpecs(raw: unknown, path: string): readonly CiBuildBenchmarkProfileSpec[] { if (raw === undefined) return []; if (!Array.isArray(raw)) throw new Error(`${path} must be an array`); const profiles = raw.map((item, index) => ciBuildBenchmarkProfileSpec(asRecord(item, `${path}[${index}]`), `${path}[${index}]`)); const names = new Set(); for (const profile of profiles) { if (names.has(profile.profile)) throw new Error(`${path} contains duplicate profile ${profile.profile}`); names.add(profile.profile); } return profiles; } function ciBuildBenchmarkProfileSpec(raw: Record, path: string): CiBuildBenchmarkProfileSpec { const profile = stringField(raw, "profile", path); validateBenchmarkProfileName(profile, `${path}.profile`); const pipelineRunPrefix = stringField(raw, "pipelineRunPrefix", path); validateKubernetesName(pipelineRunPrefix, `${path}.pipelineRunPrefix`); if (pipelineRunPrefix.length > 40) throw new Error(`${path}.pipelineRunPrefix must leave room for source and nonce suffix`); const catalogPathTemplate = stringField(raw, "catalogPathTemplate", path); validateBenchmarkCatalogPathTemplate(catalogPathTemplate, `${path}.catalogPathTemplate`); const imageTagMode = stringField(raw, "imageTagMode", path); if (imageTagMode !== "full") throw new Error(`${path}.imageTagMode currently must be full`); const timings = asRecord(raw.timings, `${path}.timings`); return { profile, runtimeLaneConfigRef: stringField(raw, "runtimeLaneConfigRef", path), pipelineRunPrefix, catalogPathTemplate, imageTagMode, pipelineTimeoutSeconds: positiveConfigIntegerField(raw, "pipelineTimeoutSeconds", path), cachePolicy: ciBuildBenchmarkCachePolicy(asRecord(raw.cachePolicy, `${path}.cachePolicy`), `${path}.cachePolicy`), requiredTimings: stringArrayField(timings, "requiredStages", `${path}.timings`), failureFamilies: stringArrayField(raw, "failureFamilies", path), }; } function ciBuildBenchmarkCachePolicy(raw: Record, path: string): CiBuildBenchmarkCachePolicy { return { noPipelineRunReuse: booleanField(raw, "noPipelineRunReuse", path), forceFullBuild: booleanField(raw, "forceFullBuild", path), forbidGitopsCatalogReuse: booleanField(raw, "forbidGitopsCatalogReuse", path), forbidDependencyCache: booleanField(raw, "forbidDependencyCache", path), forbidBuildkitCache: booleanField(raw, "forbidBuildkitCache", path), forbidRegistryMirror: booleanField(raw, "forbidRegistryMirror", path), forbidLocalPreheatedImages: booleanField(raw, "forbidLocalPreheatedImages", path), }; } function imageRewriteSpec(raw: Record, path: string): ImageRewriteSpec { const rewrite = { source: stringField(raw, "source", path), pullImage: stringField(raw, "pullImage", path), target: stringField(raw, "target", path), }; validatePublicBaseImage(rewrite.source, `${path}.source`); validatePublicBaseImage(rewrite.pullImage, `${path}.pullImage`); if (!isNodeLocalImage(rewrite.target)) throw new Error(`${path}.target must use a node-local registry output image`); return rewrite; } function validateTargetImagePolicy(target: ControlPlaneTargetSpec, imagePolicy: ControlPlaneImagePolicy): void { if (imagePolicy.requireReproducibleBuildSource && !imagePolicy.requiredSourceKinds.includes(target.tekton.toolsImage.sourceKind)) { throw new Error(`targets.${target.id}.tekton.toolsImage.sourceKind is not allowed by imagePolicy.requiredSourceKinds`); } if (imagePolicy.forbidPrivateOrNodeLocalImagesAsInputs) { for (const image of target.tekton.toolsImage.publicBaseImages) validatePublicBaseImage(image, `targets.${target.id}.tekton.toolsImage.publicBaseImages`); } if (!imagePolicy.allowNodeLocalRegistryAsBuildOutput && isNodeLocalImage(target.tekton.toolsImage.output)) { throw new Error(`targets.${target.id}.tekton.toolsImage.output uses node-local registry but imagePolicy.allowNodeLocalRegistryAsBuildOutput=false`); } } function validatePublicBaseImage(image: string, path: string): void { if (isNodeLocalImage(image)) { throw new Error(`${path} contains non-public base image ${image}`); } const publicPrefixes = ["docker.io/", "registry.k8s.io/", "ghcr.io/", "quay.io/", "gcr.io/", "public.ecr.aws/", "mcr.microsoft.com/", "cgr.dev/", "docker.m.daocloud.io/", "quay.m.daocloud.io/", "ghcr.m.daocloud.io/"]; if (!publicPrefixes.some((prefix) => image.startsWith(prefix))) throw new Error(`${path} image ${image} must use an explicit public registry prefix`); } function looksLikeImageReference(value: string): boolean { return /^(docker\.io|registry\.k8s\.io|ghcr\.io|quay\.io|gcr\.io|public\.ecr\.aws|mcr\.microsoft\.com|cgr\.dev|docker\.m\.daocloud\.io|quay\.m\.daocloud\.io|ghcr\.m\.daocloud\.io)\//u.test(value) || /^(127\.|localhost(?::|\/)|0\.0\.0\.0|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.)/u.test(value); } function isNodeLocalImage(image: string): boolean { return /^(127\.|localhost(?::|\/)|0\.0\.0\.0|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.)/u.test(image); } function nodeSpec(id: string, raw: Record): ControlPlaneNodeSpec { const registry = asRecord(raw.registry, `nodes.${id}.registry`); const egressProxy = raw.egressProxy === undefined ? null : egressProxySpec(asRecord(raw.egressProxy, `nodes.${id}.egressProxy`), `nodes.${id}.egressProxy`); const k3s = raw.k3s === undefined ? null : k3sNodeSpec(asRecord(raw.k3s, `nodes.${id}.k3s`), `nodes.${id}.k3s`); return { id, route: stringField(raw, "route", `nodes.${id}`), kubeRoute: stringField(raw, "kubeRoute", `nodes.${id}`), k3s, registry: { endpoint: stringField(registry, "endpoint", `nodes.${id}.registry`) }, egressProxy, }; } function k3sNodeSpec(raw: Record, path: string): ControlPlaneK3sNodeSpec { const kubelet = asRecord(raw.kubelet, `${path}.kubelet`); const serviceName = stringField(raw, "serviceName", path); if (!/^[A-Za-z0-9_.@-]+$/u.test(serviceName)) throw new Error(`${path}.serviceName has an unsupported systemd unit name`); const dropInPath = stringField(raw, "dropInPath", path); if (!dropInPath.startsWith("/etc/systemd/system/") || !dropInPath.endsWith(".conf") || dropInPath.includes("..")) { throw new Error(`${path}.dropInPath must be an absolute /etc/systemd/system/*.conf path`); } const nodeStatusName = stringField(raw, "nodeStatusName", path); if (!/^[A-Za-z0-9_.-]+$/u.test(nodeStatusName)) throw new Error(`${path}.nodeStatusName has an unsupported Kubernetes node name`); const execStartPre = execStartPreField(raw.execStartPre, `${path}.execStartPre`); const serverArgs = stringArrayField(raw, "serverArgs", path); if (serverArgs.length === 0 || serverArgs[0] !== "server") throw new Error(`${path}.serverArgs must start with k3s server`); for (const [index, arg] of serverArgs.entries()) { if (arg.includes("\n") || arg.includes("\r") || arg.length === 0) throw new Error(`${path}.serverArgs[${index}] must be a single non-empty argv token`); } const maxPods = positiveConfigIntegerField(kubelet, "maxPods", `${path}.kubelet`); const expectedMaxPodsArg = `max-pods=${maxPods}`; let hasExpectedMaxPodsArg = false; for (let index = 0; index < serverArgs.length - 1; index += 1) { if (serverArgs[index] === "--kubelet-arg" && serverArgs[index + 1] === expectedMaxPodsArg) hasExpectedMaxPodsArg = true; } if (!hasExpectedMaxPodsArg) throw new Error(`${path}.serverArgs must include --kubelet-arg ${expectedMaxPodsArg}`); return { serviceName, dropInPath, nodeStatusName, execStartPre, serverArgs, kubelet: { maxPods }, }; } function execStartPreField(raw: unknown, path: string): readonly (readonly string[])[] { if (raw === undefined) return []; if (!Array.isArray(raw)) throw new Error(`${path} must be an array of argv arrays`); return raw.map((item, index) => { if (!Array.isArray(item)) throw new Error(`${path}[${index}] must be an argv array`); const command = item.map((value, tokenIndex) => { if (typeof value !== "string") throw new Error(`${path}[${index}][${tokenIndex}] must be a string`); if (value.length === 0 || value.includes("\n") || value.includes("\r")) throw new Error(`${path}[${index}][${tokenIndex}] must be a single non-empty argv token`); return value; }); if (command.length === 0) throw new Error(`${path}[${index}] must not be empty`); const executable = command[0].startsWith("-") ? command[0].slice(1) : command[0]; if (!executable.startsWith("/") || executable.includes("..")) throw new Error(`${path}[${index}][0] must be an absolute executable path, optionally prefixed with -`); return command; }); } function egressProxySpec(raw: Record, path: string): ControlPlaneEgressProxySpec { const mode = stringField(raw, "mode", path); if (mode !== "k8s-service-cluster-ip") throw new Error(`${path}.mode must be k8s-service-cluster-ip`); const sourceConfigRef = optionalStringField(raw, "sourceConfigRef", path) ?? null; const source = sourceConfigRef === null ? null : resolveEgressProxySourceRef(sourceConfigRef, `${HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH}.${path}.sourceConfigRef`); const sourceType = source?.sourceType ?? stringField(raw, "sourceType", path); if (sourceType !== "subscription-url" && sourceType !== "master-shadowsocks") throw new Error(`${path}.sourceType must be subscription-url or master-shadowsocks`); if (raw.sourceType !== undefined && raw.sourceType !== sourceType) throw new Error(`${path}.sourceType must match sourceConfigRef value ${sourceType}`); const preferredOutbound = source?.preferredOutbound ?? (raw.preferredOutbound === undefined ? null : preferredOutboundField(raw, "preferredOutbound", path)); if (source !== null && source.preferredOutbound !== null && raw.preferredOutbound !== undefined && raw.preferredOutbound !== source.preferredOutbound) { throw new Error(`${path}.preferredOutbound must match sourceConfigRef value ${source.preferredOutbound}`); } if (source !== null) { if (raw.sourceRef !== undefined && raw.sourceRef !== source.sourceRef) throw new Error(`${path}.sourceRef must match sourceConfigRef value ${source.sourceRef}`); if (raw.sourceKey !== undefined && raw.sourceKey !== source.sourceKey) throw new Error(`${path}.sourceKey must match sourceConfigRef value ${source.sourceKey}`); } const sourceRef = source?.sourceRef ?? stringField(raw, "sourceRef", path); const sourceKey = source?.sourceKey ?? stringField(raw, "sourceKey", path); validateSourceRef(sourceRef, `${path}.sourceRef`); validateEnvKey(sourceKey, `${path}.sourceKey`); return { mode, clientName: stringField(raw, "clientName", path), namespace: stringField(raw, "namespace", path), serviceName: stringField(raw, "serviceName", path), port: positiveConfigIntegerField(raw, "port", path), sourceConfigRef, sourceFingerprint: source?.fingerprint ?? null, sourceRef, sourceKey, sourceType, preferredOutbound, noProxy: stringArrayField(raw, "noProxy", path), }; } function preferredOutboundField(raw: Record, key: string, path: string): "vless-reality" | "hysteria2" { const value = stringField(raw, key, path); if (value !== "vless-reality" && value !== "hysteria2") throw new Error(`${path}.${key} must be vless-reality or hysteria2`); return value; } function gitMirrorEgressProxySpec(raw: Record, path: string): ControlPlaneGitMirrorEgressProxySpec { const mode = stringField(raw, "mode", path); if (mode !== "node-global" && mode !== "direct") throw new Error(`${path}.mode must be node-global or direct`); return { mode, required: raw.required === undefined ? mode !== "direct" : booleanField(raw, "required", path), }; } function gitMirrorGithubTransportSpec(raw: Record, path: string): ControlPlaneGitMirrorGithubTransportSpec { const mode = stringField(raw, "mode", path); if (mode === "ssh") { const privateKeySecretKey = stringField(raw, "privateKeySecretKey", path); const privateKeySourceRef = stringField(raw, "privateKeySourceRef", path); const privateKeySourceKey = stringField(raw, "privateKeySourceKey", path); const privateKeySourceEncoding = secretSourceEncodingField(raw, "privateKeySourceEncoding", path); const knownHostsSecretKey = optionalStringField(raw, "knownHostsSecretKey", path) ?? null; const knownHostsSourceRef = optionalStringField(raw, "knownHostsSourceRef", path) ?? null; const knownHostsSourceKey = optionalStringField(raw, "knownHostsSourceKey", path) ?? null; const knownHostsSourceEncoding = raw.knownHostsSourceEncoding === undefined ? null : secretSourceEncodingField(raw, "knownHostsSourceEncoding", path); const knownHostsFields = [knownHostsSecretKey, knownHostsSourceRef, knownHostsSourceKey, knownHostsSourceEncoding]; if (knownHostsFields.some((value) => value !== null) && knownHostsFields.some((value) => value === null)) { throw new Error(`${path}.knownHostsSecretKey/sourceRef/sourceKey/sourceEncoding must be declared together`); } validateSecretKey(privateKeySecretKey, `${path}.privateKeySecretKey`); if (privateKeySecretKey !== "ssh-privatekey") throw new Error(`${path}.privateKeySecretKey must be ssh-privatekey for kubernetes.io/ssh-auth`); validateSourceRef(privateKeySourceRef, `${path}.privateKeySourceRef`); validateEnvKey(privateKeySourceKey, `${path}.privateKeySourceKey`); if (knownHostsSecretKey !== null) validateSecretKey(knownHostsSecretKey, `${path}.knownHostsSecretKey`); if (knownHostsSourceRef !== null) validateSourceRef(knownHostsSourceRef, `${path}.knownHostsSourceRef`); if (knownHostsSourceKey !== null) validateEnvKey(knownHostsSourceKey, `${path}.knownHostsSourceKey`); return { mode, privateKeySecretKey, privateKeySourceRef, privateKeySourceKey, privateKeySourceEncoding, knownHostsSecretKey, knownHostsSourceRef, knownHostsSourceKey, knownHostsSourceEncoding, }; } if (mode !== "https") throw new Error(`${path}.mode must be ssh or https`); const tokenSecretName = stringField(raw, "tokenSecretName", path); const tokenSecretKey = stringField(raw, "tokenSecretKey", path); const tokenSourceRef = stringField(raw, "tokenSourceRef", path); const tokenSourceKey = stringField(raw, "tokenSourceKey", path); validateKubernetesName(tokenSecretName, `${path}.tokenSecretName`); validateSecretKey(tokenSecretKey, `${path}.tokenSecretKey`); validateSourceRef(tokenSourceRef, `${path}.tokenSourceRef`); validateEnvKey(tokenSourceKey, `${path}.tokenSourceKey`); return { mode, username: stringField(raw, "username", path), tokenSecretName, tokenSecretKey, tokenSourceRef, tokenSourceKey, }; } function tektonGitWorkspaceSecretSpec( value: unknown, path: string, ciNamespace: string, githubTransport: ControlPlaneGitMirrorGithubTransportSpec, ): ControlPlaneTektonGitWorkspaceSecretSpec { const raw = asRecord(value, path); const sourceRefFrom = stringField(raw, "sourceRefFrom", path); if (sourceRefFrom !== "gitMirror.githubTransport") { throw new Error(`${path}.sourceRefFrom must be gitMirror.githubTransport`); } if (githubTransport.mode !== "ssh") { throw new Error(`${path}.sourceRefFrom=gitMirror.githubTransport requires gitMirror.githubTransport.mode=ssh`); } if (githubTransport.knownHostsSecretKey === null) { throw new Error(`${path}.sourceRefFrom=gitMirror.githubTransport requires gitMirror.githubTransport.knownHostsSecretKey`); } const name = stringField(raw, "name", path); const namespace = stringField(raw, "namespace", path); const privateKeySecretKey = stringField(raw, "privateKeySecretKey", path); const knownHostsSecretKey = stringField(raw, "knownHostsSecretKey", path); validateKubernetesName(name, `${path}.name`); validateKubernetesName(namespace, `${path}.namespace`); validateSecretKey(privateKeySecretKey, `${path}.privateKeySecretKey`); validateSecretKey(knownHostsSecretKey, `${path}.knownHostsSecretKey`); if (namespace !== ciNamespace) throw new Error(`${path}.namespace must equal targets[].ciNamespace ${ciNamespace}`); if (privateKeySecretKey !== "ssh-privatekey") throw new Error(`${path}.privateKeySecretKey must be ssh-privatekey for Tekton git SSH workspaces`); return { name, namespace, sourceRefFrom, privateKeySecretKey, knownHostsSecretKey, }; } function tektonRuntimeObserverRbacSpec( value: unknown, path: string, runtimeNamespace: string, ): ControlPlaneTektonRuntimeObserverRbacSpec { const raw = asRecord(value, path); const namespace = stringField(raw, "namespace", path); const roleName = stringField(raw, "roleName", path); const roleBindingName = stringField(raw, "roleBindingName", path); validateKubernetesName(namespace, `${path}.namespace`); validateKubernetesName(roleName, `${path}.roleName`); validateKubernetesName(roleBindingName, `${path}.roleBindingName`); if (namespace !== runtimeNamespace) throw new Error(`${path}.namespace must equal targets[].runtimeNamespace ${runtimeNamespace}`); return { namespace, roleName, roleBindingName }; } function tektonArgoObserverRbacSpec( value: unknown, path: string, argoNamespace: string, ): ControlPlaneTektonArgoObserverRbacSpec { const raw = asRecord(value, path); const namespace = stringField(raw, "namespace", path); const roleName = stringField(raw, "roleName", path); const roleBindingName = stringField(raw, "roleBindingName", path); validateKubernetesName(namespace, `${path}.namespace`); validateKubernetesName(roleName, `${path}.roleName`); validateKubernetesName(roleBindingName, `${path}.roleBindingName`); if (namespace !== argoNamespace) throw new Error(`${path}.namespace must equal targets[].argo.namespace ${argoNamespace}`); return { namespace, roleName, roleBindingName }; } function secretSourceEncodingField(raw: Record, key: string, path: string): "plain" | "base64" { const value = stringField(raw, key, path); if (value !== "plain" && value !== "base64") throw new Error(`${path}.${key} must be plain or base64`); return value; } function targetSpec(raw: Record, index: number): ControlPlaneTargetSpec { const path = `targets[${index}]`; const source = asRecord(raw.source, `${path}.source`); const gitops = asRecord(raw.gitops, `${path}.gitops`); const gitMirror = asRecord(raw.gitMirror, `${path}.gitMirror`); const tekton = asRecord(raw.tekton, `${path}.tekton`); const argo = asRecord(raw.argo, `${path}.argo`); const toolsImage = asRecord(tekton.toolsImage, `${path}.tekton.toolsImage`); const node = stringField(raw, "node", path); const lane = stringField(raw, "lane", path); const gitMirrorNamespace = stringField(gitMirror, "namespace", `${path}.gitMirror`); const serviceReadName = stringField(gitMirror, "serviceReadName", `${path}.gitMirror`); const serviceWriteName = stringField(gitMirror, "serviceWriteName", `${path}.gitMirror`); const gitMirrorEgressProxy = gitMirror.egressProxy === undefined ? null : gitMirrorEgressProxySpec(asRecord(gitMirror.egressProxy, `${path}.gitMirror.egressProxy`), `${path}.gitMirror.egressProxy`); const githubTransport = gitMirrorGithubTransportSpec(asRecord(gitMirror.githubTransport, `${path}.gitMirror.githubTransport`), `${path}.gitMirror.githubTransport`); const sourceRepository = stringField(source, "repository", `${path}.source`); const ciNamespace = stringField(raw, "ciNamespace", path); const runtimeNamespace = stringField(raw, "runtimeNamespace", path); const argoNamespace = stringField(argo, "namespace", `${path}.argo`); return { id: stringField(raw, "id", path), node, lane, enabled: booleanField(raw, "enabled", path), ciNamespace, runtimeNamespace, source: { repository: sourceRepository, branch: stringField(source, "branch", `${path}.source`) }, gitops: { branch: stringField(gitops, "branch", `${path}.gitops`), path: stringField(gitops, "path", `${path}.gitops`) }, gitMirror: { namespace: gitMirrorNamespace, serviceReadName, serviceWriteName, cachePvcName: stringField(gitMirror, "cachePvcName", `${path}.gitMirror`), cachePvcStorage: stringField(gitMirror, "cachePvcStorage", `${path}.gitMirror`), cacheHostPath: optionalStringField(gitMirror, "cacheHostPath", `${path}.gitMirror`) ?? null, servicePort: numberField(gitMirror, "servicePort", `${path}.gitMirror`), deploymentReplicas: nonNegativeIntegerField(gitMirror, "deploymentReplicas", `${path}.gitMirror`), secretName: stringField(gitMirror, "secretName", `${path}.gitMirror`), syncConfigMapName: stringField(gitMirror, "syncConfigMapName", `${path}.gitMirror`), syncJobPrefix: stringField(gitMirror, "syncJobPrefix", `${path}.gitMirror`), flushJobPrefix: stringField(gitMirror, "flushJobPrefix", `${path}.gitMirror`), readUrl: optionalStringField(gitMirror, "readUrl", `${path}.gitMirror`) ?? `http://${serviceReadName}.${gitMirrorNamespace}.svc.cluster.local/${sourceRepository}.git`, writeUrl: optionalStringField(gitMirror, "writeUrl", `${path}.gitMirror`) ?? `http://${serviceWriteName}.${gitMirrorNamespace}.svc.cluster.local/${sourceRepository}.git`, egressProxy: gitMirrorEgressProxy, githubTransport, }, tekton: { pipelineName: stringField(tekton, "pipelineName", `${path}.tekton`), serviceAccountName: stringField(tekton, "serviceAccountName", `${path}.tekton`), pipelineRunPrefix: stringField(tekton, "pipelineRunPrefix", `${path}.tekton`), gitWorkspaceSecret: tektonGitWorkspaceSecretSpec(tekton.gitWorkspaceSecret, `${path}.tekton.gitWorkspaceSecret`, ciNamespace, githubTransport), runtimeObserverRbac: tektonRuntimeObserverRbacSpec(tekton.runtimeObserverRbac, `${path}.tekton.runtimeObserverRbac`, runtimeNamespace), argoObserverRbac: tektonArgoObserverRbacSpec(tekton.argoObserverRbac, `${path}.tekton.argoObserverRbac`, argoNamespace), toolsImage: toolsImageSpec(toolsImage, `${path}.tekton.toolsImage`), }, ciBuildBenchmarks: ciBuildBenchmarkProfileSpecs(raw.ciBuildBenchmarks, `${path}.ciBuildBenchmarks`), argo: { namespace: argoNamespace, projectName: stringField(argo, "projectName", `${path}.argo`), applicationName: stringField(argo, "applicationName", `${path}.argo`), applicationFile: stringField(argo, "applicationFile", `${path}.argo`), install: argoInstallSpec(asRecord(argo.install, `${path}.argo.install`), `${path}.argo.install`), }, }; } function renderInfraManifest(_node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec): Record[] { const labels = { "app.kubernetes.io/part-of": "hwlab-node-control-plane", "hwlab.pikastech.local/node": target.node, "hwlab.pikastech.local/lane": target.lane, }; const manifests: Record[] = []; const namespaces = new Set(); const addNamespace = (name: string): void => { if (namespaces.has(name)) return; namespaces.add(name); manifests.push({ apiVersion: "v1", kind: "Namespace", metadata: { name, labels } }); }; addNamespace(target.ciNamespace); addNamespace(target.runtimeNamespace); addNamespace(target.gitMirror.namespace); manifests.push( { apiVersion: "v1", kind: "ServiceAccount", metadata: { name: target.tekton.serviceAccountName, namespace: target.ciNamespace, labels } }, tektonRuntimeObserverRole(target, labels), tektonRuntimeObserverRoleBinding(target, labels), tektonArgoObserverRole(target, labels), tektonArgoObserverRoleBinding(target, labels), { apiVersion: "v1", kind: "ConfigMap", metadata: { name: target.gitMirror.syncConfigMapName, namespace: target.gitMirror.namespace, labels: { ...labels, "app.kubernetes.io/name": "git-mirror" } }, data: { "repositories.json": JSON.stringify([{ key: target.id, repository: target.source.repository, sourceBranch: target.source.branch, gitopsBranch: target.gitops.branch }], null, 2), "server.js": gitMirrorServerJs(), "status.sh": gitMirrorStatusShell(), "sync.sh": gitMirrorSyncShell(_node, target), "flush.sh": gitMirrorFlushShell(_node, target), }, }, ); const githubTokenSecret = gitMirrorGithubTokenSecret(target, labels); if (githubTokenSecret !== null) manifests.push(githubTokenSecret); const githubSshSecret = gitMirrorGithubSshSecret(target, labels); if (githubSshSecret !== null) manifests.push(githubSshSecret); const ciGitWorkspaceSecret = tektonGitWorkspaceSecret(target, labels); if (ciGitWorkspaceSecret !== null) manifests.push(ciGitWorkspaceSecret); if (target.gitMirror.cacheHostPath === null) { manifests.push({ apiVersion: "v1", kind: "PersistentVolumeClaim", metadata: { name: target.gitMirror.cachePvcName, namespace: target.gitMirror.namespace, labels: { ...labels, "app.kubernetes.io/name": "git-mirror" } }, spec: { accessModes: ["ReadWriteOnce"], resources: { requests: { storage: target.gitMirror.cachePvcStorage } } }, }); } manifests.push( service(target.gitMirror.serviceReadName, target.gitMirror.namespace, labels, target.gitMirror.servicePort), service(target.gitMirror.serviceWriteName, target.gitMirror.namespace, labels, target.gitMirror.servicePort), gitMirrorDeployment(target.gitMirror.serviceReadName, target.gitMirror.namespace, labels, _node, target, "read"), gitMirrorDeployment(target.gitMirror.serviceWriteName, target.gitMirror.namespace, labels, _node, target, "write"), { apiVersion: "tekton.dev/v1", kind: "Pipeline", metadata: { name: target.tekton.pipelineName, namespace: target.ciNamespace, labels }, spec: { params: [ { name: "source-commit", type: "string" }, { name: "source-branch", type: "string", default: target.source.branch }, { name: "gitops-branch", type: "string", default: target.gitops.branch }, ], tasks: [{ name: "bootstrap-placeholder", taskSpec: { steps: [{ name: "notice", image: target.tekton.toolsImage.output, script: "#!/bin/sh\nset -eu\necho d601-hwlab-v03-pipeline-placeholder\n" }] } }], }, }, { apiVersion: "v1", kind: "Namespace", metadata: { name: target.argo.namespace, labels } }, { apiVersion: "v1", kind: "ConfigMap", metadata: { name: `${target.argo.applicationName}-desired`, namespace: target.argo.namespace, labels }, data: { "project.yaml": Bun.YAML.stringify(argoProjectSkeleton(target)), [target.argo.applicationFile]: Bun.YAML.stringify(argoApplicationSkeleton(target)), "note.txt": "Argo CD CRDs/controller are installed by the node control-plane bootstrap path when available; this ConfigMap preserves the desired Application until Argo is ready.\n", }, }, ); return manifests; } function tektonRuntimeObserverRole(target: ControlPlaneTargetSpec, labels: Record): Record { const rbac = target.tekton.runtimeObserverRbac; return { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", metadata: { name: rbac.roleName, namespace: rbac.namespace, labels: { ...labels, "app.kubernetes.io/name": "hwlab-runtime-observer" }, }, rules: [ { apiGroups: ["apps"], resources: ["deployments", "statefulsets"], verbs: ["get", "list", "watch"], }, ], }; } function tektonRuntimeObserverRoleBinding(target: ControlPlaneTargetSpec, labels: Record): Record { const rbac = target.tekton.runtimeObserverRbac; return { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", metadata: { name: rbac.roleBindingName, namespace: rbac.namespace, labels: { ...labels, "app.kubernetes.io/name": "hwlab-runtime-observer" }, }, subjects: [ { kind: "ServiceAccount", name: target.tekton.serviceAccountName, namespace: target.ciNamespace, }, ], roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: rbac.roleName, }, }; } function tektonArgoObserverRole(target: ControlPlaneTargetSpec, labels: Record): Record { const rbac = target.tekton.argoObserverRbac; return { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", metadata: { name: rbac.roleName, namespace: rbac.namespace, labels: { ...labels, "app.kubernetes.io/name": "hwlab-argo-observer" }, }, rules: [ { apiGroups: ["argoproj.io"], resources: ["applications"], verbs: ["get", "list", "watch"], }, ], }; } function tektonArgoObserverRoleBinding(target: ControlPlaneTargetSpec, labels: Record): Record { const rbac = target.tekton.argoObserverRbac; return { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", metadata: { name: rbac.roleBindingName, namespace: rbac.namespace, labels: { ...labels, "app.kubernetes.io/name": "hwlab-argo-observer" }, }, subjects: [ { kind: "ServiceAccount", name: target.tekton.serviceAccountName, namespace: target.ciNamespace, }, ], roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: rbac.roleName, }, }; } function gitMirrorGithubSshSecret(target: ControlPlaneTargetSpec, labels: Record): Record | null { const transport = target.gitMirror.githubTransport; if (transport.mode !== "ssh") return null; const material = gitMirrorGithubSshMaterial(transport); return { apiVersion: "v1", kind: "Secret", metadata: { name: target.gitMirror.secretName, namespace: target.gitMirror.namespace, labels: { ...labels, "app.kubernetes.io/name": "git-mirror" }, annotations: { "unidesk.ai/private-key-source-ref": transport.privateKeySourceRef, "unidesk.ai/private-key-source-key": transport.privateKeySourceKey, "unidesk.ai/private-key-target-key": transport.privateKeySecretKey, "unidesk.ai/private-key-fingerprint": material.privateKeyFingerprint, ...(transport.knownHostsSecretKey === null ? {} : { "unidesk.ai/known-hosts-source-ref": transport.knownHostsSourceRef ?? "", "unidesk.ai/known-hosts-source-key": transport.knownHostsSourceKey ?? "", "unidesk.ai/known-hosts-target-key": transport.knownHostsSecretKey, "unidesk.ai/known-hosts-fingerprint": material.knownHostsFingerprint ?? "", }), }, }, type: "kubernetes.io/ssh-auth", stringData: { [transport.privateKeySecretKey]: material.privateKey, ...(transport.knownHostsSecretKey === null || material.knownHosts === null ? {} : { [transport.knownHostsSecretKey]: material.knownHosts }), }, }; } function tektonGitWorkspaceSecret(target: ControlPlaneTargetSpec, labels: Record): Record | null { const transport = target.gitMirror.githubTransport; if (transport.mode !== "ssh") return null; const secret = target.tekton.gitWorkspaceSecret; const material = gitMirrorGithubSshMaterial(transport); if (material.knownHosts === null || material.knownHostsFingerprint === null) { throw new Error(`targets.${target.id}.tekton.gitWorkspaceSecret requires known_hosts material from gitMirror.githubTransport`); } return { apiVersion: "v1", kind: "Secret", metadata: { name: secret.name, namespace: secret.namespace, labels: { ...labels, "app.kubernetes.io/name": "hwlab-tekton-git-workspace" }, annotations: { "unidesk.ai/source-ref-from": secret.sourceRefFrom, "unidesk.ai/private-key-source-ref": transport.privateKeySourceRef, "unidesk.ai/private-key-source-key": transport.privateKeySourceKey, "unidesk.ai/private-key-target-key": secret.privateKeySecretKey, "unidesk.ai/private-key-fingerprint": material.privateKeyFingerprint, "unidesk.ai/known-hosts-source-ref": transport.knownHostsSourceRef ?? "", "unidesk.ai/known-hosts-source-key": transport.knownHostsSourceKey ?? "", "unidesk.ai/known-hosts-target-key": secret.knownHostsSecretKey, "unidesk.ai/known-hosts-fingerprint": material.knownHostsFingerprint, }, }, type: "kubernetes.io/ssh-auth", stringData: { [secret.privateKeySecretKey]: material.privateKey, [secret.knownHostsSecretKey]: material.knownHosts, }, }; } function gitMirrorGithubSshMaterial(transport: Extract): { privateKey: string; knownHosts: string | null; privateKeyFingerprint: string; knownHostsFingerprint: string | null } { const privateSource = readControlPlaneSecretSource(transport.privateKeySourceRef, `gitMirror.githubTransport private key source ${transport.privateKeySourceRef} is missing; create the YAML-declared sourceRef with ${transport.privateKeySourceKey} before applying the control plane`); const privateValue = requiredEnvValue(privateSource.values, transport.privateKeySourceKey, transport.privateKeySourceRef); const privateKey = decodeSecretSourceValue(privateValue, transport.privateKeySourceEncoding, "gitMirror.githubTransport.privateKeySourceKey"); if (!/-----BEGIN [A-Z ]*PRIVATE KEY-----/u.test(privateKey)) throw new Error(`${transport.privateKeySourceRef}.${transport.privateKeySourceKey} does not contain private key material`); let knownHosts: string | null = null; let knownHostsFingerprint: string | null = null; if (transport.knownHostsSourceRef !== null && transport.knownHostsSourceKey !== null && transport.knownHostsSourceEncoding !== null) { const knownHostsSource = readControlPlaneSecretSource(transport.knownHostsSourceRef, `gitMirror.githubTransport known_hosts source ${transport.knownHostsSourceRef} is missing; create the YAML-declared sourceRef with ${transport.knownHostsSourceKey} before applying the control plane`); const knownHostsValue = requiredEnvValue(knownHostsSource.values, transport.knownHostsSourceKey, transport.knownHostsSourceRef); knownHosts = decodeSecretSourceValue(knownHostsValue, transport.knownHostsSourceEncoding, "gitMirror.githubTransport.knownHostsSourceKey"); if (!/(^|\n)(github\.com|\[github\.com\]:22)\s+(ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp256)\s+/u.test(knownHosts)) { throw new Error(`${transport.knownHostsSourceRef}.${transport.knownHostsSourceKey} must contain github.com known_hosts rows`); } knownHostsFingerprint = fingerprintSecretValues({ knownHosts }, ["knownHosts"]); } return { privateKey: privateKey.endsWith("\n") ? privateKey : `${privateKey}\n`, knownHosts: knownHosts === null ? null : (knownHosts.endsWith("\n") ? knownHosts : `${knownHosts}\n`), privateKeyFingerprint: fingerprintSecretValues({ privateKey }, ["privateKey"]), knownHostsFingerprint, }; } function readControlPlaneSecretSource(sourceRef: string, missingMessage: string): ReturnType { return readEnvSourceFile({ root: rootPath(".state", "secrets"), sourceRef, missingMessage: () => missingMessage, }); } function decodeSecretSourceValue(value: string, encoding: "plain" | "base64", path: string): string { if (encoding === "plain") return value; try { const compact = value.replace(/\s+/gu, ""); if (compact.length === 0) throw new Error("empty base64 value"); return Buffer.from(compact, "base64").toString("utf8"); } catch (error) { throw new Error(`${path} must be valid base64: ${error instanceof Error ? error.message : String(error)}`); } } function gitMirrorGithubTokenSecret(target: ControlPlaneTargetSpec, labels: Record): Record | null { const transport = target.gitMirror.githubTransport; if (transport.mode !== "https") return null; const token = gitMirrorGithubHttpsToken(transport); return { apiVersion: "v1", kind: "Secret", metadata: { name: transport.tokenSecretName, namespace: target.gitMirror.namespace, labels: { ...labels, "app.kubernetes.io/name": "git-mirror" }, annotations: { "unidesk.ai/source-ref": transport.tokenSourceRef, "unidesk.ai/source-key": transport.tokenSourceKey, "unidesk.ai/target-key": transport.tokenSecretKey, "unidesk.ai/fingerprint": token.fingerprint, }, }, type: "Opaque", stringData: { [transport.tokenSecretKey]: token.value }, }; } function gitMirrorGithubHttpsToken(transport: Extract): { value: string; fingerprint: string } { const absolute = transport.tokenSourceRef.startsWith("/"); const source = readEnvSourceFile({ root: absolute ? "/" : rootPath("."), sourceRef: absolute ? transport.tokenSourceRef.slice(1) : transport.tokenSourceRef, missingMessage: () => `gitMirror.githubTransport token source ${transport.tokenSourceRef} is missing; create the YAML-declared sourceRef with ${transport.tokenSourceKey} before applying the control plane`, }); const value = requiredEnvValue(source.values, transport.tokenSourceKey, transport.tokenSourceRef); return { value, fingerprint: fingerprintSecretValues({ [transport.tokenSourceKey]: value }, [transport.tokenSourceKey]), }; } function service(name: string, namespace: string, labels: Record, port: number): Record { return { apiVersion: "v1", kind: "Service", metadata: { name, namespace, labels: { ...labels, "app.kubernetes.io/name": "git-mirror" } }, spec: { type: "ClusterIP", selector: { "app.kubernetes.io/name": name }, ports: [{ name: "http", port, targetPort: "http" }] }, }; } function gitMirrorConfigHash(node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec): string { return sha256Short(JSON.stringify({ repositories: [{ key: target.id, repository: target.source.repository, sourceBranch: target.source.branch, gitopsBranch: target.gitops.branch }], githubTransport: gitMirrorGithubTransportSummary(target.gitMirror.githubTransport), server: gitMirrorServerJs(), status: gitMirrorStatusShell(), sync: gitMirrorSyncShell(node, target), flush: gitMirrorFlushShell(node, target), })); } function gitMirrorDeployment(name: string, namespace: string, labels: Record, node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, mode: "read" | "write"): Record { return { apiVersion: "apps/v1", kind: "Deployment", metadata: { name, namespace, labels: { ...labels, "app.kubernetes.io/name": name, "hwlab.pikastech.local/git-mirror-mode": mode } }, spec: { replicas: target.gitMirror.deploymentReplicas, selector: { matchLabels: { "app.kubernetes.io/name": name } }, template: { metadata: { labels: { ...labels, "app.kubernetes.io/name": name, "hwlab.pikastech.local/git-mirror-mode": mode }, annotations: { "checksum/config": gitMirrorConfigHash(node, target) }, }, spec: { containers: [{ name: "git-mirror", image: target.tekton.toolsImage.output, command: ["node", "/etc/git-mirror/server.js"], env: [ { name: "PORT", value: String(target.gitMirror.servicePort) }, { name: "GIT_PROJECT_ROOT", value: "/cache" }, { name: "GIT_MIRROR_MODE", value: mode }, ], ports: [{ name: "http", containerPort: target.gitMirror.servicePort }], volumeMounts: [{ name: "cache", mountPath: "/cache" }, { name: "config", mountPath: "/etc/git-mirror" }], }], volumes: [ { name: "cache", ...(target.gitMirror.cacheHostPath === null ? { persistentVolumeClaim: { claimName: target.gitMirror.cachePvcName } } : { hostPath: { path: target.gitMirror.cacheHostPath, type: "DirectoryOrCreate" } }) }, { name: "config", configMap: { name: target.gitMirror.syncConfigMapName, defaultMode: 0o755 } }, ], }, }, }, }; } function gitMirrorServerJs(): string { return String.raw`const http = require('node:http'); const { spawn } = require('node:child_process'); const projectRoot = process.env.GIT_PROJECT_ROOT || '/cache'; const port = Number.parseInt(process.env.PORT || '8080', 10); function sendHealth(res) { res.writeHead(200, { 'content-type': 'application/json' }); res.end(JSON.stringify({ ok: true, mode: process.env.GIT_MIRROR_MODE || null, projectRoot })); } function parseHeaders(buffer) { const crlf = buffer.indexOf('\r\n\r\n'); const lf = buffer.indexOf('\n\n'); const headerEnd = crlf >= 0 ? crlf + 4 : (lf >= 0 ? lf + 2 : -1); if (headerEnd < 0) return null; const headerText = buffer.slice(0, headerEnd).toString('latin1').trim(); const rest = buffer.slice(headerEnd); const headers = {}; let status = 200; for (const line of headerText.split(/\r?\n/u)) { const index = line.indexOf(':'); if (index < 0) continue; const key = line.slice(0, index).trim(); const value = line.slice(index + 1).trim(); if (key.toLowerCase() === 'status') { const parsed = Number.parseInt(value.split(' ')[0] || '', 10); if (Number.isInteger(parsed)) status = parsed; } else { headers[key] = value; } } return { status, headers, rest }; } function cgiHeaderEnv(req) { const env = {}; for (const [key, value] of Object.entries(req.headers)) { if (value === undefined) continue; const joined = Array.isArray(value) ? value.join(', ') : String(value); const normalized = key.toUpperCase().replace(/-/g, '_'); if (normalized === 'CONTENT_TYPE') env.CONTENT_TYPE = joined; else if (normalized === 'CONTENT_LENGTH') env.CONTENT_LENGTH = joined; else env['HTTP_' + normalized] = joined; } return env; } function handleGit(req, res) { const url = new URL(req.url || '/', 'http://git-mirror.local'); const env = { ...process.env, ...cgiHeaderEnv(req), GIT_PROJECT_ROOT: projectRoot, GIT_HTTP_EXPORT_ALL: '1', PATH_INFO: decodeURIComponent(url.pathname), REQUEST_METHOD: req.method || 'GET', QUERY_STRING: url.search.slice(1), CONTENT_TYPE: req.headers['content-type'] || '', CONTENT_LENGTH: req.headers['content-length'] || '', REMOTE_USER: 'git', }; const child = spawn('git', ['http-backend'], { env }); let pending = Buffer.alloc(0); let headersSent = false; child.stderr.on('data', (chunk) => process.stderr.write(chunk)); child.on('error', (error) => { if (!headersSent) { res.writeHead(500, { 'content-type': 'text/plain' }); headersSent = true; } res.end(String(error && error.message || error)); }); child.stdout.on('data', (chunk) => { if (headersSent) { res.write(chunk); return; } pending = Buffer.concat([pending, chunk]); const parsed = parseHeaders(pending); if (!parsed) return; headersSent = true; res.writeHead(parsed.status, parsed.headers); if (parsed.rest.length) res.write(parsed.rest); }); child.on('close', (code) => { if (!headersSent) { res.writeHead(code === 0 ? 200 : 500, { 'content-type': 'text/plain' }); headersSent = true; if (pending.length) res.write(pending); } res.end(); }); req.pipe(child.stdin); } http.createServer((req, res) => { if ((req.url || '').startsWith('/healthz')) return sendHealth(res); return handleGit(req, res); }).listen(port, '0.0.0.0', () => { console.log(JSON.stringify({ event: 'git-mirror-http-started', port, projectRoot, mode: process.env.GIT_MIRROR_MODE || null })); }); `; } function gitMirrorStatusShell(): string { return String.raw`#!/bin/sh set -eu node <<'NODE' const { execFileSync } = require('node:child_process'); const { readFileSync, existsSync } = require('node:fs'); const repositories = JSON.parse(readFileSync('/etc/git-mirror/repositories.json', 'utf8')); function readJson(path) { try { return existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : null; } catch { return null; } } function rev(repo, ref) { try { return execFileSync('git', ['--git-dir=' + repo, 'rev-parse', '--verify', ref + '^{commit}'], { encoding: 'utf8' }).trim(); } catch { return null; } } const items = {}; for (const spec of repositories) { const repoPath = '/cache/' + spec.repository + '.git'; const localSource = rev(repoPath, 'refs/heads/' + spec.sourceBranch); const githubSource = rev(repoPath, 'refs/mirror-stage/heads/' + spec.sourceBranch); const localGitops = rev(repoPath, 'refs/heads/' + spec.gitopsBranch); const githubGitops = rev(repoPath, 'refs/mirror-stage/heads/' + spec.gitopsBranch); items[spec.key] = { repository: spec.repository, sourceBranch: spec.sourceBranch, localSource, githubSource, gitopsBranch: spec.gitopsBranch, localGitops, githubGitops, sourceInSync: Boolean(localSource && githubSource && localSource === githubSource), gitopsInSync: Boolean(localGitops && githubGitops && localGitops === githubGitops), pendingFlush: Boolean(localGitops && (!githubGitops || localGitops !== githubGitops)), }; } const first = items[repositories[0]?.key] || {}; const pendingFlush = Object.values(items).some((item) => Boolean(item.pendingFlush)); console.log(JSON.stringify({ localSource: first.localSource || null, githubSource: first.githubSource || null, localGitops: first.localGitops || null, githubGitops: first.githubGitops || null, refSources: { localSource: 'refs/heads/' + (first.sourceBranch || ''), githubSource: 'refs/mirror-stage/heads/' + (first.sourceBranch || ''), localGitops: 'refs/heads/' + (first.gitopsBranch || ''), githubGitops: 'refs/mirror-stage/heads/' + (first.gitopsBranch || ''), githubFieldsAreMirrorStageCache: true }, pendingFlush, flushNeeded: pendingFlush, githubInSync: Object.values(items).every((item) => item.sourceInSync === true && item.gitopsInSync === true), repositories: items, lastSync: readJson('/cache/HWLAB.last-sync.json'), lastFlush: readJson('/cache/HWLAB.last-flush.json'), })); NODE `; } function gitMirrorProxyPrelude(node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec): string { const gitMirrorProxy = target.gitMirror.egressProxy; const transport = target.gitMirror.githubTransport; const useDirect = gitMirrorProxy?.mode === "direct"; const proxyRequired = gitMirrorProxy?.required === true; if (!useDirect && gitMirrorProxy?.mode !== "node-global") throw new Error(`targets.${target.id}.gitMirror.egressProxy.mode must be node-global or direct`); if (!useDirect && proxyRequired && node.egressProxy === null) throw new Error(`nodes.${node.id}.egressProxy is required by targets.${target.id}.gitMirror.egressProxy`); if (!useDirect && node.egressProxy === null) throw new Error(`nodes.${node.id}.egressProxy is missing; git-mirror GitHub transport cannot use node-global proxy`); const proxyHost = useDirect || node.egressProxy === null ? "" : `${node.egressProxy.serviceName}.${node.egressProxy.namespace}.svc.cluster.local`; const proxyPort = useDirect || node.egressProxy === null ? 0 : node.egressProxy.port; const proxyUrl = useDirect ? "" : `http://${proxyHost}:${proxyPort}`; const noProxy = useDirect || node.egressProxy === null ? "" : node.egressProxy.noProxy.join(","); const proxySource = useDirect || node.egressProxy === null ? "sourceType=direct sourceRef=- sourceFingerprint=-" : `sourceType=${node.egressProxy.sourceType} sourceRef=${node.egressProxy.sourceRef} sourceFingerprint=${node.egressProxy.sourceFingerprint ?? "-"}`; const proxySummary = useDirect ? `git-mirror-egress-proxy mode=direct required=false transport=${transport.mode} source=yaml` : transport.mode === "https" ? `git-mirror-egress-proxy client=${node.egressProxy?.clientName} mode=node-global required=${proxyRequired ? "true" : "false"} host=${proxyHost} port=${proxyPort} transport=https proxy=HTTP_PROXY authSecret=${transport.tokenSecretName} authKey=${transport.tokenSecretKey} authSourceRef=${transport.tokenSourceRef} ${proxySource} source=yaml` : `git-mirror-egress-proxy client=${node.egressProxy?.clientName} mode=node-global required=${proxyRequired ? "true" : "false"} host=${proxyHost} port=${proxyPort} transport=ssh ssh=GIT_SSH-wrapper ${proxySource} source=yaml`; const proxyCommand = useDirect ? "" : `ProxyCommand=node /tmp/hwlab-github-proxy-connect.cjs ${proxyHost} ${proxyPort} %h %p`; const common = [ `printf '%s\\n' ${shQuote(proxySummary)} >&2`, useDirect ? "unset HTTP_PROXY HTTPS_PROXY ALL_PROXY http_proxy https_proxy all_proxy" : `export HTTP_PROXY=${shQuote(proxyUrl)}`, useDirect ? "export NO_PROXY='*'" : `export HTTPS_PROXY=${shQuote(proxyUrl)}`, useDirect ? "export no_proxy='*'" : `export ALL_PROXY=${shQuote(proxyUrl)}`, useDirect ? "" : `export http_proxy=${shQuote(proxyUrl)}`, useDirect ? "" : `export https_proxy=${shQuote(proxyUrl)}`, useDirect ? "" : `export all_proxy=${shQuote(proxyUrl)}`, useDirect ? "" : `export NO_PROXY=${shQuote(noProxy)}`, useDirect ? "" : `export no_proxy=${shQuote(noProxy)}`, `repository=${shQuote(target.source.repository)}`, `source_branch=${shQuote(target.source.branch)}`, `gitops_branch=${shQuote(target.gitops.branch)}`, "repo=\"/cache/${repository}.git\"", ].filter(Boolean); const proxyConnectBlock = useDirect ? [] : [ "cat > /tmp/hwlab-github-proxy-connect.cjs <<'NODE_PROXY'", "#!/usr/bin/env node", "const net = require('node:net');", "const [proxyHost, proxyPortRaw, targetHost, targetPortRaw] = process.argv.slice(2);", "const proxyPort = Number.parseInt(proxyPortRaw || '', 10);", "const targetPort = Number.parseInt(targetPortRaw || '', 10);", "if (!proxyHost || !Number.isInteger(proxyPort) || !targetHost || !Number.isInteger(targetPort)) {", " console.error('hwlab git-mirror proxy-connect: invalid ProxyCommand arguments');", " process.exit(64);", "}", "let settled = false;", "let tunnelEstablished = false;", "function finish(code, message) {", " if (settled) return;", " settled = true;", " if (message) console.error('hwlab git-mirror proxy-connect: ' + message);", " process.exit(code);", "}", "const socket = net.createConnection({ host: proxyHost, port: proxyPort });", "let buffer = Buffer.alloc(0);", "socket.setTimeout(15000, () => { socket.destroy(); finish(65, 'timeout connecting via ' + proxyHost + ':' + proxyPort + ' to ' + targetHost + ':' + targetPort); });", "socket.on('connect', () => socket.write('CONNECT ' + targetHost + ':' + targetPort + ' HTTP/1.1\\r\\nHost: ' + targetHost + ':' + targetPort + '\\r\\nProxy-Connection: Keep-Alive\\r\\n\\r\\n'));", "socket.on('error', (error) => finish(tunnelEstablished ? 69 : 66, (tunnelEstablished ? 'tunnel socket error: ' : 'tcp error connecting to proxy: ') + (error && error.message ? error.message : String(error))));", "socket.on('close', () => { if (!tunnelEstablished) finish(68, 'proxy closed before CONNECT completed via ' + proxyHost + ':' + proxyPort + ' to ' + targetHost + ':' + targetPort); else finish(0); });", "function onData(chunk) {", " buffer = Buffer.concat([buffer, chunk]);", " const headerEnd = buffer.indexOf('\\r\\n\\r\\n');", " if (headerEnd === -1 && buffer.length < 8192) return;", " if (headerEnd === -1) { socket.destroy(); finish(68, 'proxy response header exceeded 8192 bytes before CONNECT status via ' + proxyHost + ':' + proxyPort + ' to ' + targetHost + ':' + targetPort); return; }", " const head = buffer.slice(0, headerEnd + 4).toString('latin1');", " const statusLine = head.split('\\r\\n', 1)[0] || '';", " const statusCode = Number.parseInt(statusLine.split(' ')[1] || '', 10);", " if (!statusLine.startsWith('HTTP/1.') || !Number.isInteger(statusCode) || statusCode < 200 || statusCode > 299) {", " const safeStatus = statusLine.replace(/[^\\x20-\\x7e]/g, '?').slice(0, 160);", " socket.destroy();", " finish(67, 'proxy CONNECT failed via ' + proxyHost + ':' + proxyPort + ' to ' + targetHost + ':' + targetPort + ': ' + safeStatus);", " return;", " }", " socket.off('data', onData);", " socket.setTimeout(0);", " tunnelEstablished = true;", " const rest = buffer.slice(headerEnd + 4);", " if (rest.length) process.stdout.write(rest);", " process.stdin.on('error', () => {});", " process.stdout.on('error', () => {});", " process.stdin.pipe(socket);", " socket.pipe(process.stdout);", "}", "socket.on('data', onData);", "NODE_PROXY", "chmod 0700 /tmp/hwlab-github-proxy-connect.cjs", ]; if (transport.mode === "https") { return [ ...common, "if [ -z \"${GITHUB_TOKEN:-}\" ]; then echo 'hwlab git-mirror https auth: missing GITHUB_TOKEN secret env' >&2; exit 64; fi", "cat > /tmp/hwlab-git-askpass.sh <<'SH_ASKPASS'", "#!/bin/sh", "case \"$1\" in", " *Username*) printf '%s\\n' \"${GITHUB_USERNAME:-x-access-token}\" ;;", " *Password*) printf '%s\\n' \"$GITHUB_TOKEN\" ;;", " *) printf '\\n' ;;", "esac", "SH_ASKPASS", "chmod 0700 /tmp/hwlab-git-askpass.sh", `export GITHUB_USERNAME=${shQuote(transport.username)}`, "export GIT_ASKPASS=/tmp/hwlab-git-askpass.sh", "export GIT_TERMINAL_PROMPT=0", "unset GIT_SSH", "unset GIT_SSH_COMMAND", "remote=\"https://github.com/${repository}.git\"", ].join("\n"); } const privateKeyPath = transport.mode === "ssh" ? `/git-ssh/${transport.privateKeySecretKey}` : "/git-ssh/ssh-privatekey"; const knownHostsCopy = transport.mode === "ssh" && transport.knownHostsSecretKey !== null ? [`cp ${shQuote(`/git-ssh/${transport.knownHostsSecretKey}`)} /root/.ssh/known_hosts`, "chmod 0600 /root/.ssh/known_hosts"] : []; return [ "mkdir -p /root/.ssh", `cp ${shQuote(privateKeyPath)} /root/.ssh/id_rsa`, "chmod 0400 /root/.ssh/id_rsa", ...knownHostsCopy, ...common, ...proxyConnectBlock, "cat > /tmp/hwlab-git-ssh-proxy.sh <<'SH_PROXY'", "#!/bin/sh", useDirect ? `exec ssh -i /root/.ssh/id_rsa -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/root/.ssh/known_hosts -o ConnectTimeout=15 -o ServerAliveInterval=5 -o ServerAliveCountMax=1 "$@"` : `exec ssh -i /root/.ssh/id_rsa -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/root/.ssh/known_hosts -o ConnectTimeout=15 -o ServerAliveInterval=5 -o ServerAliveCountMax=1 -o ${shQuote(proxyCommand)} "$@"`, "SH_PROXY", "chmod 0700 /tmp/hwlab-git-ssh-proxy.sh", "export GIT_SSH=/tmp/hwlab-git-ssh-proxy.sh", "unset GIT_SSH_COMMAND", "remote=\"ssh://git@ssh.github.com:443/${repository}.git\"", ].filter(Boolean).join("\n"); } function gitMirrorSyncShell(node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec): string { return [ "#!/bin/sh", "set -eu", "started_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)", gitMirrorProxyPrelude(node, target), "mkdir -p \"$(dirname \"$repo\")\"", "if [ -d \"$repo/objects\" ] && [ -f \"$repo/HEAD\" ]; then", " git --git-dir=\"$repo\" remote set-url origin \"$remote\" || git --git-dir=\"$repo\" remote add origin \"$remote\"", "else", " rm -rf \"$repo\"", " git init --bare \"$repo\"", " git --git-dir=\"$repo\" remote add origin \"$remote\"", "fi", "git --git-dir=\"$repo\" config uploadpack.allowReachableSHA1InWant true", "git --git-dir=\"$repo\" config uploadpack.allowAnySHA1InWant true", "git --git-dir=\"$repo\" config http.uploadpack true", "git --git-dir=\"$repo\" config http.receivepack true", "timeout 240 git --git-dir=\"$repo\" fetch origin \"+refs/heads/${source_branch}:refs/mirror-stage/heads/${source_branch}\"", "source_sha=$(git --git-dir=\"$repo\" rev-parse --verify \"refs/mirror-stage/heads/${source_branch}^{commit}\")", "git --git-dir=\"$repo\" update-ref \"refs/heads/${source_branch}\" \"$source_sha\"", "if timeout 240 git --git-dir=\"$repo\" fetch origin \"+refs/heads/${gitops_branch}:refs/mirror-stage/heads/${gitops_branch}\"; then", " github_gitops=$(git --git-dir=\"$repo\" rev-parse --verify \"refs/mirror-stage/heads/${gitops_branch}^{commit}\" 2>/dev/null || true)", " local_gitops=$(git --git-dir=\"$repo\" rev-parse --verify \"refs/heads/${gitops_branch}^{commit}\" 2>/dev/null || true)", " if [ -z \"$local_gitops\" ] && [ -n \"$github_gitops\" ]; then", " git --git-dir=\"$repo\" update-ref \"refs/heads/${gitops_branch}\" \"$github_gitops\"", " elif [ -n \"$local_gitops\" ] && [ -n \"$github_gitops\" ] && [ \"$local_gitops\" != \"$github_gitops\" ] && git --git-dir=\"$repo\" merge-base --is-ancestor \"$local_gitops\" \"$github_gitops\"; then", " git --git-dir=\"$repo\" update-ref \"refs/heads/${gitops_branch}\" \"$github_gitops\"", " fi", "fi", "git --git-dir=\"$repo\" update-server-info", "export repository source_branch gitops_branch started_at", "node <<'NODE' | tee /cache/HWLAB.last-sync.json", "const { execFileSync } = require('node:child_process');", "const repository = process.env.repository;", "const sourceBranch = process.env.source_branch;", "const gitopsBranch = process.env.gitops_branch;", "const repoPath = `/cache/${repository}.git`;", "function rev(ref) { try { return execFileSync('git', ['--git-dir=' + repoPath, 'rev-parse', '--verify', ref + '^{commit}'], { encoding: 'utf8' }).trim(); } catch { return null; } }", "const localSource = rev(`refs/heads/${sourceBranch}`);", "const githubSource = rev(`refs/mirror-stage/heads/${sourceBranch}`);", "const localGitops = rev(`refs/heads/${gitopsBranch}`);", "const githubGitops = rev(`refs/mirror-stage/heads/${gitopsBranch}`);", "const pendingFlush = Boolean(localGitops && (!githubGitops || localGitops !== githubGitops));", "console.log(JSON.stringify({ event: 'git-mirror-sync', repo: repository, status: 'succeeded', startedAt: process.env.started_at, syncedAt: new Date().toISOString(), localSource, githubSource, gitopsBranch, localGitops, githubGitops, sourceInSync: Boolean(localSource && githubSource && localSource === githubSource), gitopsInSync: Boolean(localGitops && githubGitops && localGitops === githubGitops), pendingFlush }));", "NODE", "cat /cache/HWLAB.last-sync.json", "", ].join("\n"); } function gitMirrorFlushShell(node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec): string { return [ "#!/bin/sh", "set -eu", "started_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)", gitMirrorProxyPrelude(node, target), "test -d \"$repo/objects\"", "git --git-dir=\"$repo\" remote set-url origin \"$remote\" || git --git-dir=\"$repo\" remote add origin \"$remote\"", "local_gitops=$(git --git-dir=\"$repo\" rev-parse --verify \"refs/heads/${gitops_branch}^{commit}\" 2>/dev/null || true)", "push_status=skipped", "push_exit=0", "fetch_status=skipped", "fetch_exit=0", "fetch_attempt=0", "fetch_max_attempts=5", "if [ -n \"$local_gitops\" ]; then", " set +e", " timeout 240 git --git-dir=\"$repo\" -c remote.origin.mirror=false push origin \"refs/heads/${gitops_branch}:refs/heads/${gitops_branch}\"", " push_exit=$?", " set -e", " if [ \"$push_exit\" = \"0\" ]; then", " push_status=succeeded", " fetch_retry_delay=1", " while [ \"$fetch_attempt\" -lt \"$fetch_max_attempts\" ]; do", " fetch_attempt=$((fetch_attempt + 1))", " echo \"git-mirror post-push fetch attempt ${fetch_attempt}/${fetch_max_attempts}\" >&2", " set +e", " timeout 240 git --git-dir=\"$repo\" fetch origin \"+refs/heads/${gitops_branch}:refs/mirror-stage/heads/${gitops_branch}\"", " fetch_exit=$?", " set -e", " if [ \"$fetch_exit\" = \"0\" ]; then fetch_status=succeeded; break; fi", " fetch_status=failed", " if [ \"$fetch_attempt\" -lt \"$fetch_max_attempts\" ]; then", " echo \"git-mirror post-push fetch retry ${fetch_attempt}/${fetch_max_attempts} failed exit=${fetch_exit}; backoff=${fetch_retry_delay}s\" >&2", " sleep \"$fetch_retry_delay\"", " if [ \"$fetch_retry_delay\" -lt 16 ]; then fetch_retry_delay=$((fetch_retry_delay * 2)); fi", " fi", " done", " else", " push_status=failed", " fi", "fi", "github_gitops=$(git --git-dir=\"$repo\" rev-parse --verify \"refs/mirror-stage/heads/${gitops_branch}^{commit}\" 2>/dev/null || true)", "pending=false; if [ -n \"$local_gitops\" ] && { [ -z \"$github_gitops\" ] || [ \"$local_gitops\" != \"$github_gitops\" ]; }; then pending=true; fi", "status=succeeded", "partial_success=", "degraded_reason=", "exit_code=0", "if [ \"$push_status\" = \"failed\" ]; then", " status=failed", " degraded_reason=git-mirror-push-failed", " exit_code=$push_exit", "elif [ \"$push_status\" = \"succeeded\" ] && [ \"$fetch_status\" = \"failed\" ]; then", " status=partial-success", " partial_success=push-succeeded-fetch-failed", " degraded_reason=git-mirror-post-push-fetch-failed", " exit_code=44", "fi", "export repository gitops_branch started_at local_gitops github_gitops pending push_status push_exit fetch_status fetch_exit fetch_attempt fetch_max_attempts status partial_success degraded_reason", "node <<'NODE' | tee /cache/HWLAB.last-flush.json", "const payload = { event: 'git-mirror-flush', repo: process.env.repository, status: process.env.status || 'failed', partialSuccess: process.env.partial_success || null, degradedReason: process.env.degraded_reason || null, startedAt: process.env.started_at, flushedAt: new Date().toISOString(), gitopsBranch: process.env.gitops_branch, localGitops: process.env.local_gitops || null, githubGitops: process.env.github_gitops || null, pendingFlush: process.env.pending === 'true', stages: { push: process.env.push_status || null, pushExitCode: Number.parseInt(process.env.push_exit || '0', 10), postPushFetch: process.env.fetch_status || null, postPushFetchExitCode: Number.parseInt(process.env.fetch_exit || '0', 10), postPushFetchAttempts: Number.parseInt(process.env.fetch_attempt || '0', 10), postPushFetchMaxAttempts: Number.parseInt(process.env.fetch_max_attempts || '0', 10) } };", "console.log(JSON.stringify(payload));", "NODE", "cat /cache/HWLAB.last-flush.json", "if [ \"$exit_code\" != \"0\" ]; then exit \"$exit_code\"; fi", "", ].join("\n"); } function argoDesiredManifest(target: ControlPlaneTargetSpec): Record[] { return [argoProjectSkeleton(target), argoApplicationSkeleton(target)]; } function argoProjectSkeleton(target: ControlPlaneTargetSpec): Record { return { apiVersion: "argoproj.io/v1alpha1", kind: "AppProject", metadata: { name: target.argo.projectName, namespace: target.argo.namespace }, spec: { sourceRepos: [target.gitMirror.readUrl], destinations: [{ server: "https://kubernetes.default.svc", namespace: target.runtimeNamespace }], clusterResourceWhitelist: [{ group: "*", kind: "*" }], namespaceResourceWhitelist: [{ group: "*", kind: "*" }], }, }; } function argoApplicationSkeleton(target: ControlPlaneTargetSpec): Record { return { apiVersion: "argoproj.io/v1alpha1", kind: "Application", metadata: { name: target.argo.applicationName, namespace: target.argo.namespace }, spec: { project: target.argo.projectName, source: { repoURL: target.gitMirror.readUrl, targetRevision: target.gitops.branch, path: target.gitops.path }, destination: { server: "https://kubernetes.default.svc", namespace: target.runtimeNamespace }, syncPolicy: { automated: { prune: true, selfHeal: true } }, }, }; } function planSummary(node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec): Record { return { id: target.id, node: node.id, kubeRoute: node.kubeRoute, lane: target.lane, enabled: target.enabled, ciNamespace: target.ciNamespace, runtimeNamespace: target.runtimeNamespace, k3sNodeConfig: k3sNodeConfigPlan(node), registry: node.registry.endpoint, egressProxy: controlPlaneEgressProxySummary(node.egressProxy), sourceBranch: target.source.branch, gitopsBranch: target.gitops.branch, gitopsPath: target.gitops.path, gitMirrorNamespace: target.gitMirror.namespace, readUrl: target.gitMirror.readUrl, writeUrl: target.gitMirror.writeUrl, pipeline: target.tekton.pipelineName, pipelineRunPrefix: target.tekton.pipelineRunPrefix, serviceAccount: target.tekton.serviceAccountName, gitWorkspaceSecret: tektonGitWorkspaceSecretSummary(target), runtimeObserverRbac: target.tekton.runtimeObserverRbac, argoObserverRbac: target.tekton.argoObserverRbac, toolsImage: target.tekton.toolsImage, argoApplication: target.argo.applicationName, argoInstall: { enabled: target.argo.install.enabled, version: target.argo.install.version, manifestUrl: target.argo.install.manifestUrl, imageRewrites: target.argo.install.imageRewrites, }, }; } function expectedSummary(node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec): Record { return { sourceRepo: target.source.repository, branch: target.source.branch, gitopsBranch: target.gitops.branch, runtimePath: target.gitops.path, runtimeNamespace: target.runtimeNamespace, namespace: target.ciNamespace, k3sNodeConfig: k3sNodeConfigPlan(node), gitMirror: { namespace: target.gitMirror.namespace, readUrl: target.gitMirror.readUrl, writeUrl: target.gitMirror.writeUrl, cachePvc: target.gitMirror.cachePvcName, cachePvcStorage: target.gitMirror.cachePvcStorage, cacheHostPath: target.gitMirror.cacheHostPath, servicePort: target.gitMirror.servicePort, deploymentReplicas: target.gitMirror.deploymentReplicas, syncConfigMap: target.gitMirror.syncConfigMapName, egressProxy: target.gitMirror.egressProxy, effectiveEgressProxy: gitMirrorEffectiveEgressProxySummary(node, target), githubTransport: gitMirrorGithubTransportSummary(target.gitMirror.githubTransport), statusSummaryKeys: ["localSource", "githubSource", "localGitops", "githubGitops", "pendingFlush", "flushNeeded", "githubInSync"], }, pipeline: target.tekton.pipelineName, pipelineRunPrefix: target.tekton.pipelineRunPrefix, serviceAccount: target.tekton.serviceAccountName, gitWorkspaceSecret: tektonGitWorkspaceSecretSummary(target), runtimeObserverRbac: target.tekton.runtimeObserverRbac, argoObserverRbac: target.tekton.argoObserverRbac, toolsImage: target.tekton.toolsImage, argoNamespace: target.argo.namespace, argoApplication: target.argo.applicationName, argoInstall: { enabled: target.argo.install.enabled, sourceKind: target.argo.install.sourceKind, version: target.argo.install.version, manifestUrl: target.argo.install.manifestUrl, preloadImages: target.argo.install.preloadImages, imageRewrites: target.argo.install.imageRewrites, requiredCrds: target.argo.install.requiredCrds, expectedDeployments: target.argo.install.expectedDeployments, expectedStatefulSets: target.argo.install.expectedStatefulSets, }, registry: node.registry.endpoint, imagePolicy: { noPrivateInputImages: true, buildInput: { sourceKind: target.tekton.toolsImage.sourceKind, context: target.tekton.toolsImage.context, dockerfile: target.tekton.toolsImage.dockerfile ?? null, dockerfileInline: target.tekton.toolsImage.dockerfileInline ?? null, composeFile: target.tekton.toolsImage.composeFile ?? null, publicBaseImages: target.tekton.toolsImage.publicBaseImages }, outputImage: target.tekton.toolsImage.output, }, }; } function k3sNodeConfigPlan(node: ControlPlaneNodeSpec): Record { if (node.k3s === null) return { managed: false }; const dropIn = k3sDropInContent(node.k3s); return { managed: true, serviceName: node.k3s.serviceName, dropInPath: node.k3s.dropInPath, nodeStatusName: node.k3s.nodeStatusName, desiredMaxPods: node.k3s.kubelet.maxPods, dropInSha256: sha256Short(dropIn), execStartPreCount: node.k3s.execStartPre.length, serverArgCount: node.k3s.serverArgs.length, }; } function k3sDropInContent(spec: ControlPlaneK3sNodeSpec): string { return [ "# Managed by UniDesk. Source: config/hwlab-node-control-plane.yaml nodes..k3s", "[Service]", ...spec.execStartPre.map((command) => `ExecStartPre=${command.map(systemdExecArg).join(" ")}`), "ExecStart=", `ExecStart=${["/usr/local/bin/k3s", ...spec.serverArgs].map(systemdExecArg).join(" ")}`, "", ].join("\n"); } function controlPlaneEgressProxySummary(proxy: ControlPlaneEgressProxySpec | null): Record | null { if (proxy === null) return null; return { mode: proxy.mode, clientName: proxy.clientName, namespace: proxy.namespace, serviceName: proxy.serviceName, port: proxy.port, sourceConfigRef: proxy.sourceConfigRef, sourceType: proxy.sourceType, sourceRef: proxy.sourceRef, sourceKey: proxy.sourceKey, sourceFingerprint: proxy.sourceFingerprint, preferredOutbound: proxy.preferredOutbound, valuesPrinted: false, }; } function gitMirrorEffectiveEgressProxySummary(node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec): Record { const config = target.gitMirror.egressProxy; if (config === null || config.mode === "direct") { return { mode: "direct", required: false, transport: target.gitMirror.githubTransport.mode, valuesPrinted: false, }; } const proxy = node.egressProxy; return { mode: config.mode, required: config.required, transport: target.gitMirror.githubTransport.mode, ready: proxy !== null, nodeProxy: controlPlaneEgressProxySummary(proxy), valuesPrinted: false, }; } function gitMirrorGithubTransportSummary(transport: ControlPlaneGitMirrorGithubTransportSpec): Record { if (transport.mode === "ssh") { return { mode: "ssh", privateKeySecretKey: transport.privateKeySecretKey, privateKeySourceRef: transport.privateKeySourceRef, privateKeySourceKey: transport.privateKeySourceKey, privateKeySourceEncoding: transport.privateKeySourceEncoding, knownHostsSecretKey: transport.knownHostsSecretKey, knownHostsSourceRef: transport.knownHostsSourceRef, knownHostsSourceKey: transport.knownHostsSourceKey, knownHostsSourceEncoding: transport.knownHostsSourceEncoding, valuesPrinted: false, }; } return { mode: "https", username: transport.username, tokenSecretName: transport.tokenSecretName, tokenSecretKey: transport.tokenSecretKey, tokenSourceRef: transport.tokenSourceRef, tokenSourceKey: transport.tokenSourceKey, valuesPrinted: false, }; } function tektonGitWorkspaceSecretSummary(target: ControlPlaneTargetSpec): Record { const transport = target.gitMirror.githubTransport; const secret = target.tekton.gitWorkspaceSecret; return { name: secret.name, namespace: secret.namespace, sourceRefFrom: secret.sourceRefFrom, privateKeySecretKey: secret.privateKeySecretKey, privateKeySourceRef: transport.mode === "ssh" ? transport.privateKeySourceRef : null, privateKeySourceKey: transport.mode === "ssh" ? transport.privateKeySourceKey : null, knownHostsSecretKey: secret.knownHostsSecretKey, knownHostsSourceRef: transport.mode === "ssh" ? transport.knownHostsSourceRef : null, knownHostsSourceKey: transport.mode === "ssh" ? transport.knownHostsSourceKey : null, valuesPrinted: false, }; } function systemdExecArg(value: string): string { if (/^[A-Za-z0-9_@%+=:,./-]+$/u.test(value)) return value; return `"${value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("$", "\\$").replaceAll("`", "\\`")}"`; } function statusScript(nodeSpec: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec): string { const requiredCrds = shellJsonArray(target.argo.install.requiredCrds); const argoDeployments = shellJsonArray(target.argo.install.expectedDeployments); const argoStatefulSets = shellJsonArray(target.argo.install.expectedStatefulSets); const k3s = nodeSpec.k3s; const k3sDropIn = k3s === null ? "" : k3sDropInContent(k3s); const gitMirrorEgressProxyJson = JSON.stringify(gitMirrorEffectiveEgressProxySummary(nodeSpec, target)); return ` set +e node=${shQuote(target.node)} lane=${shQuote(target.lane)} ci_ns=${shQuote(target.ciNamespace)} runtime_ns=${shQuote(target.runtimeNamespace)} gitmirror_ns=${shQuote(target.gitMirror.namespace)} read_deploy=${shQuote(target.gitMirror.serviceReadName)} write_deploy=${shQuote(target.gitMirror.serviceWriteName)} read_svc=${shQuote(target.gitMirror.serviceReadName)} write_svc=${shQuote(target.gitMirror.serviceWriteName)} cache_pvc=${shQuote(target.gitMirror.cachePvcName)} cache_host_path=${shQuote(target.gitMirror.cacheHostPath ?? "")} github_transport_mode=${shQuote(target.gitMirror.githubTransport.mode)} github_ssh_secret=${shQuote(target.gitMirror.githubTransport.mode === "ssh" ? target.gitMirror.secretName : "")} github_ssh_private_key=${shQuote(target.gitMirror.githubTransport.mode === "ssh" ? target.gitMirror.githubTransport.privateKeySecretKey : "")} github_ssh_private_source_ref=${shQuote(target.gitMirror.githubTransport.mode === "ssh" ? target.gitMirror.githubTransport.privateKeySourceRef : "")} github_ssh_private_source_key=${shQuote(target.gitMirror.githubTransport.mode === "ssh" ? target.gitMirror.githubTransport.privateKeySourceKey : "")} github_ssh_known_hosts_key=${shQuote(target.gitMirror.githubTransport.mode === "ssh" ? target.gitMirror.githubTransport.knownHostsSecretKey ?? "" : "")} github_ssh_known_hosts_source_ref=${shQuote(target.gitMirror.githubTransport.mode === "ssh" ? target.gitMirror.githubTransport.knownHostsSourceRef ?? "" : "")} github_ssh_known_hosts_source_key=${shQuote(target.gitMirror.githubTransport.mode === "ssh" ? target.gitMirror.githubTransport.knownHostsSourceKey ?? "" : "")} github_token_secret=${shQuote(target.gitMirror.githubTransport.mode === "https" ? target.gitMirror.githubTransport.tokenSecretName : "")} github_token_key=${shQuote(target.gitMirror.githubTransport.mode === "https" ? target.gitMirror.githubTransport.tokenSecretKey : "")} github_token_source_ref=${shQuote(target.gitMirror.githubTransport.mode === "https" ? target.gitMirror.githubTransport.tokenSourceRef : "")} github_token_source_key=${shQuote(target.gitMirror.githubTransport.mode === "https" ? target.gitMirror.githubTransport.tokenSourceKey : "")} gitmirror_egress_proxy_json=${shQuote(gitMirrorEgressProxyJson)} pipeline=${shQuote(target.tekton.pipelineName)} service_account=${shQuote(target.tekton.serviceAccountName)} runtime_observer_role=${shQuote(target.tekton.runtimeObserverRbac.roleName)} runtime_observer_rolebinding=${shQuote(target.tekton.runtimeObserverRbac.roleBindingName)} ci_git_secret=${shQuote(target.tekton.gitWorkspaceSecret.name)} ci_git_private_key=${shQuote(target.tekton.gitWorkspaceSecret.privateKeySecretKey)} ci_git_known_hosts_key=${shQuote(target.tekton.gitWorkspaceSecret.knownHostsSecretKey)} ci_git_source_ref_from=${shQuote(target.tekton.gitWorkspaceSecret.sourceRefFrom)} ci_git_private_source_ref=${shQuote(target.gitMirror.githubTransport.mode === "ssh" ? target.gitMirror.githubTransport.privateKeySourceRef : "")} ci_git_private_source_key=${shQuote(target.gitMirror.githubTransport.mode === "ssh" ? target.gitMirror.githubTransport.privateKeySourceKey : "")} ci_git_known_hosts_source_ref=${shQuote(target.gitMirror.githubTransport.mode === "ssh" ? target.gitMirror.githubTransport.knownHostsSourceRef ?? "" : "")} ci_git_known_hosts_source_key=${shQuote(target.gitMirror.githubTransport.mode === "ssh" ? target.gitMirror.githubTransport.knownHostsSourceKey ?? "" : "")} argo_ns=${shQuote(target.argo.namespace)} argo_project=${shQuote(target.argo.projectName)} argo_app=${shQuote(target.argo.applicationName)} argo_observer_role=${shQuote(target.tekton.argoObserverRbac.roleName)} argo_observer_rolebinding=${shQuote(target.tekton.argoObserverRbac.roleBindingName)} registry=${shQuote(nodeSpec.registry.endpoint)} tools_image=${shQuote(target.tekton.toolsImage.output)} required_crds_json=${shQuote(requiredCrds)} argo_deployments_json=${shQuote(argoDeployments)} argo_statefulsets_json=${shQuote(argoStatefulSets)} k3s_managed=${k3s === null ? "false" : "true"} k3s_service=${shQuote(k3s?.serviceName ?? "")} k3s_dropin=${shQuote(k3s?.dropInPath ?? "")} k3s_node=${shQuote(k3s?.nodeStatusName ?? "")} k3s_desired_max_pods=${shQuote(String(k3s?.kubelet.maxPods ?? ""))} k3s_expected_sha=${shQuote(k3s === null ? "" : sha256Short(k3sDropIn))} exists_ns() { kubectl get ns "$1" >/dev/null 2>&1 && printf true || printf false; } exists_res() { kubectl -n "$1" get "$2" "$3" >/dev/null 2>&1 && printf true || printf false; } deploy_ready() { desired=$(kubectl -n "$1" get deploy "$2" -o 'jsonpath={.spec.replicas}' 2>/dev/null || true); ready=$(kubectl -n "$1" get deploy "$2" -o 'jsonpath={.status.readyReplicas}' 2>/dev/null || true); [ -n "$desired" ] && [ "$desired" -gt 0 ] 2>/dev/null && [ "\${ready:-0}" = "$desired" ] && printf true || printf false; } sts_ready() { desired=$(kubectl -n "$1" get statefulset "$2" -o 'jsonpath={.spec.replicas}' 2>/dev/null || true); ready=$(kubectl -n "$1" get statefulset "$2" -o 'jsonpath={.status.readyReplicas}' 2>/dev/null || true); [ -n "$desired" ] && [ "$desired" -gt 0 ] 2>/dev/null && [ "\${ready:-0}" = "$desired" ] && printf true || printf false; } endpoint_ready() { endpoints=$(kubectl -n "$1" get endpoints "$2" -o 'jsonpath={.subsets[*].addresses[*].ip}' 2>/dev/null || true); [ -n "$endpoints" ] && printf true || printf false; } can_i_runtime() { kubectl auth can-i "$1" "$2" --as="system:serviceaccount:$ci_ns:$service_account" -n "$runtime_ns" >/dev/null 2>&1 && printf true || printf false; } can_i_argo() { kubectl auth can-i "$1" "$2" --as="system:serviceaccount:$ci_ns:$service_account" -n "$argo_ns" >/dev/null 2>&1 && printf true || printf false; } runtime_observer_role_exists=$(exists_res "$runtime_ns" role "$runtime_observer_role") runtime_observer_rolebinding_exists=$(exists_res "$runtime_ns" rolebinding "$runtime_observer_rolebinding") runtime_observer_can_list_deployments=$(can_i_runtime list deployments.apps) runtime_observer_can_list_statefulsets=$(can_i_runtime list statefulsets.apps) runtime_observer_ready=false if [ "$runtime_observer_role_exists" = true ] && [ "$runtime_observer_rolebinding_exists" = true ] && [ "$runtime_observer_can_list_deployments" = true ] && [ "$runtime_observer_can_list_statefulsets" = true ]; then runtime_observer_ready=true; fi argo_observer_role_exists=$(exists_res "$argo_ns" role "$argo_observer_role") argo_observer_rolebinding_exists=$(exists_res "$argo_ns" rolebinding "$argo_observer_rolebinding") argo_observer_can_get_application=$(can_i_argo get applications.argoproj.io) argo_observer_ready=false if [ "$argo_observer_role_exists" = true ] && [ "$argo_observer_rolebinding_exists" = true ] && [ "$argo_observer_can_get_application" = true ]; then argo_observer_ready=true; fi github_transport_json=$(python3 - "$github_transport_mode" "$gitmirror_ns" "$github_ssh_secret" "$github_ssh_private_key" "$github_ssh_private_source_ref" "$github_ssh_private_source_key" "$github_ssh_known_hosts_key" "$github_ssh_known_hosts_source_ref" "$github_ssh_known_hosts_source_key" "$github_token_secret" "$github_token_key" "$github_token_source_ref" "$github_token_source_key" <<'PY' import hashlib, json, subprocess, sys mode, namespace, ssh_secret, ssh_private_key, ssh_private_source_ref, ssh_private_source_key, ssh_known_hosts_key, ssh_known_hosts_source_ref, ssh_known_hosts_source_key, token_secret, token_key, token_source_ref, token_source_key = sys.argv[1:14] def read_secret(name): proc = subprocess.run(["kubectl", "-n", namespace, "get", "secret", name, "-o", "json"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) if proc.returncode != 0: return False, {}, {} try: obj = json.loads(proc.stdout) except Exception: obj = {} return True, obj.get("data") or {}, obj.get("metadata", {}).get("annotations") or {} def fingerprint(value): return "sha256:" + hashlib.sha256(value.encode()).hexdigest()[:16] if value else None if mode == "ssh": exists, data, annotations = read_secret(ssh_secret) private_encoded = data.get(ssh_private_key) if isinstance(data, dict) else None known_hosts_encoded = data.get(ssh_known_hosts_key) if ssh_known_hosts_key and isinstance(data, dict) else None private_present = isinstance(private_encoded, str) and len(private_encoded) > 0 known_hosts_expected = bool(ssh_known_hosts_key) known_hosts_present = isinstance(known_hosts_encoded, str) and len(known_hosts_encoded) > 0 print(json.dumps({ "mode": mode, "required": True, "ready": exists and private_present and (not known_hosts_expected or known_hosts_present), "secretName": ssh_secret, "privateKeySecretKey": ssh_private_key, "privateKeySourceRef": ssh_private_source_ref, "privateKeySourceKey": ssh_private_source_key, "privateKeySecretExists": exists, "privateKeyPresent": private_present, "privateKeyBytes": len(private_encoded) if private_present else 0, "privateKeyFingerprint": annotations.get("unidesk.ai/private-key-fingerprint") or fingerprint(private_encoded), "knownHostsSecretKey": ssh_known_hosts_key or None, "knownHostsSourceRef": ssh_known_hosts_source_ref or None, "knownHostsSourceKey": ssh_known_hosts_source_key or None, "knownHostsPresent": (known_hosts_present if known_hosts_expected else None), "knownHostsBytes": (len(known_hosts_encoded) if known_hosts_present else 0) if known_hosts_expected else None, "knownHostsFingerprint": annotations.get("unidesk.ai/known-hosts-fingerprint") or fingerprint(known_hosts_encoded), "valuesPrinted": False, })) raise SystemExit(0) if mode != "https": print(json.dumps({"mode": mode, "required": True, "ready": False, "valuesPrinted": False})) raise SystemExit(0) exists, data, _ = read_secret(token_secret) encoded = data.get(token_key) if isinstance(data, dict) else None present = isinstance(encoded, str) and len(encoded) > 0 print(json.dumps({"mode": mode, "required": True, "ready": exists and present, "tokenSecretName": token_secret, "tokenSecretKey": token_key, "tokenSourceRef": token_source_ref, "tokenSourceKey": token_source_key, "tokenSecretExists": exists, "tokenKeyPresent": present, "tokenKeyBytes": len(encoded) if present else 0, "tokenFingerprint": fingerprint(encoded), "valuesPrinted": False})) PY ) ci_git_workspace_json=$(python3 - "$ci_ns" "$ci_git_secret" "$ci_git_private_key" "$ci_git_known_hosts_key" "$ci_git_source_ref_from" "$ci_git_private_source_ref" "$ci_git_private_source_key" "$ci_git_known_hosts_source_ref" "$ci_git_known_hosts_source_key" <<'PY' import hashlib, json, subprocess, sys namespace, secret, private_key, known_hosts_key, source_ref_from, private_source_ref, private_source_key, known_hosts_source_ref, known_hosts_source_key = sys.argv[1:10] proc = subprocess.run(["kubectl", "-n", namespace, "get", "secret", secret, "-o", "json"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) exists = proc.returncode == 0 obj = {} if exists: try: obj = json.loads(proc.stdout) except Exception: obj = {} data = obj.get("data") or {} annotations = obj.get("metadata", {}).get("annotations") or {} def fingerprint(value): return "sha256:" + hashlib.sha256(value.encode()).hexdigest()[:16] if value else None private_encoded = data.get(private_key) if isinstance(data, dict) else None known_hosts_encoded = data.get(known_hosts_key) if isinstance(data, dict) else None private_present = isinstance(private_encoded, str) and len(private_encoded) > 0 known_hosts_present = isinstance(known_hosts_encoded, str) and len(known_hosts_encoded) > 0 print(json.dumps({ "required": True, "ready": exists and private_present and known_hosts_present, "namespace": namespace, "secretName": secret, "sourceRefFrom": source_ref_from, "privateKeySecretKey": private_key, "privateKeySourceRef": private_source_ref, "privateKeySourceKey": private_source_key, "privateKeySecretExists": exists, "privateKeyPresent": private_present, "privateKeyBytes": len(private_encoded) if private_present else 0, "privateKeyFingerprint": annotations.get("unidesk.ai/private-key-fingerprint") or fingerprint(private_encoded), "knownHostsSecretKey": known_hosts_key, "knownHostsSourceRef": known_hosts_source_ref, "knownHostsSourceKey": known_hosts_source_key, "knownHostsPresent": known_hosts_present, "knownHostsBytes": len(known_hosts_encoded) if known_hosts_present else 0, "knownHostsFingerprint": annotations.get("unidesk.ai/known-hosts-fingerprint") or fingerprint(known_hosts_encoded), "valuesPrinted": False, })) PY ) registry_ready=false if command -v curl >/dev/null 2>&1; then curl -fsS --max-time 3 "http://$registry/v2/" >/tmp/hwlab-registry.out 2>/tmp/hwlab-registry.err && registry_ready=true; fi tools_repo_tag=\${tools_image#\${registry}/} tools_repo=\${tools_repo_tag%:*} tools_tag=\${tools_repo_tag##*:} tools_image_ready=false if [ "$tools_repo" != "$tools_repo_tag" ] && command -v curl >/dev/null 2>&1; then curl -fsS --max-time 5 "http://$registry/v2/$tools_repo/manifests/$tools_tag" >/tmp/hwlab-tools-image.out 2>/tmp/hwlab-tools-image.err && tools_image_ready=true; fi cache_host_path_ready=false if [ -n "$cache_host_path" ] && kubectl -n "$gitmirror_ns" exec deploy/"$read_deploy" -- sh -lc 'test -d /cache' >/dev/null 2>&1; then cache_host_path_ready=true; fi k3s_fragment=$(python3 - "$k3s_managed" "$k3s_service" "$k3s_dropin" "$k3s_node" "$k3s_desired_max_pods" "$k3s_expected_sha" <<'PY' import hashlib, json, re, subprocess, sys managed = sys.argv[1] == "true" service, dropin, node_name, desired_raw, expected_sha = sys.argv[2:7] def run(args): return subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) def to_int(value): try: return int(value) except Exception: return None if not managed: print(json.dumps({"managed": False, "ready": True})) raise SystemExit(0) desired = to_int(desired_raw) node_json = run(["kubectl", "get", "node", node_name, "-o", "json"]) capacity = None allocatable = None node_ready = False if node_json.returncode == 0: data = json.loads(node_json.stdout) capacity = to_int(data.get("status", {}).get("capacity", {}).get("pods")) allocatable = to_int(data.get("status", {}).get("allocatable", {}).get("pods")) for condition in data.get("status", {}).get("conditions", []): if condition.get("type") == "Ready": node_ready = condition.get("status") == "True" unit = run(["systemctl", "cat", service]) unit_text = unit.stdout if unit.returncode == 0 else "" dropin_read = run(["cat", dropin]) dropin_exists = dropin_read.returncode == 0 dropin_text = dropin_read.stdout if dropin_exists else "" dropin_sha = "sha256:" + hashlib.sha256(dropin_text.encode()).hexdigest() if dropin_exists else None matches = re.findall(r"max-pods=([0-9]+)", unit_text + "\\n" + dropin_text) configured = to_int(matches[-1]) if matches else None dropin_matches = dropin_sha == expected_sha ready = dropin_matches and capacity == desired and allocatable == desired source = "managed-dropin" if dropin_matches else ("systemd-or-config" if configured is not None else "kubelet-default") print(json.dumps({ "managed": True, "ready": ready, "serviceName": service, "dropInPath": dropin, "dropInExists": dropin_exists, "dropInSha256": dropin_sha, "expectedDropInSha256": expected_sha, "dropInMatches": dropin_matches, "configuredMaxPods": configured, "desiredMaxPods": desired, "liveNodeName": node_name, "liveCapacityPods": capacity, "liveAllocatablePods": allocatable, "nodeReady": node_ready, "restartRequired": not ready, "source": source, "unitReadable": unit.returncode == 0, })) PY ) python3 - "$required_crds_json" "$argo_deployments_json" "$argo_statefulsets_json" <<'PY' >/tmp/hwlab-node-status-fragments.json import json, subprocess, sys required_crds=json.loads(sys.argv[1]) deployments=json.loads(sys.argv[2]) statefulsets=json.loads(sys.argv[3]) ns="${target.argo.namespace}" def run(args): return subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) def exists(args): return run(args).returncode == 0 def ready(kind, name): data = run(["kubectl", "-n", ns, "get", kind, name, "-o", "json"]) if data.returncode != 0: return {"name": name, "exists": False, "ready": False, "desired": None, "readyReplicas": None} obj=json.loads(data.stdout) desired=int(obj.get("spec", {}).get("replicas") or 0) ready_replicas=int(obj.get("status", {}).get("readyReplicas") or 0) return {"name": name, "exists": True, "ready": desired > 0 and ready_replicas == desired, "desired": desired, "readyReplicas": ready_replicas} crds=[{"name": name, "exists": exists(["kubectl", "get", "crd", name])} for name in required_crds] deploy=[ready("deployment", name) for name in deployments] sts=[ready("statefulset", name) for name in statefulsets] print(json.dumps({"crds": crds, "deployments": deploy, "statefulSets": sts, "crdsReady": all(item["exists"] for item in crds), "deploymentsReady": all(item["ready"] for item in deploy) if deploy else True, "statefulSetsReady": all(item["ready"] for item in sts) if sts else True})) PY argo_fragment=$(cat /tmp/hwlab-node-status-fragments.json 2>/dev/null || printf '{}') cat </dev/null 2>&1 && printf true || printf false),"controllerReady":$(deploy_ready tekton-pipelines tekton-pipelines-controller),"webhookReady":$(deploy_ready tekton-pipelines tekton-pipelines-webhook)},"ciNamespace":{"name":"$ci_ns","exists":$(exists_ns "$ci_ns"),"serviceAccountExists":$(exists_res "$ci_ns" serviceaccount "$service_account"),"pipelineExists":$(exists_res "$ci_ns" pipeline "$pipeline"),"gitWorkspaceSecret":$ci_git_workspace_json},"gitMirror":{"namespace":"$gitmirror_ns","namespaceExists":$(exists_ns "$gitmirror_ns"),"readDeploymentReady":$(deploy_ready "$gitmirror_ns" "$read_deploy"),"writeDeploymentReady":$(deploy_ready "$gitmirror_ns" "$write_deploy"),"readServiceExists":$(exists_res "$gitmirror_ns" service "$read_svc"),"writeServiceExists":$(exists_res "$gitmirror_ns" service "$write_svc"),"readEndpointsReady":$(endpoint_ready "$gitmirror_ns" "$read_svc"),"writeEndpointsReady":$(endpoint_ready "$gitmirror_ns" "$write_svc"),"cachePvcExists":$(exists_res "$gitmirror_ns" pvc "$cache_pvc"),"cacheHostPath":"$cache_host_path","cacheHostPathReady":$cache_host_path_ready,"egressProxy":$gitmirror_egress_proxy_json,"githubTransport":$github_transport_json,"summary":{"localSource":null,"githubSource":null,"localGitops":null,"githubGitops":null,"pendingFlush":null,"flushNeeded":null,"githubInSync":null}},"argo":{"namespace":"$argo_ns","namespaceExists":$(exists_ns "$argo_ns"),"installed":$(kubectl get crd applications.argoproj.io appprojects.argoproj.io >/dev/null 2>&1 && printf true || printf false),"projectExists":$(kubectl -n "$argo_ns" get appproject "$argo_project" >/dev/null 2>&1 && printf true || printf false),"applicationExists":$(kubectl -n "$argo_ns" get application "$argo_app" >/dev/null 2>&1 && printf true || printf false),"argoObserverRbac":{"roleName":"$argo_observer_role","roleExists":$argo_observer_role_exists,"roleBindingName":"$argo_observer_rolebinding","roleBindingExists":$argo_observer_rolebinding_exists,"serviceAccountNamespace":"$ci_ns","serviceAccountName":"$service_account","canGetApplication":$argo_observer_can_get_application,"ready":$argo_observer_ready},"install":$argo_fragment},"registry":{"endpoint":"$registry","ready":$registry_ready,"toolsImage":"$tools_image","toolsImageReady":$tools_image_ready},"runtimeNamespace":{"name":"$runtime_ns","exists":$(exists_ns "$runtime_ns"),"runtimeObserverRbac":{"roleName":"$runtime_observer_role","roleExists":$runtime_observer_role_exists,"roleBindingName":"$runtime_observer_rolebinding","roleBindingExists":$runtime_observer_rolebinding_exists,"serviceAccountNamespace":"$ci_ns","serviceAccountName":"$service_account","canListDeployments":$runtime_observer_can_list_deployments,"canListStatefulSets":$runtime_observer_can_list_statefulsets,"ready":$runtime_observer_ready}}}} JSON `; } function applyScript(yaml: string, node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec): string { const encoded = Buffer.from(yaml, "utf8").toString("base64"); return ` set +e manifest=$(mktemp /tmp/hwlab-node-infra.XXXXXX.yaml) printf %s ${shQuote(encoded)} | base64 -d >"$manifest" field_manager=${shQuote(controlPlaneFieldManager(target))} kubectl apply --server-side --force-conflicts --field-manager="$field_manager" -f "$manifest" >/tmp/hwlab-node-infra-apply.out 2>/tmp/hwlab-node-infra-apply.err kubectl_rc=$? ${k3sApplyScriptFragment(node.k3s, target)} python3 - "$kubectl_rc" "$k3s_report_file" <<'PY' import json, pathlib, sys k3s_report = {} try: k3s_report = json.loads(pathlib.Path(sys.argv[2]).read_text(errors='replace')) except Exception as exc: k3s_report = {"managed": None, "ok": False, "parseError": str(exc)} out=pathlib.Path('/tmp/hwlab-node-infra-apply.out').read_text(errors='replace') if pathlib.Path('/tmp/hwlab-node-infra-apply.out').exists() else '' err=pathlib.Path('/tmp/hwlab-node-infra-apply.err').read_text(errors='replace') if pathlib.Path('/tmp/hwlab-node-infra-apply.err').exists() else '' print(json.dumps({'k3sNodeConfig': k3s_report, 'kubernetesApply': {'applyExitCode': int(sys.argv[1]), 'stdoutPreview': out[-2000:], 'stderrPreview': err[-2000:], 'runtimeRolloutTriggered': False, 'pk01Touched': False}}, ensure_ascii=False)) PY rm -f "$manifest" if [ "$kubectl_rc" != 0 ]; then exit "$kubectl_rc"; fi exit "$k3s_rc" `; } function controlPlaneFieldManager(target: ControlPlaneTargetSpec): string { return `unidesk-hwlab-${target.node.toLowerCase()}-${target.lane}-control-plane`; } function k3sApplyScriptFragment(spec: ControlPlaneK3sNodeSpec | null, target: ControlPlaneTargetSpec): string { if (spec === null) { return ` k3s_report_file=$(mktemp /tmp/hwlab-node-k3s.XXXXXX.json) printf '{"managed":false,"ok":true,"mutation":false}\\n' >"$k3s_report_file" k3s_rc=0 `; } const content = k3sDropInContent(spec); const encoded = Buffer.from(content, "utf8").toString("base64"); return ` k3s_report_file=$(mktemp /tmp/hwlab-node-k3s.XXXXXX.json) k3s_service=${shQuote(spec.serviceName)} k3s_dropin=${shQuote(spec.dropInPath)} k3s_node=${shQuote(spec.nodeStatusName)} k3s_namespace=${shQuote(target.ciNamespace)} k3s_image=${shQuote(target.tekton.toolsImage.output)} k3s_desired_max_pods=${shQuote(String(spec.kubelet.maxPods))} k3s_expected_sha=${shQuote(sha256Short(content))} k3s_before_capacity=$(kubectl get node "$k3s_node" -o 'jsonpath={.status.capacity.pods}' 2>/dev/null || true) k3s_before_allocatable=$(kubectl get node "$k3s_node" -o 'jsonpath={.status.allocatable.pods}' 2>/dev/null || true) capacity_restart=false if [ "$k3s_before_capacity" != "$k3s_desired_max_pods" ] || [ "$k3s_before_allocatable" != "$k3s_desired_max_pods" ]; then capacity_restart=true; fi k3s_current_dropin_sha= if [ -f "$k3s_dropin" ]; then k3s_current_dropin_sha=$(sha256sum "$k3s_dropin" | awk '{print "sha256:"$1}'); fi if [ "$k3s_current_dropin_sha" = "$k3s_expected_sha" ] && [ "$capacity_restart" = false ]; then python3 - "$k3s_current_dropin_sha" "$k3s_expected_sha" "$k3s_service" "$k3s_dropin" "$k3s_node" "$k3s_desired_max_pods" "$k3s_before_capacity" "$k3s_before_allocatable" <<'PY' >"$k3s_report_file" import json, sys dropin_sha, expected_sha, service, dropin, node_name, desired, before_capacity, before_allocatable = sys.argv[1:9] print(json.dumps({ "managed": True, "ok": True, "mutation": False, "applyMode": "noop", "completionPending": False, "serviceName": service, "dropInPath": dropin, "dropInSha256": dropin_sha, "expectedDropInSha256": expected_sha, "dropInMatches": dropin_sha == expected_sha, "nodeName": node_name, "desiredMaxPods": int(desired), "beforeCapacityPods": int(before_capacity) if before_capacity.isdigit() else None, "beforeAllocatablePods": int(before_allocatable) if before_allocatable.isdigit() else None, }, ensure_ascii=False)) PY k3s_rc=0 else k3s_job="hwlab-node-k3s-config-$(date +%s)" k3s_job_manifest=$(mktemp /tmp/hwlab-node-k3s-job.XXXXXX.json) k3s_host_script=$(mktemp /tmp/hwlab-node-k3s-host.XXXXXX.sh) k3s_job_apply_stdout=/tmp/hwlab-node-k3s-job-apply.out k3s_job_apply_stderr=/tmp/hwlab-node-k3s-job-apply.err k3s_docker_stdout=/tmp/hwlab-node-k3s-docker.out k3s_docker_stderr=/tmp/hwlab-node-k3s-docker.err k3s_host_report="/tmp/$k3s_job-report.json" rm -f "$k3s_host_report" python3 - "$k3s_job_manifest" "$k3s_host_script" "$k3s_job" "$k3s_namespace" "$k3s_image" "$k3s_dropin" ${shQuote(encoded)} "$k3s_service" "$k3s_desired_max_pods" "$k3s_expected_sha" "$capacity_restart" "$k3s_host_report" <<'PY' import json, os, shlex, sys manifest_path, host_script_path, job, namespace, image, dropin, encoded, service, desired, expected_sha, capacity_restart, report_path = sys.argv[1:13] script = f"""#!/bin/sh set -eu expected=/tmp/unidesk-k3s-dropin.conf printf %s {shlex.quote(encoded)} | base64 -d > "$expected" host_dropin=/host{shlex.quote(dropin)} host_report=/host{shlex.quote(report_path)} mkdir -p "$(dirname "$host_dropin")" before_sha= if [ -f "$host_dropin" ]; then before_sha=$(sha256sum "$host_dropin" | awk '{{print "sha256:"$1}}'); fi changed=false if ! cmp -s "$expected" "$host_dropin" 2>/dev/null; then cp "$expected" "$host_dropin" chown 0:0 "$host_dropin" 2>/dev/null || true chmod 0644 "$host_dropin" changed=true fi nsenter_path=$(command -v nsenter || true) host_systemctl() {{ if command -v chroot >/dev/null 2>&1 && [ -x /host/usr/bin/systemctl ]; then chroot /host /usr/bin/systemctl "$@" return $? fi if [ -n "$nsenter_path" ]; then "$nsenter_path" -t 1 -m -u -i -n -p -- /usr/bin/systemctl "$@" return $? fi return 127 }} daemon_reload_rc=0 restart_rc=0 restarted=false if command -v chroot >/dev/null 2>&1 || [ -n "$nsenter_path" ]; then host_systemctl daemon-reload || daemon_reload_rc=$? if [ "$changed" = true ] || [ {shlex.quote(capacity_restart)} = true ]; then restarted=true host_systemctl restart {shlex.quote(service)} || restart_rc=$? fi else daemon_reload_rc=127 restart_rc=127 fi after_sha= if [ -f "$host_dropin" ]; then after_sha=$(sha256sum "$host_dropin" | awk '{{print "sha256:"$1}}'); fi service_active=unknown if command -v chroot >/dev/null 2>&1 || [ -n "$nsenter_path" ]; then service_active=$(host_systemctl is-active {shlex.quote(service)} 2>/dev/null || true); fi python3 - "$changed" "$restarted" "$daemon_reload_rc" "$restart_rc" "$before_sha" "$after_sha" "$service_active" "$nsenter_path" <<'REPORT' >"$host_report" import json, sys changed, restarted = sys.argv[1] == "true", sys.argv[2] == "true" daemon_reload_rc, restart_rc = int(sys.argv[3] or "0"), int(sys.argv[4] or "0") print(json.dumps({{ "jobChanged": changed, "jobRestarted": restarted, "daemonReloadExitCode": daemon_reload_rc, "restartExitCode": restart_rc, "beforeDropInSha256": sys.argv[5] or None, "dropInSha256": sys.argv[6] or None, "expectedDropInSha256": {json.dumps(expected_sha)}, "dropInMatches": sys.argv[6] == {json.dumps(expected_sha)}, "serviceActiveText": sys.argv[7] or None, "nsenterPresent": bool(sys.argv[8]), }})) REPORT chmod 0644 "$host_report" 2>/dev/null || true cat "$host_report" """ with open(host_script_path, "w", encoding="utf-8") as handle: handle.write(script) os.chmod(host_script_path, 0o755) manifest = { "apiVersion": "batch/v1", "kind": "Job", "metadata": {"name": job, "namespace": namespace, "labels": {"app.kubernetes.io/part-of": "hwlab-node-control-plane", "unidesk.ai/operation": "k3s-node-config"}}, "spec": { "backoffLimit": 0, "ttlSecondsAfterFinished": 300, "template": { "metadata": {"labels": {"app.kubernetes.io/part-of": "hwlab-node-control-plane", "unidesk.ai/operation": "k3s-node-config"}}, "spec": { "restartPolicy": "Never", "hostPID": True, "hostNetwork": True, "containers": [{ "name": "apply-k3s-node-config", "image": image, "imagePullPolicy": "IfNotPresent", "securityContext": {"privileged": True}, "command": ["/bin/sh", "-lc", script], "volumeMounts": [{"name": "host-root", "mountPath": "/host"}], }], "volumes": [{"name": "host-root", "hostPath": {"path": "/", "type": "Directory"}}], }, }, }, } with open(manifest_path, "w", encoding="utf-8") as handle: json.dump(manifest, handle) PY k3s_render_rc=$? if [ "$k3s_render_rc" != 0 ]; then python3 - "$k3s_render_rc" "$k3s_expected_sha" "$k3s_service" "$k3s_dropin" "$k3s_node" "$k3s_desired_max_pods" <<'PY' >"$k3s_report_file" import json, sys render_rc = int(sys.argv[1] or "1") expected_sha, service, dropin, node_name, desired = sys.argv[2:7] print(json.dumps({ "managed": True, "ok": False, "mutation": False, "renderExitCode": render_rc, "serviceName": service, "dropInPath": dropin, "expectedDropInSha256": expected_sha, "nodeName": node_name, "desiredMaxPods": int(desired), }, ensure_ascii=False)) PY k3s_rc=$k3s_render_rc else kubectl apply -f "$k3s_job_manifest" >"$k3s_job_apply_stdout" 2>"$k3s_job_apply_stderr" k3s_job_apply_rc=$? k3s_apply_mode=kubernetes-job k3s_docker_rc=127 if [ "$k3s_job_apply_rc" != 0 ] && command -v docker >/dev/null 2>&1; then k3s_apply_mode=docker-host-fallback docker run --rm --privileged --pid=host --network=host -v /:/host --entrypoint /bin/sh "$k3s_image" "/host$k3s_host_script" >"$k3s_docker_stdout" 2>"$k3s_docker_stderr" k3s_docker_rc=$? fi k3s_submit_rc=$k3s_job_apply_rc if [ "$k3s_job_apply_rc" != 0 ] && [ "$k3s_docker_rc" = 0 ]; then k3s_submit_rc=0; fi python3 - "$k3s_submit_rc" "$k3s_job_apply_rc" "$k3s_docker_rc" "$k3s_apply_mode" "$k3s_before_capacity" "$k3s_before_allocatable" "$k3s_expected_sha" "$k3s_service" "$k3s_dropin" "$k3s_node" "$k3s_desired_max_pods" "$k3s_job" "$k3s_namespace" "$k3s_host_report" "$k3s_job_apply_stdout" "$k3s_job_apply_stderr" "$k3s_docker_stdout" "$k3s_docker_stderr" <<'PY' >"$k3s_report_file" import json, pathlib, sys submit_rc, job_apply_rc, docker_rc = [int(value or "0") for value in sys.argv[1:4]] apply_mode = sys.argv[4] before_capacity, before_allocatable = sys.argv[5:7] expected_sha, service, dropin, node_name, desired, job_name, namespace, host_report = sys.argv[7:15] def read(path): return pathlib.Path(path).read_text(errors='replace') if pathlib.Path(path).exists() else '' try: host_report_data = json.loads(read(host_report) or "{}") except Exception: host_report_data = {} apply_ok = submit_rc == 0 print(json.dumps({ "managed": True, "ok": apply_ok, "mutation": apply_ok, "completionPending": apply_ok and apply_mode == "kubernetes-job", "applyMode": apply_mode, "jobName": job_name, "namespace": namespace, "jobApplyExitCode": job_apply_rc, "dockerFallbackExitCode": docker_rc, "serviceName": service, "dropInPath": dropin, "dropInSha256": host_report_data.get("dropInSha256"), "expectedDropInSha256": expected_sha, "dropInMatches": host_report_data.get("dropInSha256") == expected_sha if host_report_data else None, "daemonReloadExitCode": host_report_data.get("daemonReloadExitCode"), "restartExitCode": host_report_data.get("restartExitCode"), "serviceActive": host_report_data.get("serviceActiveText") == "active" if host_report_data else None, "nodeName": node_name, "desiredMaxPods": int(desired), "beforeCapacityPods": int(before_capacity) if before_capacity.isdigit() else None, "beforeAllocatablePods": int(before_allocatable) if before_allocatable.isdigit() else None, "hostReportPath": host_report, "statusCommand": f"bun scripts/cli.ts hwlab nodes control-plane infra status --node {node_name.upper()} --lane ${target.lane}", "jobCompletionCommand": f"kubectl -n {namespace} wait --for=condition=complete job/{job_name} --timeout=120s", "jobLogsCommand": f"kubectl -n {namespace} logs job/{job_name} --tail=120", "jobApplyStdoutPreview": read(sys.argv[15])[-1000:], "jobApplyStderrPreview": read(sys.argv[16])[-1000:], "dockerStdoutPreview": read(sys.argv[17])[-1000:], "dockerStderrPreview": read(sys.argv[18])[-1000:], }, ensure_ascii=False)) PY k3s_rc=$k3s_submit_rc fi rm -f "$k3s_job_manifest" "$k3s_host_script" fi `; } function toolsImageStatus(node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, timeoutSeconds: number): { registryReady: boolean; toolsImageReady: boolean; result: Record; } { const result = runTransK3s(node.kubeRoute, registryStatusScript(node.registry.endpoint, target.tekton.toolsImage.output), timeoutSeconds); const parsed = parseRemoteJson(result.stdout); const status = typeof parsed === "object" && parsed !== null ? parsed as Record : {}; return { registryReady: boolField(status, "registryReady"), toolsImageReady: boolField(status, "toolsImageReady"), result: { status, command: compactCommandResult(result), }, }; } function applyNext(node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, imageStatus: { registryReady: boolean; toolsImageReady: boolean }): Record { if (!imageStatus.registryReady) { return { status: `bun scripts/cli.ts hwlab nodes control-plane infra status --node ${node.id} --lane ${target.lane}`, blockedBy: "node-local-registry-not-ready", }; } if (!imageStatus.toolsImageReady) { return { status: `bun scripts/cli.ts hwlab nodes control-plane infra status --node ${node.id} --lane ${target.lane}`, blockedBy: "tools-image-missing", applyBootstrap: `bun scripts/cli.ts hwlab nodes control-plane infra apply --node ${node.id} --lane ${target.lane} --confirm`, buildToolsImage: "准备受控 D601 tools-image build/publish 入口后提升 control-plane readiness。", }; } return { apply: `bun scripts/cli.ts hwlab nodes control-plane infra apply --node ${node.id} --lane ${target.lane} --confirm` }; } function statusNext( node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, registry: Record, gitMirror: Record, argo: Record, ciNamespace: Record, runtimeNamespace: Record, k3sNodeConfig: Record, ): Record { const bootstrapMissing = !boolField(ciNamespace, "exists") || !boolField(record(ciNamespace.gitWorkspaceSecret), "ready") || !boolField(gitMirror, "namespaceExists") || !boolField(gitMirror, "readServiceExists") || !boolField(gitMirror, "writeServiceExists") || (!boolField(gitMirror, "cachePvcExists") && !boolField(gitMirror, "cacheHostPathReady")); const blockers: string[] = []; if (node.k3s !== null && !boolField(k3sNodeConfig, "ready")) blockers.push("k3s-node-config-not-applied"); if (!boolField(registry, "ready")) blockers.push("node-local-registry-not-ready"); if (!boolField(registry, "toolsImageReady")) blockers.push("tools-image-missing"); if (!boolField(record(ciNamespace.gitWorkspaceSecret), "ready")) blockers.push("ci-git-workspace-secret-not-ready"); if (!boolField(runtimeNamespace, "exists")) blockers.push("runtime-namespace-missing"); if (!boolField(record(runtimeNamespace.runtimeObserverRbac), "ready")) blockers.push("runtime-observer-rbac-not-ready"); if (bootstrapMissing) blockers.push("control-plane-bootstrap-missing"); const gitMirrorGithubTransport = record(gitMirror.githubTransport); if (gitMirrorGithubTransport.required === true && !boolField(gitMirrorGithubTransport, "ready")) blockers.push("git-mirror-github-token-secret-not-ready"); const argoInstall = record(argo.install); if (!boolField(argo, "installed")) blockers.push("argocd-not-installed"); else if (!boolField(argoInstall, "crdsReady")) blockers.push("argocd-crds-not-ready"); else if (!boolField(argoInstall, "deploymentsReady")) blockers.push("argocd-deployments-not-ready"); else if (!boolField(argoInstall, "statefulSetsReady")) blockers.push("argocd-statefulsets-not-ready"); else if (!boolField(argo, "projectExists")) blockers.push("argocd-project-missing"); else if (!boolField(argo, "applicationExists")) blockers.push("argocd-application-missing"); if (!boolField(record(argo.argoObserverRbac), "ready")) blockers.push("argocd-observer-rbac-not-ready"); const next: Record = { status: `bun scripts/cli.ts hwlab nodes control-plane infra status --node ${node.id} --lane ${target.lane}`, dryRun: `bun scripts/cli.ts hwlab nodes control-plane infra apply --node ${node.id} --lane ${target.lane} --dry-run`, }; if (blockers.length > 0) { next.blockedBy = blockers[0]; next.blockers = blockers; } if (!boolField(registry, "toolsImageReady")) { next.buildToolsImage = "准备受控 D601 tools-image build/publish 入口后提升 control-plane readiness。"; } if (!boolField(argo, "installed")) { next.installArgo = "准备受控 D601 Argo CD 安装入口后再进入 runtime rollout。"; } if (node.k3s !== null && !boolField(k3sNodeConfig, "ready")) { next.applyK3sNodeConfig = `bun scripts/cli.ts hwlab nodes control-plane infra apply --node ${node.id} --lane ${target.lane} --confirm`; } if (bootstrapMissing) next.applyBootstrap = `bun scripts/cli.ts hwlab nodes control-plane infra apply --node ${node.id} --lane ${target.lane} --confirm`; else next.reapplyBootstrap = `bun scripts/cli.ts hwlab nodes control-plane infra apply --node ${node.id} --lane ${target.lane} --confirm`; return next; } function registryStatusScript(registryEndpoint: string, toolsImage: string): string { return ` set +e registry=${shQuote(registryEndpoint)} tools_image=${shQuote(toolsImage)} registry_ready=false if command -v curl >/dev/null 2>&1; then curl -fsS --max-time 3 "http://$registry/v2/" >/tmp/hwlab-registry.out 2>/tmp/hwlab-registry.err && registry_ready=true; fi tools_repo_tag=\${tools_image#\${registry}/} tools_repo=\${tools_repo_tag%:*} tools_tag=\${tools_repo_tag##*:} tools_image_ready=false if [ "$tools_repo" != "$tools_repo_tag" ] && command -v curl >/dev/null 2>&1; then curl -fsS --max-time 5 "http://$registry/v2/$tools_repo/manifests/$tools_tag" >/tmp/hwlab-tools-image.out 2>/tmp/hwlab-tools-image.err && tools_image_ready=true; fi cat < ["--build-arg", `${key}=${value}`]); const proxyArgs = node.egressProxy === null ? [] : ["--build-arg", "HTTP_PROXY", "--build-arg", "HTTPS_PROXY", "--build-arg", "ALL_PROXY", "--build-arg", "NO_PROXY", "--build-arg", "http_proxy", "--build-arg", "https_proxy", "--build-arg", "all_proxy", "--build-arg", "no_proxy"]; const networkArgs = target.tekton.toolsImage.buildNetwork === null ? [] : ["--network", target.tekton.toolsImage.buildNetwork]; const dockerBuildArgs = [...networkArgs, ...buildArgs, ...proxyArgs, "-f", "$dockerfile", "-t", "$image", "$context_dir"].join(" "); return ` set -eu state_dir=${shQuote(stateDir)} mkdir -p "$state_dir" if [ -s "$state_dir/pid" ] && kill -0 "$(cat "$state_dir/pid")" >/dev/null 2>&1; then printf '{"started":false,"reason":"job-already-running","pid":%s,"stateDir":"%s"}\\n' "$(cat "$state_dir/pid")" "$state_dir" exit 0 fi cat >"$state_dir/job.sh" <<'JOB' #!/bin/sh set -eu state_dir=${shQuote(stateDir)} image=${shQuote(target.tekton.toolsImage.output)} context_dir="$state_dir/context" dockerfile="$state_dir/${target.tekton.toolsImage.dockerfileInline?.filename ?? "Dockerfile"}" log="$state_dir/job.log" status="$state_dir/status.json" write_status() { state="$1"; shift message="$1"; shift || true python3 - "$status" "$state" "$message" "$image" <<'PY' import json, pathlib, sys, time path=pathlib.Path(sys.argv[1]) payload={"state":sys.argv[2],"message":sys.argv[3],"image":sys.argv[4],"updatedAt":time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())} path.write_text(json.dumps(payload, ensure_ascii=False) + "\\n") PY } run_job() { write_status running starting rm -rf "$context_dir" mkdir -p "$context_dir" printf %s ${shQuote(dockerfileEncoded)} | base64 -d >"$dockerfile" ${proxyExportBlock(node)} docker build ${dockerBuildArgs} || return "$?" docker run --rm "$image" sh -lc 'node --version && npm --version && bun --version && git --version && python3 --version && docker --version && ssh -V' || return "$?" docker push "$image" || return "$?" image_id="$(docker image inspect "$image" --format '{{.Id}}' 2>/dev/null || true)" digest="$(docker image inspect "$image" --format '{{join .RepoDigests ","}}' 2>/dev/null || true)" python3 - "$status" "$image" "$image_id" "$digest" <<'PY' import json, pathlib, sys, time path=pathlib.Path(sys.argv[1]) path.write_text(json.dumps({"state":"succeeded","message":"image-built-and-pushed","image":sys.argv[2],"imageId":sys.argv[3] or None,"repoDigests":[item for item in sys.argv[4].split(",") if item],"updatedAt":time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())}, ensure_ascii=False) + "\\n") PY } run_job >>"$log" 2>&1 || { rc=$? write_status failed "exit-$rc" exit "$rc" } JOB chmod +x "$state_dir/job.sh" : >"$state_dir/job.log" nohup "$state_dir/job.sh" >/dev/null 2>&1 & pid=$! printf '%s' "$pid" >"$state_dir/pid" printf '{"started":true,"pid":%s,"stateDir":"%s","statusCommand":"bun scripts/cli.ts hwlab nodes control-plane infra tools-image status --node %s --lane %s","logsCommand":"bun scripts/cli.ts hwlab nodes control-plane infra tools-image logs --node %s --lane %s"}\\n' "$pid" "$state_dir" ${shQuote(node.id)} ${shQuote(target.lane)} ${shQuote(node.id)} ${shQuote(target.lane)} `; } function argoApplyStartScript(node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, desiredYaml: string): string { const stateDir = remoteJobStateDir(target, "argo"); const desiredEncoded = Buffer.from(desiredYaml, "utf8").toString("base64"); const rewritesEncoded = Buffer.from(JSON.stringify(target.argo.install.imageRewrites), "utf8").toString("base64"); const preloadEncoded = Buffer.from(JSON.stringify(target.argo.install.preloadImages), "utf8").toString("base64"); return ` set -eu state_dir=${shQuote(stateDir)} mkdir -p "$state_dir" if [ -s "$state_dir/pid" ] && kill -0 "$(cat "$state_dir/pid")" >/dev/null 2>&1; then printf '{"started":false,"reason":"job-already-running","pid":%s,"stateDir":"%s"}\\n' "$(cat "$state_dir/pid")" "$state_dir" exit 0 fi cat >"$state_dir/job.sh" <<'JOB' #!/bin/sh set -eu state_dir=${shQuote(stateDir)} namespace=${shQuote(target.argo.namespace)} manifest_url=${shQuote(target.argo.install.manifestUrl)} field_manager=${shQuote(target.argo.install.fieldManager)} readiness_timeout=${shQuote(String(target.argo.install.readinessTimeoutSeconds))} log="$state_dir/job.log" status="$state_dir/status.json" install_yaml="$state_dir/install.yaml" rendered_yaml="$state_dir/install.rendered.yaml" desired_yaml="$state_dir/desired.yaml" write_status() { state="$1"; shift message="$1"; shift || true python3 - "$status" "$state" "$message" <<'PY' import json, pathlib, sys, time path=pathlib.Path(sys.argv[1]) path.write_text(json.dumps({"state":sys.argv[2],"message":sys.argv[3],"updatedAt":time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())}, ensure_ascii=False) + "\\n") PY } { write_status running starting ${proxyExportBlock(node)} printf %s ${shQuote(desiredEncoded)} | base64 -d >"$desired_yaml" printf %s ${shQuote(rewritesEncoded)} | base64 -d >"$state_dir/image-rewrites.json" printf %s ${shQuote(preloadEncoded)} | base64 -d >"$state_dir/preload-images.json" kubectl create namespace "$namespace" --dry-run=client -o yaml | kubectl apply --server-side --field-manager="$field_manager" -f - || exit "$?" python3 - "$state_dir/preload-images.json" "$state_dir/image-rewrites.json" <<'PY' >"$state_dir/pull-images.sh" import json, pathlib, shlex, sys preload=json.loads(pathlib.Path(sys.argv[1]).read_text()) rewrites=json.loads(pathlib.Path(sys.argv[2]).read_text()) print("#!/bin/sh") print("set -eu") seen=set() for item in rewrites: pull=item["pullImage"] target=item["target"] if target in seen: continue seen.add(target) print("docker pull " + shlex.quote(pull)) print("docker tag " + shlex.quote(pull) + " " + shlex.quote(target)) print("docker push " + shlex.quote(target)) for image in preload: if image not in seen and image.startswith("127.0.0.1:5000/"): print("docker image inspect " + shlex.quote(image) + " >/dev/null") PY chmod +x "$state_dir/pull-images.sh" "$state_dir/pull-images.sh" || exit "$?" curl -fsSL --max-time 60 "$manifest_url" >"$install_yaml" || exit "$?" python3 - "$install_yaml" "$state_dir/image-rewrites.json" "$rendered_yaml" ${shQuote(target.argo.install.imagePullPolicy)} <<'PY' import json, pathlib, sys text=pathlib.Path(sys.argv[1]).read_text() rewrites=json.loads(pathlib.Path(sys.argv[2]).read_text()) for item in rewrites: text=text.replace(item["source"], item["target"]) policy=sys.argv[4] text=text.replace("imagePullPolicy: Always", "imagePullPolicy: " + policy) pathlib.Path(sys.argv[3]).write_text(text) PY kubectl apply --server-side --field-manager="$field_manager" -n "$namespace" -f "$rendered_yaml" || exit "$?" deadline=$(( $(date +%s) + readiness_timeout )) while [ "$(date +%s)" -lt "$deadline" ]; do kubectl get crd applications.argoproj.io appprojects.argoproj.io >/dev/null 2>&1 && break sleep 5 done kubectl get crd applications.argoproj.io appprojects.argoproj.io >/dev/null || exit "$?" kubectl apply --server-side --field-manager="$field_manager" -f "$desired_yaml" || exit "$?" write_status succeeded argocd-install-applied } >>"$log" 2>&1 || { rc=$? write_status failed "exit-$rc" exit "$rc" } JOB chmod +x "$state_dir/job.sh" : >"$state_dir/job.log" nohup "$state_dir/job.sh" >/dev/null 2>&1 & pid=$! printf '%s' "$pid" >"$state_dir/pid" printf '{"started":true,"pid":%s,"stateDir":"%s","statusCommand":"bun scripts/cli.ts hwlab nodes control-plane infra argo status --node %s --lane %s","logsCommand":"bun scripts/cli.ts hwlab nodes control-plane infra argo logs --node %s --lane %s"}\\n' "$pid" "$state_dir" ${shQuote(node.id)} ${shQuote(target.lane)} ${shQuote(node.id)} ${shQuote(target.lane)} `; } function ciBuildBenchmarkStartScript( target: ControlPlaneTargetSpec, profile: CiBuildBenchmarkProfileSpec, manifest: Record, pipelineName: string, pipelineRun: string, sourceCommit: string, catalogPath: string, ): string { const stateDir = ciBuildBenchmarkStateDir(target, profile.profile); const manifestB64 = Buffer.from(JSON.stringify(manifest), "utf8").toString("base64"); return ` set -eu state_dir=${shQuote(stateDir)} status_file="$state_dir/status.json" ns=${shQuote(target.ciNamespace)} profile=${shQuote(profile.profile)} pipeline=${shQuote(pipelineName)} pipeline_run=${shQuote(pipelineRun)} source_commit=${shQuote(sourceCommit)} catalog_path=${shQuote(catalogPath)} mkdir -p "$state_dir" previous_run= if [ -s "$status_file" ]; then previous_run=$(python3 - "$status_file" <<'PY' || true import json, sys try: data=json.load(open(sys.argv[1], encoding="utf-8")) print(data.get("pipelineRun") or "") except Exception: print("") PY ) fi if [ -n "$previous_run" ]; then previous_status=$(kubectl -n "$ns" get pipelinerun "$previous_run" -o 'jsonpath={.status.conditions[?(@.type=="Succeeded")].status}' 2>/dev/null || true) if [ -n "$previous_status" ] && [ "$previous_status" != "True" ] && [ "$previous_status" != "False" ]; then python3 - "$state_dir" "$previous_run" "$profile" ${shQuote(target.node)} ${shQuote(target.lane)} <<'PY' import json, sys state_dir, previous_run, profile, node, lane = sys.argv[1:6] print(json.dumps({ "started": False, "state": "already-running", "pipelineRun": previous_run, "stateDir": state_dir, "statusCommand": f"bun scripts/cli.ts hwlab nodes control-plane infra ci-build-benchmark status --node {node} --lane {lane} --profile {profile}", "logsCommand": f"bun scripts/cli.ts hwlab nodes control-plane infra ci-build-benchmark logs --node {node} --lane {lane} --profile {profile}", }, ensure_ascii=False)) PY exit 0 fi fi manifest_path="$state_dir/$pipeline_run.json" printf '%s' ${shQuote(manifestB64)} | base64 -d >"$manifest_path" set +e pipeline_check=$(kubectl -n "$ns" get pipeline "$pipeline" -o name 2>&1) pipeline_check_rc=$? create_output= create_rc=0 if [ "$pipeline_check_rc" = 0 ]; then create_output=$(kubectl create -f "$manifest_path" 2>&1) create_rc=$? else create_output="$pipeline_check" create_rc="$pipeline_check_rc" fi printf '%s\\n' "$create_output" >"$state_dir/create.log" python3 - "$status_file" "$state_dir" "$pipeline_run" "$source_commit" "$profile" "$catalog_path" "$create_rc" "$create_output" ${shQuote(target.node)} ${shQuote(target.lane)} <<'PY' import datetime, json, sys status_file, state_dir, pipeline_run, source_commit, profile, catalog_path, rc_raw, output, node, lane = sys.argv[1:11] rc=int(rc_raw or "0") payload={ "started": rc == 0, "state": "started" if rc == 0 else "failed", "pipelineRun": pipeline_run, "sourceCommit": source_commit, "profile": profile, "catalogPath": catalog_path, "stateDir": state_dir, "createdAt": datetime.datetime.now(datetime.timezone.utc).isoformat(), "exitCode": rc, "createOutputTail": output[-2000:], "statusCommand": f"bun scripts/cli.ts hwlab nodes control-plane infra ci-build-benchmark status --node {node} --lane {lane} --profile {profile}", "logsCommand": f"bun scripts/cli.ts hwlab nodes control-plane infra ci-build-benchmark logs --node {node} --lane {lane} --profile {profile}", } open(status_file, "w", encoding="utf-8").write(json.dumps(payload, ensure_ascii=False)) print(json.dumps(payload, ensure_ascii=False)) PY exit "$create_rc" `; } function ciBuildBenchmarkStatusScript(target: ControlPlaneTargetSpec, profile: CiBuildBenchmarkProfileSpec, tailLines: number, includeLogs: boolean): string { const stateDir = ciBuildBenchmarkStateDir(target, profile.profile); return ` set +e state_dir=${shQuote(stateDir)} status_file="$state_dir/status.json" ns=${shQuote(target.ciNamespace)} profile=${shQuote(profile.profile)} tail_lines=${shQuote(String(tailLines))} include_logs=${includeLogs ? "true" : "false"} tmp_dir=$(mktemp -d) pipeline_run= if [ -s "$status_file" ]; then pipeline_run=$(python3 - "$status_file" <<'PY' || true import json, sys try: data=json.load(open(sys.argv[1], encoding="utf-8")) print(data.get("pipelineRun") or "") except Exception: print("") PY ) fi if [ -z "$pipeline_run" ]; then pipeline_run=$(kubectl -n "$ns" get pipelinerun -l "unidesk.ai/benchmark=ci-build,unidesk.ai/benchmark-profile=$profile" -o 'jsonpath={range .items[*]}{.metadata.creationTimestamp}{" "}{.metadata.name}{"\\n"}{end}' 2>/dev/null | sort | tail -n 1 | awk '{print $2}') fi if [ -n "$pipeline_run" ]; then kubectl -n "$ns" get pipelinerun "$pipeline_run" -o json >"$tmp_dir/pipelinerun.json" 2>"$tmp_dir/pipelinerun.err" kubectl -n "$ns" get taskrun -l "tekton.dev/pipelineRun=$pipeline_run" -o json >"$tmp_dir/taskruns.json" 2>"$tmp_dir/taskruns.err" kubectl -n "$ns" get pod -l "tekton.dev/pipelineRun=$pipeline_run" -o json >"$tmp_dir/pods.json" 2>"$tmp_dir/pods.err" if [ "$include_logs" = true ]; then kubectl -n "$ns" logs -l "tekton.dev/pipelineRun=$pipeline_run" --all-containers --tail="$tail_lines" --prefix=true >"$tmp_dir/logs.txt" 2>"$tmp_dir/logs.err" || true fi fi python3 - "$state_dir" "$status_file" "$tmp_dir" "$pipeline_run" "$include_logs" "$tail_lines" <<'PY' import json, pathlib, sys state_dir=pathlib.Path(sys.argv[1]) status_path=pathlib.Path(sys.argv[2]) tmp_dir=pathlib.Path(sys.argv[3]) pipeline_run=sys.argv[4] include_logs=sys.argv[5] == "true" tail_lines=int(sys.argv[6]) log_tail_limit=min(6000, max(2000, tail_lines * 80)) def read_json(path): try: return json.loads(path.read_text(encoding="utf-8")) except Exception: return None def read_text(path, limit=4000): try: return path.read_text(encoding="utf-8", errors="replace")[-limit:] except Exception: return "" def succeeded_condition(obj): for cond in obj.get("status", {}).get("conditions", []) or []: if cond.get("type") == "Succeeded": return cond return {} status=None if status_path.exists(): status=read_json(status_path) pr=read_json(tmp_dir / "pipelinerun.json") if pipeline_run else None trs=read_json(tmp_dir / "taskruns.json") if pipeline_run else None pods=read_json(tmp_dir / "pods.json") if pipeline_run else None pr_cond=succeeded_condition(pr or {}) task_runs=[] for item in (trs or {}).get("items", []) or []: cond=succeeded_condition(item) labels=item.get("metadata", {}).get("labels", {}) or {} task_runs.append({ "name": item.get("metadata", {}).get("name"), "pipelineTask": labels.get("tekton.dev/pipelineTask"), "status": cond.get("status"), "reason": cond.get("reason"), "message": cond.get("message"), "startTime": item.get("status", {}).get("startTime"), "completionTime": item.get("status", {}).get("completionTime"), "podName": item.get("status", {}).get("podName"), }) pod_rows=[] for item in (pods or {}).get("items", []) or []: phase=item.get("status", {}).get("phase") pod_rows.append({ "name": item.get("metadata", {}).get("name"), "phase": phase, "startTime": item.get("status", {}).get("startTime"), }) state="not-started" if pr: status_value=pr_cond.get("status") if status_value == "True": state="succeeded" elif status_value == "False": state="failed" elif status_value: state="running" else: state="pending" elif pipeline_run: state="missing" payload={ "stateDir": str(state_dir), "status": status, "pipelineRunName": pipeline_run or None, "state": state, "pipelineRun": None if not pr else { "name": pr.get("metadata", {}).get("name"), "status": pr_cond.get("status"), "reason": pr_cond.get("reason"), "message": pr_cond.get("message"), "createdAt": pr.get("metadata", {}).get("creationTimestamp"), "startTime": pr.get("status", {}).get("startTime"), "completionTime": pr.get("status", {}).get("completionTime"), "sourceCommit": (pr.get("metadata", {}).get("labels", {}) or {}).get("hwlab.pikastech.local/source-commit"), "catalogPath": (pr.get("metadata", {}).get("annotations", {}) or {}).get("unidesk.ai/catalog-path"), }, "taskRuns": task_runs, "pods": pod_rows, "errors": { "pipelinerun": read_text(tmp_dir / "pipelinerun.err"), "taskruns": read_text(tmp_dir / "taskruns.err"), "pods": read_text(tmp_dir / "pods.err"), "logs": read_text(tmp_dir / "logs.err") if include_logs else "", }, "logTail": read_text(tmp_dir / "logs.txt", log_tail_limit) if include_logs else "", } print(json.dumps(payload, ensure_ascii=False)) PY rm -rf "$tmp_dir" `; } function remoteJobStatusScript(target: ControlPlaneTargetSpec, name: "tools-image" | "argo", tailLines: number): string { const stateDir = remoteJobStateDir(target, name); return ` set +e state_dir=${shQuote(stateDir)} status_file="$state_dir/status.json" log_file="$state_dir/job.log" pid_file="$state_dir/pid" running=false pid=null if [ -s "$pid_file" ]; then pid_raw="$(cat "$pid_file" 2>/dev/null || true)" if [ -n "$pid_raw" ] && kill -0 "$pid_raw" >/dev/null 2>&1; then running=true; pid="$pid_raw"; else pid="$pid_raw"; fi fi python3 - "$state_dir" "$status_file" "$log_file" "$running" "$pid" ${shQuote(String(tailLines))} <<'PY' import json, pathlib, sys state_dir=pathlib.Path(sys.argv[1]) status_path=pathlib.Path(sys.argv[2]) log_path=pathlib.Path(sys.argv[3]) running=sys.argv[4] == "true" pid=None if sys.argv[5] in ("", "null") else sys.argv[5] tail_lines=int(sys.argv[6]) status=None if status_path.exists(): try: status=json.loads(status_path.read_text()) except Exception as error: status={"parseError": str(error), "raw": status_path.read_text(errors="replace")[-1000:]} log_tail="" if log_path.exists(): lines=log_path.read_text(errors="replace").splitlines() log_tail="\\n".join(lines[-tail_lines:]) print(json.dumps({"stateDir": str(state_dir), "pid": pid, "running": running, "status": status, "logBytes": log_path.stat().st_size if log_path.exists() else 0, "logTail": log_tail}, ensure_ascii=False)) PY `; } function remoteJobLogs(node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, name: "tools-image" | "argo", options: ToolsImageOptions | ArgoOptions): Record { const result = runTransK3s(node.kubeRoute, remoteJobStatusScript(target, name, options.tailLines), options.timeoutSeconds); const parsed = parseRemoteJson(result.stdout); return { ok: result.exitCode === 0, command: `hwlab nodes control-plane infra ${name === "tools-image" ? "tools-image" : "argo"} logs`, configPath: HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, node: node.id, lane: target.lane, mutation: false, job: typeof parsed === "object" && parsed !== null ? parsed : { stdoutPreview: result.stdout.slice(0, 2000) }, result: compactCommandResult(result), }; } function manifestObjectSummary(manifest: readonly Record[]): Record[] { return manifest.map((item) => { const metadata = record(item.metadata); return { kind: item.kind ?? null, namespace: metadata.namespace ?? null, name: metadata.name ?? null }; }); } function runTransK3s(kubeRoute: string, script: string, timeoutSeconds: number): CommandResult { return runCommand(["/root/.local/bin/trans", kubeRoute, "sh", "--", script], rootPath(), { timeoutMs: timeoutSeconds * 1000 }); } function proxyExportBlock(node: ControlPlaneNodeSpec): string { const proxy = node.egressProxy; if (proxy === null) return " : # no egress proxy configured\n"; const noProxy = [...new Set(["localhost", "127.0.0.1", "::1", "127.0.0.1:5000", "localhost:5000", ...proxy.noProxy])]; return ` proxy_ip="$(kubectl -n ${shQuote(proxy.namespace)} get svc ${shQuote(proxy.serviceName)} -o 'jsonpath={.spec.clusterIP}' 2>/dev/null || true)" if [ -z "$proxy_ip" ]; then echo "egress proxy service missing: ${proxy.namespace}/${proxy.serviceName}" >&2; exit 41; fi export HTTP_PROXY="http://$proxy_ip:${proxy.port}" export HTTPS_PROXY="$HTTP_PROXY" export ALL_PROXY="$HTTP_PROXY" export http_proxy="$HTTP_PROXY" export https_proxy="$HTTP_PROXY" export all_proxy="$HTTP_PROXY" export NO_PROXY=${shQuote(noProxy.join(","))} export no_proxy="$NO_PROXY" `; } function remoteJobStateDir(target: ControlPlaneTargetSpec, name: "tools-image" | "argo"): string { return `/tmp/unidesk-hwlab-node-control-plane/${target.id}/${name}`; } function shellJsonArray(items: readonly string[]): string { return JSON.stringify([...items]); } function parseRemoteJson(text: string): unknown { const trimmed = text.trim(); if (trimmed.length === 0) return null; try { return JSON.parse(trimmed); } catch { const start = trimmed.indexOf("{"); const end = trimmed.lastIndexOf("}"); if (start >= 0 && end > start) { try { return JSON.parse(trimmed.slice(start, end + 1)); } catch {} } } return null; } function ciBuildBenchmarkStateDir(target: ControlPlaneTargetSpec, profile: string): string { return `/tmp/unidesk-hwlab-node-control-plane/${target.id}/ci-build-benchmark-${profile}`; } function ciBuildBenchmarkLiveOk(job: Record, expectedServices: readonly string[], profile: CiBuildBenchmarkProfileSpec): boolean { const pipelineRun = record(job.pipelineRun); const pipelineStatus = renderCell(pipelineRun.status, ""); if (pipelineStatus === "False") return false; if (pipelineStatus !== "True") return true; const taskRuns = ciBuildBenchmarkTaskRunRecords(job); for (const service of expectedServices) { if (!taskRuns.some((task) => task.pipelineTask === `build-${service}`)) return false; } return ciBuildBenchmarkPolicyOk(job, profile.cachePolicy); } function ciBuildBenchmarkTaskRows(job: Record): Record[] { const pipelineRun = record(job.pipelineRun); const rows: Record[] = []; if (Object.keys(pipelineRun).length > 0) { rows.push({ task: "pipeline-total", status: ciBuildBenchmarkStatusText(pipelineRun.status), duration: durationBetweenIso(pipelineRun.startTime, pipelineRun.completionTime), start: shortIsoTime(pipelineRun.startTime), end: shortIsoTime(pipelineRun.completionTime), }); } const taskRuns = ciBuildBenchmarkTaskRunRecords(job).sort((left, right) => renderCell(left.startTime, "").localeCompare(renderCell(right.startTime, ""))); for (const task of taskRuns) { rows.push({ task: renderCell(task.pipelineTask ?? task.name), status: ciBuildBenchmarkStatusText(task.status), duration: durationBetweenIso(task.startTime, task.completionTime), start: shortIsoTime(task.startTime), end: shortIsoTime(task.completionTime), }); } return rows; } function ciBuildBenchmarkServiceRows(job: Record, servicesValue: unknown): Record[] { const services = ciBuildBenchmarkExpectedServices(servicesValue); if (services.length === 0) return []; const pipelineRun = record(job.pipelineRun); const pipelineTerminal = pipelineRun.status === "True" || pipelineRun.status === "False"; const taskRuns = ciBuildBenchmarkTaskRunRecords(job); return services.map((service) => { const task = taskRuns.find((item) => item.pipelineTask === `build-${service}`); if (task === undefined) { const status = pipelineTerminal ? "missing" : "pending"; return { service, task: `build-${service}`, status, duration: "-", failure: pipelineRun.status === "True" ? "cache-hit-forbidden" : "-", }; } const taskStatus = ciBuildBenchmarkStatusText(task.status); const failure = task.status === "False" ? classifyCiBuildBenchmarkFailure(`${renderCell(task.reason, "")}\n${renderCell(task.message, "")}`) : "-"; return { service, task: renderCell(task.pipelineTask ?? task.name), status: taskStatus, duration: durationBetweenIso(task.startTime, task.completionTime), failure, }; }); } function ciBuildBenchmarkFailureRows(job: Record, serviceRows: readonly Record[], benchmark: Record): Record[] { const counts = new Map(); const add = (family: string, scope: string): void => { if (family === "-" || family.length === 0) return; const existing = counts.get(family) ?? { count: 0, scopes: [] }; existing.count += 1; if (!existing.scopes.includes(scope)) existing.scopes.push(scope); counts.set(family, existing); }; for (const row of serviceRows) add(row.failure, row.service); const pipelineRun = record(job.pipelineRun); if (pipelineRun.status === "False") { add(classifyCiBuildBenchmarkFailure(`${renderCell(pipelineRun.reason, "")}\n${renderCell(pipelineRun.message, "")}`), "pipeline"); } const cachePolicy = record(benchmark.cachePolicy); if (cachePolicy.forbidBuildkitCache === true && ciBuildBenchmarkLogHasBuildkitCache(job)) add("cache-hit-forbidden", "buildkit-cache"); if (cachePolicy.forbidGitopsCatalogReuse === true && ciBuildBenchmarkLogHasReuse(job)) add("cache-hit-forbidden", "artifact-reuse"); return [...counts.entries()].map(([family, value]) => ({ family, count: String(value.count), scope: value.scopes.join(",") })); } function ciBuildBenchmarkPolicyOk(job: Record, cachePolicy: CiBuildBenchmarkCachePolicy): boolean { if (cachePolicy.forbidBuildkitCache && ciBuildBenchmarkLogHasBuildkitCache(job)) return false; if (cachePolicy.forbidGitopsCatalogReuse && ciBuildBenchmarkLogHasReuse(job)) return false; return true; } function ciBuildBenchmarkLogHasBuildkitCache(job: Record): boolean { const logTail = typeof job.logTail === "string" ? job.logTail : ""; return /"buildkitCacheRef"\s*:\s*"[^"]+"|--import-cache|--export-cache|writing cache image manifest/iu.test(logTail); } function ciBuildBenchmarkLogHasReuse(job: Record): boolean { const logTail = typeof job.logTail === "string" ? job.logTail : ""; return /"reusedFrom"\s*:\s*"(?!null)|"status"\s*:\s*"reused"/iu.test(logTail); } function ciBuildBenchmarkTaskRunRecords(job: Record): Record[] { return Array.isArray(job.taskRuns) ? job.taskRuns.map(record) : []; } function ciBuildBenchmarkExpectedServices(value: unknown): string[] { return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string" && item.length > 0) : []; } function ciBuildBenchmarkStatusText(value: unknown): string { if (value === "True") return "succeeded"; if (value === "False") return "failed"; if (value === "Unknown") return "running"; return renderCell(value, "pending"); } function classifyCiBuildBenchmarkFailure(text: string): string { const value = text.toLowerCase(); if (/cache-hit-forbidden|reused-from|reuse/i.test(text)) return "cache-hit-forbidden"; if (/no such host|could not resolve|enotfound|dns/i.test(text)) return "dns"; if (/429|rate limit|too many requests|toomanyrequests/i.test(text)) return "rate-limit"; if (/tls|certificate|x509|timeout|timed out|i\/o timeout/i.test(text)) return "tls-timeout"; if (/proxy|connect|connection reset|connection refused|econn/i.test(text)) return "proxy-connect"; if (/unauthorized|authentication required|permission denied|forbidden|denied/i.test(text)) return "auth"; if (/imagepullbackoff|errimagepull|imagepolicy|pull access denied/i.test(text)) return "image-policy"; if (/push|registry|blob upload|manifest invalid|manifest unknown/i.test(text)) return "registry-push"; return value.trim().length === 0 ? "unknown" : "build-script"; } function durationBetweenIso(startValue: unknown, endValue: unknown): string { if (typeof startValue !== "string" || startValue.length === 0) return "-"; const start = Date.parse(startValue); if (!Number.isFinite(start)) return "-"; const end = typeof endValue === "string" && endValue.length > 0 ? Date.parse(endValue) : Date.now(); if (!Number.isFinite(end) || end < start) return "-"; return formatDurationMs(end - start); } function formatDurationMs(ms: number): string { const seconds = Math.round(ms / 1000); const minutes = Math.floor(seconds / 60); const rest = seconds % 60; return minutes > 0 ? `${minutes}m${String(rest).padStart(2, "0")}s` : `${seconds}s`; } function shortIsoTime(value: unknown): string { if (typeof value !== "string" || value.length === 0) return "-"; return value.replace(/^\d{4}-\d{2}-\d{2}T/u, "").replace(/Z$/u, "Z"); } function shortDisplay(value: string): string { return /^[0-9a-f]{40}$/iu.test(value) ? value.slice(0, 12).toLowerCase() : value; } function validateBenchmarkProfileName(value: string, path: string): void { if (!/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/u.test(value) || value.length > 63) throw new Error(`${path} must be a DNS-label style benchmark profile`); } function validateBenchmarkCatalogPathTemplate(value: string, path: string): void { if (!value.includes("{profile}") || !value.includes("{pipelineRun}")) throw new Error(`${path} must include {profile} and {pipelineRun}`); if (value.startsWith("/") || value.includes("\n") || value.includes("\r")) throw new Error(`${path} must be a relative repo path template`); const rendered = value.replace(/\{profile\}/gu, "profile").replace(/\{pipelineRun\}/gu, "pipeline-run"); if (rendered.split("/").some((segment) => segment === ".." || segment.length === 0)) throw new Error(`${path} must not contain empty or parent path segments`); if (!rendered.endsWith(".json")) throw new Error(`${path} must render to a JSON catalog path`); } function asRecord(value: unknown, path: string): Record { if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${path} must be an object`); return value as Record; } function record(value: unknown): Record { return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record : {}; } function renderTable(headers: string[], rows: string[][]): string[] { const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => row[index]?.length ?? 0))); const render = (row: string[]) => row.map((cell, index) => cell.padEnd(widths[index] ?? cell.length)).join(" ").trimEnd(); return [render(headers), ...rows.map(render)]; } function renderCell(value: unknown, fallback = "-"): string { if (value === undefined || value === null || value === "") return fallback; return String(value); } function optionsModeFromCommand(command: unknown): string { const value = String(command ?? ""); if (value.endsWith(" status") || value.endsWith(" logs")) return "status"; return "benchmark"; } function stringField(obj: Record, key: string, path: string): string { const value = obj[key]; if (typeof value !== "string" || value.length === 0) throw new Error(`${path}.${key} must be a non-empty string`); return value; } function optionalStringField(obj: Record, key: string, path: string): string | undefined { const value = obj[key]; if (value === undefined) return undefined; if (typeof value !== "string" || value.length === 0) throw new Error(`${path}.${key} must be a non-empty string`); return value; } function validateKubernetesName(value: string, path: string): void { if (!/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/u.test(value) || value.length > 253) throw new Error(`${path} must be a Kubernetes resource name`); } function validateSecretKey(value: string, path: string): void { if (!/^[A-Za-z0-9._-]+$/u.test(value)) throw new Error(`${path} must be a Kubernetes Secret key`); } function validateEnvKey(value: string, path: string): void { if (!/^[A-Z0-9_]+$/u.test(value)) throw new Error(`${path} must be an env key`); } function validateSourceRef(value: string, path: string): void { if (!/^[A-Za-z0-9_./-]+$/u.test(value) || value.includes("..")) throw new Error(`${path} has an unsupported sourceRef format`); } function numberField(obj: Record, key: string, path: string): number { const value = obj[key]; if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) throw new Error(`${path}.${key} must be a positive integer`); return value; } function positiveConfigIntegerField(obj: Record, key: string, path: string): number { const value = obj[key]; if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) throw new Error(`${path}.${key} must be a positive integer`); return value; } function nonNegativeIntegerField(obj: Record, key: string, path: string): number { const value = obj[key]; if (typeof value !== "number" || !Number.isInteger(value) || value < 0) throw new Error(`${path}.${key} must be a non-negative integer`); return value; } function numberArrayField(obj: Record, key: string, path: string): number[] { const value = obj[key]; if (!Array.isArray(value) || value.some((item) => typeof item !== "number" || !Number.isInteger(item))) throw new Error(`${path}.${key} must be an array of integers`); return [...value] as number[]; } function stringArrayField(obj: Record, key: string, path: string): string[] { const value = obj[key]; if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.length === 0)) throw new Error(`${path}.${key} must be an array of non-empty strings`); return [...value] as string[]; } function stringRecordField(obj: Record, path: string): Record { const result: Record = {}; for (const [key, value] of Object.entries(obj)) { if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(key)) throw new Error(`${path}.${key} has an unsupported key format`); if (typeof value !== "string" || value.length === 0) throw new Error(`${path}.${key} must be a non-empty string`); result[key] = value; } return result; } function booleanField(obj: Record, key: string, path: string): boolean { const value = obj[key]; if (typeof value !== "boolean") throw new Error(`${path}.${key} must be a boolean`); return value; } function boolField(obj: Record, key: string): boolean { return obj[key] === true; } function numberValue(value: unknown): number | null { return typeof value === "number" && Number.isFinite(value) ? value : null; } function requiredOption(args: string[], name: string): string { const index = args.indexOf(name); if (index === -1) throw new Error(`${name} is required`); const value = args[index + 1]; if (value === undefined || value.startsWith("--") || value.length === 0) throw new Error(`${name} requires a value`); return value; } function optionValue(args: string[], name: string): string | null { const index = args.indexOf(name); if (index === -1) return null; const value = args[index + 1]; if (value === undefined || value.startsWith("--") || value.length === 0) throw new Error(`${name} requires a value`); return value; } function positiveIntegerOption(args: string[], name: string, defaultValue: number, maxValue: number): number { const raw = optionValue(args, name); if (raw === null) return defaultValue; const value = Number(raw); if (!Number.isInteger(value) || value <= 0) throw new Error(`${name} must be a positive integer`); return Math.min(value, maxValue); } function compactCommandResult(result: CommandResult): Record { return { exitCode: result.exitCode, timedOut: result.timedOut, stdoutBytes: Buffer.byteLength(result.stdout), stderrBytes: Buffer.byteLength(result.stderr), stdoutTail: result.stdout.slice(-2000), stderrTail: result.stderr.slice(-2000), }; } function shQuote(value: string): string { return `'${value.replace(/'/gu, `'"'"'`)}'`; } function validateHttpsUrl(value: string, path: string): void { let parsed: URL; try { parsed = new URL(value); } catch { throw new Error(`${path} must be a valid URL`); } if (parsed.protocol !== "https:") throw new Error(`${path} must use https://`); } function sha256Short(text: string): string { return `sha256:${createHash("sha256").update(text).digest("hex")}`; }