fix: preflight codex stdio mount resources

This commit is contained in:
Codex
2026-05-24 07:52:14 +00:00
parent 51c2531f6f
commit 48abac41c6
5 changed files with 199 additions and 7 deletions
@@ -0,0 +1,15 @@
{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": {
"name": "hwlab-code-agent-codex-config",
"namespace": "hwlab-dev",
"labels": {
"app.kubernetes.io/name": "hwlab-code-agent-codex-config",
"hwlab.pikastech.local/service-id": "hwlab-cloud-api"
}
},
"data": {
"config.toml": "model_provider = \"OpenAI\"\nmodel = \"gpt-5.4\"\nreview_model = \"gpt-5.4\"\nmodel_reasoning_effort = \"xhigh\"\ndisable_response_storage = true\nnetwork_access = \"enabled\"\nmodel_context_window = 1000000\nmodel_auto_compact_token_limit = 900000\n\n[model_providers.OpenAI]\nname = \"OpenAI\"\nbase_url = \"https://hyueapi.com\"\nwire_api = \"responses\"\nrequires_openai_auth = true\n\n[model_providers.crs]\nrequest_max_retries = 1000\nstream_max_retries = 1000\nname = \"crs\"\nbase_url = \"https://api.gptclubapi.xyz/openai\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nenv_key = \"CRS_OAI_KEY\"\n"
}
}
+1
View File
@@ -3,6 +3,7 @@
"kind": "Kustomization",
"resources": [
"namespace.yaml",
"code-agent-codex-config.yaml",
"services.yaml",
"workloads.yaml"
],
+6
View File
@@ -108,6 +108,12 @@ apply preflight 检查的 SecretRef 固定为:
- `hwlab-cloud-api-dev-db/database-url`
- `hwlab-cloud-api-dev-db-admin/admin-url`
- `hwlab-code-agent-provider/openai-api-key`
- `hwlab-code-agent-codex-auth/auth.json`
Code Agent Codex stdio 还必须在 preflight 阶段验证非 secret ConfigMap
`hwlab-code-agent-codex-config/config.toml` 的存在性和 key 名;该 ConfigMap 由
`deploy/k8s/base/code-agent-codex-config.yaml` 纳入 DEV desired-state。Codex
auth 仍然只能作为外部 Secret 管理,禁止把 `auth.json` 内容写进仓库或报告。
preflight 和报告只能记录 Secret 名、key 名、存在性、key-name 观察结果和脱敏
命令摘要;不得读取或打印 Secret value。阻塞时输出结构化 blocker,包含
+115 -4
View File
@@ -39,7 +39,11 @@ const forbiddenControlPlanePattern = /docker-desktop|desktop-control-plane|127\.
const requiredDevSecretRefs = Object.freeze([
Object.freeze({ secretName: "hwlab-cloud-api-dev-db", secretKey: "database-url" }),
Object.freeze({ secretName: "hwlab-cloud-api-dev-db-admin", secretKey: "admin-url" }),
Object.freeze({ secretName: "hwlab-code-agent-provider", secretKey: "openai-api-key" })
Object.freeze({ secretName: "hwlab-code-agent-provider", secretKey: "openai-api-key" }),
Object.freeze({ secretName: "hwlab-code-agent-codex-auth", secretKey: "auth.json" })
]);
const requiredDevConfigMapRefs = Object.freeze([
Object.freeze({ configMapName: "hwlab-code-agent-codex-config", configMapKey: "config.toml" })
]);
const lockAnnotationPrefix = "hwlab.pikastech.local";
const lockAnnotationFields = {
@@ -1288,6 +1292,13 @@ function parseSecretDescribeKeys(stdout) {
return [...keys].sort();
}
function parseKeyLines(stdout) {
return String(stdout ?? "")
.split(/\r?\n/u)
.map((line) => line.trim())
.filter(Boolean);
}
function commandObservation(args, result) {
return {
command: shellCommand("kubectl", args),
@@ -1483,6 +1494,97 @@ function secretRefBlocker(ref, observation) {
});
}
async function observeRequiredConfigMapRef(ctx, kubectlContext, args, ref) {
const existsArgs = ["-n", args.targetNamespace, "get", "configmap", ref.configMapName, "-o", "name"];
const keyArgs = [
"-n",
args.targetNamespace,
"get",
"configmap",
ref.configMapName,
"-o",
"go-template={{range $k,$v := .data}}{{printf \"%s\\n\" $k}}{{end}}"
];
const exists = await preflightKubectl(ctx, kubectlContext, existsArgs, { timeoutMs: 10000 });
if (exists.code !== 0) {
return {
configMapName: ref.configMapName,
configMapKey: ref.configMapKey,
status: "missing-configmap",
exists: false,
keyPresent: false,
keysObserved: [],
existenceCommand: shellCommand("kubectl", existsArgs),
keyCommand: null,
error: commandSummary(exists),
configMapValueRead: false,
configMapValuePrinted: false
};
}
const keys = await preflightKubectl(ctx, kubectlContext, keyArgs, { timeoutMs: 10000 });
if (keys.code !== 0) {
return {
configMapName: ref.configMapName,
configMapKey: ref.configMapKey,
status: "key-observation-blocked",
exists: true,
keyPresent: false,
keysObserved: [],
existenceCommand: shellCommand("kubectl", existsArgs),
keyCommand: shellCommand("kubectl", keyArgs),
error: commandSummary(keys),
configMapValueRead: false,
configMapValuePrinted: false
};
}
const keysObserved = parseKeyLines(keys.stdout);
const keyPresent = keysObserved.includes(ref.configMapKey);
return {
configMapName: ref.configMapName,
configMapKey: ref.configMapKey,
status: keyPresent ? "present" : "missing-key",
exists: true,
keyPresent,
keysObserved,
existenceCommand: shellCommand("kubectl", existsArgs),
keyCommand: shellCommand("kubectl", keyArgs),
configMapValueRead: false,
configMapValuePrinted: false
};
}
function configMapRefBlocker(ref, observation) {
const scope = `configmap:${ref.configMapName}/${ref.configMapKey}`;
if (observation.status === "missing-configmap") {
return blocker({
type: "runtime_blocker",
scope,
reason: `Required DEV ConfigMap ${ref.configMapName} was not observed in ${defaultNamespace}.`,
impact: "The Code Agent Codex stdio provider pod would fail to mount config.toml after mutation.",
safeNextAction: `Create or restore ConfigMap ${ref.configMapName}/${ref.configMapKey} in ${defaultNamespace}, then retry DEV CD.`,
retryable: true
});
}
if (observation.status === "key-observation-blocked") {
return blocker({
type: "runtime_blocker",
scope,
reason: `Required DEV ConfigMap ${ref.configMapName} exists but key names could not be verified.`,
impact: "DEV CD cannot prove the Code Agent Codex config mount before applying workloads.",
safeNextAction: `Restore key-name visibility for ${ref.configMapName}, then retry.`,
retryable: true
});
}
return blocker({
type: "runtime_blocker",
scope,
reason: `Required DEV ConfigMap key ${ref.configMapName}/${ref.configMapKey} was not observed.`,
impact: "The Code Agent Codex stdio provider pod would reference a missing config.toml key after mutation.",
safeNextAction: `Add key ${ref.configMapKey} to ConfigMap ${ref.configMapName}, then retry DEV CD.`,
retryable: true
});
}
async function runDevCdPreflight({ ctx, args, kubectlContext }) {
const blockers = [];
const preflight = {
@@ -1502,11 +1604,14 @@ async function runDevCdPreflight({ ctx, args, kubectlContext }) {
},
controlPlane: null,
secretRefs: [],
configMaps: [],
safety: {
writeSideEffectsAttempted: false,
prodTouched: false,
secretValuesRead: false,
secretValuesPrinted: false,
configMapValuesRead: false,
configMapValuesPrinted: false,
secretKeyNamesOnly: true
},
blockers
@@ -1560,14 +1665,20 @@ async function runDevCdPreflight({ ctx, args, kubectlContext }) {
}
if (blockers.length === 0) {
preflight.secretRefs = await Promise.all(
requiredDevSecretRefs.map((ref) => observeRequiredSecretRef(ctx, kubectlContext, args, ref))
);
[preflight.secretRefs, preflight.configMaps] = await Promise.all([
Promise.all(requiredDevSecretRefs.map((ref) => observeRequiredSecretRef(ctx, kubectlContext, args, ref))),
Promise.all(requiredDevConfigMapRefs.map((ref) => observeRequiredConfigMapRef(ctx, kubectlContext, args, ref)))
]);
for (const [index, observation] of preflight.secretRefs.entries()) {
if (observation.status !== "present") {
blockers.push(secretRefBlocker(requiredDevSecretRefs[index], observation));
}
}
for (const [index, observation] of preflight.configMaps.entries()) {
if (observation.status !== "present") {
blockers.push(configMapRefBlocker(requiredDevConfigMapRefs[index], observation));
}
}
}
preflight.status = blockers.length === 0 ? "pass" : "blocked";
+62 -3
View File
@@ -236,7 +236,11 @@ function makeRunCommand({
secretRefs = {
"hwlab-cloud-api-dev-db": ["database-url"],
"hwlab-cloud-api-dev-db-admin": ["admin-url"],
"hwlab-code-agent-provider": ["openai-api-key"]
"hwlab-code-agent-provider": ["openai-api-key"],
"hwlab-code-agent-codex-auth": ["auth.json"]
},
configMaps = {
"hwlab-code-agent-codex-config": ["config.toml"]
}
} = {}) {
let lease = heldLock ? leaseFromLock(heldLock) : null;
@@ -314,6 +318,17 @@ function makeRunCommand({
stderr: ""
};
}
if (command.includes("kubectl") && args.includes("get") && args.includes("configmap")) {
const name = args[args.indexOf("configmap") + 1];
const keys = configMaps[name];
if (!keys) {
return { code: 1, stdout: "", stderr: `Error from server (NotFound): configmaps "${name}" not found` };
}
if (args.some((arg) => String(arg).startsWith("go-template="))) {
return { code: 0, stdout: `${keys.join("\n")}\n`, stderr: "" };
}
return { code: 0, stdout: `configmap/${name}\n`, stderr: "" };
}
if (command.includes("kubectl") && args.includes("get") && args.includes("lease")) {
if (!lease) return { code: 1, stdout: "", stderr: "Error from server (NotFound): leases.coordination.k8s.io \"hwlab-dev-cd-lock\" not found" };
return { code: 0, stdout: `${JSON.stringify(lease)}\n`, stderr: "" };
@@ -1169,11 +1184,18 @@ test("apply preflight passes with D601 native k3s and required SecretRef keys be
[
"hwlab-cloud-api-dev-db/database-url:present",
"hwlab-cloud-api-dev-db-admin/admin-url:present",
"hwlab-code-agent-provider/openai-api-key:present"
"hwlab-code-agent-provider/openai-api-key:present",
"hwlab-code-agent-codex-auth/auth.json:present"
]
);
assert.deepEqual(
preflight.configMaps.map((ref) => `${ref.configMapName}/${ref.configMapKey}:${ref.status}`),
["hwlab-code-agent-codex-config/config.toml:present"]
);
assert.equal(preflight.safety.secretValuesRead, false);
assert.equal(preflight.safety.secretValuesPrinted, false);
assert.equal(preflight.safety.configMapValuesRead, false);
assert.equal(preflight.safety.configMapValuesPrinted, false);
const firstWriteIndex = commandLog.findIndex((entry) =>
entry.command.includes("kubectl") && ["create", "replace", "apply", "patch", "delete", "rollout", "wait", "logs"].some((verb) => entry.args.includes(verb))
@@ -1206,7 +1228,8 @@ test("apply preflight blocks missing SecretRef before Lease or side-effect comma
commandLog,
secretRefs: {
"hwlab-cloud-api-dev-db": ["database-url"],
"hwlab-cloud-api-dev-db-admin": ["admin-url"]
"hwlab-cloud-api-dev-db-admin": ["admin-url"],
"hwlab-code-agent-codex-auth": ["auth.json"]
}
}),
httpGetJson: makeHttpGetJson(),
@@ -1227,6 +1250,42 @@ test("apply preflight blocks missing SecretRef before Lease or side-effect comma
assertNoWriteSideEffects(commandLog);
});
test("apply preflight blocks missing Codex ConfigMap before Lease or workload apply", async () => {
const repoRoot = await makeRepo();
const commandLog = [];
let output = "";
const code = await runDevCdApply([
"--apply",
"--confirm-dev",
"--confirmed-non-production",
"--owner-task-id",
"task-missing-codex-config",
"--kubeconfig",
"/etc/rancher/k3s/k3s.yaml",
"--report", stateReport(repoRoot, "dev-cd-apply.json")
], {
repoRoot,
env: {},
runCommand: makeRunCommand({
commandLog,
configMaps: {}
}),
httpGetJson: makeHttpGetJson(),
now: () => new Date(iso(100000)),
stdout: { write: (chunk) => { output += chunk; } }
});
const summary = JSON.parse(output);
const writtenReport = JSON.parse(await readFile(stateReport(repoRoot, "dev-cd-apply.json"), "utf8"));
assert.equal(code, 2);
assert.equal(summary.status, "blocked");
assert.equal(summary.preflight.status, "blocked");
assert.equal(summary.blockers.some((blocker) => blocker.scope === "configmap:hwlab-code-agent-codex-config/config.toml"), true);
assert.equal(writtenReport.devCdApply.preflight.safety.writeSideEffectsAttempted, false);
assert.equal(writtenReport.devCdApply.preflight.safety.configMapValuesPrinted, false);
assertNoWriteSideEffects(commandLog);
});
test("apply preflight blocks non-D601 kubeconfig path before kubectl mutation", async () => {
const repoRoot = await makeRepo();
const commandLog = [];