162 lines
7.0 KiB
TypeScript
162 lines
7.0 KiB
TypeScript
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. install module for scripts/src/ci.ts.
|
|
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
|
|
|
// Moved mechanically from scripts/src/ci.ts:1429-1559 for #903.
|
|
|
|
import { randomUUID } from "node:crypto";
|
|
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join, posix as posixPath } from "node:path";
|
|
import { blockedCatalogArtifactIds, catalogSummary, findCiCatalogArtifact, loadCiCatalog, supportedSourceBuildArtifactIds, type CiCatalogArtifact, type CiSourceBuildCatalogArtifact, type CiUpstreamImageCatalogArtifact } from "../ci-catalog";
|
|
import { runCommand } from "../command";
|
|
import { type UniDeskConfig, repoRoot, rootPath } from "../config";
|
|
import { ensureGithubSshIdentityForProvider, gitSshHttpConnectProxySource } from "../deploy-ssh-identity";
|
|
import { jobWithTail, listJobs, readJob, startJob } from "../jobs";
|
|
import { coreInternalFetch } from "../microservices";
|
|
import {
|
|
artifactRegistryReadonlyResultFromCommand,
|
|
buildArtifactRegistryReadonlyProbe,
|
|
parseArtifactRegistryOptions,
|
|
type ArtifactRegistryReadonlyProbe,
|
|
} from "../artifact-registry";
|
|
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
|
|
import { runSshCommandCapture } from "../ssh";
|
|
|
|
import type { CiInstallOptions } from "./types";
|
|
import { status } from "./cleanup";
|
|
import { ciTargetGuardShellLines, shellQuote } from "./options";
|
|
import { emitCiInstallProgress } from "./preflight";
|
|
import { prewarmCiRuntimeImages, remoteApplyManifest, runRemoteBackground } from "./remote";
|
|
import { tektonPipelineReleaseUrl, tektonTriggersInterceptorsUrl, tektonTriggersReleaseUrl } from "./types";
|
|
|
|
export async function install(config: UniDeskConfig, options: CiInstallOptions): Promise<Record<string, unknown>> {
|
|
if (!existsSync(rootPath(options.target.pipelineManifest))) {
|
|
throw new Error("CI manifests are missing");
|
|
}
|
|
emitCiInstallProgress("install", "started", {
|
|
providerId: options.target.providerId,
|
|
skipPrewarm: options.skipPrewarm,
|
|
skipTektonInstall: options.skipTektonInstall,
|
|
});
|
|
try {
|
|
if (options.skipPrewarm) {
|
|
emitCiInstallProgress("prewarm", "skipped", { providerId: options.target.providerId });
|
|
} else {
|
|
emitCiInstallProgress("prewarm", "started", { providerId: options.target.providerId });
|
|
await prewarmCiRuntimeImages(options.target);
|
|
emitCiInstallProgress("prewarm", "succeeded", { providerId: options.target.providerId });
|
|
}
|
|
if (options.skipTektonInstall) {
|
|
emitCiInstallProgress("install-tekton", "skipped", { providerId: options.target.providerId });
|
|
} else {
|
|
emitCiInstallProgress("install-tekton", "started", { providerId: options.target.providerId });
|
|
const installTektonScript = [
|
|
"set -euo pipefail",
|
|
...ciTargetGuardShellLines(options.target),
|
|
`kubectl apply -f ${shellQuote(tektonPipelineReleaseUrl)}`,
|
|
"kubectl wait --for=condition=Available deployment --all -n tekton-pipelines --timeout=900s",
|
|
`kubectl apply -f ${shellQuote(tektonTriggersReleaseUrl)}`,
|
|
`kubectl apply -f ${shellQuote(tektonTriggersInterceptorsUrl)}`,
|
|
"kubectl wait --for=condition=Available deployment --all -n tekton-pipelines --timeout=900s",
|
|
"kubectl wait --for=condition=Available deployment --all -n tekton-pipelines-resolvers --timeout=900s",
|
|
].join("\n");
|
|
const installTekton = await runRemoteBackground("install-tekton", installTektonScript, 1_200_000, options.target);
|
|
if (!installTekton.ok) throw new Error(`Tekton install failed: ${installTekton.stderr || installTekton.stdout}`);
|
|
emitCiInstallProgress("install-tekton", "succeeded", { providerId: options.target.providerId });
|
|
}
|
|
for (const manifest of [
|
|
"src/components/microservices/k3sctl-adapter/k3s/ci/tekton-install.yaml",
|
|
options.target.pipelineManifest,
|
|
"src/components/microservices/k3sctl-adapter/k3s/ci/unidesk-ci.triggers.yaml",
|
|
]) {
|
|
emitCiInstallProgress("apply-manifest", "started", { providerId: options.target.providerId, manifest });
|
|
await remoteApplyManifest(config, manifest, options.target);
|
|
emitCiInstallProgress("apply-manifest", "succeeded", { providerId: options.target.providerId, manifest });
|
|
}
|
|
emitCiInstallProgress("status", "started", { providerId: options.target.providerId });
|
|
const summary = await status(options.target);
|
|
emitCiInstallProgress("status", "succeeded", { providerId: options.target.providerId });
|
|
emitCiInstallProgress("install", "succeeded", { providerId: options.target.providerId });
|
|
return {
|
|
...summary,
|
|
install: {
|
|
providerId: options.target.providerId,
|
|
prewarm: options.skipPrewarm ? "skipped" : "completed",
|
|
tektonInstall: options.skipTektonInstall ? "skipped" : "completed",
|
|
},
|
|
};
|
|
} catch (error) {
|
|
emitCiInstallProgress("install", "failed", {
|
|
providerId: options.target.providerId,
|
|
errorTail: (error instanceof Error ? error.message : String(error)).slice(-1200),
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export function ciInstallCommand(options: CiInstallOptions): string[] {
|
|
return [
|
|
"bun",
|
|
"scripts/cli.ts",
|
|
"ci",
|
|
"install",
|
|
"--target",
|
|
options.target.targetId,
|
|
"--wait",
|
|
...(options.skipPrewarm ? ["--skip-prewarm"] : []),
|
|
...(options.skipTektonInstall ? ["--skip-tekton-install"] : []),
|
|
];
|
|
}
|
|
|
|
export function installAsync(options: CiInstallOptions): Record<string, unknown> {
|
|
const command = ciInstallCommand(options);
|
|
const job = startJob(
|
|
"ci_install",
|
|
command,
|
|
`Install/refresh Tekton CI on ${options.target.providerId} native k3s`,
|
|
);
|
|
return {
|
|
ok: true,
|
|
mode: "async",
|
|
providerId: options.target.providerId,
|
|
skipped: {
|
|
prewarm: options.skipPrewarm,
|
|
tektonInstall: options.skipTektonInstall,
|
|
},
|
|
job: {
|
|
id: job.id,
|
|
status: job.status,
|
|
command: job.command,
|
|
stdoutFile: job.stdoutFile,
|
|
stderrFile: job.stderrFile,
|
|
note: job.note,
|
|
},
|
|
next: [
|
|
`bun scripts/cli.ts ci install-status ${job.id}`,
|
|
`bun scripts/cli.ts job status ${job.id} --tail-bytes 12000`,
|
|
],
|
|
boundary: "ci install is fire-and-forget by default; use --wait only for explicit synchronous debugging.",
|
|
};
|
|
}
|
|
|
|
export function latestCiInstallJobId(): string {
|
|
const job = listJobs().find((item) => item.name === "ci_install");
|
|
if (job === undefined) throw new Error("no ci_install job found");
|
|
return job.id;
|
|
}
|
|
|
|
export function installStatus(id: string): Record<string, unknown> {
|
|
const jobId = id === "latest" || id.length === 0 ? latestCiInstallJobId() : id;
|
|
const job = readJob(jobId);
|
|
if (job.name !== "ci_install") {
|
|
throw new Error(`job ${jobId} is ${job.name}, not ci_install`);
|
|
}
|
|
return {
|
|
ok: job.status !== "failed" && job.status !== "canceled",
|
|
job: jobWithTail(job, 12_000),
|
|
next: job.status === "running" || job.status === "queued"
|
|
? [`bun scripts/cli.ts ci install-status ${job.id}`]
|
|
: ["bun scripts/cli.ts ci status"],
|
|
};
|
|
}
|