173 lines
8.8 KiB
TypeScript
173 lines
8.8 KiB
TypeScript
import { argoDesiredManifest, argoInternalHttpIdentity } from "./hwlab-node-control-plane-argo-model";
|
|
import { manifestObjectSummary, remoteJobLogs, remoteJobStateDir, remoteJobStatusScript, runTargetK3s } from "./hwlab-node-control-plane-executor";
|
|
import { HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, type ArgoOptions, type ControlPlaneNodeSpec, type ControlPlaneTargetSpec } from "./hwlab-node-control-plane-model";
|
|
import {
|
|
boolField,
|
|
compactCommandResult,
|
|
controlPlaneEgressProxySummary,
|
|
record,
|
|
runtimeHostProxyConfig,
|
|
runtimeProxyReady,
|
|
sha256Short,
|
|
statusScript,
|
|
} from "./hwlab-node-control-plane-runtime";
|
|
import { argoApplyStartScript, parseRemoteJson } from "./hwlab-node-control-plane-runtime-delivery";
|
|
|
|
export function argoCommandStatus(node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, options: ArgoOptions): Record<string, unknown> {
|
|
const result = runTargetK3s(node, statusScript(node, target), options.timeoutSeconds);
|
|
const status = record(parseRemoteJson(result.stdout));
|
|
const argo = record(record(status.components).argo);
|
|
const argoInstall = record(argo.install);
|
|
const internalHttp = record(argoInstall.internalHttp);
|
|
const reconciliation = record(argoInstall.reconciliation);
|
|
const runtimeProxy = record(argoInstall.runtimeProxy);
|
|
const jobResult = runTargetK3s(node, remoteJobStatusScript(target, "argo", 0), 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")
|
|
&& boolField(internalHttp, "ready")
|
|
&& boolField(reconciliation, "ready")
|
|
&& runtimeProxyReady(argoInstall);
|
|
return {
|
|
ok,
|
|
command: "hwlab nodes control-plane infra argo status",
|
|
configPath: HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH,
|
|
node: node.id,
|
|
lane: target.lane,
|
|
executionPlane: node.kubeExecution,
|
|
mutation: false,
|
|
expected: {
|
|
namespace: target.argo.namespace,
|
|
project: target.argo.projectName,
|
|
application: target.argo.applicationName,
|
|
install: {
|
|
version: target.argo.install.version,
|
|
expectedDeploymentCount: target.argo.install.expectedDeployments.length,
|
|
expectedStatefulSetCount: target.argo.install.expectedStatefulSets.length,
|
|
internalHttp: target.argo.install.internalHttp,
|
|
reconciliation: target.argo.install.reconciliation,
|
|
},
|
|
},
|
|
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"),
|
|
internalHttpReady: boolField(internalHttp, "ready"),
|
|
reconciliationReady: boolField(reconciliation, "ready"),
|
|
runtimeProxyReady: runtimeProxyReady(argoInstall),
|
|
},
|
|
argo: {
|
|
namespace: argo.namespace ?? target.argo.namespace,
|
|
installed: argo.installed === true,
|
|
projectExists: argo.projectExists === true,
|
|
applicationExists: argo.applicationExists === true,
|
|
observerRbacReady: record(argo.argoObserverRbac).ready === true,
|
|
install: {
|
|
crdsReady: argoInstall.crdsReady === true,
|
|
deploymentsReady: argoInstall.deploymentsReady === true,
|
|
statefulSetsReady: argoInstall.statefulSetsReady === true,
|
|
internalHttp: {
|
|
enabled: internalHttp.enabled === true,
|
|
ready: internalHttp.ready === true,
|
|
configMapName: internalHttp.configMapName ?? null,
|
|
configMatches: internalHttp.configMatches === true,
|
|
deploymentName: internalHttp.deploymentName ?? null,
|
|
rolloutMatches: internalHttp.rolloutMatches === true,
|
|
},
|
|
reconciliation: {
|
|
enabled: reconciliation.enabled === true,
|
|
ready: reconciliation.ready === true,
|
|
configMapName: reconciliation.configMapName ?? null,
|
|
timeoutSeconds: reconciliation.timeoutSeconds ?? null,
|
|
jitterSeconds: reconciliation.jitterSeconds ?? null,
|
|
timeoutMatches: reconciliation.timeoutMatches === true,
|
|
jitterMatches: reconciliation.jitterMatches === true,
|
|
},
|
|
runtimeProxy: { enabled: runtimeProxy.enabled === true, ready: runtimeProxy.ready === true, mode: runtimeProxy.mode ?? null },
|
|
},
|
|
},
|
|
job: typeof jobStatus === "object" && jobStatus !== null ? jobStatus : { parseError: "job status did not return JSON" },
|
|
result: {
|
|
k3s: { exitCode: result.exitCode, timedOut: result.timedOut, stdoutBytes: Buffer.byteLength(result.stdout), stderrBytes: Buffer.byteLength(result.stderr) },
|
|
job: { exitCode: jobResult.exitCode, timedOut: jobResult.timedOut, stdoutBytes: Buffer.byteLength(jobResult.stdout), stderrBytes: Buffer.byteLength(jobResult.stderr) },
|
|
},
|
|
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}` },
|
|
};
|
|
}
|
|
|
|
export function argoApply(node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, options: ArgoOptions): Record<string, unknown> {
|
|
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),
|
|
runtimeProxy: runtimeHostProxyConfig(node, target.argo.install.runtimeProxy),
|
|
internalHttp: target.argo.install.internalHttp === null ? null : { ...target.argo.install.internalHttp, rolloutIdentity: argoInternalHttpIdentity(target) },
|
|
reconciliation: target.argo.install.reconciliation,
|
|
};
|
|
const applyRunner = argoApplyStartScript(node, target, desiredYaml);
|
|
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,
|
|
executionPlane: node.kubeExecution,
|
|
mode: "dry-run",
|
|
mutation: false,
|
|
applyPlan,
|
|
desiredYaml: { objects: desired.length, bytes: Buffer.byteLength(desiredYaml), sha256: sha256Short(desiredYaml), preview: desiredYaml },
|
|
runner: { bytes: Buffer.byteLength(applyRunner), sha256: sha256Short(applyRunner) },
|
|
next: { confirm: `bun scripts/cli.ts hwlab nodes control-plane infra argo apply --node ${node.id} --lane ${target.lane} --confirm` },
|
|
};
|
|
}
|
|
const result = runTargetK3s(node, applyRunner, 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,
|
|
executionPlane: node.kubeExecution,
|
|
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}`,
|
|
},
|
|
};
|
|
}
|
|
|
|
export function runArgoControlPlaneCommand(node: ControlPlaneNodeSpec, target: ControlPlaneTargetSpec, options: ArgoOptions): Record<string, unknown> {
|
|
if (options.action === "status") return argoCommandStatus(node, target, options);
|
|
if (options.action === "logs") return remoteJobLogs(node, target, "argo", options);
|
|
return argoApply(node, target, options);
|
|
}
|