feat: 接入 selfmedia 受控交付
This commit is contained in:
@@ -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)]))),
|
||||
|
||||
Reference in New Issue
Block a user