feat: 收敛 PaC 首发 bootstrap
This commit is contained in:
@@ -27,17 +27,24 @@ export function renderMirrorPlan(result: Record<string, unknown>): RenderedCliRe
|
||||
}
|
||||
|
||||
export function renderMirrorBootstrap(result: Record<string, unknown>): RenderedCliResult {
|
||||
const repos = arrayRecords(result.repositories).map((repo) => [stringValue(repo.key), stringValue(repo.owner), stringValue(repo.name), boolText(record(repo.create).ok)]);
|
||||
const repos = arrayRecords(result.repositories).map((repo) => [
|
||||
stringValue(repo.key),
|
||||
stringValue(repo.owner, stringValue(repo.giteaOwner)),
|
||||
stringValue(repo.name, stringValue(repo.giteaRepo)),
|
||||
result.mode === "dry-run" ? "planned" : boolText(record(repo.create).ok),
|
||||
]);
|
||||
const blockers = Array.isArray(result.blockers) ? result.blockers.map(String) : [];
|
||||
const next = record(result.next);
|
||||
return rendered(result, "platform-infra gitea mirror bootstrap", [
|
||||
"PLATFORM-INFRA GITEA MIRROR BOOTSTRAP",
|
||||
...table(["OK", "MUTATION", "TARGET"], [[boolText(result.ok), boolText(result.mutation), stringValue(record(result.target).id)]]),
|
||||
...table(["OK", "MODE", "MUTATION", "TARGET"], [[boolText(result.ok), stringValue(result.mode), boolText(result.mutation), stringValue(record(result.target).id)]]),
|
||||
"",
|
||||
"REPOSITORIES",
|
||||
...(repos.length === 0 ? ["-"] : table(["KEY", "OWNER", "REPO", "API_OK"], repos)),
|
||||
...(repos.length === 0 ? ["-"] : table(["KEY", "OWNER", "REPO", "STATE"], repos)),
|
||||
...(blockers.length === 0 ? [] : ["", `BLOCKERS ${blockers.join(",")}`]),
|
||||
...remoteErrorLines(result),
|
||||
"",
|
||||
`NEXT ${stringValue(next.webhookStatus)}`,
|
||||
`NEXT ${stringValue(result.mode === "dry-run" ? next.confirm : next.webhookStatus)}`,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,8 @@ interface ApplyOptions extends CommonOptions {
|
||||
|
||||
interface MirrorOptions extends CommonOptions {
|
||||
confirm: boolean;
|
||||
dryRun: boolean;
|
||||
wait: boolean;
|
||||
repoKey: string | null;
|
||||
}
|
||||
|
||||
@@ -127,6 +129,7 @@ export function giteaHelp(scope?: string): Record<string, unknown> {
|
||||
usage: [
|
||||
"bun scripts/cli.ts platform-infra gitea apply --target <NODE> --dry-run",
|
||||
"bun scripts/cli.ts platform-infra gitea apply --target <NODE> --confirm",
|
||||
"bun scripts/cli.ts platform-infra gitea mirror bootstrap --target <NODE> --dry-run",
|
||||
"bun scripts/cli.ts platform-infra gitea mirror bootstrap --target <NODE> --confirm",
|
||||
"bun scripts/cli.ts platform-infra gitea mirror webhook apply --target <NODE> --confirm",
|
||||
],
|
||||
@@ -357,8 +360,28 @@ async function mirrorStatus(config: UniDeskConfig, options: CommonOptions & { re
|
||||
async function mirrorBootstrap(config: UniDeskConfig, options: MirrorOptions): Promise<Record<string, unknown>> {
|
||||
const gitea = readGiteaConfig();
|
||||
const target = resolveCommandTarget(gitea, options.targetId);
|
||||
if (!options.confirm) return { ok: false, action: "platform-infra-gitea-mirror-bootstrap", mutation: false, mode: "missing-confirm", error: "mirror bootstrap requires --confirm" };
|
||||
const repos = selectedRepositories(gitea, target, options.repoKey);
|
||||
if (!options.confirm) {
|
||||
const credentials = credentialSummaries(gitea);
|
||||
const blockers = credentials
|
||||
.filter((item) => item.id === "github-upstream" && (item.present !== true || item.requiredKeysPresent !== true))
|
||||
.map((item) => String(item.id));
|
||||
const repoFlag = options.repoKey === null ? "" : ` --repo ${options.repoKey}`;
|
||||
return {
|
||||
ok: blockers.length === 0,
|
||||
action: "platform-infra-gitea-mirror-bootstrap",
|
||||
mutation: false,
|
||||
mode: "dry-run",
|
||||
target: targetSummary(target),
|
||||
repositories: repos.map((repo) => repositorySummary(gitea, repo)),
|
||||
credentials,
|
||||
blockers,
|
||||
next: {
|
||||
...mirrorReadOnlyNextCommands(target.id),
|
||||
confirm: `bun scripts/cli.ts platform-infra gitea mirror bootstrap --target ${target.id}${repoFlag} --confirm`,
|
||||
},
|
||||
};
|
||||
}
|
||||
const secrets = ensureMirrorSecrets(gitea, true, true);
|
||||
const result = await capture(config, target.route, ["sh"], remoteScript("mirror-bootstrap", gitea, target, "", { ...options, dryRun: false, wait: false }, { repos, secrets }).script);
|
||||
const parsed = parseJsonOutput(result.stdout);
|
||||
@@ -366,6 +389,7 @@ async function mirrorBootstrap(config: UniDeskConfig, options: MirrorOptions): P
|
||||
ok: result.exitCode === 0 && parsed?.ok === true,
|
||||
action: "platform-infra-gitea-mirror-bootstrap",
|
||||
mutation: true,
|
||||
mode: "confirmed",
|
||||
target: targetSummary(target),
|
||||
repositories: arrayRecords(record(parsed).repositories),
|
||||
credentials: credentialSummaries(gitea),
|
||||
@@ -1759,11 +1783,14 @@ function parseMirrorWebhookStatusOptions(args: string[]): MirrorWebhookStatusOpt
|
||||
function parseMirrorOptions(args: string[]): MirrorOptions {
|
||||
const commonArgs: string[] = [];
|
||||
let confirm = false;
|
||||
let dryRun = false;
|
||||
let repoKey: string | null = null;
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index];
|
||||
if (arg === "--confirm") {
|
||||
confirm = true;
|
||||
} else if (arg === "--dry-run") {
|
||||
dryRun = true;
|
||||
} else if (arg === "--repo") {
|
||||
const value = args[index + 1];
|
||||
if (value === undefined || value.startsWith("--")) throw new CliInputError("--repo requires a value", {
|
||||
@@ -1786,7 +1813,15 @@ function parseMirrorOptions(args: string[]): MirrorOptions {
|
||||
}
|
||||
}
|
||||
}
|
||||
return { ...parseCommonOptions(commonArgs), confirm, repoKey };
|
||||
if (confirm && dryRun) throw new CliInputError("Gitea mirror bootstrap accepts only one of --confirm or --dry-run", {
|
||||
code: "mutually-exclusive-options",
|
||||
argument: "--confirm --dry-run",
|
||||
usage: [
|
||||
"bun scripts/cli.ts platform-infra gitea mirror bootstrap --target <node> --dry-run",
|
||||
"bun scripts/cli.ts platform-infra gitea mirror bootstrap --target <node> --confirm",
|
||||
],
|
||||
});
|
||||
return { ...parseCommonOptions(commonArgs), confirm, dryRun: dryRun || !confirm, wait: false, repoKey };
|
||||
}
|
||||
|
||||
function parseCommonOptions(args: string[]): CommonOptions {
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import type { GiteaConfig, GiteaMirrorRepository } from "./platform-infra-gitea-config";
|
||||
import { renderMirrorBootstrap } from "./platform-infra-gitea-render";
|
||||
import { resolvePacBootstrapMirrorRepository } from "./platform-infra-pipelines-as-code-bootstrap";
|
||||
import { runPlatformInfraPipelinesAsCodeCommand } from "./platform-infra-pipelines-as-code";
|
||||
|
||||
const mirror: GiteaMirrorRepository = {
|
||||
key: "widgets-nc01",
|
||||
targetId: "NC01",
|
||||
upstream: { repository: "acme/widgets", cloneUrl: "https://github.com/acme/widgets.git", branch: "main", visibility: "public" },
|
||||
gitea: { owner: "mirrors", name: "acme-widgets", mirrorMode: "controlled-push", publicRead: true, readUrl: "http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/acme-widgets.git" },
|
||||
gitops: { branch: "main", flushDisposition: "not-a-gitops-branch" },
|
||||
snapshot: { prefix: "refs/unidesk/snapshots/widgets-main-nc01" },
|
||||
legacyGitMirror: null,
|
||||
};
|
||||
|
||||
function fixtureConfig(repositories: GiteaMirrorRepository[]): GiteaConfig {
|
||||
return { sourceAuthority: { repositories } } as unknown as GiteaConfig;
|
||||
}
|
||||
|
||||
test("PaC bootstrap uses one exact YAML-owned Gitea repository", () => {
|
||||
const selected = resolvePacBootstrapMirrorRepository(fixtureConfig([mirror]), "nc01", {
|
||||
id: "widgets-nc01",
|
||||
owner: "mirrors",
|
||||
repo: "acme-widgets",
|
||||
cloneUrl: `${mirror.gitea.readUrl}/`,
|
||||
});
|
||||
expect(selected.key).toBe("widgets-nc01");
|
||||
expect(() => resolvePacBootstrapMirrorRepository(fixtureConfig([]), "NC01", {
|
||||
id: "widgets-nc01",
|
||||
owner: "mirrors",
|
||||
repo: "acme-widgets",
|
||||
cloneUrl: mirror.gitea.readUrl,
|
||||
})).toThrow("no exact match");
|
||||
expect(() => resolvePacBootstrapMirrorRepository(fixtureConfig([mirror, { ...mirror }]), "NC01", {
|
||||
id: "widgets-nc01",
|
||||
owner: "mirrors",
|
||||
repo: "acme-widgets",
|
||||
cloneUrl: mirror.gitea.readUrl,
|
||||
})).toThrow("2 exact matches");
|
||||
});
|
||||
|
||||
test("Gitea mirror bootstrap defaults to a visible dry-run instead of an empty refusal", () => {
|
||||
const rendered = renderMirrorBootstrap({
|
||||
ok: true,
|
||||
action: "platform-infra-gitea-mirror-bootstrap",
|
||||
mode: "dry-run",
|
||||
mutation: false,
|
||||
target: { id: "NC01" },
|
||||
repositories: [{ key: "widgets-nc01", giteaOwner: "mirrors", giteaRepo: "acme-widgets" }],
|
||||
blockers: [],
|
||||
next: { confirm: "bun scripts/cli.ts platform-infra gitea mirror bootstrap --target NC01 --repo widgets-nc01 --confirm" },
|
||||
}).renderedText;
|
||||
expect(rendered).toContain("MODE MUTATION TARGET");
|
||||
expect(rendered).toContain("widgets-nc01");
|
||||
expect(rendered).toContain("--repo widgets-nc01 --confirm");
|
||||
expect(rendered).not.toContain("missing-confirm");
|
||||
});
|
||||
|
||||
test("PaC apply checks the Gitea repository before dry-run return and cluster mutation", () => {
|
||||
const remote = readFileSync(resolve(import.meta.dir, "platform-infra-pipelines-as-code-remote.sh"), "utf8");
|
||||
const action = remote.indexOf("apply_action() {");
|
||||
const preflight = remote.indexOf("if ! repository_preflight", action);
|
||||
const dryRun = remote.indexOf('if [ "$UNIDESK_PAC_DRY_RUN" = "1" ]', action);
|
||||
const release = remote.indexOf(" apply_release", action);
|
||||
expect(action).toBeGreaterThanOrEqual(0);
|
||||
expect(preflight).toBeGreaterThan(action);
|
||||
expect(dryRun).toBeGreaterThan(preflight);
|
||||
expect(release).toBeGreaterThan(dryRun);
|
||||
expect(remote).toContain('"error":"gitea-repository-missing"');
|
||||
});
|
||||
|
||||
test("platform bootstrap help advertises the composite entry before low-level apply", async () => {
|
||||
const help = await runPlatformInfraPipelinesAsCodeCommand({} as never, ["help", "platform-bootstrap"]);
|
||||
const usage = (help as Record<string, unknown>).usage as string[];
|
||||
expect(usage[0]).toContain("pipelines-as-code bootstrap");
|
||||
expect(usage).toContain("bun scripts/cli.ts platform-infra pipelines-as-code bootstrap --target <NODE> --consumer <consumer> --confirm");
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { GiteaConfig, GiteaMirrorRepository } from "./platform-infra-gitea-config";
|
||||
|
||||
export interface PacBootstrapRepositoryIdentity {
|
||||
readonly id: string;
|
||||
readonly owner: string;
|
||||
readonly repo: string;
|
||||
readonly cloneUrl: string;
|
||||
}
|
||||
|
||||
export function resolvePacBootstrapMirrorRepository(
|
||||
gitea: GiteaConfig,
|
||||
targetId: string,
|
||||
repository: PacBootstrapRepositoryIdentity,
|
||||
): GiteaMirrorRepository {
|
||||
const candidates = gitea.sourceAuthority.repositories.filter((item) => (
|
||||
item.targetId.toLowerCase() === targetId.toLowerCase()
|
||||
&& item.gitea.owner === repository.owner
|
||||
&& item.gitea.name === repository.repo
|
||||
&& repositoryUrlIdentity(item.gitea.readUrl) === repositoryUrlIdentity(repository.cloneUrl)
|
||||
));
|
||||
if (candidates.length !== 1) {
|
||||
const disposition = candidates.length === 0 ? "no exact match" : `${candidates.length} exact matches`;
|
||||
throw new Error(
|
||||
`cannot resolve the YAML-owned Gitea bootstrap repository for PaC repository ${repository.id} on ${targetId}: ${disposition}; `
|
||||
+ "match targetId, gitea owner/name, and read URL in config/platform-infra/gitea.yaml",
|
||||
);
|
||||
}
|
||||
return candidates[0];
|
||||
}
|
||||
|
||||
function repositoryUrlIdentity(value: string): string {
|
||||
const parsed = new URL(value);
|
||||
const path = parsed.pathname.replace(/\/+$/u, "");
|
||||
return `${parsed.protocol}//${parsed.host}${path}`;
|
||||
}
|
||||
@@ -100,6 +100,37 @@ gitea_api() {
|
||||
return 22
|
||||
}
|
||||
|
||||
gitea_api_status() {
|
||||
method="$1"
|
||||
path="$2"
|
||||
status=$(curl -sS -o /dev/null -w '%{http_code}' \
|
||||
-u "$UNIDESK_PAC_GITEA_ADMIN_USERNAME:$UNIDESK_PAC_GITEA_ADMIN_PASSWORD" \
|
||||
-X "$method" \
|
||||
"$(api_url "$path")" 2>/dev/null) || status=000
|
||||
printf '%s' "${status:-000}"
|
||||
}
|
||||
|
||||
repository_preflight() {
|
||||
repository_path="repos/$UNIDESK_PAC_GITEA_OWNER/$UNIDESK_PAC_GITEA_REPO"
|
||||
repository_status=$(gitea_api_status GET "$repository_path")
|
||||
case "$repository_status" in
|
||||
2*) return 0 ;;
|
||||
404)
|
||||
printf '{"ok":false,"mode":"preflight-blocked","mutation":false,"error":"gitea-repository-missing","preflight":{"repositoryExists":false,"httpStatus":"404","repository":"%s/%s","valuesPrinted":false},"valuesPrinted":false}\n' \
|
||||
"$(json_string "$UNIDESK_PAC_GITEA_OWNER")" \
|
||||
"$(json_string "$UNIDESK_PAC_GITEA_REPO")"
|
||||
return 22
|
||||
;;
|
||||
*)
|
||||
printf '{"ok":false,"mode":"preflight-blocked","mutation":false,"error":"gitea-repository-preflight-failed","preflight":{"repositoryExists":false,"httpStatus":"%s","repository":"%s/%s","valuesPrinted":false},"valuesPrinted":false}\n' \
|
||||
"$(json_string "$repository_status")" \
|
||||
"$(json_string "$UNIDESK_PAC_GITEA_OWNER")" \
|
||||
"$(json_string "$UNIDESK_PAC_GITEA_REPO")"
|
||||
return 22
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
ensure_token() {
|
||||
name="unidesk-pac-$(date +%Y%m%d%H%M%S)"
|
||||
body=$(printf '{"name":"%s","scopes":["all"]}' "$name")
|
||||
@@ -268,8 +299,17 @@ wait_ready() {
|
||||
}
|
||||
|
||||
apply_action() {
|
||||
preflight_tmp=$(mktemp)
|
||||
if ! repository_preflight >"$preflight_tmp"; then
|
||||
cat "$preflight_tmp"
|
||||
rm -f "$preflight_tmp"
|
||||
return 22
|
||||
fi
|
||||
rm -f "$preflight_tmp"
|
||||
if [ "$UNIDESK_PAC_DRY_RUN" = "1" ]; then
|
||||
printf '{"ok":true,"mode":"dry-run","mutation":false,"valuesPrinted":false}\n'
|
||||
printf '{"ok":true,"mode":"dry-run","mutation":false,"preflight":{"repositoryExists":true,"httpStatus":"2xx","repository":"%s/%s","valuesPrinted":false},"valuesPrinted":false}\n' \
|
||||
"$(json_string "$UNIDESK_PAC_GITEA_OWNER")" \
|
||||
"$(json_string "$UNIDESK_PAC_GITEA_REPO")"
|
||||
return
|
||||
fi
|
||||
apply_release
|
||||
@@ -311,7 +351,7 @@ apply_action() {
|
||||
hook_id=$(ensure_webhook)
|
||||
ready=false
|
||||
if wait_ready; then ready=true; fi
|
||||
printf '{"ok":%s,"mode":"confirmed","mutation":true,"controllerReady":%s,"argoBootstrap":%s,"webhook":{"present":true,"id":"%s","url":"%s"},"repository":"%s","namespace":"%s","valuesPrinted":false}\n' "$ready" "$ready" "$argo_bootstrap" "$(json_string "$hook_id")" "$(json_string "$UNIDESK_PAC_WEBHOOK_URL")" "$(json_string "$UNIDESK_PAC_REPOSITORY_NAME")" "$(json_string "$UNIDESK_PAC_TARGET_NAMESPACE")"
|
||||
printf '{"ok":%s,"mode":"confirmed","mutation":true,"controllerReady":%s,"argoBootstrap":%s,"preflight":{"repositoryExists":true,"httpStatus":"2xx","repository":"%s/%s","valuesPrinted":false},"webhook":{"present":true,"id":"%s","url":"%s"},"repository":"%s","namespace":"%s","valuesPrinted":false}\n' "$ready" "$ready" "$argo_bootstrap" "$(json_string "$UNIDESK_PAC_GITEA_OWNER")" "$(json_string "$UNIDESK_PAC_GITEA_REPO")" "$(json_string "$hook_id")" "$(json_string "$UNIDESK_PAC_WEBHOOK_URL")" "$(json_string "$UNIDESK_PAC_REPOSITORY_NAME")" "$(json_string "$UNIDESK_PAC_TARGET_NAMESPACE")"
|
||||
}
|
||||
|
||||
condition_status() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user