feat: 支持 runner tool credential 装配
This commit is contained in:
@@ -12,7 +12,13 @@ const selfTest: SelfTestCase = async (context) => {
|
||||
const server = await startManagerServer({ port: 0, host: "127.0.0.1", sourceCommit: "self-test", store: new MemoryAgentRunStore() });
|
||||
try {
|
||||
const client = new ManagerClient(server.baseUrl);
|
||||
const item = await createRunWithCommand(client, context, "job smoke", "selftest-job-render", 15_000);
|
||||
const githubToolCredentials = [{
|
||||
tool: "github",
|
||||
purpose: "pull-request",
|
||||
secretRef: { name: "agentrun-v01-tool-github-pr", keys: ["GH_TOKEN"] },
|
||||
projection: { kind: "env", envName: "GH_TOKEN", secretKey: "GH_TOKEN" },
|
||||
}];
|
||||
const item = await createRunWithCommand(client, { ...context, toolCredentials: githubToolCredentials }, "job smoke", "selftest-job-render", 15_000);
|
||||
const rendered = renderRunnerJobDryRun({
|
||||
run: await client.get(`/api/v1/runs/${item.runId}`) as RunRecord,
|
||||
commandId: item.commandId,
|
||||
@@ -27,6 +33,7 @@ const selfTest: SelfTestCase = async (context) => {
|
||||
assert.equal(((rendered.retention as JsonRecord).ttlSecondsAfterFinished), 86_400);
|
||||
assert.equal((rendered.jobIdentity as { serviceAccountName?: string }).serviceAccountName, "agentrun-v01-runner");
|
||||
assertRunnerJobUsesWritableCodexHome(rendered.manifest as JsonRecord, context.codexHome, "codex-0", "/var/run/agentrun/secrets/codex-0");
|
||||
assertRunnerJobUsesToolCredential(rendered, "GH_TOKEN", "agentrun-v01-tool-github-pr", "GH_TOKEN");
|
||||
assert.equal(runnerEnvValue(rendered.manifest as JsonRecord, "HWLAB_DEVICE_POD_SESSION_TOKEN"), "REDACTED");
|
||||
assert.deepEqual((((rendered.transientEnv as JsonRecord).names) as string[]), ["HWLAB_DEVICE_POD_SESSION_TOKEN"]);
|
||||
assertNoSecretLeak(rendered);
|
||||
@@ -70,7 +77,7 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
|
||||
});
|
||||
try {
|
||||
const jobClient = new ManagerClient(serverWithKubectl.baseUrl);
|
||||
const jobItem = await createRunWithCommand(jobClient, context, "job create smoke", "selftest-job-create", 15_000);
|
||||
const jobItem = await createRunWithCommand(jobClient, { ...context, toolCredentials: githubToolCredentials }, "job create smoke", "selftest-job-create", 15_000);
|
||||
const created = await jobClient.post(`/api/v1/runs/${jobItem.runId}/runner-jobs`, {
|
||||
commandId: jobItem.commandId,
|
||||
attemptId: "attempt_selftest_create",
|
||||
@@ -86,11 +93,12 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
|
||||
assert.equal((manifest.spec as JsonRecord).ttlSecondsAfterFinished, 86_400);
|
||||
assert.equal(runnerEnvValue(manifest, "HWLAB_DEVICE_POD_SESSION_TOKEN"), "test-token-material");
|
||||
assert.equal(runnerEnvValue(manifest, "HWLAB_CLOUD_API_URL"), "http://cloud.test");
|
||||
assertRunnerJobUsesToolCredential({ manifest, toolCredentials: (created as JsonRecord).toolCredentials } as JsonRecord, "GH_TOKEN", "agentrun-v01-tool-github-pr", "GH_TOKEN");
|
||||
assertNoSecretLeak(created);
|
||||
} finally {
|
||||
await new Promise<void>((resolve) => serverWithKubectl.server.close(() => resolve()));
|
||||
}
|
||||
return { name: "runner-k8s-job", tests: ["runner-k8s-job-dry-run", "runner-k8s-job-deepseek-profile-dry-run", "runner-k8s-job-create-api", "runner-k8s-job-retention-ttl", "runner-job-transient-env"] };
|
||||
return { name: "runner-k8s-job", tests: ["runner-k8s-job-dry-run", "runner-k8s-job-deepseek-profile-dry-run", "runner-k8s-job-create-api", "runner-k8s-job-retention-ttl", "runner-job-transient-env", "runner-job-tool-credential-env"] };
|
||||
} finally {
|
||||
await new Promise<void>((resolve) => server.server.close(() => resolve()));
|
||||
}
|
||||
@@ -108,6 +116,27 @@ function runnerEnvValue(manifest: JsonRecord, name: string): unknown {
|
||||
return env.find((item) => item.name === name)?.value;
|
||||
}
|
||||
|
||||
function assertRunnerJobUsesToolCredential(rendered: JsonRecord, envName: string, secretName: string, secretKey: string): void {
|
||||
const manifest = rendered.manifest as JsonRecord;
|
||||
const spec = manifest.spec as JsonRecord;
|
||||
const template = spec.template as JsonRecord;
|
||||
const podSpec = template.spec as JsonRecord;
|
||||
const containers = podSpec.containers as JsonRecord[];
|
||||
const runner = containers[0] as JsonRecord;
|
||||
const env = runner.env as JsonRecord[];
|
||||
const entry = env.find((item) => item.name === envName) as JsonRecord | undefined;
|
||||
assert.ok(entry, `${envName} env should be projected from a SecretRef`);
|
||||
assert.equal(entry.value, undefined);
|
||||
const valueFrom = entry.valueFrom as JsonRecord;
|
||||
const secretKeyRef = valueFrom.secretKeyRef as JsonRecord;
|
||||
assert.equal(secretKeyRef.name, secretName);
|
||||
assert.equal(secretKeyRef.key, secretKey);
|
||||
|
||||
const summary = rendered.toolCredentials as JsonRecord;
|
||||
assert.equal(summary.valuesPrinted, false);
|
||||
assert.equal(summary.count, 1);
|
||||
}
|
||||
|
||||
function assertRunnerJobUsesWritableCodexHome(manifest: JsonRecord, expectedCodexHome: string, volumeName: string, projectionPath: string): void {
|
||||
const spec = manifest.spec as JsonRecord;
|
||||
const template = spec.template as JsonRecord;
|
||||
|
||||
@@ -25,6 +25,8 @@ export interface SelfTestResult {
|
||||
|
||||
export type SelfTestCase = (context: SelfTestContext) => Promise<SelfTestResult> | SelfTestResult;
|
||||
|
||||
type SelfTestRunContext = Pick<SelfTestContext, "workspace" | "codexHome"> & Partial<Pick<SelfTestContext, "deepseekHome">> & { backendProfile?: BackendProfile; includeOnlyProfile?: BackendProfile; toolCredentials?: JsonRecord[] };
|
||||
|
||||
export async function createSelfTestContext(root: string): Promise<SelfTestContext> {
|
||||
const tmp = await mkdtemp(path.join(os.tmpdir(), "agentrun-selftest-"));
|
||||
const codexHome = path.join(tmp, "codex-home");
|
||||
@@ -52,7 +54,7 @@ export async function createSelfTestContext(root: string): Promise<SelfTestConte
|
||||
};
|
||||
}
|
||||
|
||||
export async function createRunWithCommand(client: ManagerClient, context: Pick<SelfTestContext, "workspace" | "codexHome"> & Partial<Pick<SelfTestContext, "deepseekHome">> & { backendProfile?: BackendProfile; includeOnlyProfile?: BackendProfile }, prompt: string, idempotencyKey: string, timeoutMs: number): Promise<{ runId: string; commandId: string }> {
|
||||
export async function createRunWithCommand(client: ManagerClient, context: SelfTestRunContext, prompt: string, idempotencyKey: string, timeoutMs: number): Promise<{ runId: string; commandId: string }> {
|
||||
const backendProfile = context.backendProfile ?? "codex";
|
||||
const run = await client.post("/api/v1/runs", {
|
||||
tenantId: "unidesk",
|
||||
@@ -65,7 +67,7 @@ export async function createRunWithCommand(client: ManagerClient, context: Pick<
|
||||
approval: "never",
|
||||
timeoutMs,
|
||||
network: "default",
|
||||
secretScope: { allowCredentialEcho: false, providerCredentials: providerCredentials(context, backendProfile) },
|
||||
secretScope: { allowCredentialEcho: false, providerCredentials: providerCredentials(context, backendProfile), ...toolCredentialScope(context) },
|
||||
},
|
||||
traceSink: null,
|
||||
}) as { id: string };
|
||||
@@ -75,6 +77,10 @@ export async function createRunWithCommand(client: ManagerClient, context: Pick<
|
||||
return { runId: run.id, commandId: command.id };
|
||||
}
|
||||
|
||||
function toolCredentialScope(context: { toolCredentials?: JsonRecord[] }): JsonRecord {
|
||||
return context.toolCredentials ? { toolCredentials: context.toolCredentials } : {};
|
||||
}
|
||||
|
||||
function providerCredentials(context: Pick<SelfTestContext, "codexHome"> & Partial<Pick<SelfTestContext, "deepseekHome">> & { includeOnlyProfile?: BackendProfile }, backendProfile: BackendProfile): JsonRecord[] {
|
||||
const profiles: BackendProfile[] = context.includeOnlyProfile ? [context.includeOnlyProfile] : [backendProfile];
|
||||
return profiles.map((profile) => ({
|
||||
|
||||
Reference in New Issue
Block a user