ci: migrate sentinel to gitea pac

This commit is contained in:
Codex
2026-07-05 14:06:37 +00:00
parent 79e9288d5f
commit 376ab626be
15 changed files with 561 additions and 158 deletions
+180 -99
View File
@@ -39,6 +39,7 @@ interface PacConfig {
};
defaults: {
targetId: string;
consumerId: string;
};
release: {
version: string;
@@ -67,7 +68,12 @@ interface PacConfig {
branch: string;
};
};
repository: {
repositories: PacRepository[];
consumers: PacConsumer[];
}
interface PacRepository {
id: string;
name: string;
namespace: string;
providerType: "gitea";
@@ -80,8 +86,10 @@ interface PacConfig {
webhookSecretKey: string;
concurrencyLimit: number;
params: Record<string, string>;
};
consumer: {
}
interface PacConsumer {
id: string;
node: string;
lane: string;
namespace: string;
@@ -89,11 +97,12 @@ interface PacConfig {
pipelineRunPrefix: string;
argoNamespace: string;
argoApplication: string;
};
repositoryRef: string;
}
interface CommonOptions {
targetId: string | null;
consumerId: string | null;
full: boolean;
raw: boolean;
}
@@ -164,8 +173,13 @@ function readPacConfig(): PacConfig {
const gitea = y.objectField(root, "gitea", "");
const admin = y.objectField(gitea, "admin", "gitea");
const webhook = y.objectField(gitea, "webhook", "gitea");
const repository = y.objectField(root, "repository", "");
const consumer = y.objectField(root, "consumer", "");
const repositories = root.repositories === undefined
? [parseRepository(y.objectField(root, "repository", ""), "repository")]
: y.arrayOfRecords(root.repositories, "repositories").map((item, index) => parseRepository(item, `repositories[${index}]`));
const consumers = root.consumers === undefined
? [parseConsumer(y.objectField(root, "consumer", ""), "consumer", repositories[0]?.id ?? "default")]
: y.arrayOfRecords(root.consumers, "consumers").map((item, index) => parseConsumer(item, `consumers[${index}]`, repositories[0]?.id ?? "default"));
const defaults = y.objectField(root, "defaults", "");
const parsed: PacConfig = {
version: y.integerField(root, "version", ""),
kind: "platform-infra-pipelines-as-code",
@@ -176,7 +190,8 @@ function readPacConfig(): PacConfig {
relatedIssues: y.numberArrayField(y.objectField(root, "metadata", ""), "relatedIssues", "metadata"),
},
defaults: {
targetId: y.stringField(y.objectField(root, "defaults", ""), "targetId", "defaults"),
targetId: y.stringField(defaults, "targetId", "defaults"),
consumerId: typeof defaults.consumerId === "string" && defaults.consumerId.length > 0 ? defaults.consumerId : consumers[0]?.id ?? "default",
},
release: {
version: y.stringField(release, "version", "release"),
@@ -205,34 +220,47 @@ function readPacConfig(): PacConfig {
branch: y.stringField(webhook, "branch", "gitea.webhook"),
},
},
repository: {
name: y.kubernetesNameField(repository, "name", "repository"),
namespace: y.kubernetesNameField(repository, "namespace", "repository"),
providerType: y.enumField(repository, "providerType", "repository", ["gitea"] as const),
url: urlField(repository, "url", "repository"),
cloneUrl: urlField(repository, "cloneUrl", "repository"),
owner: y.stringField(repository, "owner", "repository"),
repo: y.stringField(repository, "repo", "repository"),
secretName: y.kubernetesNameField(repository, "secretName", "repository"),
tokenKey: y.stringField(repository, "tokenKey", "repository"),
webhookSecretKey: y.stringField(repository, "webhookSecretKey", "repository"),
concurrencyLimit: positiveInteger(repository, "concurrencyLimit", "repository"),
params: stringRecord(y.objectField(repository, "params", "repository"), "repository.params"),
},
consumer: {
node: y.stringField(consumer, "node", "consumer"),
lane: y.stringField(consumer, "lane", "consumer"),
namespace: y.kubernetesNameField(consumer, "namespace", "consumer"),
pipeline: y.kubernetesNameField(consumer, "pipeline", "consumer"),
pipelineRunPrefix: y.stringField(consumer, "pipelineRunPrefix", "consumer"),
argoNamespace: y.kubernetesNameField(consumer, "argoNamespace", "consumer"),
argoApplication: y.kubernetesNameField(consumer, "argoApplication", "consumer"),
},
repositories,
consumers,
};
validateConfig(parsed);
return parsed;
}
function parseRepository(repository: Record<string, unknown>, path: string): PacRepository {
const name = y.kubernetesNameField(repository, "name", path);
return {
id: typeof repository.id === "string" && repository.id.length > 0 ? repository.id : name,
name,
namespace: y.kubernetesNameField(repository, "namespace", path),
providerType: y.enumField(repository, "providerType", path, ["gitea"] as const),
url: urlField(repository, "url", path),
cloneUrl: urlField(repository, "cloneUrl", path),
owner: y.stringField(repository, "owner", path),
repo: y.stringField(repository, "repo", path),
secretName: y.kubernetesNameField(repository, "secretName", path),
tokenKey: y.stringField(repository, "tokenKey", path),
webhookSecretKey: y.stringField(repository, "webhookSecretKey", path),
concurrencyLimit: positiveInteger(repository, "concurrencyLimit", path),
params: stringRecord(y.objectField(repository, "params", path), `${path}.params`),
};
}
function parseConsumer(consumer: Record<string, unknown>, path: string, defaultRepositoryRef: string): PacConsumer {
const id = typeof consumer.id === "string" && consumer.id.length > 0 ? consumer.id : `${y.stringField(consumer, "node", path)}-${y.stringField(consumer, "lane", path)}`;
return {
id,
node: y.stringField(consumer, "node", path),
lane: y.stringField(consumer, "lane", path),
namespace: y.kubernetesNameField(consumer, "namespace", path),
pipeline: y.kubernetesNameField(consumer, "pipeline", path),
pipelineRunPrefix: y.stringField(consumer, "pipelineRunPrefix", path),
argoNamespace: y.kubernetesNameField(consumer, "argoNamespace", path),
argoApplication: y.kubernetesNameField(consumer, "argoApplication", path),
repositoryRef: typeof consumer.repositoryRef === "string" && consumer.repositoryRef.length > 0 ? consumer.repositoryRef : defaultRepositoryRef,
};
}
function parseTarget(record: Record<string, unknown>, index: number): PacTarget {
const path = `targets[${index}]`;
return {
@@ -247,10 +275,22 @@ function parseTarget(record: Record<string, unknown>, index: number): PacTarget
function validateConfig(config: PacConfig): void {
if (config.version !== 1) throw new Error(`${configLabel}.version must be 1`);
resolveTarget(config, config.defaults.targetId);
if (config.repository.providerType !== "gitea") throw new Error(`${configLabel}.repository.providerType must be gitea`);
if (config.release.waitTimeoutSeconds > 55) throw new Error(`${configLabel}.release.waitTimeoutSeconds must fit the 60s trans budget`);
if (config.repository.namespace !== config.consumer.namespace) throw new Error(`${configLabel}.repository.namespace must match consumer.namespace`);
if (new URL(config.repository.cloneUrl).origin !== new URL(config.gitea.internalBaseUrl).origin) throw new Error(`${configLabel}.repository.cloneUrl must use the configured internal Gitea base URL`);
resolveConsumer(config, config.defaults.consumerId);
const ids = new Set<string>();
for (const repository of config.repositories) {
if (ids.has(repository.id)) throw new Error(`${configLabel}.repositories id must be unique: ${repository.id}`);
ids.add(repository.id);
if (repository.providerType !== "gitea") throw new Error(`${configLabel}.repositories.${repository.id}.providerType must be gitea`);
if (new URL(repository.cloneUrl).origin !== new URL(config.gitea.internalBaseUrl).origin) throw new Error(`${configLabel}.repositories.${repository.id}.cloneUrl must use the configured internal Gitea base URL`);
}
const consumerIds = new Set<string>();
for (const consumer of config.consumers) {
if (consumerIds.has(consumer.id)) throw new Error(`${configLabel}.consumers id must be unique: ${consumer.id}`);
consumerIds.add(consumer.id);
const repository = resolveRepository(config, consumer.repositoryRef);
if (repository.namespace !== consumer.namespace) throw new Error(`${configLabel}.consumers.${consumer.id}.namespace must match repository namespace`);
}
if (!config.gitea.webhook.events.includes("push")) throw new Error(`${configLabel}.gitea.webhook.events must include push`);
}
@@ -262,30 +302,48 @@ function resolveTarget(config: PacConfig, targetId: string | null): PacTarget {
return target;
}
function resolveConsumer(config: PacConfig, consumerId: string | null): PacConsumer {
const id = consumerId ?? config.defaults.consumerId;
const consumer = config.consumers.find((item) => item.id.toLowerCase() === id.toLowerCase());
if (consumer === undefined) throw new Error(`unknown Pipelines-as-Code consumer ${id}; known consumers: ${config.consumers.map((item) => item.id).join(", ")}`);
return consumer;
}
function resolveRepository(config: PacConfig, repositoryRef: string): PacRepository {
const repository = config.repositories.find((item) => item.id === repositoryRef || item.name === repositoryRef);
if (repository === undefined) throw new Error(`unknown Pipelines-as-Code repository ${repositoryRef}; known repositories: ${config.repositories.map((item) => item.id).join(", ")}`);
return repository;
}
function plan(options: CommonOptions): Record<string, unknown> {
const pac = readPacConfig();
const target = resolveTarget(pac, options.targetId);
const consumer = resolveConsumer(pac, options.consumerId);
const repository = resolveRepository(pac, consumer.repositoryRef);
const secrets = secretSummaries(pac);
return {
ok: true,
action: "platform-infra-pipelines-as-code-plan",
mutation: false,
target: targetSummary(target),
config: configSummary(pac),
config: configSummary(pac, consumer, repository),
release: pac.release,
repository: repositorySummary(pac),
repository: repositorySummary(repository),
consumer,
secrets,
policy: policyChecks(pac),
next: nextCommands(target.id),
policy: policyChecks(repository),
next: nextCommands(target.id, consumer.id, pac.defaults.consumerId),
};
}
async function apply(config: UniDeskConfig, options: ApplyOptions): Promise<Record<string, unknown>> {
const pac = readPacConfig();
const target = resolveTarget(pac, options.targetId);
const consumer = resolveConsumer(pac, options.consumerId);
const repository = resolveRepository(pac, consumer.repositoryRef);
const secrets = ensureSecrets(pac, !options.dryRun, options.dryRun);
const releaseManifest = options.dryRun ? "" : await fetchReleaseManifest(pac);
const result = await capture(config, target.route, ["sh"], remoteScript("apply", pac, target, options, secrets, releaseManifest));
const result = await capture(config, target.route, ["sh"], remoteScript("apply", pac, target, repository, consumer, options, secrets, releaseManifest));
const parsed = parseJsonOutput(result.stdout);
return {
ok: result.exitCode === 0 && parsed?.ok === true,
@@ -293,20 +351,23 @@ async function apply(config: UniDeskConfig, options: ApplyOptions): Promise<Reco
mutation: !options.dryRun,
mode: options.dryRun ? "dry-run" : "confirmed",
target: targetSummary(target),
config: compactConfigSummary(pac),
config: compactConfigSummary(pac, consumer, repository),
release: { version: pac.release.version, manifestUrl: pac.release.manifestUrl },
repository: repositorySummary(pac),
repository: repositorySummary(repository),
consumer,
secrets: secretSummaries(pac),
remote: parsed ?? compactCapture(result, { full: true }),
next: nextCommands(target.id),
next: nextCommands(target.id, consumer.id, pac.defaults.consumerId),
};
}
async function status(config: UniDeskConfig, options: CommonOptions): Promise<Record<string, unknown>> {
const pac = readPacConfig();
const target = resolveTarget(pac, options.targetId);
const consumer = resolveConsumer(pac, options.consumerId);
const repository = resolveRepository(pac, consumer.repositoryRef);
const secrets = ensureSecrets(pac, false);
const result = await capture(config, target.route, ["sh"], remoteScript("status", pac, target, { ...options, confirm: false, dryRun: true, wait: false }, secrets, ""));
const result = await capture(config, target.route, ["sh"], remoteScript("status", pac, target, repository, consumer, { ...options, confirm: false, dryRun: true, wait: false }, secrets, ""));
const parsed = parseJsonOutput(result.stdout);
const summary = parsed === null ? null : statusSummary(parsed);
return {
@@ -314,27 +375,31 @@ async function status(config: UniDeskConfig, options: CommonOptions): Promise<Re
action: "platform-infra-pipelines-as-code-status",
mutation: false,
target: targetSummary(target),
config: compactConfigSummary(pac),
config: compactConfigSummary(pac, consumer, repository),
consumer,
summary,
remote: parsed === null ? compactCapture(result, { full: true }) : options.raw || options.full ? parsed : summary,
next: nextCommands(target.id),
next: nextCommands(target.id, consumer.id, pac.defaults.consumerId),
};
}
async function webhookTest(config: UniDeskConfig, options: WebhookTestOptions): Promise<Record<string, unknown>> {
const pac = readPacConfig();
const target = resolveTarget(pac, options.targetId);
const consumer = resolveConsumer(pac, options.consumerId);
const repository = resolveRepository(pac, consumer.repositoryRef);
if (!options.confirm) return { ok: false, action: "platform-infra-pipelines-as-code-webhook-test", mutation: false, mode: "missing-confirm", error: "webhook-test requires --confirm" };
const secrets = ensureSecrets(pac, false);
const result = await capture(config, target.route, ["sh"], remoteScript("webhook-test", pac, target, { ...options, dryRun: false, wait: false }, secrets, ""));
const result = await capture(config, target.route, ["sh"], remoteScript("webhook-test", pac, target, repository, consumer, { ...options, dryRun: false, wait: false }, secrets, ""));
const parsed = parseJsonOutput(result.stdout);
return {
ok: result.exitCode === 0 && parsed?.ok === true,
action: "platform-infra-pipelines-as-code-webhook-test",
mutation: true,
target: targetSummary(target),
consumer,
remote: parsed ?? compactCapture(result, { full: true }),
next: nextCommands(target.id),
next: nextCommands(target.id, consumer.id, pac.defaults.consumerId),
};
}
@@ -346,13 +411,14 @@ async function fetchReleaseManifest(pac: PacConfig): Promise<string> {
return text;
}
function remoteScript(action: "apply" | "status" | "webhook-test", pac: PacConfig, target: PacTarget, options: ApplyOptions | WebhookTestOptions, secrets: SecretMaterial, releaseManifest: string): string {
function remoteScript(action: "apply" | "status" | "webhook-test", pac: PacConfig, target: PacTarget, repository: PacRepository, consumer: PacConsumer, options: ApplyOptions | WebhookTestOptions, secrets: SecretMaterial, releaseManifest: string): string {
const webhookUrl = `${pac.gitea.internalBaseUrl.replace(/\/+$/u, "").replace(/gitea-http\.[^.]+\.svc\.cluster\.local:3000/u, `${pac.release.controllerServiceName}.${pac.release.namespace}.svc.cluster.local:${pac.release.controllerServicePort}`)}`;
const env: Record<string, string> = {
UNIDESK_PAC_ACTION: action,
UNIDESK_PAC_FIELD_MANAGER: fieldManager,
UNIDESK_PAC_TARGET_ID: target.id,
UNIDESK_PAC_TARGET_NAMESPACE: target.namespace,
UNIDESK_PAC_TARGET_NAMESPACE: consumer.namespace,
UNIDESK_PAC_CONSUMER_ID: consumer.id,
UNIDESK_PAC_RELEASE_NAMESPACE: pac.release.namespace,
UNIDESK_PAC_RELEASE_MANIFEST_B64: Buffer.from(releaseManifest, "utf8").toString("base64"),
UNIDESK_PAC_CONTROLLER_SERVICE_NAME: pac.release.controllerServiceName,
@@ -362,20 +428,22 @@ function remoteScript(action: "apply" | "status" | "webhook-test", pac: PacConfi
UNIDESK_PAC_GITEA_ADMIN_USERNAME: secrets.adminUsername,
UNIDESK_PAC_GITEA_ADMIN_PASSWORD: secrets.adminPassword,
UNIDESK_PAC_GITEA_API_USERNAME: pac.gitea.admin.apiUsername,
UNIDESK_PAC_GITEA_OWNER: pac.repository.owner,
UNIDESK_PAC_GITEA_REPO: pac.repository.repo,
UNIDESK_PAC_GITEA_OWNER: repository.owner,
UNIDESK_PAC_GITEA_REPO: repository.repo,
UNIDESK_PAC_WEBHOOK_URL: webhookUrl,
UNIDESK_PAC_WEBHOOK_SECRET: secrets.webhookSecret,
UNIDESK_PAC_REPOSITORY_NAME: pac.repository.name,
UNIDESK_PAC_REPOSITORY_URL: pac.repository.url,
UNIDESK_PAC_SECRET_NAME: pac.repository.secretName,
UNIDESK_PAC_TOKEN_KEY: pac.repository.tokenKey,
UNIDESK_PAC_WEBHOOK_SECRET_KEY: pac.repository.webhookSecretKey,
UNIDESK_PAC_CONCURRENCY_LIMIT: String(pac.repository.concurrencyLimit),
UNIDESK_PAC_PARAMS_JSON: JSON.stringify(pac.repository.params),
UNIDESK_PAC_PIPELINE_RUN_PREFIX: pac.consumer.pipelineRunPrefix,
UNIDESK_PAC_ARGO_NAMESPACE: pac.consumer.argoNamespace,
UNIDESK_PAC_ARGO_APPLICATION: pac.consumer.argoApplication,
UNIDESK_PAC_REPOSITORY_NAME: repository.name,
UNIDESK_PAC_REPOSITORY_URL: repository.url,
UNIDESK_PAC_SECRET_NAME: repository.secretName,
UNIDESK_PAC_TOKEN_KEY: repository.tokenKey,
UNIDESK_PAC_WEBHOOK_SECRET_KEY: repository.webhookSecretKey,
UNIDESK_PAC_CONCURRENCY_LIMIT: String(repository.concurrencyLimit),
UNIDESK_PAC_PARAMS_JSON: JSON.stringify(repository.params),
UNIDESK_PAC_PIPELINE_RUN_PREFIX: consumer.pipelineRunPrefix,
UNIDESK_PAC_ARGO_NAMESPACE: consumer.argoNamespace,
UNIDESK_PAC_ARGO_APPLICATION: consumer.argoApplication,
UNIDESK_PAC_PART_OF: consumer.id.startsWith("sentinel") ? "hwlab-web-probe-sentinel" : "agentrun",
UNIDESK_PAC_SPEC: pac.metadata.relatedIssues.includes(1555) ? "GH-1552/GH-1555" : pac.metadata.spec,
};
const exports = Object.entries(env).map(([key, value]) => `export ${key}=${shQuote(value)}`).join("\n");
return `${exports}\n${readFileSync(remoteScriptFile, "utf8")}`;
@@ -470,11 +538,11 @@ function statusSummary(payload: Record<string, unknown>): Record<string, unknown
};
}
function policyChecks(pac: PacConfig): Array<Record<string, unknown>> {
function policyChecks(repository: PacRepository): Array<Record<string, unknown>> {
return [
{ name: "single-path", ok: true, detail: "Gitea webhook -> Pipelines-as-Code -> Tekton -> Argo/k8s runtime; no Gitea Actions/act_runner/branch-follower fallback." },
{ name: "yaml-source-of-truth", ok: true, detail: `${configLabel} owns PaC release, Repository CR, Gitea webhook and AgentRun JD01 v0.2 params.` },
{ name: "gitea-internal-source", ok: pac.repository.cloneUrl.includes(".svc.cluster.local"), detail: "Tekton source clone uses the internal k3s Gitea service URL." },
{ name: "yaml-source-of-truth", ok: true, detail: `${configLabel} owns PaC release, Repository CR, Gitea webhook and consumer params.` },
{ name: "gitea-internal-source", ok: repository.cloneUrl.includes(".svc.cluster.local"), detail: "Tekton source clone uses the internal k3s Gitea service URL." },
{ name: "runtime-zero-docker", ok: true, detail: "Runtime starts at k8s pulling already-built images; CI build may use Tekton/BuildKit." },
];
}
@@ -483,51 +551,60 @@ function targetSummary(target: PacTarget): Record<string, unknown> {
return { id: target.id, route: target.route, namespace: target.namespace, role: target.role };
}
function configSummary(pac: PacConfig): Record<string, unknown> {
function configSummary(pac: PacConfig, consumer: PacConsumer, repository: PacRepository): Record<string, unknown> {
return {
path: configLabel,
metadata: pac.metadata,
release: pac.release,
gitea: { configRef: pac.gitea.configRef, internalBaseUrl: pac.gitea.internalBaseUrl, webhookBranch: pac.gitea.webhook.branch },
repository: repositorySummary(pac),
consumer: pac.consumer,
repositories: pac.repositories.map(repositorySummary),
consumers: pac.consumers,
repository: repositorySummary(repository),
consumer,
valuesPrinted: false,
};
}
function compactConfigSummary(pac: PacConfig): Record<string, unknown> {
function compactConfigSummary(pac: PacConfig, consumer: PacConsumer, repository: PacRepository): Record<string, unknown> {
return {
path: configLabel,
releaseVersion: pac.release.version,
repository: pac.repository.name,
providerType: pac.repository.providerType,
sourceUrl: pac.repository.cloneUrl,
consumer: `${pac.consumer.node}/${pac.consumer.lane}`,
pipeline: pac.consumer.pipeline,
repository: repository.name,
providerType: repository.providerType,
sourceUrl: repository.cloneUrl,
consumer: `${consumer.node}/${consumer.lane}`,
consumerId: consumer.id,
pipeline: consumer.pipeline,
valuesPrinted: false,
};
}
function repositorySummary(pac: PacConfig): Record<string, unknown> {
function repositorySummary(repository: PacRepository): Record<string, unknown> {
return {
name: pac.repository.name,
namespace: pac.repository.namespace,
providerType: pac.repository.providerType,
url: pac.repository.url,
cloneUrl: pac.repository.cloneUrl,
owner: pac.repository.owner,
repo: pac.repository.repo,
secretName: pac.repository.secretName,
concurrencyLimit: pac.repository.concurrencyLimit,
params: Object.keys(pac.repository.params).sort(),
id: repository.id,
name: repository.name,
namespace: repository.namespace,
providerType: repository.providerType,
url: repository.url,
cloneUrl: repository.cloneUrl,
owner: repository.owner,
repo: repository.repo,
secretName: repository.secretName,
concurrencyLimit: repository.concurrencyLimit,
params: Object.keys(repository.params).sort(),
};
}
function nextCommands(targetId: string): Record<string, string> {
function consumerSuffix(consumerId: string, defaultConsumerId: string): string {
return consumerId === defaultConsumerId ? "" : ` --consumer ${consumerId}`;
}
function nextCommands(targetId: string, consumerId: string, defaultConsumerId: string): Record<string, string> {
const suffix = consumerSuffix(consumerId, defaultConsumerId);
return {
apply: `bun scripts/cli.ts platform-infra pipelines-as-code apply --target ${targetId} --confirm`,
status: `bun scripts/cli.ts platform-infra pipelines-as-code status --target ${targetId}`,
webhookTest: `bun scripts/cli.ts platform-infra pipelines-as-code webhook-test --target ${targetId} --confirm`,
apply: `bun scripts/cli.ts platform-infra pipelines-as-code apply --target ${targetId}${suffix} --confirm`,
status: `bun scripts/cli.ts platform-infra pipelines-as-code status --target ${targetId}${suffix}`,
webhookTest: `bun scripts/cli.ts platform-infra pipelines-as-code webhook-test --target ${targetId}${suffix} --confirm`,
};
}
@@ -535,18 +612,19 @@ function renderPlan(result: Record<string, unknown>): RenderedCliResult {
const target = record(result.target);
const config = record(result.config);
const repository = record(result.repository);
const consumer = record(result.consumer);
const secrets = arrayRecords(result.secrets);
const policy = arrayRecords(result.policy);
const lines = [
"PLATFORM-INFRA PIPELINES-AS-CODE PLAN",
...table(["TARGET", "NAMESPACE", "RELEASE", "REPOSITORY"], [[stringValue(target.id), stringValue(target.namespace), stringValue(record(config.release).version), stringValue(repository.name)]]),
...table(["TARGET", "NAMESPACE", "RELEASE", "REPOSITORY", "CONSUMER"], [[stringValue(target.id), stringValue(repository.namespace), stringValue(record(config.release).version), stringValue(repository.name), stringValue(consumer.id)]]),
"",
"PATH",
...table(["STAGE", "AUTHORITY", "DETAIL"], [
["source", "Gitea", stringValue(repository.cloneUrl)],
["trigger", "Pipelines-as-Code", "Gitea push webhook"],
["ci", "Tekton", stringValue(record(config.consumer).pipeline)],
["cd", "Argo", stringValue(record(config.consumer).argoApplication)],
["ci", "Tekton", stringValue(consumer.pipeline)],
["cd", "Argo", stringValue(consumer.argoApplication)],
]),
"",
"SECRETS",
@@ -580,13 +658,14 @@ function renderApply(result: Record<string, unknown>): RenderedCliResult {
function renderStatus(result: Record<string, unknown>): RenderedCliResult {
const summary = record(result.summary);
const consumer = record(result.consumer);
const latest = record(summary.latestPipelineRun);
const taskRuns = arrayRecords(summary.taskRuns);
const artifact = record(summary.artifact);
const argo = record(summary.argo);
const lines = [
"PLATFORM-INFRA PIPELINES-AS-CODE STATUS",
...table(["READY", "CRD", "CONTROLLER", "WEBHOOKS", "REPOSITORY"], [[boolText(summary.ready), boolText(summary.crdPresent), stringValue(summary.controllerReady), stringValue(summary.webhookCount), compactLine(stringValue(summary.repositoryCondition))]]),
...table(["CONSUMER", "READY", "CRD", "CONTROLLER", "WEBHOOKS", "REPOSITORY"], [[stringValue(consumer.id), boolText(summary.ready), boolText(summary.crdPresent), stringValue(summary.controllerReady), stringValue(summary.webhookCount), compactLine(stringValue(summary.repositoryCondition))]]),
"",
"LATEST PIPELINERUN",
...table(["NAME", "STATUS", "REASON", "DURATION_S", "SOURCE"], [[stringValue(latest.name), stringValue(latest.status), stringValue(latest.reason), stringValue(latest.durationSeconds), short(stringValue(latest.sourceCommit))]]),
@@ -595,13 +674,13 @@ function renderStatus(result: Record<string, unknown>): RenderedCliResult {
...(taskRuns.length === 0 ? ["-"] : table(["TASKRUN", "STATUS", "REASON", "DURATION_S"], taskRuns.map((item) => [short(stringValue(item.name), 56), stringValue(item.status), stringValue(item.reason), stringValue(item.durationSeconds)]))),
"",
"IMAGE / GITOPS",
...table(["IMAGE_STATUS", "ENV_ID", "DIGEST", "GITOPS"], [[stringValue(artifact.imageStatus), stringValue(artifact.envIdentity), short(stringValue(artifact.digest), 18), short(stringValue(artifact.gitopsCommit))]]),
...table(["IMAGE_STATUS", "ENV_REUSE", "ENV_ID", "DIGEST", "GITOPS"], [[stringValue(artifact.imageStatus), stringValue(artifact.envReuse), stringValue(artifact.envIdentity), short(stringValue(artifact.digest), 18), short(stringValue(artifact.gitopsCommit))]]),
"",
"ARGO",
...table(["SYNC", "HEALTH", "REVISION"], [[stringValue(argo.sync), stringValue(argo.health), short(stringValue(argo.revision))]]),
"",
"NEXT",
` full: bun scripts/cli.ts platform-infra pipelines-as-code status --target ${stringValue(record(result.target).id)} --full`,
` full: ${stringValue(record(result.next).status)} --full`,
];
return rendered(result, "platform-infra pipelines-as-code status", lines);
}
@@ -630,7 +709,7 @@ function parseApplyOptions(args: string[]): ApplyOptions {
else if (arg === "--wait") wait = true;
else {
commonArgs.push(arg);
if (arg === "--target" || arg === "--node") {
if (arg === "--target" || arg === "--node" || arg === "--consumer") {
commonArgs.push(args[index + 1] ?? "");
index += 1;
}
@@ -648,7 +727,7 @@ function parseWebhookTestOptions(args: string[]): WebhookTestOptions {
if (arg === "--confirm") confirm = true;
else {
commonArgs.push(arg);
if (arg === "--target" || arg === "--node") {
if (arg === "--target" || arg === "--node" || arg === "--consumer") {
commonArgs.push(args[index + 1] ?? "");
index += 1;
}
@@ -659,15 +738,17 @@ function parseWebhookTestOptions(args: string[]): WebhookTestOptions {
function parseCommonOptions(args: string[]): CommonOptions {
let targetId: string | null = null;
let consumerId: string | null = null;
let full = false;
let raw = false;
for (let index = 0; index < args.length; index += 1) {
const arg = args[index];
if (arg === "--target" || arg === "--node") {
if (arg === "--target" || arg === "--node" || arg === "--consumer") {
const value = args[index + 1];
if (value === undefined || value.startsWith("--")) throw new Error(`${arg} requires a value`);
if (!/^[A-Za-z0-9._-]+$/u.test(value)) throw new Error(`${arg} must be a simple target id`);
targetId = value;
if (!/^[A-Za-z0-9._-]+$/u.test(value)) throw new Error(`${arg} must be a simple id`);
if (arg === "--consumer") consumerId = value;
else targetId = value;
index += 1;
} else if (arg === "--full") {
full = true;
@@ -678,7 +759,7 @@ function parseCommonOptions(args: string[]): CommonOptions {
throw new Error(`unsupported pipelines-as-code option: ${arg}`);
}
}
return { targetId, full, raw };
return { targetId, consumerId, full, raw };
}
function positiveInteger(obj: Record<string, unknown>, key: string, path: string): number {