Merge pull request #1652 from pikasTech/fix/workbench-db-pool-yaml
支持 runtime store 连接池 YAML 配置
This commit is contained in:
@@ -334,6 +334,7 @@
|
|||||||
"runtimePath": { "type": "string" },
|
"runtimePath": { "type": "string" },
|
||||||
"imageTagMode": { "type": "string", "enum": ["short", "full"] },
|
"imageTagMode": { "type": "string", "enum": ["short", "full"] },
|
||||||
"workbench": { "$ref": "#/$defs/workbenchUiPolicy" },
|
"workbench": { "$ref": "#/$defs/workbenchUiPolicy" },
|
||||||
|
"runtimeStore": { "$ref": "#/$defs/runtimeStore" },
|
||||||
"sourceRepo": { "type": "string" },
|
"sourceRepo": { "type": "string" },
|
||||||
"envReuseServices": {
|
"envReuseServices": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
@@ -359,6 +360,20 @@
|
|||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"runtimeStore": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"postgres": { "$ref": "#/$defs/postgresRuntimeStore" }
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"postgresRuntimeStore": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"poolMax": { "type": "integer", "minimum": 1 }
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"workbenchUiPolicy": {
|
"workbenchUiPolicy": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -137,11 +137,13 @@ test("postgres pool config makes HWLAB_CLOUD_DB_SSL_MODE authoritative over URL
|
|||||||
const disabled = buildPostgresPoolConfig({
|
const disabled = buildPostgresPoolConfig({
|
||||||
dbUrl: "postgres://hwlab_user:fixture-pass@db.example.test:5432/hwlab?sslmode=require&application_name=hwlab",
|
dbUrl: "postgres://hwlab_user:fixture-pass@db.example.test:5432/hwlab?sslmode=require&application_name=hwlab",
|
||||||
sslMode: "disable",
|
sslMode: "disable",
|
||||||
timeoutMs: "2500"
|
timeoutMs: "2500",
|
||||||
|
poolMax: "20"
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(disabled.ssl, false);
|
assert.equal(disabled.ssl, false);
|
||||||
assert.equal(disabled.connectionTimeoutMillis, 2500);
|
assert.equal(disabled.connectionTimeoutMillis, 2500);
|
||||||
|
assert.equal(disabled.max, 20);
|
||||||
const disabledUrl = new URL(disabled.connectionString);
|
const disabledUrl = new URL(disabled.connectionString);
|
||||||
assert.equal(disabledUrl.searchParams.get("sslmode"), null);
|
assert.equal(disabledUrl.searchParams.get("sslmode"), null);
|
||||||
assert.equal(disabledUrl.searchParams.get("ssl"), null);
|
assert.equal(disabledUrl.searchParams.get("ssl"), null);
|
||||||
@@ -163,7 +165,8 @@ test("configured postgres runtime passes normalized pool config to pg", async ()
|
|||||||
env: {
|
env: {
|
||||||
HWLAB_CLOUD_RUNTIME_ADAPTER: "postgres",
|
HWLAB_CLOUD_RUNTIME_ADAPTER: "postgres",
|
||||||
HWLAB_CLOUD_RUNTIME_DURABLE: "true",
|
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",
|
dbUrl: "postgres://hwlab_user:fixture-pass@db.example.test:5432/hwlab?sslmode=require",
|
||||||
sslMode: "disable",
|
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.length, 1);
|
||||||
assert.equal(pools[0].config.ssl, false);
|
assert.equal(pools[0].config.ssl, false);
|
||||||
assert.equal(pools[0].config.connectionTimeoutMillis, 1800);
|
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(new URL(pools[0].config.connectionString).searchParams.get("sslmode"), null);
|
||||||
assert.equal(store.summary().blocker, RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED);
|
assert.equal(store.summary().blocker, RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED);
|
||||||
assert.equal(store.summary().durabilityContract.blockedLayer, "schema");
|
assert.equal(store.summary().durabilityContract.blockedLayer, "schema");
|
||||||
|
|||||||
@@ -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_ADAPTER_ENV = "HWLAB_CLOUD_RUNTIME_ADAPTER";
|
||||||
export const RUNTIME_DURABLE_ENV = "HWLAB_CLOUD_RUNTIME_DURABLE";
|
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;
|
const requiredPostgresSchema = CLOUD_RUNTIME_DURABLE_TABLE_COLUMNS;
|
||||||
|
|
||||||
@@ -88,12 +89,14 @@ export function createConfiguredCloudRuntimeStore(options = {}) {
|
|||||||
return createCloudRuntimeStore(options);
|
return createCloudRuntimeStore(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildPostgresPoolConfig({ dbUrl, sslMode = "require", timeoutMs } = {}) {
|
export function buildPostgresPoolConfig({ dbUrl, sslMode = "require", timeoutMs, poolMax } = {}) {
|
||||||
const normalizedSslMode = normalizePostgresSslMode(sslMode);
|
const normalizedSslMode = normalizePostgresSslMode(sslMode);
|
||||||
|
const normalizedPoolMax = normalizeRuntimePoolMax(poolMax);
|
||||||
return {
|
return {
|
||||||
connectionString: normalizePostgresConnectionStringForSslMode(dbUrl, normalizedSslMode),
|
connectionString: normalizePostgresConnectionStringForSslMode(dbUrl, normalizedSslMode),
|
||||||
ssl: postgresSslOption(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({
|
this.pool = new Pool(buildPostgresPoolConfig({
|
||||||
dbUrl: this.dbUrl,
|
dbUrl: this.dbUrl,
|
||||||
sslMode: this.sslMode,
|
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;
|
return this.pool;
|
||||||
}
|
}
|
||||||
@@ -2232,3 +2236,14 @@ function normalizeRuntimeTimeoutMs(value) {
|
|||||||
}
|
}
|
||||||
return Math.min(parsed, 5000);
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -935,7 +935,11 @@ function deployServicesForProfile(deploy, profile) {
|
|||||||
.filter((service) => allowed.has(service.serviceId))
|
.filter((service) => allowed.has(service.serviceId))
|
||||||
.map((service) => {
|
.map((service) => {
|
||||||
const copy = cloneJson(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];
|
return [service.serviceId, copy];
|
||||||
}));
|
}));
|
||||||
const laneServices = isRuntimeLane(profile) && Array.isArray(deploy.lanes?.[profile]?.services)
|
const laneServices = isRuntimeLane(profile) && Array.isArray(deploy.lanes?.[profile]?.services)
|
||||||
@@ -944,7 +948,10 @@ function deployServicesForProfile(deploy, profile) {
|
|||||||
for (const override of laneServices) {
|
for (const override of laneServices) {
|
||||||
const existing = services.get(override.serviceId) ?? {
|
const existing = services.get(override.serviceId) ?? {
|
||||||
serviceId: 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, {
|
services.set(override.serviceId, {
|
||||||
...existing,
|
...existing,
|
||||||
@@ -972,6 +979,15 @@ function runtimeWorkbenchTraceTimelinePolicy(deploy, profile) {
|
|||||||
return Object.keys(result).length > 0 ? result : null;
|
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) {
|
function booleanEnv(value) {
|
||||||
return value ? "1" : "0";
|
return value ? "1" : "0";
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user