From 0f826f12d31ed9a8da92069b0df6b2a329bbcb6d Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 14 Jun 2026 07:19:03 +0000 Subject: [PATCH] refactor: source codex sentinel names from yaml --- config/platform-infra/sub2api-codex-pool.yaml | 2 + .../platform-infra-sub2api-codex-sentinel.ts | 23 +++++++++-- scripts/src/platform-infra.ts | 38 +++++++------------ 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/config/platform-infra/sub2api-codex-pool.yaml b/config/platform-infra/sub2api-codex-pool.yaml index 022786e8..b1f17588 100644 --- a/config/platform-infra/sub2api-codex-pool.yaml +++ b/config/platform-infra/sub2api-codex-pool.yaml @@ -187,6 +187,8 @@ sentinel: credentialsSecretName: sub2api-account-sentinel-profiles stateConfigMapName: sub2api-account-sentinel-state cronJobName: sub2api-account-sentinel + roleName: sub2api-account-sentinel + roleBindingName: sub2api-account-sentinel model: gpt-5.5 endpoint: responses marker: diff --git a/scripts/src/platform-infra-sub2api-codex-sentinel.ts b/scripts/src/platform-infra-sub2api-codex-sentinel.ts index 24565cb8..729891dd 100644 --- a/scripts/src/platform-infra-sub2api-codex-sentinel.ts +++ b/scripts/src/platform-infra-sub2api-codex-sentinel.ts @@ -14,6 +14,8 @@ export interface CodexPoolSentinelConfig { credentialsSecretName: string; stateConfigMapName: string; cronJobName: string; + roleName: string; + roleBindingName: string; model: string; endpoint: "responses"; marker: { @@ -110,6 +112,8 @@ export function defaultCodexPoolSentinelConfig(): CodexPoolSentinelConfig { credentialsSecretName: "sub2api-account-sentinel-profiles", stateConfigMapName: "sub2api-account-sentinel-state", cronJobName: "sub2api-account-sentinel", + roleName: "sub2api-account-sentinel", + roleBindingName: "sub2api-account-sentinel", model: "gpt-5.5", endpoint: "responses", marker: { @@ -199,6 +203,8 @@ export function readCodexPoolSentinelConfig(value: unknown, defaults: CodexPoolS credentialsSecretName: readDnsName(valueAt(value, "credentialsSecretName"), `${sourcePath}.sentinel.credentialsSecretName`, defaults.credentialsSecretName), stateConfigMapName: readDnsName(valueAt(value, "stateConfigMapName"), `${sourcePath}.sentinel.stateConfigMapName`, defaults.stateConfigMapName), cronJobName: readDnsName(valueAt(value, "cronJobName"), `${sourcePath}.sentinel.cronJobName`, defaults.cronJobName), + roleName: readRequiredDnsName(valueAt(value, "roleName"), `${sourcePath}.sentinel.roleName`), + roleBindingName: readRequiredDnsName(valueAt(value, "roleBindingName"), `${sourcePath}.sentinel.roleBindingName`), model: readModelName(valueAt(value, "model"), `${sourcePath}.sentinel.model`, defaults.model), endpoint: "responses", marker: { @@ -276,6 +282,8 @@ export function codexPoolSentinelSummary(config: CodexPoolSentinelConfig): Recor actionsEnabled: config.actions.enabled, schedule: config.schedule, cronJobName: config.cronJobName, + roleName: config.roleName, + roleBindingName: config.roleBindingName, configMapName: config.configMapName, credentialsSecretName: config.credentialsSecretName, stateConfigMapName: config.stateConfigMapName, @@ -391,7 +399,7 @@ metadata: apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: - name: ${config.serviceAccountName} + name: ${config.roleName} namespace: ${options.namespace} labels: app.kubernetes.io/name: ${config.cronJobName} @@ -411,7 +419,7 @@ rules: apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: - name: ${config.serviceAccountName} + name: ${config.roleBindingName} namespace: ${options.namespace} labels: app.kubernetes.io/name: ${config.cronJobName} @@ -423,7 +431,7 @@ subjects: namespace: ${options.namespace} roleRef: kind: Role - name: ${config.serviceAccountName} + name: ${config.roleName} apiGroup: rbac.authorization.k8s.io --- apiVersion: batch/v1 @@ -2097,6 +2105,15 @@ function readDnsName(value: unknown, key: string, fallback: string): string { return text; } +function readRequiredDnsName(value: unknown, key: string): string { + if (value === undefined || value === null) throw new Error(`${key} is required`); + if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${key} must be a non-empty string`); + if (/[\r\n]/u.test(value)) throw new Error(`${key} must not contain newlines`); + const text = value.trim(); + if (!/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/u.test(text)) throw new Error(`${key} must be a Kubernetes DNS label`); + return text; +} + function readModelName(value: unknown, key: string, fallback: string): string { const text = readString(value, key, fallback); if (!/^[A-Za-z0-9._:-]+$/u.test(text)) throw new Error(`${key} has an unsupported model name`); diff --git a/scripts/src/platform-infra.ts b/scripts/src/platform-infra.ts index b8ea2e6f..6ec2c332 100644 --- a/scripts/src/platform-infra.ts +++ b/scripts/src/platform-infra.ts @@ -1023,36 +1023,26 @@ function managedResourceCleanupPlan(sub2api: Sub2ApiConfig, target: Sub2ApiTarge } function codexPoolSentinelResourceNames(): ManagedResourceCleanupPlan["sentinel"] { - const defaults: ManagedResourceCleanupPlan["sentinel"] = { - enabled: false, - cronJobName: "sub2api-account-sentinel", - configMapName: "sub2api-account-sentinel-config", - credentialsSecretName: "sub2api-account-sentinel-profiles", - stateConfigMapName: "sub2api-account-sentinel-state", - serviceAccountName: "sub2api-account-sentinel", - roleName: "sub2api-account-sentinel", - roleBindingName: "sub2api-account-sentinel", - }; - if (!existsSync(codexPoolConfigPath)) return defaults; + if (!existsSync(codexPoolConfigPath)) throw new Error(`${codexPoolConfigPath} is required for Sub2API codex-pool sentinel cleanup resource names`); const parsed = Bun.YAML.parse(readFileSync(codexPoolConfigPath, "utf8")) as unknown; - if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return defaults; + if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`${codexPoolConfigPath} must be an object`); const sentinel = (parsed as Record).sentinel; - if (typeof sentinel !== "object" || sentinel === null || Array.isArray(sentinel)) return defaults; + if (typeof sentinel !== "object" || sentinel === null || Array.isArray(sentinel)) throw new Error(`${codexPoolConfigPath}.sentinel must be an object`); const record = sentinel as Record; - const text = (key: string, fallback: string): string => { - const value = record[key]; - return typeof value === "string" && value.length > 0 ? value : fallback; + const resourceName = (key: string): string => { + const value = stringField(record, key, "sentinel"); + validateKubernetesResourceName(value, `sentinel.${key}`); + return value; }; - const serviceAccountName = text("serviceAccountName", defaults.serviceAccountName); return { enabled: false, - cronJobName: text("cronJobName", defaults.cronJobName), - configMapName: text("configMapName", defaults.configMapName), - credentialsSecretName: text("credentialsSecretName", defaults.credentialsSecretName), - stateConfigMapName: text("stateConfigMapName", defaults.stateConfigMapName), - serviceAccountName, - roleName: serviceAccountName, - roleBindingName: serviceAccountName, + cronJobName: resourceName("cronJobName"), + configMapName: resourceName("configMapName"), + credentialsSecretName: resourceName("credentialsSecretName"), + stateConfigMapName: resourceName("stateConfigMapName"), + serviceAccountName: resourceName("serviceAccountName"), + roleName: resourceName("roleName"), + roleBindingName: resourceName("roleBindingName"), }; }