fix: 支持 runtime store 连接池 YAML 配置

This commit is contained in:
lyon
2026-06-20 08:01:59 +08:00
parent 037043b7c8
commit c318e8eadf
4 changed files with 57 additions and 7 deletions
+15
View File
@@ -334,6 +334,7 @@
"runtimePath": { "type": "string" },
"imageTagMode": { "type": "string", "enum": ["short", "full"] },
"workbench": { "$ref": "#/$defs/workbenchUiPolicy" },
"runtimeStore": { "$ref": "#/$defs/runtimeStore" },
"sourceRepo": { "type": "string" },
"envReuseServices": {
"type": "array",
@@ -359,6 +360,20 @@
},
"additionalProperties": false
},
"runtimeStore": {
"type": "object",
"properties": {
"postgres": { "$ref": "#/$defs/postgresRuntimeStore" }
},
"additionalProperties": false
},
"postgresRuntimeStore": {
"type": "object",
"properties": {
"poolMax": { "type": "integer", "minimum": 1 }
},
"additionalProperties": false
},
"workbenchUiPolicy": {
"type": "object",
"properties": {
+6 -2
View File
@@ -137,11 +137,13 @@ test("postgres pool config makes HWLAB_CLOUD_DB_SSL_MODE authoritative over URL
const disabled = buildPostgresPoolConfig({
dbUrl: "postgres://hwlab_user:fixture-pass@db.example.test:5432/hwlab?sslmode=require&application_name=hwlab",
sslMode: "disable",
timeoutMs: "2500"
timeoutMs: "2500",
poolMax: "20"
});
assert.equal(disabled.ssl, false);
assert.equal(disabled.connectionTimeoutMillis, 2500);
assert.equal(disabled.max, 20);
const disabledUrl = new URL(disabled.connectionString);
assert.equal(disabledUrl.searchParams.get("sslmode"), null);
assert.equal(disabledUrl.searchParams.get("ssl"), null);
@@ -163,7 +165,8 @@ test("configured postgres runtime passes normalized pool config to pg", async ()
env: {
HWLAB_CLOUD_RUNTIME_ADAPTER: "postgres",
HWLAB_CLOUD_RUNTIME_DURABLE: "true",
HWLAB_CLOUD_DB_PROBE_TIMEOUT_MS: "1800"
HWLAB_CLOUD_DB_PROBE_TIMEOUT_MS: "1800",
HWLAB_CLOUD_DB_POOL_MAX: "20"
},
dbUrl: "postgres://hwlab_user:fixture-pass@db.example.test:5432/hwlab?sslmode=require",
sslMode: "disable",
@@ -186,6 +189,7 @@ test("configured postgres runtime passes normalized pool config to pg", async ()
assert.equal(pools.length, 1);
assert.equal(pools[0].config.ssl, false);
assert.equal(pools[0].config.connectionTimeoutMillis, 1800);
assert.equal(pools[0].config.max, 20);
assert.equal(new URL(pools[0].config.connectionString).searchParams.get("sslmode"), null);
assert.equal(store.summary().blocker, RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED);
assert.equal(store.summary().durabilityContract.blockedLayer, "schema");
+18 -3
View File
@@ -36,6 +36,7 @@ export const RUNTIME_DURABILITY_REQUIRED_EVIDENCE = "runtime_adapter_schema_migr
export const RUNTIME_ADAPTER_ENV = "HWLAB_CLOUD_RUNTIME_ADAPTER";
export const RUNTIME_DURABLE_ENV = "HWLAB_CLOUD_RUNTIME_DURABLE";
export const RUNTIME_DB_POOL_MAX_ENV = "HWLAB_CLOUD_DB_POOL_MAX";
const requiredPostgresSchema = CLOUD_RUNTIME_DURABLE_TABLE_COLUMNS;
@@ -88,12 +89,14 @@ export function createConfiguredCloudRuntimeStore(options = {}) {
return createCloudRuntimeStore(options);
}
export function buildPostgresPoolConfig({ dbUrl, sslMode = "require", timeoutMs } = {}) {
export function buildPostgresPoolConfig({ dbUrl, sslMode = "require", timeoutMs, poolMax } = {}) {
const normalizedSslMode = normalizePostgresSslMode(sslMode);
const normalizedPoolMax = normalizeRuntimePoolMax(poolMax);
return {
connectionString: normalizePostgresConnectionStringForSslMode(dbUrl, normalizedSslMode),
ssl: postgresSslOption(normalizedSslMode),
connectionTimeoutMillis: normalizeRuntimeTimeoutMs(timeoutMs)
connectionTimeoutMillis: normalizeRuntimeTimeoutMs(timeoutMs),
...(normalizedPoolMax !== null ? { max: normalizedPoolMax } : {})
};
}
@@ -1446,7 +1449,8 @@ export class PostgresCloudRuntimeStore {
this.pool = new Pool(buildPostgresPoolConfig({
dbUrl: this.dbUrl,
sslMode: this.sslMode,
timeoutMs: this.env?.HWLAB_CLOUD_DB_PROBE_TIMEOUT_MS
timeoutMs: this.env?.HWLAB_CLOUD_DB_PROBE_TIMEOUT_MS,
poolMax: this.env?.[RUNTIME_DB_POOL_MAX_ENV]
}));
return this.pool;
}
@@ -2232,3 +2236,14 @@ function normalizeRuntimeTimeoutMs(value) {
}
return Math.min(parsed, 5000);
}
function normalizeRuntimePoolMax(value) {
if (value === undefined || value === null || String(value).trim() === "") return null;
const parsed = Number.parseInt(String(value), 10);
if (!Number.isInteger(parsed) || parsed <= 0 || String(parsed) !== String(value).trim()) {
const error = new Error(`${RUNTIME_DB_POOL_MAX_ENV} must be a positive integer`);
error.code = "HWLAB_CLOUD_DB_POOL_MAX_INVALID";
throw error;
}
return parsed;
}
+18 -2
View File
@@ -935,7 +935,11 @@ function deployServicesForProfile(deploy, profile) {
.filter((service) => allowed.has(service.serviceId))
.map((service) => {
const copy = cloneJson(service);
copy.env = mergeEnvMaps(copy.env, serviceDeclarationEnvForProfile(deploy, profile, service.serviceId));
copy.env = mergeEnvMaps(
copy.env,
serviceDeclarationEnvForProfile(deploy, profile, service.serviceId),
runtimeStoreEnvForProfile(deploy, profile, service.serviceId)
);
return [service.serviceId, copy];
}));
const laneServices = isRuntimeLane(profile) && Array.isArray(deploy.lanes?.[profile]?.services)
@@ -944,7 +948,10 @@ function deployServicesForProfile(deploy, profile) {
for (const override of laneServices) {
const existing = services.get(override.serviceId) ?? {
serviceId: override.serviceId,
env: serviceDeclarationEnvForProfile(deploy, profile, override.serviceId)
env: mergeEnvMaps(
serviceDeclarationEnvForProfile(deploy, profile, override.serviceId),
runtimeStoreEnvForProfile(deploy, profile, override.serviceId)
)
};
services.set(override.serviceId, {
...existing,
@@ -972,6 +979,15 @@ function runtimeWorkbenchTraceTimelinePolicy(deploy, profile) {
return Object.keys(result).length > 0 ? result : null;
}
function runtimeStoreEnvForProfile(deploy, profile, serviceId) {
if (!isRuntimeLane(profile) || serviceId !== "hwlab-cloud-api") return {};
const postgres = runtimeLaneConfig(deploy, profile)?.runtimeStore?.postgres;
if (!postgres || typeof postgres !== "object" || Array.isArray(postgres)) return {};
const env = {};
if (Number.isInteger(postgres.poolMax)) env.HWLAB_CLOUD_DB_POOL_MAX = String(postgres.poolMax);
return env;
}
function booleanEnv(value) {
return value ? "1" : "0";
}