feat: add provider profile removal

This commit is contained in:
Codex
2026-06-08 05:29:11 +08:00
parent 509c2aa6fd
commit 601d8190d0
8 changed files with 121 additions and 12 deletions
+42
View File
@@ -75,6 +75,37 @@ export async function getProviderProfileConfig(profileValue: string, options: Pr
};
}
export async function removeProviderProfile(profileValue: string, options: ProviderProfileOptions = {}): Promise<JsonRecord> {
const profile = validateBackendProfile(profileValue);
const spec = requiredSpec(profile);
const namespace = profileNamespace(options);
const secret = await kubectlGetSecret(spec.defaultSecretName, namespace, options.kubectlCommand ?? "kubectl");
const data = asOptionalRecord(secret?.data);
const annotations = asOptionalRecord(asOptionalRecord(secret?.metadata)?.annotations);
if (secret) await kubectlDeleteSecret(spec.defaultSecretName, namespace, options.kubectlCommand ?? "kubectl");
return {
action: "provider-profile-removed",
mutation: true,
profile,
configured: false,
removed: Boolean(secret),
...(secret ? {} : { alreadyAbsent: true }),
...(isBuiltinProviderProfile(profile) ? { builtinCapabilityRetained: true } : {}),
secretRef: secretRefSummary(profile, namespace),
deletedResourceVersion: stringPath(secret, ["metadata", "resourceVersion"]),
credentialHashSuffix: hashDataKey(data, "auth.json") ?? stringPath(annotations, [`${credentialAnnotationPrefix}-credential-hash-suffix`]),
configHashSuffix: hashDataKey(data, "config.toml") ?? stringPath(annotations, [`${credentialAnnotationPrefix}-config-hash-suffix`]),
updatedAt: new Date().toISOString(),
valuesPrinted: false,
pollCommands: {
list: "./scripts/agentrun provider-profiles list",
show: `./scripts/agentrun provider-profiles show ${profile}`,
setKey: `./scripts/agentrun provider-profiles set-key ${profile} --key-stdin`,
setConfig: `./scripts/agentrun provider-profiles set-config ${profile} --config-stdin`,
},
};
}
export async function setProviderProfileConfig(profileValue: string, body: unknown, options: ProviderProfileOptions = {}): Promise<JsonRecord> {
const profile = validateBackendProfile(profileValue);
const spec = requiredSpec(profile);
@@ -304,6 +335,10 @@ function compareProviderProfiles(left: string, right: string): number {
return left.localeCompare(right);
}
function isBuiltinProviderProfile(profile: BackendProfile): boolean {
return backendProfileSpecs.some((item) => item.profile === profile);
}
function providerProfileFromSecret(secret: JsonRecord): BackendProfile | null {
const metadata = asOptionalRecord(secret.metadata);
const labels = asOptionalRecord(metadata?.labels);
@@ -615,6 +650,13 @@ async function kubectlUpsertSecret(manifest: JsonRecord, kubectlCommand: string)
throw new AgentRunError("infra-failed", `kubectl replace provider profile secret ${namespace}/${name} failed with code ${replace.code}`, { httpStatus: 502, details: redactJson({ stderr: redactText(replace.stderr.slice(-2000)), stdout: redactText(replace.stdout.slice(-1000)) }) });
}
async function kubectlDeleteSecret(name: string, namespace: string, kubectlCommand: string): Promise<void> {
const result = await runKubectl(kubectlCommand, ["delete", "secret", name, "-n", namespace, "--ignore-not-found=true"]);
if (result.code !== 0) {
throw new AgentRunError("infra-failed", `kubectl delete provider profile secret ${namespace}/${name} failed with code ${result.code}`, { httpStatus: 502, details: redactJson({ stderr: redactText(result.stderr.slice(-2000)), stdout: redactText(result.stdout.slice(-1000)) }) });
}
}
function isKubectlNotFoundFailure(result: { stdout: string; stderr: string }): boolean {
return /notfound|not found|not-found/iu.test(`${result.stderr}\n${result.stdout}`);
}
+2 -1
View File
@@ -14,7 +14,7 @@ import { runnerJobStatusSummary } from "./runner-job-status.js";
import { createSessionPvc, deleteSessionPvc, getSessionPvcSummary, refreshSessionPvcSummary, runSessionStorageGc } from "./session-pvc.js";
import type { SessionPvcSummary } from "./session-pvc.js";
import type { SessionPvcOptions } from "./session-pvc.js";
import { getProviderProfileConfig, getProviderProfileValidation, listProviderProfiles, setProviderProfileConfig, setProviderProfileCredential, showProviderProfile, validateProviderProfile } from "./provider-profiles.js";
import { getProviderProfileConfig, getProviderProfileValidation, listProviderProfiles, removeProviderProfile, setProviderProfileConfig, setProviderProfileCredential, showProviderProfile, validateProviderProfile } from "./provider-profiles.js";
function pvcOptions(defaults: { kubectlCommand?: string } | undefined): SessionPvcOptions {
return defaults?.kubectlCommand ? { kubectlCommand: defaults.kubectlCommand } : {};
@@ -96,6 +96,7 @@ async function route({ method, url, body, store, sourceCommit, runnerJobDefaults
if (method === "GET" && path === "/api/v1/provider-profiles") return await listProviderProfiles(providerProfileDefaults) as JsonValue;
const providerProfileMatch = path.match(/^\/api\/v1\/provider-profiles\/([^/]+)$/u);
if (method === "GET" && providerProfileMatch) return await showProviderProfile(providerProfileMatch[1] ?? "", providerProfileDefaults) as JsonValue;
if (method === "DELETE" && providerProfileMatch) return await removeProviderProfile(providerProfileMatch[1] ?? "", providerProfileDefaults) as JsonValue;
const providerConfigMatch = path.match(/^\/api\/v1\/provider-profiles\/([^/]+)\/config$/u);
if (method === "GET" && providerConfigMatch) return await getProviderProfileConfig(providerConfigMatch[1] ?? "", providerProfileDefaults) as JsonValue;
if (method === "PUT" && providerConfigMatch) return await setProviderProfileConfig(providerConfigMatch[1] ?? "", body, providerProfileDefaults) as JsonValue;