343 lines
15 KiB
TypeScript
343 lines
15 KiB
TypeScript
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. cleanup-scripts module for scripts/src/agentrun.ts.
|
|
|
|
// Moved mechanically from scripts/src/agentrun.ts:3187-3310 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 { runYamlLaneGitMirrorFlushJob, runYamlLaneGitMirrorSyncJob, yamlLanePipelineRunCreateScript } from "./secrets";
|
|
import { capture, captureJsonPayload, compactCapture, isGitSha, progressEvent, stringOrNull } from "./utils";
|
|
import { runYamlLaneGitopsPublishJob, runYamlLaneK3sBuildImageJob, waitForYamlLaneBuildImage, waitForYamlLaneSourceBootstrap, yamlLaneBuildImageSubmitScript, yamlLaneK3sSourceStatusScript, yamlLaneSourceBootstrapSubmitScript, yamlLaneSourceRestoreScript } from "./yaml-lane";
|
|
|
|
export async function triggerCurrentYamlLaneConfirmed(config: UniDeskConfig, spec: AgentRunLaneSpec, configPath: string, waited: boolean): Promise<Record<string, unknown>> {
|
|
const result = await triggerCurrentYamlLaneConfirmedSteps(config, spec, configPath, waited);
|
|
if (spec.source.statusMode === "k3s-git-mirror") {
|
|
return {
|
|
...result,
|
|
sourceWorkspaceRestore: { ok: true, status: "skipped", statusMode: spec.source.statusMode, reason: "k3s-git-mirror-does-not-use-host-worktree", valuesPrinted: false },
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
const restore = await capture(config, spec.nodeRoute, ["sh", "--", yamlLaneSourceRestoreScript(spec)]);
|
|
const restorePayload = captureJsonPayload(restore);
|
|
const restoreOk = restore.exitCode === 0 && restorePayload.ok === true;
|
|
const existingWarnings = Array.isArray(result.warnings) ? result.warnings.filter((item): item is string => typeof item === "string") : [];
|
|
return {
|
|
...result,
|
|
warnings: restoreOk ? existingWarnings : Array.from(new Set([...existingWarnings, "source-worktree-restore-failed"])),
|
|
sourceWorkspaceRestore: restorePayload,
|
|
sourceWorkspaceRestoreCapture: compactCapture(restore, { full: !restoreOk, stdoutTailChars: 3000, stderrTailChars: 3000 }),
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
export async function triggerCurrentYamlLaneConfirmedSteps(config: UniDeskConfig, spec: AgentRunLaneSpec, configPath: string, waited: boolean): Promise<Record<string, unknown>> {
|
|
const source = await resolveTriggerCurrentSource(config, spec, configPath, waited);
|
|
if (source.ok !== true) return source;
|
|
const sourceCommit = stringOrNull(source.sourceCommit);
|
|
const bootstrapPayload = source.sourcePayload;
|
|
if (sourceCommit === null || !isGitSha(sourceCommit)) {
|
|
return {
|
|
ok: false,
|
|
command: "agentrun control-plane trigger-current",
|
|
mode: waited ? "confirmed-waited" : "confirmed-trigger",
|
|
configPath,
|
|
target: agentRunLaneSummary(spec),
|
|
phase: "source-resolve",
|
|
degradedReason: "yaml-lane-source-commit-invalid",
|
|
result: bootstrapPayload,
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
const buildResult = await buildTriggerCurrentImage(config, spec, sourceCommit, bootstrapPayload, configPath, waited);
|
|
if (buildResult.ok !== true) return buildResult;
|
|
const build = buildResult.buildStatus;
|
|
const buildSubmitPayload = buildResult.buildSubmitPayload;
|
|
const buildPayload = build.payload;
|
|
const digest = stringOrNull(buildPayload.digest);
|
|
const envIdentity = stringOrNull(buildPayload.envIdentity);
|
|
if (build.ok !== true || digest === null || envIdentity === null) {
|
|
return {
|
|
ok: false,
|
|
command: "agentrun control-plane trigger-current",
|
|
mode: waited ? "confirmed-waited" : "confirmed-trigger",
|
|
configPath,
|
|
target: agentRunLaneSummary(spec),
|
|
phase: "image-build",
|
|
sourceCommit,
|
|
degradedReason: "yaml-lane-image-build-failed",
|
|
sourceBootstrap: bootstrapPayload,
|
|
buildSubmit: buildSubmitPayload,
|
|
result: buildPayload,
|
|
buildStatus: build,
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
const image = agentRunImageArtifact(spec, { sourceCommit, envIdentity, digest, status: stringOrNull(buildPayload.status) ?? "built" });
|
|
const renderedFiles = renderAgentRunGitopsFiles(spec, { sourceCommit, image });
|
|
const mirrorPrePublish = await runYamlLaneGitMirrorSyncJob(config, spec);
|
|
if (mirrorPrePublish.ok !== true) {
|
|
return {
|
|
ok: false,
|
|
command: "agentrun control-plane trigger-current",
|
|
mode: waited ? "confirmed-waited" : "confirmed-trigger",
|
|
configPath,
|
|
target: agentRunLaneSummary(spec),
|
|
phase: "git-mirror-prepublish-sync",
|
|
sourceCommit,
|
|
image,
|
|
degradedReason: "yaml-lane-git-mirror-prepublish-sync-failed",
|
|
sourceBootstrap: bootstrapPayload,
|
|
imageBuild: buildPayload,
|
|
result: mirrorPrePublish,
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
progressEvent("agentrun.yaml-lane.gitops-publish.progress", {
|
|
node: spec.nodeId,
|
|
lane: spec.lane,
|
|
sourceCommit,
|
|
status: "submitting",
|
|
});
|
|
const gitops = await runYamlLaneGitopsPublishJob(config, spec, sourceCommit, renderedFiles);
|
|
const gitopsPayload = gitops.payload;
|
|
if (gitops.ok !== true || gitopsPayload.ok === false) {
|
|
return {
|
|
ok: false,
|
|
command: "agentrun control-plane trigger-current",
|
|
mode: waited ? "confirmed-waited" : "confirmed-trigger",
|
|
configPath,
|
|
target: agentRunLaneSummary(spec),
|
|
phase: "gitops-publish",
|
|
sourceCommit,
|
|
image,
|
|
degradedReason: "yaml-lane-gitops-publish-failed",
|
|
sourceBootstrap: bootstrapPayload,
|
|
imageBuild: buildPayload,
|
|
gitMirrorPrePublish: mirrorPrePublish,
|
|
result: gitopsPayload,
|
|
gitopsPublishStatus: gitops,
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
const mirrorFlush = await runYamlLaneGitMirrorFlushJob(config, spec);
|
|
if (mirrorFlush.ok !== true) {
|
|
return {
|
|
ok: false,
|
|
command: "agentrun control-plane trigger-current",
|
|
mode: waited ? "confirmed-waited" : "confirmed-trigger",
|
|
configPath,
|
|
target: agentRunLaneSummary(spec),
|
|
phase: "git-mirror-flush",
|
|
sourceCommit,
|
|
image,
|
|
gitops: gitopsPayload,
|
|
gitMirrorPrePublish: mirrorPrePublish,
|
|
degradedReason: "yaml-lane-git-mirror-flush-failed",
|
|
result: mirrorFlush,
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
const pipelineRun = agentRunPipelineRunName(spec, sourceCommit);
|
|
const created = await capture(config, spec.nodeKubeRoute, ["sh", "--", yamlLanePipelineRunCreateScript(spec, sourceCommit, pipelineRun)]);
|
|
const createPayload = captureJsonPayload(created);
|
|
return {
|
|
ok: created.exitCode === 0 && createPayload.ok !== false,
|
|
command: "agentrun control-plane trigger-current",
|
|
mode: waited ? "confirmed-waited" : "confirmed-trigger",
|
|
mutation: true,
|
|
configPath,
|
|
target: agentRunLaneSummary(spec),
|
|
sourceCommit,
|
|
pipelineRun,
|
|
image,
|
|
sourceBootstrap: bootstrapPayload,
|
|
imageBuildSubmit: buildSubmitPayload,
|
|
imageBuild: buildPayload,
|
|
renderedFiles: {
|
|
count: renderedFiles.length,
|
|
digest: renderedFilesDigest(renderedFiles),
|
|
},
|
|
gitops: gitopsPayload,
|
|
gitMirrorPrePublish: mirrorPrePublish,
|
|
gitMirror: mirrorFlush,
|
|
created: createPayload,
|
|
capture: compactCapture(created, { full: created.exitCode !== 0, stdoutTailChars: 4000, stderrTailChars: 4000 }),
|
|
next: {
|
|
status: `bun scripts/cli.ts agentrun control-plane status --node ${spec.nodeId} --lane ${spec.lane}`,
|
|
statusByPipelineRun: `bun scripts/cli.ts agentrun control-plane status --node ${spec.nodeId} --lane ${spec.lane} --pipeline-run ${pipelineRun} --full`,
|
|
},
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
async function resolveTriggerCurrentSource(config: UniDeskConfig, spec: AgentRunLaneSpec, configPath: string, waited: boolean): Promise<Record<string, unknown> & { ok: boolean; sourceCommit?: string | null; sourcePayload?: Record<string, unknown> }> {
|
|
if (spec.source.statusMode === "k3s-git-mirror") {
|
|
progressEvent("agentrun.yaml-lane.source-snapshot.progress", {
|
|
node: spec.nodeId,
|
|
lane: spec.lane,
|
|
statusMode: spec.source.statusMode,
|
|
status: "syncing",
|
|
});
|
|
const sourceSync = await runYamlLaneGitMirrorSyncJob(config, spec);
|
|
if (sourceSync.ok !== true) {
|
|
return {
|
|
ok: false,
|
|
command: "agentrun control-plane trigger-current",
|
|
mode: waited ? "confirmed-waited" : "confirmed-trigger",
|
|
configPath,
|
|
target: agentRunLaneSummary(spec),
|
|
phase: "source-snapshot-sync",
|
|
degradedReason: "yaml-lane-k3s-source-snapshot-sync-failed",
|
|
result: sourceSync,
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
progressEvent("agentrun.yaml-lane.source-status.progress", {
|
|
node: spec.nodeId,
|
|
lane: spec.lane,
|
|
statusMode: spec.source.statusMode,
|
|
status: "probing-snapshot",
|
|
});
|
|
const sourceStatus = await capture(config, spec.nodeKubeRoute, ["sh", "--", yamlLaneK3sSourceStatusScript(spec)]);
|
|
const sourcePayload = captureJsonPayload(sourceStatus);
|
|
const sourceCommit = stringOrNull(sourcePayload.sourceCommit) ?? stringOrNull(sourcePayload.remoteBranchCommit);
|
|
if (sourceStatus.exitCode !== 0 || sourcePayload.ok !== true || sourceCommit === null || !isGitSha(sourceCommit)) {
|
|
return {
|
|
ok: false,
|
|
command: "agentrun control-plane trigger-current",
|
|
mode: waited ? "confirmed-waited" : "confirmed-trigger",
|
|
configPath,
|
|
target: agentRunLaneSummary(spec),
|
|
phase: "source-status",
|
|
degradedReason: stringOrNull(sourcePayload.degradedReason) ?? "yaml-lane-k3s-source-status-failed",
|
|
gitMirrorSync: sourceSync,
|
|
result: sourcePayload,
|
|
capture: compactCapture(sourceStatus, { full: true, stdoutTailChars: 5000, stderrTailChars: 5000 }),
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
return { ok: true, sourceCommit, sourcePayload: { ...sourcePayload, gitMirrorSync: sourceSync, valuesPrinted: false }, valuesPrinted: false };
|
|
}
|
|
|
|
progressEvent("agentrun.yaml-lane.source-bootstrap.progress", {
|
|
node: spec.nodeId,
|
|
lane: spec.lane,
|
|
status: "submitting",
|
|
});
|
|
const bootstrapSubmit = await capture(config, spec.nodeRoute, ["sh", "--", yamlLaneSourceBootstrapSubmitScript(spec)]);
|
|
const bootstrapSubmitPayload = captureJsonPayload(bootstrapSubmit);
|
|
if (bootstrapSubmit.exitCode !== 0 || bootstrapSubmitPayload.ok === false) {
|
|
return {
|
|
ok: false,
|
|
command: "agentrun control-plane trigger-current",
|
|
mode: waited ? "confirmed-waited" : "confirmed-trigger",
|
|
configPath,
|
|
target: agentRunLaneSummary(spec),
|
|
phase: "source-bootstrap-submit",
|
|
degradedReason: "yaml-lane-source-bootstrap-submit-failed",
|
|
result: bootstrapSubmitPayload,
|
|
capture: compactCapture(bootstrapSubmit, { full: true, stdoutTailChars: 5000, stderrTailChars: 5000 }),
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
const bootstrap = await waitForYamlLaneSourceBootstrap(config, spec, stringOrNull(bootstrapSubmitPayload.jobId));
|
|
const sourcePayload = bootstrap.payload;
|
|
const sourceCommit = stringOrNull(sourcePayload.sourceCommit);
|
|
if (bootstrap.ok !== true || sourceCommit === null || !isGitSha(sourceCommit)) {
|
|
return {
|
|
ok: false,
|
|
command: "agentrun control-plane trigger-current",
|
|
mode: waited ? "confirmed-waited" : "confirmed-trigger",
|
|
configPath,
|
|
target: agentRunLaneSummary(spec),
|
|
phase: "source-bootstrap",
|
|
degradedReason: "yaml-lane-source-bootstrap-failed",
|
|
result: sourcePayload,
|
|
bootstrapStatus: bootstrap,
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
return { ok: true, sourceCommit, sourcePayload, sourceBootstrapSubmit: bootstrapSubmitPayload, valuesPrinted: false };
|
|
}
|
|
|
|
async function buildTriggerCurrentImage(config: UniDeskConfig, spec: AgentRunLaneSpec, sourceCommit: string, sourcePayload: Record<string, unknown>, configPath: string, waited: boolean): Promise<Record<string, unknown> & { ok: boolean; buildStatus?: Record<string, unknown> & { ok: boolean; payload: Record<string, unknown> }; buildSubmitPayload?: Record<string, unknown> }> {
|
|
if (spec.source.statusMode === "k3s-git-mirror") {
|
|
const build = await runYamlLaneK3sBuildImageJob(config, spec, sourceCommit);
|
|
const buildSubmitPayload = {
|
|
ok: build.ok !== false,
|
|
status: "submitted",
|
|
mode: "k3s-buildkit-job",
|
|
jobName: stringOrNull(build.jobName) ?? null,
|
|
valuesPrinted: false,
|
|
};
|
|
if (build.ok !== true) {
|
|
return {
|
|
ok: false,
|
|
command: "agentrun control-plane trigger-current",
|
|
mode: waited ? "confirmed-waited" : "confirmed-trigger",
|
|
configPath,
|
|
target: agentRunLaneSummary(spec),
|
|
phase: "image-build",
|
|
sourceCommit,
|
|
degradedReason: "yaml-lane-k3s-image-build-failed",
|
|
sourceBootstrap: sourcePayload,
|
|
buildSubmit: buildSubmitPayload,
|
|
result: build.payload,
|
|
buildStatus: build,
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
return { ok: true, buildStatus: build, buildSubmitPayload, valuesPrinted: false };
|
|
}
|
|
|
|
const buildSubmit = await capture(config, spec.nodeRoute, ["sh", "--", yamlLaneBuildImageSubmitScript(spec, sourceCommit)]);
|
|
const buildSubmitPayload = captureJsonPayload(buildSubmit);
|
|
if (buildSubmit.exitCode !== 0 || buildSubmitPayload.ok === false) {
|
|
return {
|
|
ok: false,
|
|
command: "agentrun control-plane trigger-current",
|
|
mode: waited ? "confirmed-waited" : "confirmed-trigger",
|
|
configPath,
|
|
target: agentRunLaneSummary(spec),
|
|
phase: "image-build-submit",
|
|
sourceCommit,
|
|
degradedReason: "yaml-lane-image-build-submit-failed",
|
|
sourceBootstrap: sourcePayload,
|
|
result: buildSubmitPayload,
|
|
capture: compactCapture(buildSubmit, { full: true, stdoutTailChars: 5000, stderrTailChars: 5000 }),
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
const build = await waitForYamlLaneBuildImage(config, spec, sourceCommit, stringOrNull(buildSubmitPayload.jobId));
|
|
return { ok: true, buildStatus: build, buildSubmitPayload, valuesPrinted: false };
|
|
}
|