refactor: normalize sub2api yaml envelope

This commit is contained in:
Codex
2026-06-14 01:54:15 +00:00
parent ac4ae5079c
commit a6123b1d48
4 changed files with 122 additions and 51 deletions
@@ -1,3 +1,13 @@
version: 1
kind: platform-infra-sub2api-codex-pool
metadata:
id: sub2api-codex-pool
owner: unidesk
relatedIssues:
- 339
- 340
pool:
groupName: unidesk-codex-pool
apiKeyName: unidesk-codex-pool-api-key
+16
View File
@@ -1,7 +1,23 @@
version: 1
kind: platform-infra-sub2api
metadata:
id: sub2api
owner: unidesk
relatedIssues:
- 220
- 339
- 340
image:
repository: weishaw/sub2api
tag: 0.1.136
pullPolicy: IfNotPresent
dependencyImages:
postgres: postgres:18-alpine
redis: redis:8-alpine
targets:
- id: G14
route: G14:k3s
+61 -1
View File
@@ -131,6 +131,13 @@ export interface CodexTempUnschedulablePolicy {
}
interface CodexPoolConfig {
version: number;
kind: string;
metadata: {
id: string;
owner: string;
relatedIssues: number[];
};
groupName: string;
apiKeyName: string;
apiKeySecretName: string;
@@ -1209,11 +1216,17 @@ function resolveProfileFiles(codexDir: string, profile: CodexPoolProfileConfig):
function readCodexPoolConfig(): CodexPoolConfig {
const defaults = defaultCodexPoolConfig();
if (!existsSync(codexPoolConfigPath)) return defaults;
if (!existsSync(codexPoolConfigPath)) throw new Error(`${codexPoolConfigPath} is required`);
const parsed = Bun.YAML.parse(readFileSync(codexPoolConfigPath, "utf8")) as unknown;
if (!isRecord(parsed)) throw new Error(`${codexPoolConfigPath} must contain a YAML object`);
const version = integerConfigField(parsed, "version", "");
if (version !== 1) throw new Error(`${codexPoolConfigPath}.version must be 1`);
const kind = requiredStringConfigField(parsed, "kind", "");
if (kind !== "platform-infra-sub2api-codex-pool") throw new Error(`${codexPoolConfigPath}.kind must be platform-infra-sub2api-codex-pool`);
const metadata = requiredRecordConfigField(parsed, "metadata", "");
const pool = parsed.pool;
if (!isRecord(pool)) throw new Error(`${codexPoolConfigPath}.pool must be a YAML object`);
if (!isRecord(parsed.profiles)) throw new Error(`${codexPoolConfigPath}.profiles must be a YAML object`);
rejectSchedulableYamlField(pool, "pool");
const defaultTempUnschedulable = readTempUnschedulablePolicy(pool.defaultTempUnschedulable, "pool.defaultTempUnschedulable", defaults.defaultTempUnschedulable);
const defaultAccountPriorityValue = readAccountPriority(pool.defaultAccountPriority, "pool.defaultAccountPriority");
@@ -1233,6 +1246,13 @@ function readCodexPoolConfig(): CodexPoolConfig {
? Math.max(1, declaredAccountCapacity)
: readOwnerConcurrency(pool.minOwnerConcurrency, "pool.minOwnerConcurrency");
const config: CodexPoolConfig = {
version,
kind,
metadata: {
id: requiredStringConfigField(metadata, "id", "metadata"),
owner: requiredStringConfigField(metadata, "owner", "metadata"),
relatedIssues: integerArrayConfigField(metadata, "relatedIssues", "metadata"),
},
groupName: stringValue(pool.groupName) ?? defaults.groupName,
apiKeyName: stringValue(pool.apiKeyName) ?? defaults.apiKeyName,
apiKeySecretName: stringValue(pool.apiKeySecretName) ?? defaults.apiKeySecretName,
@@ -1264,6 +1284,13 @@ function readCodexPoolConfig(): CodexPoolConfig {
function defaultCodexPoolConfig(): CodexPoolConfig {
return {
version: 1,
kind: "platform-infra-sub2api-codex-pool",
metadata: {
id: "sub2api-codex-pool",
owner: "unidesk",
relatedIssues: [],
},
groupName: defaultPoolGroupName,
apiKeyName: defaultPoolApiKeyName,
apiKeySecretName: defaultPoolApiKeySecretName,
@@ -1853,6 +1880,9 @@ function tempUnschedulableSummary(policy: CodexTempUnschedulablePolicy): Record<
function codexPoolConfigSummary(pool: CodexPoolConfig): Record<string, unknown> {
const accountCapacityTotal = desiredAccountCapacityTotal(pool);
return {
version: pool.version,
kind: pool.kind,
metadata: pool.metadata,
groupName: pool.groupName,
apiKeyName: pool.apiKeyName,
apiKeySecretName: pool.apiKeySecretName,
@@ -3395,6 +3425,36 @@ function stringValue(value: unknown): string | null {
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
}
function requiredStringConfigField(obj: Record<string, unknown>, key: string, path: string): string {
const value = stringValue(obj[key]);
const prefix = path.length > 0 ? `${path}.` : "";
if (value === null) throw new Error(`${codexPoolConfigPath}.${prefix}${key} must be a non-empty string`);
return value;
}
function requiredRecordConfigField(obj: Record<string, unknown>, key: string, path: string): Record<string, unknown> {
const value = obj[key];
const prefix = path.length > 0 ? `${path}.` : "";
if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.${prefix}${key} must be a YAML object`);
return value;
}
function integerConfigField(obj: Record<string, unknown>, key: string, path: string): number {
const value = obj[key];
const prefix = path.length > 0 ? `${path}.` : "";
if (typeof value !== "number" || !Number.isInteger(value)) throw new Error(`${codexPoolConfigPath}.${prefix}${key} must be an integer`);
return value;
}
function integerArrayConfigField(obj: Record<string, unknown>, key: string, path: string): number[] {
const value = obj[key];
const prefix = path.length > 0 ? `${path}.` : "";
if (!Array.isArray(value) || !value.every((item) => typeof item === "number" && Number.isInteger(item))) {
throw new Error(`${codexPoolConfigPath}.${prefix}${key} must be an array of integers`);
}
return value;
}
function numberValue(value: unknown): number | null {
if (typeof value === "number" && Number.isFinite(value)) return value;
if (typeof value === "string" && value.trim().length > 0) {
+35 -50
View File
@@ -24,6 +24,13 @@ type DatabaseMode = "bundled" | "external-pending" | "external-active";
type RedisMode = "bundled-persistent" | "local-ephemeral";
interface Sub2ApiConfig {
version: number;
kind: string;
metadata: {
id: string;
owner: string;
relatedIssues: number[];
};
image: {
repository: string;
tag: string;
@@ -391,6 +398,12 @@ function readSub2ApiConfig(): Sub2ApiConfig {
const parsed = Bun.YAML.parse(readFileSync(configPath, "utf8")) as unknown;
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`${configPath} must contain a YAML object`);
const root = parsed as Record<string, unknown>;
const version = integerField(root, "version", "");
if (version !== 1) throw new Error(`${configPath}.version must be 1`);
const kind = stringField(root, "kind", "");
if (kind !== "platform-infra-sub2api") throw new Error(`${configPath}.kind must be platform-infra-sub2api`);
const metadata = objectField(root, "metadata", "");
const relatedIssues = integerArrayField(metadata, "relatedIssues", "metadata");
const image = (parsed as { image?: unknown }).image;
if (typeof image !== "object" || image === null || Array.isArray(image)) throw new Error(`${configPath}.image must be an object`);
const record = image as Record<string, unknown>;
@@ -410,6 +423,13 @@ function readSub2ApiConfig(): Sub2ApiConfig {
const targets = parseTargets(root);
const runtime = parseRuntime(root);
return {
version,
kind,
metadata: {
id: stringField(metadata, "id", "metadata"),
owner: stringField(metadata, "owner", "metadata"),
relatedIssues,
},
image: { repository, tag, pullPolicy },
dependencyImages,
security: {
@@ -427,7 +447,7 @@ function readSub2ApiConfig(): Sub2ApiConfig {
function parseDependencyImages(root: Record<string, unknown>): Sub2ApiConfig["dependencyImages"] {
const value = root.dependencyImages;
if (value === undefined) return { postgres: "postgres:18-alpine", redis: "redis:8-alpine" };
if (value === undefined) throw new Error(`${configPath}.dependencyImages must be an object`);
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${configPath}.dependencyImages must be an object`);
const record = value as Record<string, unknown>;
const postgres = stringField(record, "postgres", "dependencyImages");
@@ -439,7 +459,7 @@ function parseDependencyImages(root: Record<string, unknown>): Sub2ApiConfig["de
function parseTargets(root: Record<string, unknown>): Sub2ApiTargetConfig[] {
const value = root.targets;
if (value === undefined) return defaultTargets();
if (value === undefined) throw new Error(`${configPath}.targets must be an array`);
if (!Array.isArray(value)) throw new Error(`${configPath}.targets must be an array`);
const targets = value.map((item, index) => {
if (typeof item !== "object" || item === null || Array.isArray(item)) throw new Error(`${configPath}.targets[${index}] must be an object`);
@@ -476,26 +496,6 @@ function parseTargets(root: Record<string, unknown>): Sub2ApiTargetConfig[] {
return targets;
}
function defaultTargets(): Sub2ApiTargetConfig[] {
return [
{
id: "G14",
route: "G14:k3s",
namespace,
role: "active",
enabled: true,
databaseMode: "bundled",
redisMode: "bundled-persistent",
appReplicas: 1,
redisReplicas: 1,
image: {},
dependencyImages: {},
publicExposure: null,
egressProxy: null,
},
];
}
function targetImageOverride(record: Record<string, unknown>, path: string): Partial<Sub2ApiConfig["image"]> {
if (record.image === undefined) return {};
const image = objectField(record, "image", path);
@@ -638,34 +638,7 @@ function parseEgressProxyConfig(value: unknown, path: string): Sub2ApiEgressProx
function parseRuntime(root: Record<string, unknown>): Sub2ApiConfig["runtime"] {
const value = root.runtime;
if (value === undefined) {
return {
database: {
mode: "external",
sourceRef: "platform-db/postgres-active.env",
sourceKeys: {
user: "SUB2API_DB_USER",
password: "SUB2API_DB_PASSWORD",
dbName: "SUB2API_DB_NAME",
},
secretName,
passwordKey: "POSTGRES_PASSWORD",
host: "pika01-postgres.pending.local",
port: 5432,
user: "sub2api",
dbName: "sub2api",
sslMode: "prefer",
pendingAllowed: true,
},
secrets: {
root: ".state/secrets",
appSourceRef: "platform-infra/sub2api.env",
},
redis: { serviceName: "sub2api-redis", persistence: false },
appData: { mode: "persistent-pvc" },
sentinel: { mode: "singleton", enabledOnTargets: ["G14"] },
};
}
if (value === undefined) throw new Error(`${configPath}.runtime must be an object`);
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${configPath}.runtime must be an object`);
const runtime = value as Record<string, unknown>;
const database = objectField(runtime, "database", "runtime");
@@ -760,6 +733,14 @@ function integerField(obj: Record<string, unknown>, key: string, path: string):
return value;
}
function integerArrayField(obj: Record<string, unknown>, key: string, path: string): number[] {
const value = obj[key];
if (!Array.isArray(value) || !value.every((item) => typeof item === "number" && Number.isInteger(item))) {
throw new Error(`${configPath}.${path}.${key} must be an array of integers`);
}
return value;
}
function isKubernetesName(value: string): boolean {
return /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/u.test(value);
}
@@ -1541,6 +1522,10 @@ function plan(options: TargetOptions): Record<string, unknown> {
serviceDns: `${serviceName}.${target.namespace}.svc.cluster.local:8080`,
},
config: {
path: configPath,
version: sub2api.version,
kind: sub2api.kind,
metadata: sub2api.metadata,
image: imageRef(sub2api, target),
pullPolicy: targetImage(sub2api, target).pullPolicy,
dependencyImages: targetDependencyImages(sub2api, target),