feat: 支持 runner tool credential 装配

This commit is contained in:
Codex
2026-06-02 00:22:38 +08:00
parent ac14a06436
commit 159b99e763
10 changed files with 163 additions and 14 deletions
+55 -4
View File
@@ -33,6 +33,14 @@ interface CredentialProjection {
projectionMountPath: string;
}
interface ToolCredentialProjection {
tool: string;
purpose: string | null;
secretRef: SecretRef;
envName: string;
secretKey: string;
}
export function renderRunnerJobDryRun(options: RunnerJobRenderOptions): JsonRecord {
const render = renderRunnerJobManifest({ ...options, dryRun: true });
const manifest = redactTransientEnvInManifest(render.manifest, options.transientEnv ?? []);
@@ -56,6 +64,7 @@ export function renderRunnerJobDryRun(options: RunnerJobRenderOptions): JsonReco
sourceCommit: render.sourceCommit,
},
secretRefs: render.secretRefs.map((item) => ({ profile: item.profile, name: item.secretRef.name, namespace: item.secretRef.namespace ?? render.namespace, keys: item.secretRef.keys ?? [], mountPath: item.runtimeMountPath, projectionPath: item.projectionMountPath, writableCopy: true, valuesPrinted: false })),
toolCredentials: summarizeToolCredentials(render.toolCredentials, render.namespace),
transientEnv: summarizeTransientEnv(options.transientEnv ?? []),
retention: {
ttlSecondsAfterFinished: render.ttlSecondsAfterFinished,
@@ -69,7 +78,7 @@ export function renderRunnerJobDryRun(options: RunnerJobRenderOptions): JsonReco
};
}
export function renderRunnerJobManifest(options: RunnerJobRenderOptions): { manifest: JsonRecord; namespace: string; jobName: string; runnerId: string; attemptId: string; sourceCommit: string; serviceAccountName: string; secretRefs: CredentialProjection[]; warnings: string[]; ttlSecondsAfterFinished: number } {
export function renderRunnerJobManifest(options: RunnerJobRenderOptions): { manifest: JsonRecord; namespace: string; jobName: string; runnerId: string; attemptId: string; sourceCommit: string; serviceAccountName: string; secretRefs: CredentialProjection[]; toolCredentials: ToolCredentialProjection[]; warnings: string[]; ttlSecondsAfterFinished: number } {
const namespace = options.namespace ?? "agentrun-v01";
const attemptId = options.attemptId ?? `attempt_${Date.now().toString(36)}`;
const runnerId = options.runnerId ?? `runner_${shortHash(`${options.run.id}:${attemptId}:${options.commandId}`)}`;
@@ -78,9 +87,10 @@ export function renderRunnerJobManifest(options: RunnerJobRenderOptions): { mani
const ttlSecondsAfterFinished = options.ttlSecondsAfterFinished ?? 86_400;
const jobName = `agentrun-v01-runner-${shortDnsHash(options.run.id, attemptId)}`;
const secretRefs = credentialProjections(options.run, namespace);
const toolCredentials = toolCredentialProjections(options.run, namespace);
const warnings: string[] = [];
if (secretRefs.length === 0) warnings.push("run executionPolicy.secretScope 未声明 provider SecretRefrunner 将按 secret-unavailable 上报,而不会降级直连外部凭据");
const env = runnerEnv(options, { namespace, jobName, runnerId, attemptId, sourceCommit, secretRefs });
const env = runnerEnv(options, { namespace, jobName, runnerId, attemptId, sourceCommit, secretRefs, toolCredentials });
const manifest: JsonRecord = {
apiVersion: "batch/v1",
kind: "Job",
@@ -136,10 +146,10 @@ export function renderRunnerJobManifest(options: RunnerJobRenderOptions): { mani
},
},
};
return { manifest, namespace, jobName, runnerId, attemptId, sourceCommit, serviceAccountName, secretRefs, warnings, ttlSecondsAfterFinished };
return { manifest, namespace, jobName, runnerId, attemptId, sourceCommit, serviceAccountName, secretRefs, toolCredentials, warnings, ttlSecondsAfterFinished };
}
function runnerEnv(options: RunnerJobRenderOptions, context: { namespace: string; jobName: string; runnerId: string; attemptId: string; sourceCommit: string; secretRefs: CredentialProjection[] }): JsonRecord[] {
function runnerEnv(options: RunnerJobRenderOptions, context: { namespace: string; jobName: string; runnerId: string; attemptId: string; sourceCommit: string; secretRefs: CredentialProjection[]; toolCredentials: ToolCredentialProjection[] }): JsonRecord[] {
const selectedSecret = context.secretRefs.find((item) => item.profile === options.run.backendProfile);
const codexHome = selectedSecret?.runtimeMountPath ?? defaultRuntimeHome(options.run.backendProfile);
return [
@@ -161,10 +171,23 @@ function runnerEnv(options: RunnerJobRenderOptions, context: { namespace: string
{ name: "HOME", value: "/home/agentrun" },
{ name: "CODEX_HOME", value: codexHome },
...(selectedSecret ? [{ name: "AGENTRUN_CODEX_SECRET_HOME", value: selectedSecret.projectionMountPath }] : []),
...toolCredentialEnvVars(context.toolCredentials),
...transientEnvVars(options.transientEnv ?? []),
];
}
function toolCredentialEnvVars(items: ToolCredentialProjection[]): JsonRecord[] {
return items.map((item) => ({
name: item.envName,
valueFrom: {
secretKeyRef: {
name: item.secretRef.name,
key: item.secretKey,
},
},
}));
}
function transientEnvVars(items: RunnerTransientEnv[]): JsonRecord[] {
return items.map((item) => ({ name: item.name, value: item.value }));
}
@@ -177,6 +200,22 @@ function summarizeTransientEnv(items: RunnerTransientEnv[]): JsonRecord {
};
}
function summarizeToolCredentials(items: ToolCredentialProjection[], namespace: string): JsonRecord {
return {
count: items.length,
items: items.map((item) => ({
tool: item.tool,
purpose: item.purpose,
name: item.secretRef.name,
namespace: item.secretRef.namespace ?? namespace,
keys: item.secretRef.keys ?? [],
projection: { kind: "env", envName: item.envName, secretKey: item.secretKey },
valuesPrinted: false,
})),
valuesPrinted: false,
};
}
function redactTransientEnvInManifest(manifest: JsonRecord, items: RunnerTransientEnv[]): JsonRecord {
if (items.length === 0) return manifest;
const names = new Set(items.map((item) => item.name));
@@ -206,6 +245,18 @@ function credentialProjections(run: RunRecord, namespace: string): CredentialPro
}));
}
function toolCredentialProjections(run: RunRecord, namespace: string): ToolCredentialProjection[] {
const policy: ExecutionPolicy = run.executionPolicy;
const credentials = policy.secretScope.toolCredentials ?? [];
return credentials.map((item) => ({
tool: item.tool,
purpose: item.purpose ?? null,
secretRef: item.secretRef.namespace ? item.secretRef : { ...item.secretRef, namespace },
envName: item.projection.envName,
secretKey: item.projection.secretKey ?? item.secretRef.keys?.[0] ?? item.projection.envName,
}));
}
function secretVolume(item: CredentialProjection): JsonRecord {
const secret: JsonRecord = {
secretName: item.secretRef.name,