Merge pull request #95 from pikasTech/fix/provider-profile-config-toml
feat: 支持 provider profile config.toml 管理
This commit is contained in:
@@ -40,12 +40,14 @@ Provider profile 管理 API 属于 `agentrun-mgr` 公共 REST API 的服务端
|
|||||||
```http
|
```http
|
||||||
GET /api/v1/provider-profiles
|
GET /api/v1/provider-profiles
|
||||||
GET /api/v1/provider-profiles/:profile
|
GET /api/v1/provider-profiles/:profile
|
||||||
|
GET /api/v1/provider-profiles/:profile/config
|
||||||
|
PUT /api/v1/provider-profiles/:profile/config
|
||||||
PUT /api/v1/provider-profiles/:profile/credential
|
PUT /api/v1/provider-profiles/:profile/credential
|
||||||
POST /api/v1/provider-profiles/:profile/validate
|
POST /api/v1/provider-profiles/:profile/validate
|
||||||
GET /api/v1/provider-profiles/:profile/validations/:validationId
|
GET /api/v1/provider-profiles/:profile/validations/:validationId
|
||||||
```
|
```
|
||||||
|
|
||||||
所有成功和失败响应都必须是 JSON。失败响应至少包含 `failureKind`、`message` 和 `requestId`。所有响应不得包含 API Key 原文、Codex `auth.json` 明文、Codex `config.toml` 明文、base64 Secret data、Authorization header、Kubernetes token 或 provider request header。
|
所有成功和失败响应都必须是 JSON。失败响应至少包含 `failureKind`、`message` 和 `requestId`。除显式 `GET /api/v1/provider-profiles/:profile/config` 返回 `config.toml` 明文供 HWLAB admin 管理页查看外,其他响应不得包含 API Key 原文、Codex `auth.json` 明文、Codex `config.toml` 明文、base64 Secret data、Authorization header、Kubernetes token 或 provider request header。
|
||||||
|
|
||||||
### `GET /api/v1/provider-profiles`
|
### `GET /api/v1/provider-profiles`
|
||||||
|
|
||||||
@@ -63,6 +65,10 @@ GET /api/v1/provider-profiles/:profile/validations/:validationId
|
|||||||
|
|
||||||
Secret 缺失时仍要返回 profile capability,并把状态标为 `configured=false` 或 `failureKind=secret-unavailable`;不得因为 Secret 未配置而隐藏 profile。
|
Secret 缺失时仍要返回 profile capability,并把状态标为 `configured=false` 或 `failureKind=secret-unavailable`;不得因为 Secret 未配置而隐藏 profile。
|
||||||
|
|
||||||
|
### `GET/PUT /api/v1/provider-profiles/:profile/config`
|
||||||
|
|
||||||
|
`GET` 返回当前 profile 的 `configToml`、SecretRef、resourceVersion 和 hash 后缀,供 HWLAB admin 管理页查看。`PUT` 接收 `configToml`,保存时只替换同一 Secret 的 `config.toml`,保留现有 `auth.json`,并返回 resourceVersion 和 `configHashSuffix`。
|
||||||
|
|
||||||
### `PUT /api/v1/provider-profiles/:profile/credential`
|
### `PUT /api/v1/provider-profiles/:profile/credential`
|
||||||
|
|
||||||
请求体由 HWLAB 后端或受控 CLI 发送,最小形态:
|
请求体由 HWLAB 后端或受控 CLI 发送,最小形态:
|
||||||
@@ -143,7 +149,9 @@ AgentRun CLI 提供 operator 和综合联调入口:
|
|||||||
```bash
|
```bash
|
||||||
./scripts/agentrun provider-profiles list
|
./scripts/agentrun provider-profiles list
|
||||||
./scripts/agentrun provider-profiles show deepseek
|
./scripts/agentrun provider-profiles show deepseek
|
||||||
|
./scripts/agentrun provider-profiles config deepseek
|
||||||
./scripts/agentrun provider-profiles set-key deepseek --key-stdin
|
./scripts/agentrun provider-profiles set-key deepseek --key-stdin
|
||||||
|
./scripts/agentrun provider-profiles set-config deepseek --config-stdin
|
||||||
./scripts/agentrun provider-profiles validate deepseek --wait --timeout-ms 120000
|
./scripts/agentrun provider-profiles validate deepseek --wait --timeout-ms 120000
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ async function dispatch(args: ParsedArgs): Promise<JsonValue> {
|
|||||||
if (group === "backends" && command === "list") return client(args).get("/api/v1/backends");
|
if (group === "backends" && command === "list") return client(args).get("/api/v1/backends");
|
||||||
if (group === "provider-profiles" && command === "list") return client(args).get("/api/v1/provider-profiles");
|
if (group === "provider-profiles" && command === "list") return client(args).get("/api/v1/provider-profiles");
|
||||||
if (group === "provider-profiles" && command === "show" && id) return client(args).get(`/api/v1/provider-profiles/${encodeURIComponent(normalizeProfile(id))}`);
|
if (group === "provider-profiles" && command === "show" && id) return client(args).get(`/api/v1/provider-profiles/${encodeURIComponent(normalizeProfile(id))}`);
|
||||||
|
if (group === "provider-profiles" && command === "config" && id) return client(args).get(`/api/v1/provider-profiles/${encodeURIComponent(normalizeProfile(id))}/config`);
|
||||||
if (group === "provider-profiles" && command === "set-key" && id) return setProviderProfileKey(args, id);
|
if (group === "provider-profiles" && command === "set-key" && id) return setProviderProfileKey(args, id);
|
||||||
|
if (group === "provider-profiles" && command === "set-config" && id) return setProviderProfileConfig(args, id);
|
||||||
if (group === "provider-profiles" && command === "validate" && id) return validateProviderProfileCli(args, id);
|
if (group === "provider-profiles" && command === "validate" && id) return validateProviderProfileCli(args, id);
|
||||||
if (group === "secrets" && command === "codex" && id === "render") return renderCodexSecret(args);
|
if (group === "secrets" && command === "codex" && id === "render") return renderCodexSecret(args);
|
||||||
if (group === "sessions" && command === "ps") return listSessions(args);
|
if (group === "sessions" && command === "ps") return listSessions(args);
|
||||||
@@ -419,6 +421,23 @@ async function setProviderProfileKey(args: ParsedArgs, profileValue: string): Pr
|
|||||||
return await client(args).put(`/api/v1/provider-profiles/${encodeURIComponent(profile)}/credential`, body) as JsonRecord;
|
return await client(args).put(`/api/v1/provider-profiles/${encodeURIComponent(profile)}/credential`, body) as JsonRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function setProviderProfileConfig(args: ParsedArgs, profileValue: string): Promise<JsonRecord> {
|
||||||
|
const profile = normalizeProfile(profileValue);
|
||||||
|
if (args.flags.get("config-stdin") !== true) throw new AgentRunError("schema-invalid", "provider-profiles set-config requires --config-stdin", { httpStatus: 2 });
|
||||||
|
const configToml = await readStdinText();
|
||||||
|
if (configToml.trim().length === 0) throw new AgentRunError("schema-invalid", "stdin config.toml is empty", { httpStatus: 2 });
|
||||||
|
return await client(args).put(`/api/v1/provider-profiles/${encodeURIComponent(profile)}/config`, {
|
||||||
|
configToml,
|
||||||
|
reason: optionalFlag(args, "reason") ?? "operator-cli",
|
||||||
|
delegatedBy: {
|
||||||
|
system: optionalFlag(args, "delegated-system") ?? "operator-cli",
|
||||||
|
userId: optionalFlag(args, "delegated-user-id") ?? null,
|
||||||
|
username: optionalFlag(args, "delegated-username") ?? null,
|
||||||
|
requestId: optionalFlag(args, "delegated-request-id") ?? null,
|
||||||
|
},
|
||||||
|
}) as JsonRecord;
|
||||||
|
}
|
||||||
|
|
||||||
async function validateProviderProfileCli(args: ParsedArgs, profileValue: string): Promise<JsonRecord> {
|
async function validateProviderProfileCli(args: ParsedArgs, profileValue: string): Promise<JsonRecord> {
|
||||||
const profile = normalizeProfile(profileValue);
|
const profile = normalizeProfile(profileValue);
|
||||||
const started = await client(args).post(`/api/v1/provider-profiles/${encodeURIComponent(profile)}/validate`, {}) as JsonRecord;
|
const started = await client(args).post(`/api/v1/provider-profiles/${encodeURIComponent(profile)}/validate`, {}) as JsonRecord;
|
||||||
@@ -770,7 +789,9 @@ function help(): JsonRecord {
|
|||||||
"secrets codex render --dry-run [--profile codex|deepseek|minimax-m3] [--codex-home <dir>] [--namespace agentrun-v01] [--secret-name <name>]",
|
"secrets codex render --dry-run [--profile codex|deepseek|minimax-m3] [--codex-home <dir>] [--namespace agentrun-v01] [--secret-name <name>]",
|
||||||
"provider-profiles list",
|
"provider-profiles list",
|
||||||
"provider-profiles show <profile>",
|
"provider-profiles show <profile>",
|
||||||
|
"provider-profiles config <profile>",
|
||||||
"provider-profiles set-key <profile> --key-stdin [--model <model>] [--base-url <url>]",
|
"provider-profiles set-key <profile> --key-stdin [--model <model>] [--base-url <url>]",
|
||||||
|
"provider-profiles set-config <profile> --config-stdin",
|
||||||
"provider-profiles validate <profile> [--wait] [--timeout-ms <ms>]",
|
"provider-profiles validate <profile> [--wait] [--timeout-ms <ms>]",
|
||||||
"backends list",
|
"backends list",
|
||||||
"server start [--port <port>] [--host <host>] [--foreground]",
|
"server start [--port <port>] [--host <host>] [--foreground]",
|
||||||
|
|||||||
@@ -48,6 +48,92 @@ export async function showProviderProfile(profile: string, options: ProviderProf
|
|||||||
return providerProfileStatus(validateBackendProfile(profile), options);
|
return providerProfileStatus(validateBackendProfile(profile), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getProviderProfileConfig(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");
|
||||||
|
if (!secret) throw new AgentRunError("secret-unavailable", `provider profile ${profile} Secret is not configured`, { httpStatus: 404, details: { profile, secretRef: secretRefSummary(profile, namespace) } });
|
||||||
|
const data = asOptionalRecord(secret.data);
|
||||||
|
const annotations = asOptionalRecord(asOptionalRecord(secret.metadata)?.annotations);
|
||||||
|
const configToml = configTomlFromData(data, profile);
|
||||||
|
return {
|
||||||
|
action: "provider-profile-config-read",
|
||||||
|
profile,
|
||||||
|
configured: hasRequiredKeys(data, spec.requiredSecretKeys),
|
||||||
|
secretRef: secretRefSummary(profile, namespace),
|
||||||
|
resourceVersion: stringPath(secret, ["metadata", "resourceVersion"]),
|
||||||
|
credentialHashSuffix: hashDataKey(data, "auth.json") ?? stringPath(annotations, [`${credentialAnnotationPrefix}-credential-hash-suffix`]),
|
||||||
|
configHashSuffix: shortHash(configToml),
|
||||||
|
updatedAt: stringPath(annotations, [`${credentialAnnotationPrefix}-updated-at`]) ?? stringPath(secret, ["metadata", "creationTimestamp"]),
|
||||||
|
configToml,
|
||||||
|
configTomlPrinted: true,
|
||||||
|
credentialValuesPrinted: false,
|
||||||
|
valuesPrinted: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setProviderProfileConfig(profileValue: string, body: unknown, options: ProviderProfileOptions = {}): Promise<JsonRecord> {
|
||||||
|
const profile = validateBackendProfile(profileValue);
|
||||||
|
const spec = requiredSpec(profile);
|
||||||
|
const record = asRecord(body ?? {}, "providerProfileConfig");
|
||||||
|
const configToml = configTomlField(record);
|
||||||
|
const delegatedBy = delegatedBySummary(record.delegatedBy);
|
||||||
|
const namespace = profileNamespace(options);
|
||||||
|
const existingSecret = await kubectlGetSecret(spec.defaultSecretName, namespace, options.kubectlCommand ?? "kubectl");
|
||||||
|
if (!existingSecret) throw new AgentRunError("secret-unavailable", `provider profile ${profile} Secret is not configured`, { httpStatus: 404, details: { profile, secretRef: secretRefSummary(profile, namespace) } });
|
||||||
|
const existingData = asOptionalRecord(existingSecret.data);
|
||||||
|
const authJsonData = dataKey(existingData, "auth.json");
|
||||||
|
if (!authJsonData) throw new AgentRunError("secret-unavailable", `provider profile ${profile} auth.json is not configured`, { httpStatus: 400, details: { profile, secretRef: secretRefSummary(profile, namespace), key: "auth.json" } });
|
||||||
|
const credentialHashSuffix = hashDataKey(existingData, "auth.json");
|
||||||
|
const secretManifest: JsonRecord = {
|
||||||
|
apiVersion: "v1",
|
||||||
|
kind: "Secret",
|
||||||
|
metadata: {
|
||||||
|
name: spec.defaultSecretName,
|
||||||
|
namespace,
|
||||||
|
labels: {
|
||||||
|
"app.kubernetes.io/part-of": "agentrun",
|
||||||
|
"agentrun.pikastech.local/profile": profile,
|
||||||
|
},
|
||||||
|
annotations: {
|
||||||
|
[`${credentialAnnotationPrefix}-profile`]: profile,
|
||||||
|
...(credentialHashSuffix ? { [`${credentialAnnotationPrefix}-credential-hash-suffix`]: credentialHashSuffix } : {}),
|
||||||
|
[`${credentialAnnotationPrefix}-config-hash-suffix`]: shortHash(configToml),
|
||||||
|
[`${credentialAnnotationPrefix}-updated-at`]: new Date().toISOString(),
|
||||||
|
...(delegatedBy ? { [`${credentialAnnotationPrefix}-delegated-system`]: delegatedBy.system, [`${credentialAnnotationPrefix}-delegated-request-id`]: delegatedBy.requestId ?? "" } : {}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: "Opaque",
|
||||||
|
data: {
|
||||||
|
"auth.json": authJsonData,
|
||||||
|
"config.toml": base64Data(configToml),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const applied = await kubectlReplaceSecret(secretManifest, options.kubectlCommand ?? "kubectl");
|
||||||
|
return {
|
||||||
|
action: "provider-profile-config-updated",
|
||||||
|
mutation: true,
|
||||||
|
profile,
|
||||||
|
configured: true,
|
||||||
|
secretRef: secretRefSummary(profile, namespace),
|
||||||
|
resourceVersion: objectPath(applied, ["metadata", "resourceVersion"]),
|
||||||
|
credentialHashSuffix,
|
||||||
|
configHashSuffix: shortHash(configToml),
|
||||||
|
updatedAt: objectPath(applied, ["metadata", "annotations", `${credentialAnnotationPrefix}-updated-at`]) ?? new Date().toISOString(),
|
||||||
|
delegatedBy,
|
||||||
|
requiresExternalBridgeUpdate: profile === "deepseek",
|
||||||
|
configTomlPrinted: false,
|
||||||
|
credentialValuesPrinted: false,
|
||||||
|
valuesPrinted: false,
|
||||||
|
pollCommands: {
|
||||||
|
config: `./scripts/agentrun provider-profiles config ${profile}`,
|
||||||
|
show: `./scripts/agentrun provider-profiles show ${profile}`,
|
||||||
|
validate: `./scripts/agentrun provider-profiles validate ${profile} --wait --timeout-ms 120000`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function setProviderProfileCredential(profileValue: string, body: unknown, options: ProviderProfileOptions = {}): Promise<JsonRecord> {
|
export async function setProviderProfileCredential(profileValue: string, body: unknown, options: ProviderProfileOptions = {}): Promise<JsonRecord> {
|
||||||
const profile = validateBackendProfile(profileValue);
|
const profile = validateBackendProfile(profileValue);
|
||||||
const spec = requiredSpec(profile);
|
const spec = requiredSpec(profile);
|
||||||
@@ -287,6 +373,22 @@ function existingConfigAllowed(profile: BackendProfile, configToml: string): boo
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function configTomlFromData(data: JsonRecord | null, profile: BackendProfile): string {
|
||||||
|
const encoded = dataKey(data, "config.toml");
|
||||||
|
if (!encoded) throw new AgentRunError("secret-unavailable", `provider profile ${profile} config.toml is not configured`, { httpStatus: 404, details: { profile, key: "config.toml" } });
|
||||||
|
try {
|
||||||
|
return Buffer.from(encoded, "base64").toString("utf8");
|
||||||
|
} catch {
|
||||||
|
throw new AgentRunError("schema-invalid", `provider profile ${profile} config.toml is not valid base64`, { httpStatus: 400, details: { profile, key: "config.toml" } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function configTomlField(record: JsonRecord): string {
|
||||||
|
const value = record.configToml;
|
||||||
|
if (typeof value !== "string" || value.trim().length === 0) throw new AgentRunError("schema-invalid", "configToml is required", { httpStatus: 400 });
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
function configField(value: unknown, profile: BackendProfile): ProfileConfig {
|
function configField(value: unknown, profile: BackendProfile): ProfileConfig {
|
||||||
const defaults = defaultConfig(profile);
|
const defaults = defaultConfig(profile);
|
||||||
if (value === undefined || value === null) return defaults;
|
if (value === undefined || value === null) return defaults;
|
||||||
@@ -493,6 +595,11 @@ function base64Data(value: string): string {
|
|||||||
return Buffer.from(value, "utf8").toString("base64");
|
return Buffer.from(value, "utf8").toString("base64");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dataKey(data: JsonRecord | null, key: string): string | null {
|
||||||
|
const value = data?.[key];
|
||||||
|
return typeof value === "string" && value.length > 0 ? value : null;
|
||||||
|
}
|
||||||
|
|
||||||
function shortHash(value: string): string {
|
function shortHash(value: string): string {
|
||||||
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -13,7 +13,7 @@ import { runnerJobStatusSummary } from "./runner-job-status.js";
|
|||||||
import { createSessionPvc, deleteSessionPvc, getSessionPvcSummary, refreshSessionPvcSummary, runSessionStorageGc } from "./session-pvc.js";
|
import { createSessionPvc, deleteSessionPvc, getSessionPvcSummary, refreshSessionPvcSummary, runSessionStorageGc } from "./session-pvc.js";
|
||||||
import type { SessionPvcSummary } from "./session-pvc.js";
|
import type { SessionPvcSummary } from "./session-pvc.js";
|
||||||
import type { SessionPvcOptions } from "./session-pvc.js";
|
import type { SessionPvcOptions } from "./session-pvc.js";
|
||||||
import { getProviderProfileValidation, listProviderProfiles, setProviderProfileCredential, showProviderProfile, validateProviderProfile } from "./provider-profiles.js";
|
import { getProviderProfileConfig, getProviderProfileValidation, listProviderProfiles, setProviderProfileConfig, setProviderProfileCredential, showProviderProfile, validateProviderProfile } from "./provider-profiles.js";
|
||||||
|
|
||||||
function pvcOptions(defaults: { kubectlCommand?: string } | undefined): SessionPvcOptions {
|
function pvcOptions(defaults: { kubectlCommand?: string } | undefined): SessionPvcOptions {
|
||||||
return defaults?.kubectlCommand ? { kubectlCommand: defaults.kubectlCommand } : {};
|
return defaults?.kubectlCommand ? { kubectlCommand: defaults.kubectlCommand } : {};
|
||||||
@@ -95,6 +95,9 @@ async function route({ method, url, body, store, sourceCommit, runnerJobDefaults
|
|||||||
if (method === "GET" && path === "/api/v1/provider-profiles") return await listProviderProfiles(providerProfileDefaults) as JsonValue;
|
if (method === "GET" && path === "/api/v1/provider-profiles") return await listProviderProfiles(providerProfileDefaults) as JsonValue;
|
||||||
const providerProfileMatch = path.match(/^\/api\/v1\/provider-profiles\/([^/]+)$/u);
|
const providerProfileMatch = path.match(/^\/api\/v1\/provider-profiles\/([^/]+)$/u);
|
||||||
if (method === "GET" && providerProfileMatch) return await showProviderProfile(providerProfileMatch[1] ?? "", providerProfileDefaults) as JsonValue;
|
if (method === "GET" && providerProfileMatch) return await showProviderProfile(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;
|
||||||
const providerCredentialMatch = path.match(/^\/api\/v1\/provider-profiles\/([^/]+)\/credential$/u);
|
const providerCredentialMatch = path.match(/^\/api\/v1\/provider-profiles\/([^/]+)\/credential$/u);
|
||||||
if (method === "PUT" && providerCredentialMatch) return await setProviderProfileCredential(providerCredentialMatch[1] ?? "", body, providerProfileDefaults) as JsonValue;
|
if (method === "PUT" && providerCredentialMatch) return await setProviderProfileCredential(providerCredentialMatch[1] ?? "", body, providerProfileDefaults) as JsonValue;
|
||||||
const providerValidationCreateMatch = path.match(/^\/api\/v1\/provider-profiles\/([^/]+)\/validate$/u);
|
const providerValidationCreateMatch = path.match(/^\/api\/v1\/provider-profiles\/([^/]+)\/validate$/u);
|
||||||
|
|||||||
@@ -92,6 +92,27 @@ process.exit(1);
|
|||||||
assert.equal(JSON.stringify(list).includes("auth.json"), true);
|
assert.equal(JSON.stringify(list).includes("auth.json"), true);
|
||||||
assert.equal(JSON.stringify(list).includes("redacted-fixture"), false);
|
assert.equal(JSON.stringify(list).includes("redacted-fixture"), false);
|
||||||
|
|
||||||
|
const config = await client.get("/api/v1/provider-profiles/deepseek/config") as JsonRecord;
|
||||||
|
assert.equal(config.profile, "deepseek");
|
||||||
|
assert.equal(config.configToml, "model = \"fixture\"\n");
|
||||||
|
assert.equal(config.configTomlPrinted, true);
|
||||||
|
assert.equal(JSON.stringify(config).includes("redacted-fixture"), false);
|
||||||
|
|
||||||
|
const updatedConfigToml = "model = \"fixture-updated\"\n";
|
||||||
|
const updatedConfig = await client.put("/api/v1/provider-profiles/deepseek/config", {
|
||||||
|
configToml: updatedConfigToml,
|
||||||
|
delegatedBy: { system: "hwlab-v02", userId: "u1", username: "tester", requestId: "req-config-selftest" },
|
||||||
|
reason: "self-test",
|
||||||
|
}) as JsonRecord;
|
||||||
|
assert.equal(updatedConfig.profile, "deepseek");
|
||||||
|
assert.equal(updatedConfig.configTomlPrinted, false);
|
||||||
|
assert.equal(JSON.stringify(updatedConfig).includes(updatedConfigToml), false);
|
||||||
|
const configReplaceRecord = JSON.parse(await readFile(replacedSecretPath, "utf8")) as JsonRecord;
|
||||||
|
const configSecretManifest = configReplaceRecord.manifest as JsonRecord;
|
||||||
|
const configData = configSecretManifest.data as JsonRecord;
|
||||||
|
assert.equal(Buffer.from(String(configData["auth.json"]), "base64").toString("utf8").includes("redacted-fixture"), true);
|
||||||
|
assert.equal(Buffer.from(String(configData["config.toml"]), "base64").toString("utf8"), updatedConfigToml);
|
||||||
|
|
||||||
const updated = await client.put("/api/v1/provider-profiles/deepseek/credential", {
|
const updated = await client.put("/api/v1/provider-profiles/deepseek/credential", {
|
||||||
apiKey: secretText,
|
apiKey: secretText,
|
||||||
delegatedBy: { system: "hwlab-v02", userId: "u1", username: "tester", requestId: "req-selftest" },
|
delegatedBy: { system: "hwlab-v02", userId: "u1", username: "tester", requestId: "req-selftest" },
|
||||||
@@ -147,7 +168,7 @@ process.exit(1);
|
|||||||
assert.equal(finalValidation.status, "completed");
|
assert.equal(finalValidation.status, "completed");
|
||||||
assert.equal(JSON.stringify(finalValidation).includes(secretText), false);
|
assert.equal(JSON.stringify(finalValidation).includes(secretText), false);
|
||||||
assertNoSecretLeak(finalValidation);
|
assertNoSecretLeak(finalValidation);
|
||||||
return { name: "provider-profile-management", tests: ["provider-profiles-list-redacted", "provider-profile-set-key-redacted", "provider-profile-secret-replace-annotation-cleanup", "provider-profile-deepseek-moon-bridge", "provider-profile-manager-secret-rbac", "provider-profile-validation-runner-job"] };
|
return { name: "provider-profile-management", tests: ["provider-profiles-list-redacted", "provider-profile-config", "provider-profile-set-key-redacted", "provider-profile-secret-replace-annotation-cleanup", "provider-profile-deepseek-moon-bridge", "provider-profile-manager-secret-rbac", "provider-profile-validation-runner-job"] };
|
||||||
} finally {
|
} finally {
|
||||||
await new Promise<void>((resolve) => server.server.close(() => resolve()));
|
await new Promise<void>((resolve) => server.server.close(() => resolve()));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user