feat(cicd): 注册 AgentRun managed repository fragment
This commit is contained in:
@@ -363,13 +363,20 @@ export function resolveAgentRunLaneTarget(options: { node?: string | null; lane?
|
||||
}
|
||||
|
||||
export function resolveAgentRunLaneTargetsForNode(nodeId: string, env: NodeJS.ProcessEnv = process.env): readonly AgentRunLaneTarget[] {
|
||||
const targets = listAgentRunLaneTargetsForNode(nodeId, env);
|
||||
if (targets.length === 0) {
|
||||
const configPath = env.AGENTRUN_CONTROL_PLANE_CONFIG ?? rootPath(AGENTRUN_CONFIG_PATH);
|
||||
throw new Error(`${configPath}: no AgentRun lane is declared for node ${nodeId}`);
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
export function listAgentRunLaneTargetsForNode(nodeId: string, env: NodeJS.ProcessEnv = process.env): readonly AgentRunLaneTarget[] {
|
||||
const config = readAgentRunControlPlaneConfig(env);
|
||||
const targets = Object.values(config.lanes)
|
||||
return Object.values(config.lanes)
|
||||
.filter((lane) => lane.nodeId === nodeId)
|
||||
.sort((left, right) => left.lane.localeCompare(right.lane))
|
||||
.map((spec) => ({ configPath: config.sourcePath, spec }));
|
||||
if (targets.length === 0) throw new Error(`${config.sourcePath}: no AgentRun lane is declared for node ${nodeId}`);
|
||||
return targets;
|
||||
}
|
||||
|
||||
export function agentRunLaneSummary(spec: AgentRunLaneSpec): Record<string, unknown> {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "./agentrun-managed-repository-reconciler";
|
||||
import { resolveAgentRunLaneTarget } from "./agentrun-lanes";
|
||||
import { yamlLaneGitMirrorStatusScript } from "./agentrun/secrets";
|
||||
import { renderPlatformInfraGiteaDesiredFragments } from "./platform-infra-gitea-desired-fragments";
|
||||
|
||||
const controllerPath = new URL("../native/agentrun/managed-repository-reconciler.mjs", import.meta.url).pathname;
|
||||
|
||||
@@ -43,6 +44,7 @@ describe("AgentRun YAML-owned managed repository reconciler", () => {
|
||||
expect(documents[2].spec.strategy.type).toBe("Recreate");
|
||||
expect(documents[2].spec.template.spec.automountServiceAccountToken).toBe(false);
|
||||
expect(documents[2].spec.template.spec.containers[0].readinessProbe.exec.command).toContain("--health");
|
||||
expect(renderAgentRunManagedRepositoryReconcilerFragment("JD01")).toBe("");
|
||||
|
||||
const controllerSource = readFileSync(controllerPath, "utf8");
|
||||
expect(controllerSource).not.toContain("agent_skills");
|
||||
@@ -50,6 +52,47 @@ describe("AgentRun YAML-owned managed repository reconciler", () => {
|
||||
expect(controllerSource).not.toContain("github.com");
|
||||
});
|
||||
|
||||
test("registers after PaC-owned admission objects without identity or separator conflicts", () => {
|
||||
const spec = resolveAgentRunLaneTarget({ node: "NC01", lane: "nc01-v02" }).spec;
|
||||
const desired = agentRunManagedRepositoryDesiredConfig(spec);
|
||||
const fragment = renderPlatformInfraGiteaDesiredFragments("NC01");
|
||||
expect(fragment).not.toContain("---\n---");
|
||||
const documents = fragment
|
||||
.split(/^---\s*$/mu)
|
||||
.map((item) => Bun.YAML.parse(item) as any);
|
||||
expect(documents.map((item) => item.kind)).toEqual([
|
||||
"Role",
|
||||
"RoleBinding",
|
||||
"ValidatingAdmissionPolicy",
|
||||
"ValidatingAdmissionPolicyBinding",
|
||||
"ServiceAccount",
|
||||
"ConfigMap",
|
||||
"Deployment",
|
||||
]);
|
||||
const identities = documents.map((item) => [
|
||||
item.apiVersion,
|
||||
item.kind,
|
||||
item.metadata.namespace ?? "_cluster",
|
||||
item.metadata.name,
|
||||
].join("/"));
|
||||
expect(new Set(identities).size).toBe(documents.length);
|
||||
|
||||
const managed = documents.slice(-3);
|
||||
expect(managed.map((item) => item.metadata.name)).toEqual([
|
||||
spec.gitMirror.repositoryReconciler.serviceAccountName,
|
||||
spec.gitMirror.repositoryReconciler.configMapName,
|
||||
spec.gitMirror.repositoryReconciler.deploymentName,
|
||||
]);
|
||||
const config = JSON.parse(managed[1].data["config.json"]);
|
||||
expect(config.desiredHash).toBe(desired.desiredHash);
|
||||
expect(managed[1].metadata.annotations["unidesk.ai/managed-repository-desired-sha"]).toBe(desired.desiredHash);
|
||||
expect(managed[2].metadata.annotations["unidesk.ai/managed-repository-desired-sha"]).toBe(desired.desiredHash);
|
||||
expect(managed[1].metadata.annotations["unidesk.ai/managed-repository-render-sha"]).toMatch(/^[0-9a-f]{64}$/u);
|
||||
expect(managed[2].metadata.annotations["unidesk.ai/managed-repository-render-sha"]).toBe(
|
||||
managed[1].metadata.annotations["unidesk.ai/managed-repository-render-sha"],
|
||||
);
|
||||
});
|
||||
|
||||
test("requires every repository entry to own a matching remote", () => {
|
||||
const temporary = mkdtempSync("/tmp/unidesk-agentrun-repository-config-");
|
||||
try {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { rootPath } from "./config";
|
||||
import {
|
||||
resolveAgentRunLaneTargetsForNode,
|
||||
listAgentRunLaneTargetsForNode,
|
||||
type AgentRunLaneSpec,
|
||||
} from "./agentrun-lanes";
|
||||
|
||||
@@ -96,7 +96,7 @@ export function agentRunManagedRepositoryRenderHash(spec: AgentRunLaneSpec): str
|
||||
}
|
||||
|
||||
export function renderAgentRunManagedRepositoryReconcilerFragment(nodeId: string, env: NodeJS.ProcessEnv = process.env): string {
|
||||
const targets = resolveAgentRunLaneTargetsForNode(nodeId, env)
|
||||
const targets = listAgentRunLaneTargetsForNode(nodeId, env)
|
||||
.filter(({ spec }) => spec.gitMirror.repositoryReconciler.enabled);
|
||||
assertUniqueManagedRepositoryOwnership(targets.map(({ spec }) => spec));
|
||||
const objects = targets
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { renderAgentRunManagedRepositoryReconcilerFragment } from "./agentrun-managed-repository-reconciler";
|
||||
import { renderPacAdmissionDesiredFragment } from "./platform-infra-pac-provenance";
|
||||
import { renderPacConsumerRbacDesiredFragment } from "./platform-infra-pac-consumer-rbac";
|
||||
|
||||
@@ -5,6 +6,7 @@ export function renderPlatformInfraGiteaDesiredFragments(targetId: string): stri
|
||||
return [
|
||||
renderPacConsumerRbacDesiredFragment(targetId),
|
||||
renderPacAdmissionDesiredFragment(targetId),
|
||||
renderAgentRunManagedRepositoryReconcilerFragment(targetId),
|
||||
].map(normalizeFragment).filter((item) => item.length > 0).join("\n---\n");
|
||||
}
|
||||
|
||||
|
||||
@@ -79,5 +79,8 @@ test("owning YAML renders one child Application and the complete durable bridge
|
||||
"RoleBinding",
|
||||
"ValidatingAdmissionPolicy",
|
||||
"ValidatingAdmissionPolicyBinding",
|
||||
"ServiceAccount",
|
||||
"ConfigMap",
|
||||
"Deployment",
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -78,7 +78,15 @@ test("required consumer default RBAC has one automatic desired owner and no Tekt
|
||||
const verbs = role.rules.flatMap((rule: any) => rule.verbs);
|
||||
for (const verb of ["create", "patch", "update", "delete", "deletecollection"]) expect(verbs).not.toContain(verb);
|
||||
const desiredKinds = documents(renderPlatformInfraGiteaDesiredFragments("NC01")).map((item) => item.kind);
|
||||
expect(desiredKinds).toEqual(["Role", "RoleBinding", "ValidatingAdmissionPolicy", "ValidatingAdmissionPolicyBinding"]);
|
||||
expect(desiredKinds).toEqual([
|
||||
"Role",
|
||||
"RoleBinding",
|
||||
"ValidatingAdmissionPolicy",
|
||||
"ValidatingAdmissionPolicyBinding",
|
||||
"ServiceAccount",
|
||||
"ConfigMap",
|
||||
"Deployment",
|
||||
]);
|
||||
});
|
||||
|
||||
test("live admission evaluator requires exact desired hashes, observed generation, default-SA loss, and a new resource epoch", () => {
|
||||
|
||||
Reference in New Issue
Block a user