feat: add aipod spec Artificer assembly
This commit is contained in:
+31
-10
@@ -79,14 +79,26 @@ interface CredentialProjection {
|
||||
projectionMountPath: string;
|
||||
}
|
||||
|
||||
interface ToolCredentialProjection {
|
||||
type ToolCredentialProjection = ToolCredentialEnvProjection | ToolCredentialVolumeProjection;
|
||||
|
||||
interface ToolCredentialBaseProjection {
|
||||
tool: string;
|
||||
purpose: string | null;
|
||||
secretRef: SecretRef;
|
||||
}
|
||||
|
||||
interface ToolCredentialEnvProjection extends ToolCredentialBaseProjection {
|
||||
kind: "env";
|
||||
envName: string;
|
||||
secretKey: string;
|
||||
}
|
||||
|
||||
interface ToolCredentialVolumeProjection extends ToolCredentialBaseProjection {
|
||||
kind: "volume";
|
||||
volumeName: string;
|
||||
mountPath: string;
|
||||
}
|
||||
|
||||
export function renderRunnerJobDryRun(options: RunnerJobRenderOptions): JsonRecord {
|
||||
const render = renderRunnerJobManifest({ ...options, dryRun: true });
|
||||
const manifest = redactTransientEnvInManifest(render.manifest, options.transientEnv ?? []);
|
||||
@@ -176,6 +188,7 @@ export function renderRunnerJobManifest(options: RunnerJobRenderOptions): { mani
|
||||
volumeMounts: [
|
||||
{ name: "runner-home", mountPath: "/home/agentrun" },
|
||||
...secretRefs.map((item) => ({ name: item.volumeName, mountPath: item.projectionMountPath, readOnly: true })),
|
||||
...toolCredentialVolumeMounts(toolCredentials),
|
||||
...(sessionPvc ? [{ name: "agentrun-sessions", mountPath: sessionPvc.mountPath, readOnly: false }] : []),
|
||||
],
|
||||
resources: {
|
||||
@@ -192,6 +205,7 @@ export function renderRunnerJobManifest(options: RunnerJobRenderOptions): { mani
|
||||
volumes: [
|
||||
{ name: "runner-home", emptyDir: {} },
|
||||
...secretRefs.map(secretVolume),
|
||||
...toolCredentialVolumes(toolCredentials),
|
||||
...(sessionPvc ? [{ name: "agentrun-sessions", persistentVolumeClaim: { claimName: sessionPvc.pvcName } }] : []),
|
||||
],
|
||||
},
|
||||
@@ -248,7 +262,7 @@ function codexShellSandbox(policy: ExecutionPolicy): string {
|
||||
}
|
||||
|
||||
function toolCredentialEnvVars(items: ToolCredentialProjection[]): JsonRecord[] {
|
||||
return items.map((item) => ({
|
||||
return items.filter((item): item is ToolCredentialEnvProjection => item.kind === "env").map((item) => ({
|
||||
name: item.envName,
|
||||
valueFrom: {
|
||||
secretKeyRef: {
|
||||
@@ -259,6 +273,14 @@ function toolCredentialEnvVars(items: ToolCredentialProjection[]): JsonRecord[]
|
||||
}));
|
||||
}
|
||||
|
||||
function toolCredentialVolumeMounts(items: ToolCredentialProjection[]): JsonRecord[] {
|
||||
return items.filter((item): item is ToolCredentialVolumeProjection => item.kind === "volume").map((item) => ({ name: item.volumeName, mountPath: item.mountPath, readOnly: true }));
|
||||
}
|
||||
|
||||
function toolCredentialVolumes(items: ToolCredentialProjection[]): JsonRecord[] {
|
||||
return items.filter((item): item is ToolCredentialVolumeProjection => item.kind === "volume").map((item) => secretVolume({ profile: item.tool, secretRef: item.secretRef, volumeName: item.volumeName, runtimeMountPath: item.mountPath, projectionMountPath: item.mountPath }));
|
||||
}
|
||||
|
||||
function transientEnvVars(items: RunnerTransientEnv[]): JsonRecord[] {
|
||||
return items.map((item) => {
|
||||
if (item.secretRef) {
|
||||
@@ -318,7 +340,7 @@ function summarizeToolCredentials(items: ToolCredentialProjection[], namespace:
|
||||
name: item.secretRef.name,
|
||||
namespace: item.secretRef.namespace ?? namespace,
|
||||
keys: item.secretRef.keys ?? [],
|
||||
projection: { kind: "env", envName: item.envName, secretKey: item.secretKey },
|
||||
projection: item.kind === "env" ? { kind: "env", envName: item.envName, secretKey: item.secretKey } : { kind: "volume", mountPath: item.mountPath },
|
||||
valuesPrinted: false,
|
||||
})),
|
||||
valuesPrinted: false,
|
||||
@@ -363,13 +385,12 @@ function credentialSecretRef(profile: string, secretRef: SecretRef, namespace: s
|
||||
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,
|
||||
}));
|
||||
return credentials.map((item, index) => {
|
||||
const secretRef = item.secretRef.namespace ? item.secretRef : { ...item.secretRef, namespace };
|
||||
const base = { tool: item.tool, purpose: item.purpose ?? null, secretRef };
|
||||
if (item.projection.kind === "env") return { ...base, kind: "env" as const, envName: item.projection.envName, secretKey: item.projection.secretKey ?? item.secretRef.keys?.[0] ?? item.projection.envName };
|
||||
return { ...base, kind: "volume" as const, volumeName: sanitizeVolumeName(`tool-${item.tool}-${index}`), mountPath: item.projection.mountPath };
|
||||
});
|
||||
}
|
||||
|
||||
function secretVolume(item: CredentialProjection): JsonRecord {
|
||||
|
||||
@@ -43,6 +43,9 @@ interface MaterializedSkillRef {
|
||||
|
||||
interface GitCheckout {
|
||||
repoUrl: string;
|
||||
fetchRepoUrl: string;
|
||||
mirrorUsed: boolean;
|
||||
mirrorBaseUrl?: string;
|
||||
commitId: string;
|
||||
requestedCommitId?: string;
|
||||
requestedRef?: string;
|
||||
@@ -50,15 +53,24 @@ interface GitCheckout {
|
||||
treeId: string;
|
||||
}
|
||||
|
||||
interface GitMirrorConfig {
|
||||
enabled: true;
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
interface GitBundleSource {
|
||||
repoUrl: string;
|
||||
commitId?: string;
|
||||
ref?: string;
|
||||
gitMirror?: GitMirrorConfig;
|
||||
}
|
||||
|
||||
interface MaterializedGitBundle {
|
||||
name: string | null;
|
||||
repoUrl: string;
|
||||
fetchRepoUrl: string;
|
||||
mirrorUsed: boolean;
|
||||
mirrorBaseUrl: string | null;
|
||||
commitId: string;
|
||||
requestedCommitId: string | null;
|
||||
requestedRef: string | null;
|
||||
@@ -78,7 +90,8 @@ export async function materializeResourceBundle(resourceBundleRef: ResourceBundl
|
||||
await rm(assemblyRoot, { recursive: true, force: true });
|
||||
await mkdir(checkoutRoot, { recursive: true });
|
||||
await mkdir(workspacePath, { recursive: true });
|
||||
const defaultSource = defaultGitBundleSource(resourceBundleRef, env);
|
||||
const gitMirror = gitMirrorConfig(resourceBundleRef, env);
|
||||
const defaultSource = defaultGitBundleSource(resourceBundleRef, env, gitMirror);
|
||||
const checkoutCache = new Map<string, Promise<GitCheckout>>();
|
||||
const checkoutFor = (source: GitBundleSource) => {
|
||||
const key = stableHash(gitSourceIdentity(source));
|
||||
@@ -105,6 +118,10 @@ export async function materializeResourceBundle(resourceBundleRef: ResourceBundl
|
||||
phase: "resource-bundle-materialized",
|
||||
kind: "gitbundle",
|
||||
repoUrl: resourceBundleRef.repoUrl,
|
||||
fetchRepoUrl: defaultCheckout.fetchRepoUrl,
|
||||
mirrorUsed: defaultCheckout.mirrorUsed,
|
||||
mirrorBaseUrl: defaultCheckout.mirrorBaseUrl ?? null,
|
||||
gitMirror: gitMirror ? { enabled: true, baseUrl: gitMirror.baseUrl, valuesPrinted: false } : { enabled: false, baseUrl: null, valuesPrinted: false },
|
||||
commitId: defaultCheckout.commitId,
|
||||
requestedCommitId: resourceBundleRef.commitId ?? null,
|
||||
requestedRef: defaultCheckout.requestedRef ?? null,
|
||||
@@ -126,32 +143,44 @@ export async function materializeResourceBundle(resourceBundleRef: ResourceBundl
|
||||
};
|
||||
}
|
||||
|
||||
function defaultGitBundleSource(resourceBundleRef: ResourceBundleRef, env: NodeJS.ProcessEnv): GitBundleSource {
|
||||
function gitMirrorConfig(resourceBundleRef: ResourceBundleRef, env: NodeJS.ProcessEnv): GitMirrorConfig | undefined {
|
||||
return normalizeGitMirrorConfig(resourceBundleRef.gitMirror, env);
|
||||
}
|
||||
|
||||
function normalizeGitMirrorConfig(gitMirror: ResourceBundleRef["gitMirror"] | GitMirrorConfig | undefined, env: NodeJS.ProcessEnv): GitMirrorConfig | undefined {
|
||||
if (!gitMirror || gitMirror.enabled === false) return undefined;
|
||||
const baseUrl = optionalNonEmpty(gitMirror.baseUrl) ?? optionalNonEmpty(env.AGENTRUN_GIT_MIRROR_BASE_URL) ?? "http://git-mirror-http.devops-infra.svc.cluster.local";
|
||||
return { enabled: true, baseUrl: baseUrl.replace(/\/+$/u, "") };
|
||||
}
|
||||
|
||||
function defaultGitBundleSource(resourceBundleRef: ResourceBundleRef, env: NodeJS.ProcessEnv, gitMirror?: GitMirrorConfig): GitBundleSource {
|
||||
const ref = optionalNonEmpty(resourceBundleRef.ref) ?? optionalNonEmpty(env.AGENTRUN_RESOURCE_BUNDLE_REF) ?? optionalNonEmpty(env.AGENTRUN_WORKSPACE_REF) ?? optionalNonEmpty(env.AGENTRUN_WORKSPACE_BRANCH);
|
||||
if (ref) return { repoUrl: resourceBundleRef.repoUrl, ref };
|
||||
if (ref) return { repoUrl: resourceBundleRef.repoUrl, ref, ...(gitMirror ? { gitMirror } : {}) };
|
||||
const commitId = optionalNonEmpty(resourceBundleRef.commitId);
|
||||
if (commitId) return { repoUrl: resourceBundleRef.repoUrl, commitId };
|
||||
return { repoUrl: resourceBundleRef.repoUrl, ref: "HEAD" };
|
||||
if (commitId) return { repoUrl: resourceBundleRef.repoUrl, commitId, ...(gitMirror ? { gitMirror } : {}) };
|
||||
return { repoUrl: resourceBundleRef.repoUrl, ref: "HEAD", ...(gitMirror ? { gitMirror } : {}) };
|
||||
}
|
||||
|
||||
function bundleGitSource(bundle: ResourceBundleRef["bundles"][number], resourceBundleRef: ResourceBundleRef, defaultSource: GitBundleSource): GitBundleSource {
|
||||
const repoUrl = bundle.repoUrl ?? resourceBundleRef.repoUrl;
|
||||
const ref = optionalNonEmpty(bundle.ref);
|
||||
if (ref) return { repoUrl, ref };
|
||||
const mirror = defaultSource.gitMirror;
|
||||
if (ref) return { repoUrl, ref, ...(mirror ? { gitMirror: mirror } : {}) };
|
||||
const commitId = optionalNonEmpty(bundle.commitId);
|
||||
if (commitId) return { repoUrl, commitId };
|
||||
if (commitId) return { repoUrl, commitId, ...(mirror ? { gitMirror: mirror } : {}) };
|
||||
if (repoUrl === defaultSource.repoUrl) return defaultSource;
|
||||
if (defaultSource.ref) return { repoUrl, ref: defaultSource.ref };
|
||||
if (defaultSource.commitId) return { repoUrl, commitId: defaultSource.commitId };
|
||||
return { repoUrl, ref: "HEAD" };
|
||||
if (defaultSource.ref) return { repoUrl, ref: defaultSource.ref, ...(mirror ? { gitMirror: mirror } : {}) };
|
||||
if (defaultSource.commitId) return { repoUrl, commitId: defaultSource.commitId, ...(mirror ? { gitMirror: mirror } : {}) };
|
||||
return { repoUrl, ref: "HEAD", ...(mirror ? { gitMirror: mirror } : {}) };
|
||||
}
|
||||
|
||||
async function checkoutGitSource(checkoutRoot: string, source: GitBundleSource): Promise<GitCheckout> {
|
||||
const checkoutPath = path.join(checkoutRoot, stableHash(gitSourceIdentity(source)).slice(0, 16));
|
||||
const fetch = gitFetchSource(source);
|
||||
await mkdir(checkoutPath, { recursive: true });
|
||||
await git(["init"], checkoutPath);
|
||||
await git(["remote", "remove", "origin"], checkoutPath, { allowFailure: true });
|
||||
await git(["remote", "add", "origin", source.repoUrl], checkoutPath);
|
||||
await git(["remote", "add", "origin", fetch.fetchRepoUrl], checkoutPath);
|
||||
if (source.ref) {
|
||||
await git(["fetch", "--depth", "1", "origin", source.ref], checkoutPath);
|
||||
await git(["checkout", "--detach", "FETCH_HEAD"], checkoutPath);
|
||||
@@ -164,11 +193,41 @@ async function checkoutGitSource(checkoutRoot: string, source: GitBundleSource):
|
||||
const actualCommit = (await git(["rev-parse", "HEAD"], checkoutPath)).stdout.trim();
|
||||
if (source.commitId && actualCommit !== source.commitId) throw new AgentRunError("infra-failed", "gitbundle checkout did not land on requested commit", { httpStatus: 500, details: { expectedCommit: source.commitId, actualCommit } });
|
||||
const treeId = (await git(["rev-parse", "HEAD^{tree}"], checkoutPath)).stdout.trim();
|
||||
return { repoUrl: source.repoUrl, commitId: actualCommit, ...(source.commitId ? { requestedCommitId: source.commitId } : {}), ...(source.ref ? { requestedRef: source.ref } : {}), checkoutPath, treeId };
|
||||
return { repoUrl: source.repoUrl, fetchRepoUrl: fetch.fetchRepoUrl, mirrorUsed: fetch.mirrorUsed, ...(fetch.mirrorBaseUrl ? { mirrorBaseUrl: fetch.mirrorBaseUrl } : {}), commitId: actualCommit, ...(source.commitId ? { requestedCommitId: source.commitId } : {}), ...(source.ref ? { requestedRef: source.ref } : {}), checkoutPath, treeId };
|
||||
}
|
||||
|
||||
function gitSourceIdentity(source: GitBundleSource): JsonRecord {
|
||||
return { repoUrl: source.repoUrl, commitId: source.commitId ?? null, ref: source.ref ?? null };
|
||||
return { repoUrl: source.repoUrl, commitId: source.commitId ?? null, ref: source.ref ?? null, gitMirror: source.gitMirror ? { enabled: true, baseUrl: source.gitMirror.baseUrl } : null };
|
||||
}
|
||||
|
||||
export function resolveGitBundleFetchSource(repoUrl: string, gitMirror?: ResourceBundleRef["gitMirror"] | GitMirrorConfig, env: NodeJS.ProcessEnv = process.env): { fetchRepoUrl: string; mirrorUsed: boolean; mirrorBaseUrl?: string } {
|
||||
const mirror = normalizeGitMirrorConfig(gitMirror, env);
|
||||
if (!mirror) return { fetchRepoUrl: repoUrl, mirrorUsed: false };
|
||||
const githubPath = githubRepoPath(repoUrl);
|
||||
if (!githubPath) return { fetchRepoUrl: repoUrl, mirrorUsed: false };
|
||||
return { fetchRepoUrl: `${mirror.baseUrl}/${githubPath}.git`, mirrorUsed: true, mirrorBaseUrl: mirror.baseUrl };
|
||||
}
|
||||
|
||||
function gitFetchSource(source: GitBundleSource): { fetchRepoUrl: string; mirrorUsed: boolean; mirrorBaseUrl?: string } {
|
||||
return resolveGitBundleFetchSource(source.repoUrl, source.gitMirror);
|
||||
}
|
||||
|
||||
function githubRepoPath(repoUrl: string): string | null {
|
||||
const raw = repoUrl.trim();
|
||||
const scp = /^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/u.exec(raw);
|
||||
if (scp) return cleanGithubPath(scp[1] ?? "", scp[2] ?? "");
|
||||
const ssh = /^ssh:\/\/git@(?:github\.com|ssh\.github\.com)(?::\d+)?\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/u.exec(raw);
|
||||
if (ssh) return cleanGithubPath(ssh[1] ?? "", ssh[2] ?? "");
|
||||
const http = /^https?:\/\/github\.com\/([^/]+)\/([^/#?]+?)(?:\.git)?\/?$/u.exec(raw);
|
||||
if (http) return cleanGithubPath(http[1] ?? "", http[2] ?? "");
|
||||
return null;
|
||||
}
|
||||
|
||||
function cleanGithubPath(owner: string, repo: string): string | null {
|
||||
const cleanOwner = owner.trim();
|
||||
const cleanRepo = repo.trim().replace(/\.git$/u, "");
|
||||
if (!/^[A-Za-z0-9_.-]+$/u.test(cleanOwner) || !/^[A-Za-z0-9_.-]+$/u.test(cleanRepo)) return null;
|
||||
return `${cleanOwner}/${cleanRepo}`;
|
||||
}
|
||||
|
||||
async function materializeGitBundles(workspacePath: string, resourceBundleRef: ResourceBundleRef, defaultSource: GitBundleSource, defaultCheckout: GitCheckout, checkoutFor: (source: GitBundleSource) => Promise<GitCheckout>): Promise<MaterializedGitBundle[]> {
|
||||
@@ -187,7 +246,7 @@ async function materializeGitBundles(workspacePath: string, resourceBundleRef: R
|
||||
await mkdir(path.dirname(target), { recursive: true });
|
||||
await rm(target, { recursive: true, force: true });
|
||||
await cp(source, target, { recursive: true, force: true, dereference: false });
|
||||
items.push({ name: bundle.name ?? null, repoUrl: checkout.repoUrl, commitId: checkout.commitId, requestedCommitId: bundle.commitId ?? resourceBundleRef.commitId ?? null, requestedRef: checkout.requestedRef ?? null, subpath: bundle.subpath, targetPath: bundle.targetPath, sourceKind: sourceStat.isDirectory() ? "directory" : "file", sourceBytes: sourceStat.isFile() ? sourceStat.size : null });
|
||||
items.push({ name: bundle.name ?? null, repoUrl: checkout.repoUrl, fetchRepoUrl: checkout.fetchRepoUrl, mirrorUsed: checkout.mirrorUsed, mirrorBaseUrl: checkout.mirrorBaseUrl ?? null, commitId: checkout.commitId, requestedCommitId: bundle.commitId ?? resourceBundleRef.commitId ?? null, requestedRef: checkout.requestedRef ?? null, subpath: bundle.subpath, targetPath: bundle.targetPath, sourceKind: sourceStat.isDirectory() ? "directory" : "file", sourceBytes: sourceStat.isFile() ? sourceStat.size : null });
|
||||
}
|
||||
return items;
|
||||
}
|
||||
@@ -380,6 +439,9 @@ function skillSourceBundle(relativePath: string, bundles: MaterializedGitBundle[
|
||||
return {
|
||||
name: match.name,
|
||||
repoUrl: match.repoUrl,
|
||||
fetchRepoUrl: match.fetchRepoUrl,
|
||||
mirrorUsed: match.mirrorUsed,
|
||||
mirrorBaseUrl: match.mirrorBaseUrl,
|
||||
commitId: match.commitId,
|
||||
requestedCommitId: match.requestedCommitId,
|
||||
requestedRef: match.requestedRef,
|
||||
|
||||
Reference in New Issue
Block a user