refactor: split control-plane cli modules
This commit is contained in:
@@ -0,0 +1,498 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. git-mirror module for scripts/src/agentrun.ts.
|
||||
|
||||
// Moved mechanically from scripts/src/agentrun.ts:5384-5846 for #903.
|
||||
|
||||
// SPEC: PJ2026-01020108 cancel lifecycle + PJ2026-01020205 AipodSpec binding + PJ2026-01020302 session policy + PJ2026-01020305 cancel control + PJ2026-01060305/06 YAML execution policy and bounded output draft-2026-06-25-p0.
|
||||
// Exposes AgentRun lane-scoped policy, AipodSpec SecretRef binding, cancel lifecycle, and bounded default output in the UniDesk CLI.
|
||||
import { chmodSync, copyFileSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { rootPath, type UniDeskConfig } from "../config";
|
||||
import type { RenderedCliResult } from "../output";
|
||||
import { applyLocalCaddyManagedSite } from "../pk01-caddy";
|
||||
import { runSshCommandCapture, type SshCaptureResult } from "../ssh";
|
||||
import { runRemoteSshCommandCapture } from "../remote";
|
||||
import { startJob } from "../jobs";
|
||||
import {
|
||||
AGENTRUN_CONFIG_PATH,
|
||||
agentRunLaneSummary,
|
||||
agentRunPipelineRunName,
|
||||
agentRunProviderCredentialRefs,
|
||||
resolveAgentRunLaneTarget,
|
||||
type AgentRunCancelLifecycleSpec,
|
||||
type AgentRunLaneSpec,
|
||||
} from "../agentrun-lanes";
|
||||
import {
|
||||
agentRunImageArtifact,
|
||||
placeholderAgentRunImage,
|
||||
renderedFilesDigest,
|
||||
renderedObjectsDigest,
|
||||
renderAgentRunControlPlaneManifests,
|
||||
renderAgentRunGitopsFiles,
|
||||
type AgentRunArtifactService,
|
||||
} from "../agentrun-manifests";
|
||||
import { sha256Fingerprint } from "../platform-infra-ops-library";
|
||||
|
||||
import type { GitMirrorStatusOptions } from "./options";
|
||||
import { readGitMirrorStatus } from "./rest-bridge";
|
||||
import { compactCapture, shQuote } from "./utils";
|
||||
|
||||
export function cleanupRunnersFinalizeNodeScript(): string {
|
||||
return String.raw`
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const tmp = process.env.TMP_DIR;
|
||||
const deleteExit = Number(process.env.DELETE_EXIT || 0);
|
||||
const plan = JSON.parse(fs.readFileSync(path.join(tmp, "plan.json"), "utf8"));
|
||||
const jobsAfter = JSON.parse(fs.readFileSync(path.join(tmp, "jobs-after.json"), "utf8"));
|
||||
const podsAfter = JSON.parse(fs.readFileSync(path.join(tmp, "pods-after.json"), "utf8"));
|
||||
const matchLabels = plan.criteria?.selectors?.matchLabels || {};
|
||||
const jobNamePrefixes = plan.criteria?.selectors?.jobNamePrefixes || [];
|
||||
|
||||
function labelsOf(item) {
|
||||
return item?.metadata?.labels && typeof item.metadata.labels === "object" ? item.metadata.labels : {};
|
||||
}
|
||||
|
||||
function matchesLabels(labels) {
|
||||
return Object.entries(matchLabels).every(([key, value]) => labels?.[key] === value);
|
||||
}
|
||||
|
||||
function matchesPrefix(name) {
|
||||
return jobNamePrefixes.length === 0 || jobNamePrefixes.some((prefix) => name === prefix || name.startsWith(prefix + "-"));
|
||||
}
|
||||
|
||||
function isTerminalPod(pod) {
|
||||
const phase = pod?.status?.phase || "";
|
||||
return phase === "Succeeded" || phase === "Failed";
|
||||
}
|
||||
|
||||
function jobNameForPod(pod) {
|
||||
const labels = labelsOf(pod);
|
||||
if (typeof labels["job-name"] === "string") return labels["job-name"];
|
||||
const owner = (Array.isArray(pod?.metadata?.ownerReferences) ? pod.metadata.ownerReferences : []).find((entry) => entry?.kind === "Job" && typeof entry?.name === "string");
|
||||
return owner?.name || null;
|
||||
}
|
||||
|
||||
function tail(name) {
|
||||
try {
|
||||
const text = fs.readFileSync(path.join(tmp, name), "utf8");
|
||||
return text.length > 3000 ? text.slice(-3000) : text;
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
const selected = new Set(Array.isArray(plan.selectedRunnerJobs) ? plan.selectedRunnerJobs : []);
|
||||
const remainingJobs = (Array.isArray(jobsAfter.items) ? jobsAfter.items : [])
|
||||
.map((job) => ({ name: job?.metadata?.name || "", labels: labelsOf(job) }))
|
||||
.filter((item) => item.name && matchesLabels(item.labels) && matchesPrefix(item.name));
|
||||
const remainingJobNames = new Set(remainingJobs.map((item) => item.name));
|
||||
const matchedPods = (Array.isArray(podsAfter.items) ? podsAfter.items : []).filter((pod) => {
|
||||
const jobName = jobNameForPod(pod);
|
||||
return jobName !== null && remainingJobNames.has(jobName);
|
||||
});
|
||||
const remainingSelectedRunnerJobs = Array.from(selected).filter((name) => remainingJobNames.has(name));
|
||||
const deletedRunnerJobs = Array.from(selected).filter((name) => !remainingJobNames.has(name));
|
||||
console.log(JSON.stringify({
|
||||
...plan,
|
||||
ok: deleteExit === 0,
|
||||
deletion: { exitCode: deleteExit, stdoutTail: tail("delete.out"), stderrTail: tail("delete.err") },
|
||||
deletedRunnerJobs,
|
||||
deletedRunnerJobCount: deletedRunnerJobs.length,
|
||||
remainingSelectedRunnerJobs,
|
||||
remainingSelectedRunnerJobCount: remainingSelectedRunnerJobs.length,
|
||||
after: {
|
||||
runnerJobCount: remainingJobs.length,
|
||||
nonTerminalRunnerPodCount: matchedPods.filter((pod) => !isTerminalPod(pod)).length,
|
||||
overLimitCount: Math.max(0, remainingJobs.length - Number(plan.criteria?.maxRunners || 0)),
|
||||
},
|
||||
valuesPrinted: false,
|
||||
}));
|
||||
`;
|
||||
}
|
||||
|
||||
export function cleanupRunsPlanNodeScript(): string {
|
||||
return String.raw`
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const cp = require("node:child_process");
|
||||
const tmp = process.env.TMP_DIR;
|
||||
const namespace = process.env.NAMESPACE;
|
||||
const pipelineRunPrefix = process.env.PIPELINE_RUN_PREFIX || "";
|
||||
const minAgeMinutes = Number(process.env.MIN_AGE_MINUTES || 60);
|
||||
const limit = Number(process.env.LIMIT || 20);
|
||||
const now = Date.now();
|
||||
|
||||
function readJson(name) {
|
||||
return JSON.parse(fs.readFileSync(path.join(tmp, name), "utf8"));
|
||||
}
|
||||
|
||||
function conditionOf(item) {
|
||||
const conditions = Array.isArray(item?.status?.conditions) ? item.status.conditions : [];
|
||||
return conditions.find((entry) => entry.type === "Succeeded") || conditions[0] || {};
|
||||
}
|
||||
|
||||
function ageMinutes(createdAt) {
|
||||
const createdMs = Date.parse(createdAt || "");
|
||||
return Number.isFinite(createdMs) ? Math.floor((now - createdMs) / 60000) : null;
|
||||
}
|
||||
|
||||
function localPathOf(pv) {
|
||||
return pv?.spec?.local?.path || pv?.spec?.hostPath?.path || null;
|
||||
}
|
||||
|
||||
function duBytes(hostPath) {
|
||||
if (typeof hostPath !== "string" || !hostPath.startsWith("/var/lib/rancher/k3s/storage/")) return null;
|
||||
try {
|
||||
const out = cp.execFileSync("du", ["-sB1", hostPath], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
||||
const bytes = Number(String(out).trim().split(/\s+/)[0]);
|
||||
return Number.isFinite(bytes) ? bytes : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (!Number.isFinite(bytes)) return null;
|
||||
const units = ["B", "KiB", "MiB", "GiB", "TiB"];
|
||||
let value = bytes;
|
||||
let unit = 0;
|
||||
while (value >= 1024 && unit < units.length - 1) {
|
||||
value /= 1024;
|
||||
unit += 1;
|
||||
}
|
||||
return value.toFixed(unit === 0 ? 0 : 1) + units[unit];
|
||||
}
|
||||
|
||||
const pipelineRuns = readJson("pipelineruns.json");
|
||||
const pvcs = readJson("pvcs.json");
|
||||
const pvs = readJson("pvs.json");
|
||||
const pods = readJson("pods.json");
|
||||
const pvByName = new Map((Array.isArray(pvs.items) ? pvs.items : []).map((item) => [item?.metadata?.name, item]));
|
||||
const activeClaimPods = new Map();
|
||||
for (const pod of Array.isArray(pods.items) ? pods.items : []) {
|
||||
const phase = pod?.status?.phase || "";
|
||||
if (phase === "Succeeded" || phase === "Failed") continue;
|
||||
for (const volume of Array.isArray(pod?.spec?.volumes) ? pod.spec.volumes : []) {
|
||||
const claimName = volume?.persistentVolumeClaim?.claimName;
|
||||
if (!claimName) continue;
|
||||
const entry = activeClaimPods.get(claimName) || [];
|
||||
entry.push(pod?.metadata?.name || null);
|
||||
activeClaimPods.set(claimName, entry.filter(Boolean));
|
||||
}
|
||||
}
|
||||
|
||||
const pvcsByOwner = new Map();
|
||||
for (const pvc of Array.isArray(pvcs.items) ? pvcs.items : []) {
|
||||
const owner = (Array.isArray(pvc?.metadata?.ownerReferences) ? pvc.metadata.ownerReferences : []).find((entry) => entry.kind === "PipelineRun");
|
||||
if (!owner?.name) continue;
|
||||
const entry = pvcsByOwner.get(owner.name) || [];
|
||||
entry.push(pvc);
|
||||
pvcsByOwner.set(owner.name, entry);
|
||||
}
|
||||
|
||||
const allPipelineRuns = (Array.isArray(pipelineRuns.items) ? pipelineRuns.items : [])
|
||||
.map((item) => {
|
||||
const condition = conditionOf(item);
|
||||
return {
|
||||
name: item?.metadata?.name || "",
|
||||
createdAt: item?.metadata?.creationTimestamp || null,
|
||||
ageMinutes: ageMinutes(item?.metadata?.creationTimestamp),
|
||||
status: condition.status || null,
|
||||
reason: condition.reason || null,
|
||||
};
|
||||
})
|
||||
.filter((item) => pipelineRunPrefix.length === 0 || item.name.startsWith(pipelineRunPrefix + "-"));
|
||||
|
||||
const protectedActivePipelineRuns = allPipelineRuns
|
||||
.filter((item) => item.status !== "True" && item.status !== "False")
|
||||
.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
|
||||
const protectedLatestPipelineRun = allPipelineRuns
|
||||
.filter((item) => item.status === "True" || item.status === "False")
|
||||
.sort((a, b) => String(b.createdAt).localeCompare(String(a.createdAt)))[0]?.name || null;
|
||||
|
||||
const candidates = allPipelineRuns
|
||||
.filter((item) => item.status === "True" || item.status === "False")
|
||||
.filter((item) => typeof item.ageMinutes === "number" && item.ageMinutes >= minAgeMinutes)
|
||||
.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)))
|
||||
.slice(0, limit)
|
||||
.map((item) => {
|
||||
const owned = pvcsByOwner.get(item.name) || [];
|
||||
const activeMountPods = owned.flatMap((pvc) => activeClaimPods.get(pvc?.metadata?.name) || []);
|
||||
const protectedLatest = item.name === protectedLatestPipelineRun;
|
||||
return {
|
||||
...item,
|
||||
selected: activeMountPods.length === 0 && !protectedLatest,
|
||||
selectedReason: protectedLatest ? "protected-latest-pipelinerun" : activeMountPods.length === 0 ? "terminal-and-unmounted" : "owned-pvc-active-mounted",
|
||||
ownedPvcCount: owned.length,
|
||||
activeMountPods,
|
||||
};
|
||||
});
|
||||
|
||||
const selectedPipelineRuns = candidates.filter((item) => item.selected).map((item) => item.name);
|
||||
const selectedSet = new Set(selectedPipelineRuns);
|
||||
const ownedPvcs = [];
|
||||
const protectedOwnedPvcs = [];
|
||||
for (const [owner, items] of pvcsByOwner.entries()) {
|
||||
if (!selectedSet.has(owner) && !candidates.some((item) => item.name === owner)) continue;
|
||||
for (const pvc of items) {
|
||||
const volume = pvc?.spec?.volumeName || null;
|
||||
const pv = volume ? pvByName.get(volume) : null;
|
||||
const hostPath = localPathOf(pv);
|
||||
const activeMountPods = activeClaimPods.get(pvc?.metadata?.name) || [];
|
||||
const entry = {
|
||||
name: pvc?.metadata?.name || null,
|
||||
volume,
|
||||
phase: pvc?.status?.phase || null,
|
||||
ownerKind: "PipelineRun",
|
||||
owner,
|
||||
storageClass: pv?.spec?.storageClassName || null,
|
||||
reclaimPolicy: pv?.spec?.persistentVolumeReclaimPolicy || null,
|
||||
hostPath,
|
||||
estimatedBytes: duBytes(hostPath),
|
||||
activeMountPods,
|
||||
};
|
||||
if (selectedSet.has(owner)) ownedPvcs.push(entry);
|
||||
else protectedOwnedPvcs.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
const estimatedReclaimBytes = ownedPvcs.reduce((sum, item) => sum + (Number.isFinite(item.estimatedBytes) ? item.estimatedBytes : 0), 0);
|
||||
console.log(JSON.stringify({
|
||||
ok: true,
|
||||
planKind: "agentrun-ci-completed-pipelinerun-workspace-retention",
|
||||
generatedAt: new Date().toISOString(),
|
||||
namespace,
|
||||
criteria: { prefix: pipelineRunPrefix + "-", terminalStatuses: ["True", "False"], minAgeMinutes, limit },
|
||||
candidates,
|
||||
candidateCount: candidates.length,
|
||||
protectedActivePipelineRuns,
|
||||
protectedActivePipelineRunCount: protectedActivePipelineRuns.length,
|
||||
protectedLatestPipelineRun,
|
||||
selectedPipelineRuns,
|
||||
selectedPipelineRunCount: selectedPipelineRuns.length,
|
||||
ownedPvcs,
|
||||
ownedPvcCount: ownedPvcs.length,
|
||||
protectedOwnedPvcs,
|
||||
protectedOwnedPvcCount: protectedOwnedPvcs.length,
|
||||
estimatedReclaimBytes,
|
||||
estimatedReclaimHuman: formatBytes(estimatedReclaimBytes),
|
||||
}));
|
||||
`;
|
||||
}
|
||||
|
||||
export function cleanupRunsFinalizeNodeScript(): string {
|
||||
return String.raw`
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const tmp = process.env.TMP_DIR;
|
||||
const deleteExit = Number(process.env.DELETE_EXIT || 0);
|
||||
const plan = JSON.parse(fs.readFileSync(path.join(tmp, "plan.json"), "utf8"));
|
||||
const pvcsAfter = JSON.parse(fs.readFileSync(path.join(tmp, "pvcs-after.json"), "utf8"));
|
||||
const selected = new Set(Array.isArray(plan.selectedPipelineRuns) ? plan.selectedPipelineRuns : []);
|
||||
const remainingOwnedPvcs = (Array.isArray(pvcsAfter.items) ? pvcsAfter.items : [])
|
||||
.map((pvc) => {
|
||||
const owner = (Array.isArray(pvc?.metadata?.ownerReferences) ? pvc.metadata.ownerReferences : []).find((entry) => entry.kind === "PipelineRun");
|
||||
return {
|
||||
name: pvc?.metadata?.name || null,
|
||||
volume: pvc?.spec?.volumeName || null,
|
||||
phase: pvc?.status?.phase || null,
|
||||
ownerKind: owner?.kind || null,
|
||||
owner: owner?.name || null,
|
||||
};
|
||||
})
|
||||
.filter((item) => item.owner && selected.has(item.owner));
|
||||
|
||||
function tail(name) {
|
||||
try {
|
||||
const text = fs.readFileSync(path.join(tmp, name), "utf8");
|
||||
return text.length > 3000 ? text.slice(-3000) : text;
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify({
|
||||
...plan,
|
||||
ok: deleteExit === 0,
|
||||
deletedPipelineRuns: Array.from(selected),
|
||||
deletedPipelineRunCount: selected.size,
|
||||
deletion: { exitCode: deleteExit, stdoutTail: tail("delete.out"), stderrTail: tail("delete.err") },
|
||||
remainingOwnedPvcs,
|
||||
remainingOwnedPvcCount: remainingOwnedPvcs.length,
|
||||
}));
|
||||
`;
|
||||
}
|
||||
|
||||
export function cleanupReleasedPvsPlanNodeScript(): string {
|
||||
return String.raw`
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const cp = require("node:child_process");
|
||||
const tmp = process.env.TMP_DIR;
|
||||
const namespace = process.env.NAMESPACE;
|
||||
const limit = Number(process.env.LIMIT || 20);
|
||||
|
||||
function readJson(name) {
|
||||
return JSON.parse(fs.readFileSync(path.join(tmp, name), "utf8"));
|
||||
}
|
||||
|
||||
function localPathOf(pv) {
|
||||
return pv?.spec?.local?.path || pv?.spec?.hostPath?.path || null;
|
||||
}
|
||||
|
||||
function duBytes(hostPath) {
|
||||
if (typeof hostPath !== "string" || !hostPath.startsWith("/var/lib/rancher/k3s/storage/")) return null;
|
||||
try {
|
||||
const out = cp.execFileSync("du", ["-sB1", hostPath], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
||||
const bytes = Number(String(out).trim().split(/\s+/)[0]);
|
||||
return Number.isFinite(bytes) ? bytes : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (!Number.isFinite(bytes)) return null;
|
||||
const units = ["B", "KiB", "MiB", "GiB", "TiB"];
|
||||
let value = bytes;
|
||||
let unit = 0;
|
||||
while (value >= 1024 && unit < units.length - 1) {
|
||||
value /= 1024;
|
||||
unit += 1;
|
||||
}
|
||||
return value.toFixed(unit === 0 ? 0 : 1) + units[unit];
|
||||
}
|
||||
|
||||
const pvs = readJson("pvs.json");
|
||||
const candidates = (Array.isArray(pvs.items) ? pvs.items : [])
|
||||
.map((pv) => {
|
||||
const hostPath = localPathOf(pv);
|
||||
return {
|
||||
name: pv?.metadata?.name || "",
|
||||
createdAt: pv?.metadata?.creationTimestamp || null,
|
||||
phase: pv?.status?.phase || null,
|
||||
storageClass: pv?.spec?.storageClassName || null,
|
||||
reclaimPolicy: pv?.spec?.persistentVolumeReclaimPolicy || null,
|
||||
claimNamespace: pv?.spec?.claimRef?.namespace || null,
|
||||
claimName: pv?.spec?.claimRef?.name || null,
|
||||
capacity: pv?.spec?.capacity?.storage || null,
|
||||
hostPath,
|
||||
estimatedBytes: duBytes(hostPath),
|
||||
};
|
||||
})
|
||||
.filter((item) => item.phase === "Released")
|
||||
.filter((item) => item.storageClass === "local-path" && item.reclaimPolicy === "Delete")
|
||||
.filter((item) => item.claimNamespace === namespace)
|
||||
.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)))
|
||||
.slice(0, limit);
|
||||
|
||||
const selectedPersistentVolumes = candidates.map((item) => item.name);
|
||||
const estimatedReclaimBytes = candidates.reduce((sum, item) => sum + (Number.isFinite(item.estimatedBytes) ? item.estimatedBytes : 0), 0);
|
||||
console.log(JSON.stringify({
|
||||
ok: true,
|
||||
planKind: "agentrun-ci-released-local-path-pv-retention",
|
||||
generatedAt: new Date().toISOString(),
|
||||
namespace,
|
||||
criteria: { phase: "Released", storageClass: "local-path", reclaimPolicy: "Delete", claimNamespace: namespace, limit },
|
||||
candidates,
|
||||
candidateCount: candidates.length,
|
||||
selectedPersistentVolumes,
|
||||
selectedPersistentVolumeCount: selectedPersistentVolumes.length,
|
||||
estimatedReclaimBytes,
|
||||
estimatedReclaimHuman: formatBytes(estimatedReclaimBytes),
|
||||
}));
|
||||
`;
|
||||
}
|
||||
|
||||
export function cleanupReleasedPvsFinalizeNodeScript(): string {
|
||||
return String.raw`
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const tmp = process.env.TMP_DIR;
|
||||
const deleteExit = Number(process.env.DELETE_EXIT || 0);
|
||||
const plan = JSON.parse(fs.readFileSync(path.join(tmp, "plan.json"), "utf8"));
|
||||
const pvsAfter = JSON.parse(fs.readFileSync(path.join(tmp, "pvs-after.json"), "utf8"));
|
||||
const selected = new Set(Array.isArray(plan.selectedPersistentVolumes) ? plan.selectedPersistentVolumes : []);
|
||||
const remainingPersistentVolumes = (Array.isArray(pvsAfter.items) ? pvsAfter.items : [])
|
||||
.filter((pv) => selected.has(pv?.metadata?.name))
|
||||
.map((pv) => ({ name: pv?.metadata?.name || null, phase: pv?.status?.phase || null, claimNamespace: pv?.spec?.claimRef?.namespace || null, claimName: pv?.spec?.claimRef?.name || null }));
|
||||
|
||||
function tail(name) {
|
||||
try {
|
||||
const text = fs.readFileSync(path.join(tmp, name), "utf8");
|
||||
return text.length > 3000 ? text.slice(-3000) : text;
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify({
|
||||
...plan,
|
||||
ok: deleteExit === 0,
|
||||
deletedPersistentVolumes: Array.from(selected),
|
||||
deletedPersistentVolumeCount: selected.size,
|
||||
deletion: { exitCode: deleteExit, stdoutTail: tail("delete.out"), stderrTail: tail("delete.err") },
|
||||
remainingPersistentVolumes,
|
||||
remainingPersistentVolumeCount: remainingPersistentVolumes.length,
|
||||
}));
|
||||
`;
|
||||
}
|
||||
|
||||
export function refreshYamlLaneScript(spec: AgentRunLaneSpec): string {
|
||||
return [
|
||||
"set -eu",
|
||||
`namespace=${shQuote(spec.gitops.argoNamespace)}`,
|
||||
`application=${shQuote(spec.gitops.argoApplication)}`,
|
||||
"kubectl -n \"$namespace\" annotate application \"$application\" argocd.argoproj.io/refresh=hard --overwrite >/tmp/agentrun-refresh-output.txt",
|
||||
"kubectl -n \"$namespace\" get application \"$application\" -o json >/tmp/agentrun-refresh-app.json",
|
||||
"NAMESPACE=\"$namespace\" APPLICATION=\"$application\" node <<'NODE'",
|
||||
"const fs = require('node:fs');",
|
||||
"let app = {};",
|
||||
"try { app = JSON.parse(fs.readFileSync('/tmp/agentrun-refresh-app.json', 'utf8')); } catch {}",
|
||||
"console.log(JSON.stringify({",
|
||||
" ok: true,",
|
||||
" namespace: process.env.NAMESPACE,",
|
||||
" application: process.env.APPLICATION,",
|
||||
" revision: app.status?.sync?.revision ?? null,",
|
||||
" syncStatus: app.status?.sync?.status ?? null,",
|
||||
" healthStatus: app.status?.health?.status ?? null,",
|
||||
" observedAt: new Date().toISOString(),",
|
||||
" valuesPrinted: false",
|
||||
"}));",
|
||||
"NODE",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
export async function gitMirrorStatus(config: UniDeskConfig, options: GitMirrorStatusOptions = { full: false, raw: false, node: null, lane: null }): Promise<Record<string, unknown>> {
|
||||
const target = resolveAgentRunLaneTarget(options);
|
||||
const spec = target.spec;
|
||||
const observation = await readGitMirrorStatus(config, target);
|
||||
const summary = observation.summary;
|
||||
return {
|
||||
ok: observation.ok,
|
||||
command: "agentrun git-mirror status",
|
||||
mode: "yaml-declared-node-lane",
|
||||
configPath: target.configPath,
|
||||
target: agentRunLaneSummary(spec),
|
||||
namespace: spec.gitMirror.namespace,
|
||||
readUrl: spec.gitMirror.readUrl,
|
||||
writeUrl: spec.gitMirror.writeUrl,
|
||||
summary,
|
||||
...(options.raw ? { raw: observation.raw } : {}),
|
||||
probe: compactCapture(observation.result, { full: options.full || options.raw, stdoutTailChars: 6000, stderrTailChars: 3000 }),
|
||||
disclosure: {
|
||||
defaultView: "compact-low-noise",
|
||||
full: options.full,
|
||||
raw: options.raw,
|
||||
rawOmitted: !options.raw,
|
||||
probeTailOmitted: !(options.full || options.raw),
|
||||
expandWith: `bun scripts/cli.ts agentrun git-mirror status --node ${spec.nodeId} --lane ${spec.lane} --full`,
|
||||
rawWith: `bun scripts/cli.ts agentrun git-mirror status --node ${spec.nodeId} --lane ${spec.lane} --raw`,
|
||||
},
|
||||
next: {
|
||||
sync: `bun scripts/cli.ts agentrun git-mirror sync --node ${spec.nodeId} --lane ${spec.lane} --confirm`,
|
||||
flush: summary.pendingFlush === true ? `bun scripts/cli.ts agentrun git-mirror flush --node ${spec.nodeId} --lane ${spec.lane} --confirm` : null,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user