feat: 收敛 PaC 首发 bootstrap

This commit is contained in:
Codex
2026-07-13 09:27:42 +02:00
parent 72e2f13587
commit 8c216a02df
9 changed files with 393 additions and 11 deletions
+129 -2
View File
@@ -28,6 +28,9 @@ import {
} from "./platform-infra-pipelines-as-code-source-artifact";
import { pacAdmissionDesiredIdentity } from "./platform-infra-pac-provenance";
import { pacConsumerRbacDesiredIdentity, renderPacConsumerRbacDesiredFragment } from "./platform-infra-pac-consumer-rbac";
import { runPlatformInfraGiteaCommand } from "./platform-infra-gitea";
import { readGiteaConfig } from "./platform-infra-gitea-config";
import { resolvePacBootstrapMirrorRepository } from "./platform-infra-pipelines-as-code-bootstrap";
const configFile = rootPath("config", "platform-infra", "pipelines-as-code.yaml");
const configLabel = "config/platform-infra/pipelines-as-code.yaml";
@@ -240,6 +243,11 @@ export async function runPlatformInfraPipelinesAsCodeCommand(config: UniDeskConf
const result = await apply(config, options);
return options.json || options.full || options.raw ? result : renderApply(result);
}
if (action === "bootstrap") {
const options = parseApplyOptions(args.slice(1));
const result = await bootstrap(config, options);
return options.json || options.full || options.raw ? result : renderBootstrap(result);
}
if (action === "status") {
const options = parseCommonOptions(args.slice(1));
const result = await status(config, options);
@@ -353,10 +361,12 @@ function help(scope: string | null): Record<string, unknown> {
scope: "explicit-initial-platform-bootstrap",
configTruth: configLabel,
usage: [
"bun scripts/cli.ts platform-infra pipelines-as-code bootstrap --target <NODE> --consumer <consumer> --dry-run",
"bun scripts/cli.ts platform-infra pipelines-as-code bootstrap --target <NODE> --consumer <consumer> --confirm",
"bun scripts/cli.ts platform-infra pipelines-as-code apply --target <NODE> --dry-run",
"bun scripts/cli.ts platform-infra pipelines-as-code apply --target <NODE> --confirm",
],
boundary: "仅用于首次安装或显式平台 bootstrap;不得在 source PR 合并后用于交付、恢复或补跑。",
boundary: "bootstrap 是首次引导的最短入口;apply 保留为单层维护入口。两者都不得在 source PR 合并后用于交付、恢复或补跑。",
mutation: "explicit-confirm-required",
};
}
@@ -889,7 +899,7 @@ async function apply(config: UniDeskConfig, options: ApplyOptions): Promise<Reco
return {
ok: result.exitCode === 0 && parsed?.ok === true,
action: "platform-infra-pipelines-as-code-apply",
mutation: !options.dryRun,
mutation: options.dryRun ? false : parsed?.mutation !== false,
mode: options.dryRun ? "dry-run" : "confirmed",
target: targetSummary(target),
config: compactConfigSummary(pac, consumer, repository),
@@ -902,6 +912,79 @@ async function apply(config: UniDeskConfig, options: ApplyOptions): Promise<Reco
};
}
async function bootstrap(config: UniDeskConfig, options: ApplyOptions): Promise<Record<string, unknown>> {
const pac = readPacConfig();
const target = resolveTarget(pac, options.targetId);
const consumer = resolveConsumer(pac, options.consumerId);
if (consumer.node.toLowerCase() !== target.id.toLowerCase()) {
throw new Error(`Pipelines-as-Code consumer ${consumer.id} belongs to ${consumer.node}, not target ${target.id}`);
}
const repository = resolveRepository(pac, consumer.repositoryRef);
const mirror = resolvePacBootstrapMirrorRepository(readGiteaConfig(), target.id, repository);
const giteaArgs = ["mirror", "bootstrap", "--target", target.id, "--repo", mirror.key, ...(options.confirm ? ["--confirm"] : []), "--full"];
const giteaBootstrap = record(await runPlatformInfraGiteaCommand(config, giteaArgs));
if (options.confirm && giteaBootstrap.ok !== true) {
return bootstrapResult(target, consumer, repository, mirror, options, giteaBootstrap, {
ok: false,
mutation: false,
mode: "skipped",
error: "gitea-bootstrap-failed",
valuesPrinted: false,
});
}
const pacApply = await apply(config, options);
return bootstrapResult(target, consumer, repository, mirror, options, giteaBootstrap, pacApply);
}
function bootstrapResult(
target: PacTarget,
consumer: PacConsumer,
repository: PacRepository,
mirror: ReturnType<typeof resolvePacBootstrapMirrorRepository>,
options: ApplyOptions,
giteaBootstrap: Record<string, unknown>,
pacApply: Record<string, unknown>,
): Record<string, unknown> {
const remote = record(pacApply.remote);
const repositoryMissing = remote.error === "gitea-repository-missing";
const pacReady = pacApply.ok === true || (!options.confirm && repositoryMissing);
const ok = giteaBootstrap.ok === true && pacReady;
const confirm = `bun scripts/cli.ts platform-infra pipelines-as-code bootstrap --target ${target.id} --consumer ${consumer.id} --confirm`;
return {
ok,
action: "platform-infra-pipelines-as-code-bootstrap",
mutation: giteaBootstrap.mutation === true || pacApply.mutation === true,
mode: options.confirm ? "confirmed" : "dry-run",
target: targetSummary(target),
consumer: consumerObservationSummary(consumer),
repository: repositorySummary(repository),
mirrorRepository: {
key: mirror.key,
upstreamRepository: mirror.upstream.repository,
upstreamBranch: mirror.upstream.branch,
owner: mirror.gitea.owner,
repo: mirror.gitea.name,
valuesPrinted: false,
},
steps: [
{ id: "gitea-repository", ok: giteaBootstrap.ok === true, mutation: giteaBootstrap.mutation === true, mode: giteaBootstrap.mode, valuesPrinted: false },
{ id: "pac-tekton-gitops", ok: pacReady, mutation: pacApply.mutation === true, mode: pacApply.mode, repositoryMissing, valuesPrinted: false },
],
giteaBootstrap,
pipelinesAsCodeApply: pacApply,
next: options.confirm
? {
deliveryTrigger: `Merge the prepared GitHub PR for ${mirror.upstream.repository}@${mirror.upstream.branch}; the automatic chain owns mirror, PaC, Tekton, GitOps, Argo, and runtime convergence.`,
investigate: `bun scripts/cli.ts platform-infra pipelines-as-code status --target ${target.id} --consumer ${consumer.id}`,
}
: {
confirm,
boundary: "Run confirm only before the first source PR merge; do not use bootstrap as post-merge recovery.",
},
valuesPrinted: false,
};
}
async function status(config: UniDeskConfig, options: CommonOptions): Promise<Record<string, unknown>> {
const pac = readPacConfig();
const target = resolveTarget(pac, options.targetId);
@@ -2023,6 +2106,7 @@ function renderPlan(result: Record<string, unknown>): RenderedCliResult {
function renderApply(result: Record<string, unknown>): RenderedCliResult {
const target = record(result.target);
const remote = record(result.remote);
const preflight = record(remote.preflight);
const errorText = compactLine(stringValue(remote.stderrTail, stringValue(remote.error, stringValue(result.error, ""))));
const lines = [
"PLATFORM-INFRA PIPELINES-AS-CODE APPLY",
@@ -2030,7 +2114,13 @@ function renderApply(result: Record<string, unknown>): RenderedCliResult {
"",
"REMOTE",
...table(["CONTROLLER", "WEBHOOK", "REPOSITORY", "EXIT", "VALUES"], [[stringValue(remote.controllerReady), stringValue(record(remote.webhook).present), stringValue(remote.repository), stringValue(remote.exitCode, "0"), "false"]]),
...(Object.keys(preflight).length === 0 ? [] : [
"",
"PREFLIGHT",
...table(["GITEA_REPOSITORY", "HTTP", "EXISTS"], [[stringValue(preflight.repository), stringValue(preflight.httpStatus), boolText(preflight.repositoryExists)]]),
]),
...(result.ok === false && errorText.length > 0 ? ["", `ERROR ${errorText}`] : []),
...(remote.error === "gitea-repository-missing" ? ["HELP bun scripts/cli.ts platform-infra pipelines-as-code help platform-bootstrap"] : []),
"",
"NEXT",
` status: ${stringValue(record(result.next).status)}`,
@@ -2038,6 +2128,43 @@ function renderApply(result: Record<string, unknown>): RenderedCliResult {
return rendered(result, "platform-infra pipelines-as-code apply", lines);
}
function renderBootstrap(result: Record<string, unknown>): RenderedCliResult {
const target = record(result.target);
const consumer = record(result.consumer);
const mirror = record(result.mirrorRepository);
const steps = arrayRecords(result.steps);
const pacApply = record(result.pipelinesAsCodeApply);
const pacRemote = record(pacApply.remote);
const giteaBootstrap = record(result.giteaBootstrap);
const next = record(result.next);
const errorValue = [pacRemote.error, giteaBootstrap.error, pacApply.error]
.find((value): value is string => typeof value === "string" && value.length > 0) ?? "";
const error = errorValue.length === 0 ? "" : compactLine(errorValue);
const lines = [
"PLATFORM-INFRA PAC / TEKTON / GITOPS BOOTSTRAP",
...table(["TARGET", "CONSUMER", "MODE", "MUTATION", "OK"], [[stringValue(target.id), stringValue(consumer.id), stringValue(result.mode), boolText(result.mutation), boolText(result.ok)]]),
"",
"SOURCE AUTHORITY",
...table(["GITEA_KEY", "GITEA_REPOSITORY", "UPSTREAM", "BRANCH"], [[stringValue(mirror.key), `${stringValue(mirror.owner)}/${stringValue(mirror.repo)}`, stringValue(mirror.upstreamRepository), stringValue(mirror.upstreamBranch)]]),
"",
"STEPS",
...table(["STEP", "OK", "MODE", "MUTATION", "DETAIL"], steps.map((step) => [
stringValue(step.id),
boolText(step.ok),
stringValue(step.mode),
boolText(step.mutation),
step.repositoryMissing === true ? "repository-will-be-created-on-confirm" : "ready",
])),
...(error.length === 0 ? [] : ["", `ERROR ${error}`]),
"",
"NEXT",
...(result.mode === "dry-run"
? [` confirm: ${stringValue(next.confirm)}`, ` boundary: ${stringValue(next.boundary)}`]
: [` delivery: ${stringValue(next.deliveryTrigger)}`, ` investigate: ${stringValue(next.investigate)}`]),
];
return rendered(result, "platform-infra pipelines-as-code bootstrap", lines);
}
function renderStatus(result: Record<string, unknown>): RenderedCliResult {
const summary = record(result.summary);
const consumer = record(result.consumer);