feat: 接入 selfmedia 受控交付

This commit is contained in:
Codex
2026-07-13 07:56:37 +02:00
parent 48aa08bf73
commit 80ca187cb3
17 changed files with 1845 additions and 71 deletions
@@ -154,6 +154,10 @@ interface PacConsumer {
path: string;
destinationNamespace: string;
automated: boolean;
repositoryCredential: {
secretName: string;
username: string;
} | null;
} | null;
deliveryProvenance: {
required: true;
@@ -165,6 +169,11 @@ interface PacConsumer {
closeoutGitOpsMirrorFlush: boolean;
closeoutGitOpsMirrorLane: "v02" | "v03" | null;
sourceArtifact: PacSourceArtifactSpec | null;
runnerServiceAccount: {
name: string;
automountServiceAccountToken: false;
roleBindingName: string;
} | null;
}
interface CommonOptions {
@@ -495,6 +504,7 @@ function parseConsumer(consumer: Record<string, unknown>, path: string, defaultR
const id = typeof consumer.id === "string" && consumer.id.length > 0 ? consumer.id : `${y.stringField(consumer, "node", path)}-${y.stringField(consumer, "lane", path)}`;
const argoBootstrap = consumer.argoBootstrap === undefined ? null : y.objectField(consumer, "argoBootstrap", path);
const deliveryProvenance = consumer.deliveryProvenance === undefined ? null : parseConsumerDeliveryProvenance(y.objectField(consumer, "deliveryProvenance", path), `${path}.deliveryProvenance`);
const runnerServiceAccount = consumer.runnerServiceAccount === undefined ? null : y.objectField(consumer, "runnerServiceAccount", path);
return {
id,
node: y.stringField(consumer, "node", path),
@@ -511,15 +521,32 @@ function parseConsumer(consumer: Record<string, unknown>, path: string, defaultR
path: y.stringField(argoBootstrap, "path", `${path}.argoBootstrap`),
destinationNamespace: y.kubernetesNameField(argoBootstrap, "destinationNamespace", `${path}.argoBootstrap`),
automated: y.booleanField(argoBootstrap, "automated", `${path}.argoBootstrap`),
repositoryCredential: argoBootstrap.repositoryCredential === undefined ? null : (() => {
const credential = y.objectField(argoBootstrap, "repositoryCredential", `${path}.argoBootstrap`);
return {
secretName: y.kubernetesNameField(credential, "secretName", `${path}.argoBootstrap.repositoryCredential`),
username: y.stringField(credential, "username", `${path}.argoBootstrap.repositoryCredential`),
};
})(),
},
deliveryProvenance,
repositoryRef: typeof consumer.repositoryRef === "string" && consumer.repositoryRef.length > 0 ? consumer.repositoryRef : defaultRepositoryRef,
closeoutGitOpsMirrorFlush: y.booleanField(consumer, "closeoutGitOpsMirrorFlush", path),
closeoutGitOpsMirrorLane: consumer.closeoutGitOpsMirrorLane === undefined ? null : y.enumField(consumer, "closeoutGitOpsMirrorLane", path, ["v02", "v03"] as const),
sourceArtifact: consumer.sourceArtifact === undefined ? null : parseSourceArtifact(y.objectField(consumer, "sourceArtifact", path), `${path}.sourceArtifact`),
runnerServiceAccount: runnerServiceAccount === null ? null : {
name: y.kubernetesNameField(runnerServiceAccount, "name", `${path}.runnerServiceAccount`),
automountServiceAccountToken: parseFalse(runnerServiceAccount, "automountServiceAccountToken", `${path}.runnerServiceAccount`),
roleBindingName: y.kubernetesNameField(runnerServiceAccount, "roleBindingName", `${path}.runnerServiceAccount`),
},
};
}
function parseFalse(record: Record<string, unknown>, key: string, path: string): false {
if (y.booleanField(record, key, path) !== false) throw new Error(`${path}.${key} must be false`);
return false;
}
function parseConsumerDeliveryProvenance(value: Record<string, unknown>, path: string): PacConsumer["deliveryProvenance"] {
const required = y.booleanField(value, "required", path);
if (!required) return null;
@@ -537,7 +564,7 @@ function parseConsumerDeliveryProvenance(value: Record<string, unknown>, path: s
function parseSourceArtifact(value: Record<string, unknown>, path: string): PacSourceArtifactSpec {
const mode = y.enumField(value, "mode", path, ["embedded-pipeline-spec", "remote-pipeline-annotation"] as const);
const renderer = y.enumField(value, "renderer", path, ["agentrun-control-plane", "hwlab-runtime-lane"] as const);
const renderer = y.enumField(value, "renderer", path, ["agentrun-control-plane", "hwlab-runtime-lane", "selfmedia-runtime"] as const);
const pipelineRunPath = sourceArtifactPath(y.stringField(value, "pipelineRunPath", path), `${path}.pipelineRunPath`);
const pipelinePath = value.pipelinePath === undefined ? null : sourceArtifactPath(y.stringField(value, "pipelinePath", path), `${path}.pipelinePath`);
const taskRunTemplate = y.objectField(value, "taskRunTemplate", path);
@@ -545,6 +572,7 @@ function parseSourceArtifact(value: Record<string, unknown>, path: string): PacS
if (mode === "remote-pipeline-annotation" && pipelinePath === null) throw new Error(`${path}.pipelinePath is required for remote-pipeline-annotation`);
if (renderer === "agentrun-control-plane" && mode !== "embedded-pipeline-spec") throw new Error(`${path}.renderer agentrun-control-plane requires embedded-pipeline-spec`);
if (renderer === "hwlab-runtime-lane" && mode !== "remote-pipeline-annotation") throw new Error(`${path}.renderer hwlab-runtime-lane requires remote-pipeline-annotation`);
if (renderer === "selfmedia-runtime" && mode !== "embedded-pipeline-spec") throw new Error(`${path}.renderer selfmedia-runtime requires embedded-pipeline-spec`);
return {
mode,
renderer,
@@ -772,6 +800,19 @@ function validateConfig(config: PacConfig): void {
|| consumer.argoBootstrap.targetRevision !== consumer.deliveryProvenance.gitOps.targetRevision)) {
throw new Error(`${configLabel}.consumers.${consumer.id}.deliveryProvenance.gitOps must match argoBootstrap source identity`);
}
if (consumer.runnerServiceAccount !== null && consumer.runnerServiceAccount.name !== consumer.deliveryProvenance.executionServiceAccountName) {
throw new Error(`${configLabel}.consumers.${consumer.id}.runnerServiceAccount.name must match deliveryProvenance.executionServiceAccountName`);
}
}
if (consumer.sourceArtifact?.renderer === "selfmedia-runtime") {
if (consumer.runnerServiceAccount === null) throw new Error(`${configLabel}.consumers.${consumer.id}.runnerServiceAccount is required for selfmedia-runtime`);
if (consumer.argoBootstrap?.repositoryCredential === null || consumer.argoBootstrap?.repositoryCredential === undefined) {
throw new Error(`${configLabel}.consumers.${consumer.id}.argoBootstrap.repositoryCredential is required for the private selfmedia repository`);
}
if (consumer.closeoutGitOpsMirrorFlush) throw new Error(`${configLabel}.consumers.${consumer.id}.closeoutGitOpsMirrorFlush must be false for Gitea writeback GitOps`);
if (repository.params.gitops_secret_name !== repository.secretName) throw new Error(`${configLabel}.repositories.${repository.id}.params.gitops_secret_name must match secretName`);
if (repository.params.gitops_username !== consumer.argoBootstrap.repositoryCredential.username) throw new Error(`${configLabel}.consumers.${consumer.id}.Argo and Tekton Gitea usernames must match`);
if (repository.params.gitops_read_url !== consumer.argoBootstrap.repoUrl) throw new Error(`${configLabel}.consumers.${consumer.id}.Argo repoUrl must match params.gitops_read_url`);
}
}
if (!config.gitea.webhook.events.includes("push")) throw new Error(`${configLabel}.gitea.webhook.events must include push`);
@@ -1227,6 +1268,9 @@ function remoteScript(action: "apply" | "status" | "history" | "debug-step", pac
UNIDESK_PAC_CONSUMER_CONFIG_B64: Buffer.from(JSON.stringify(consumerConfig), "utf8").toString("base64"),
UNIDESK_PAC_DELIVERY_PROVENANCE_REQUIRED: consumer.deliveryProvenance?.required === true ? "1" : "0",
UNIDESK_PAC_CONSUMER_RBAC_MANIFEST_B64: Buffer.from(renderPacConsumerRbacDesiredFragment(target.id), "utf8").toString("base64"),
UNIDESK_PAC_RUNNER_SERVICE_ACCOUNT_MANIFEST_B64: Buffer.from(runnerServiceAccountManifest(consumer), "utf8").toString("base64"),
UNIDESK_PAC_RUNNER_SERVICE_ACCOUNT_NAME: consumer.runnerServiceAccount?.name ?? "",
UNIDESK_PAC_RUNNER_ROLE_BINDING_NAME: consumer.runnerServiceAccount?.roleBindingName ?? "",
UNIDESK_PAC_CONSUMER_RBAC_CONFIG_SHA256: rbacIdentity.configSha256,
UNIDESK_PAC_ADMISSION_POLICY_NAME: admissionIdentity.policyName,
UNIDESK_PAC_ADMISSION_BINDING_NAME: admissionIdentity.bindingName,
@@ -1242,6 +1286,9 @@ function remoteScript(action: "apply" | "status" | "history" | "debug-step", pac
UNIDESK_PAC_ARGO_NAMESPACE: consumer.argoNamespace,
UNIDESK_PAC_ARGO_APPLICATION: consumer.argoApplication,
UNIDESK_PAC_ARGO_BOOTSTRAP_MANIFEST_B64: Buffer.from(argoBootstrapManifest(consumer), "utf8").toString("base64"),
UNIDESK_PAC_ARGO_REPOSITORY_SECRET_NAME: consumer.argoBootstrap?.repositoryCredential?.secretName ?? "",
UNIDESK_PAC_ARGO_REPOSITORY_USERNAME: consumer.argoBootstrap?.repositoryCredential?.username ?? "",
UNIDESK_PAC_ARGO_REPOSITORY_URL: consumer.argoBootstrap?.repoUrl ?? "",
UNIDESK_PAC_PART_OF: pacPartOf(consumer.id),
UNIDESK_PAC_SPEC: kubernetesLabelValue(pac.metadata.relatedIssues.includes(1555) ? "GH-1552-GH-1555" : pac.metadata.spec),
};
@@ -1288,9 +1335,36 @@ function pacPartOf(consumerId: string): string {
if (consumerId === "unidesk-host") return "unidesk-host";
if (consumerId.startsWith("sentinel")) return "hwlab-web-probe-sentinel";
if (consumerId.startsWith("hwlab-")) return "hwlab";
if (consumerId.startsWith("selfmedia-")) return "selfmedia-factory";
return "agentrun";
}
function runnerServiceAccountManifest(consumer: PacConsumer): string {
const runner = consumer.runnerServiceAccount;
if (runner === null) return "";
const labels = {
"app.kubernetes.io/managed-by": "unidesk",
"app.kubernetes.io/part-of": pacPartOf(consumer.id),
"unidesk.ai/pac-runner": consumer.id,
};
const objects = [
{
apiVersion: "v1",
kind: "ServiceAccount",
metadata: { name: runner.name, namespace: consumer.namespace, labels },
automountServiceAccountToken: runner.automountServiceAccountToken,
},
{
apiVersion: "rbac.authorization.k8s.io/v1",
kind: "RoleBinding",
metadata: { name: runner.roleBindingName, namespace: consumer.namespace, labels },
subjects: [{ kind: "ServiceAccount", name: runner.name, namespace: consumer.namespace }],
roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "unidesk-pac-consumer-runner" },
},
];
return `${objects.map((item) => Bun.YAML.stringify(item).trim()).join("\n---\n")}\n`;
}
function argoBootstrapManifest(consumer: PacConsumer): string {
const bootstrap = consumer.argoBootstrap;
if (bootstrap === null) return "";
@@ -1411,6 +1485,7 @@ function statusSummary(payload: Record<string, unknown>): Record<string, unknown
const runtime = record(payload.runtime);
const diagnostics = record(payload.diagnostics);
const admissionProvenance = record(payload.admissionProvenance);
const consumerBootstrap = record(payload.consumerBootstrap);
const webhooks = arrayRecords(payload.webhooks);
const rawArtifact = record(payload.artifact);
const sourceObservation = record(rawArtifact.sourceObservation);
@@ -1427,7 +1502,7 @@ function statusSummary(payload: Record<string, unknown>): Record<string, unknown
};
const pipelineGate = pipelineRunGate(latest);
return {
ready: payload.crdPresent === true && String(payload.controllerReady ?? "0/0") !== "0/0" && pipelineGate.ok && diagnostics.ok !== false,
ready: payload.crdPresent === true && String(payload.controllerReady ?? "0/0") !== "0/0" && pipelineGate.ok && diagnostics.ok !== false && consumerBootstrap.ready !== false,
crdPresent: payload.crdPresent === true,
controllerReady: payload.controllerReady,
repositoryCondition: payload.repositoryCondition,
@@ -1444,6 +1519,7 @@ function statusSummary(payload: Record<string, unknown>): Record<string, unknown
runtime,
diagnostics,
admissionProvenance,
consumerBootstrap,
valuesPrinted: false,
};
}
@@ -1615,6 +1691,11 @@ function consumerObservationSummary(consumer: PacConsumer): Record<string, unkno
argoNamespace: consumer.argoNamespace,
argoApplication: consumer.argoApplication,
repositoryRef: consumer.repositoryRef,
runnerServiceAccount: consumer.runnerServiceAccount,
argoRepositoryCredential: consumer.argoBootstrap?.repositoryCredential === null || consumer.argoBootstrap?.repositoryCredential === undefined ? null : {
secretName: consumer.argoBootstrap.repositoryCredential.secretName,
usernameConfigured: consumer.argoBootstrap.repositoryCredential.username.length > 0,
},
valuesPrinted: false,
};
}
@@ -1666,6 +1747,8 @@ function compactPlanJson(result: Record<string, unknown>): Record<string, unknow
argoNamespace: consumer.argoNamespace,
argoApplication: consumer.argoApplication,
repositoryRef: consumer.repositoryRef,
runnerServiceAccount: consumer.runnerServiceAccount,
argoRepositoryCredential: consumer.argoRepositoryCredential,
},
secrets: result.secrets,
policy: result.policy,
@@ -1729,6 +1812,7 @@ function compactStatusSummary(summary: Record<string, unknown>): Record<string,
},
pipelineRunGate: summary.pipelineRunGate,
admissionProvenance: summary.admissionProvenance,
consumerBootstrap: summary.consumerBootstrap,
sourceObservation: summary.sourceObservation,
artifact: {
imageStatus: artifact.imageStatus,
@@ -1950,6 +2034,9 @@ function renderStatus(result: Record<string, unknown>): RenderedCliResult {
const argo = record(summary.argo);
const diagnostics = record(summary.diagnostics);
const admissionProvenance = record(summary.admissionProvenance);
const consumerBootstrap = record(summary.consumerBootstrap);
const bootstrapRunner = record(consumerBootstrap.runner);
const bootstrapArgo = record(consumerBootstrap.argoRepository);
const sourceObservation = record(summary.sourceObservation);
const deliveryPlan = record(sourceObservation.plan);
const repository = record(summary.repository);
@@ -1970,6 +2057,13 @@ function renderStatus(result: Record<string, unknown>): RenderedCliResult {
` admission-provenance: ready=${boolText(admissionProvenance.ready)} policy=${stringValue(admissionProvenance.policyName)} binding=${stringValue(admissionProvenance.bindingName)} active-after=${stringValue(admissionProvenance.activeAfter)} default-can-mutate=${boolText(admissionProvenance.defaultCanMutate)}`,
` admission-reasons: ${Array.isArray(admissionProvenance.reasons) ? admissionProvenance.reasons.join(",") || "-" : "-"}`,
]),
...(consumerBootstrap.configured !== true
? [" consumer-bootstrap: not-declared"]
: [
` consumer-bootstrap: ready=${boolText(consumerBootstrap.ready)} runner=${stringValue(bootstrapRunner.serviceAccount)} exists=${boolText(bootstrapRunner.exists)} automount-disabled=${boolText(bootstrapRunner.automountDisabled)} rolebinding-exact=${boolText(bootstrapRunner.roleBindingExact)}`,
` argocd-repository-secret: name=${stringValue(bootstrapArgo.secretName)} exists=${boolText(bootstrapArgo.exists)} label=${boolText(bootstrapArgo.labelReady)} keys=${boolText(bootstrapArgo.keysReady)} url=${boolText(bootstrapArgo.urlReady)} password-present=${boolText(bootstrapArgo.passwordPresent)} password-decoded=${boolText(bootstrapArgo.passwordDecoded)}`,
` consumer-bootstrap-reasons: ${Array.isArray(consumerBootstrap.reasons) ? consumerBootstrap.reasons.join(",") || "-" : "-"}`,
]),
"",
"GITEA HOOKS",
...(webhooks.length === 0 ? ["-"] : table(["HOOK", "ACTIVE", "EVENTS", "URL"], webhooks.map((item) => [stringValue(item.id), boolText(item.active), Array.isArray(item.events) ? item.events.join(",") : stringValue(item.events), short(stringValue(item.url), 56)]))),