fix: 保真渲染 AgentRun 多键工具凭据
This commit is contained in:
@@ -2,7 +2,9 @@ import { readFileSync, rmSync } from "node:fs";
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { parseAgentRunClientConfigYaml, resolveAgentRunAuth, runAgentRunCommand, stripAgentRunResourceWrapperArgs } from "./agentrun";
|
||||
import { resolveAgentRunLaneTarget } from "./agentrun-lanes";
|
||||
import { agentRunToolCredentialRefs } from "./agentrun/config";
|
||||
import { placeholderAgentRunImage, renderAgentRunGitopsFiles } from "./agentrun-manifests";
|
||||
import { collectLaneSecretSources, secretSyncScript } from "./agentrun/secrets";
|
||||
|
||||
function renderedTextOf(value: unknown): string {
|
||||
if (typeof value !== "object" || value === null) throw new Error("expected rendered CLI result");
|
||||
@@ -808,6 +810,155 @@ describe("AgentRun default transport contract", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("AgentRun YAML tool credential binding", () => {
|
||||
test("aggregates managed GitHub SSH source keys into one volume credential and one sync target", () => {
|
||||
const { spec } = resolveAgentRunLaneTarget({ node: "NC01", lane: "nc01-v02" });
|
||||
const githubSsh = agentRunToolCredentialRefs(spec).filter((credential) => credential.tool === "github" && credential.purpose === "github-ssh");
|
||||
expect(githubSsh).toEqual([{
|
||||
sourceId: "github-ssh",
|
||||
tool: "github",
|
||||
purpose: "github-ssh",
|
||||
secretRef: {
|
||||
namespace: "agentrun-v02",
|
||||
name: "agentrun-v01-tool-github-ssh",
|
||||
keys: ["id_ed25519", "known_hosts"],
|
||||
},
|
||||
projection: { kind: "volume", mountPath: "/home/agentrun/.ssh" },
|
||||
valuesPrinted: false,
|
||||
}]);
|
||||
|
||||
const sources = collectLaneSecretSources(spec).filter((source) => source.targetRef.name === "agentrun-v01-tool-github-ssh");
|
||||
expect(sources.map((source) => ({ id: source.id, sourceMode: source.sourceMode, sourceRef: source.sourceRef, targetRef: source.targetRef }))).toEqual([
|
||||
{
|
||||
id: "tool-github-ssh-private-key",
|
||||
sourceMode: "file",
|
||||
sourceRef: "deploy-ssh/github.com/id_ed25519",
|
||||
targetRef: { namespace: "agentrun-v02", name: "agentrun-v01-tool-github-ssh", key: "id_ed25519" },
|
||||
},
|
||||
{
|
||||
id: "tool-github-ssh-known-hosts",
|
||||
sourceMode: "file",
|
||||
sourceRef: "deploy-ssh/github.com/known_hosts",
|
||||
targetRef: { namespace: "agentrun-v02", name: "agentrun-v01-tool-github-ssh", key: "known_hosts" },
|
||||
},
|
||||
]);
|
||||
const syncScript = secretSyncScript(spec, sources.map((source, index) => ({ targetRef: source.targetRef, value: `fixture-${index}` })));
|
||||
const payloadMatch = syncScript.match(/^payload_b64='([^']+)'$/mu);
|
||||
expect(payloadMatch).not.toBeNull();
|
||||
const syncManifest = JSON.parse(Buffer.from(payloadMatch?.[1] ?? "", "base64").toString("utf8")) as Array<{ targetRef: { namespace: string; name: string; key: string } }>;
|
||||
expect(syncManifest.map((item) => item.targetRef)).toEqual(sources.map((source) => source.targetRef));
|
||||
expect(new Set(syncManifest.map((item) => `${item.targetRef.namespace}/${item.targetRef.name}`))).toEqual(new Set(["agentrun-v02/agentrun-v01-tool-github-ssh"]));
|
||||
expect(syncScript).toContain("groups.setdefault(key");
|
||||
expect(JSON.stringify({ githubSsh, sources })).not.toContain("/root/.ssh");
|
||||
});
|
||||
|
||||
test("preserves multi-key credentials across Aipod render and queue admission and rejects conflicts", async () => {
|
||||
const renderBodies: Record<string, any>[] = [];
|
||||
const submittedBodies: Record<string, any>[] = [];
|
||||
let conflictingKeys = false;
|
||||
const renderedTask = () => ({
|
||||
tenantId: "unidesk",
|
||||
projectId: "pikasTech/unidesk",
|
||||
queue: "commander",
|
||||
lane: "v0.1",
|
||||
backendProfile: "codex",
|
||||
providerId: "NC01",
|
||||
workspaceRef: { kind: "opaque", path: "." },
|
||||
executionPolicy: {
|
||||
sandbox: "workspace-write",
|
||||
approval: "never",
|
||||
timeoutMs: 1_800_000,
|
||||
network: "enabled",
|
||||
secretScope: {
|
||||
allowCredentialEcho: false,
|
||||
providerCredentials: [{
|
||||
profile: "codex",
|
||||
secretRef: { name: "agentrun-v01-provider-codex", keys: ["auth.json", "config.toml"] },
|
||||
}],
|
||||
toolCredentials: [
|
||||
{
|
||||
tool: "github",
|
||||
purpose: "github-pr",
|
||||
secretRef: { name: "agentrun-v01-tool-github-pr", keys: ["GH_TOKEN"] },
|
||||
projection: { kind: "env", envName: "GH_TOKEN", secretKey: "GH_TOKEN" },
|
||||
},
|
||||
{
|
||||
tool: "unidesk-ssh",
|
||||
purpose: "ssh-passthrough",
|
||||
secretRef: { name: "agentrun-v01-tool-unidesk-ssh", keys: ["UNIDESK_SSH_CLIENT_TOKEN"] },
|
||||
projection: { kind: "env", envName: "UNIDESK_SSH_CLIENT_TOKEN", secretKey: "UNIDESK_SSH_CLIENT_TOKEN" },
|
||||
},
|
||||
{
|
||||
tool: "github",
|
||||
purpose: "github-ssh",
|
||||
secretRef: {
|
||||
name: "agentrun-v01-tool-github-ssh",
|
||||
keys: conflictingKeys ? ["id_ed25519", "known_hosts", "config"] : ["id_ed25519", "known_hosts"],
|
||||
},
|
||||
projection: { kind: "volume", mountPath: "/home/agentrun/.ssh" },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
payload: { prompt: "fixture" },
|
||||
});
|
||||
const server = Bun.serve({
|
||||
port: 0,
|
||||
async fetch(request) {
|
||||
const url = new URL(request.url);
|
||||
expect(request.headers.get("authorization")).toBe("Bearer secret-value");
|
||||
if (url.pathname === "/api/v1/aipod-specs/Artificer/render") {
|
||||
renderBodies.push(await request.json() as Record<string, any>);
|
||||
return Response.json({ ok: true, data: { queueTask: renderedTask() } });
|
||||
}
|
||||
if (url.pathname === "/api/v1/queue/tasks") {
|
||||
submittedBodies.push(await request.json() as Record<string, any>);
|
||||
return Response.json({ ok: true, data: { id: "qt_fixture", state: "queued" } });
|
||||
}
|
||||
return new Response("not found", { status: 404 });
|
||||
},
|
||||
});
|
||||
const previousConfig = process.env.AGENTRUN_CLIENT_CONFIG;
|
||||
const previousKey = process.env.HWLAB_API_KEY;
|
||||
process.env.HWLAB_API_KEY = "secret-value";
|
||||
const tempConfigPath = `/tmp/unidesk-agentrun-tool-credential-test-${process.pid}-${Date.now()}.yaml`;
|
||||
try {
|
||||
process.env.AGENTRUN_CLIENT_CONFIG = tempConfigPath;
|
||||
await Bun.write(tempConfigPath, agentRunMinimalClientYaml.replace("http://agentrun.example.local:8080", server.url.href.replace(/\/$/u, "")));
|
||||
|
||||
const accepted = await runAgentRunCommand(null, ["create", "task", "--aipod", "Artificer", "--prompt", "fixture", "-o", "json"]);
|
||||
expect(accepted.ok).toBe(true);
|
||||
expect(renderBodies).toHaveLength(1);
|
||||
expect(submittedBodies).toHaveLength(1);
|
||||
for (const body of [renderBodies[0], submittedBodies[0]]) {
|
||||
const githubSsh = body?.executionPolicy?.secretScope?.toolCredentials?.filter((credential: Record<string, unknown>) => credential.tool === "github" && credential.purpose === "github-ssh");
|
||||
expect(githubSsh).toEqual([{
|
||||
tool: "github",
|
||||
purpose: "github-ssh",
|
||||
secretRef: { name: "agentrun-v01-tool-github-ssh", keys: ["id_ed25519", "known_hosts"] },
|
||||
projection: { kind: "volume", mountPath: "/home/agentrun/.ssh" },
|
||||
}]);
|
||||
}
|
||||
|
||||
conflictingKeys = true;
|
||||
const rejected = await runAgentRunCommand(null, ["create", "task", "--aipod", "Artificer", "--prompt", "fixture", "-o", "json"]);
|
||||
expect(rejected.ok).toBe(false);
|
||||
expect(submittedBodies).toHaveLength(1);
|
||||
const rejectedText = renderedTextOf(rejected);
|
||||
expect(rejectedText).toContain("validation-failed");
|
||||
expect(rejectedText).toContain("github/github-ssh keys conflict");
|
||||
expect(Buffer.byteLength(rejectedText, "utf8")).toBeLessThan(2400);
|
||||
} finally {
|
||||
server.stop(true);
|
||||
if (previousConfig === undefined) delete process.env.AGENTRUN_CLIENT_CONFIG;
|
||||
else process.env.AGENTRUN_CLIENT_CONFIG = previousConfig;
|
||||
if (previousKey === undefined) delete process.env.HWLAB_API_KEY;
|
||||
else process.env.HWLAB_API_KEY = previousKey;
|
||||
rmSync(tempConfigPath, { force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("AgentRun runner API key SecretRef", () => {
|
||||
test("renders manager runtime authority from the YAML runner SecretRef", () => {
|
||||
const { spec } = resolveAgentRunLaneTarget({ node: "NC01", lane: "nc01-v02" });
|
||||
|
||||
Reference in New Issue
Block a user