From 2980e675c4bda5974ba7abd25266ef2772a3ae8c Mon Sep 17 00:00:00 2001 From: Lyon <88232613+pikasTech@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:53:59 +0800 Subject: [PATCH] fix(hwlab): route d601 v03 postgres through gateway (#957) Co-authored-by: Codex --- config/hwlab-node-lanes.yaml | 13 +-- config/platform-db/postgres-pk01.yaml | 93 +++++++++++++++++++ scripts/src/hwlab-node-lanes.ts | 18 ++++ scripts/src/hwlab-node/plan.ts | 1 + scripts/src/hwlab-node/public-exposure.ts | 3 +- scripts/src/hwlab-node/render.ts | 39 ++++++-- scripts/src/hwlab-node/secret-scripts.ts | 20 +++- scripts/src/hwlab-node/web-probe.ts | 38 ++++++-- .../k3sctl-adapter/k3s/code-queue.k8s.yaml | 7 +- 9 files changed, 209 insertions(+), 23 deletions(-) diff --git a/config/hwlab-node-lanes.yaml b/config/hwlab-node-lanes.yaml index 413d5b24..3dd5aa5e 100644 --- a/config/hwlab-node-lanes.yaml +++ b/config/hwlab-node-lanes.yaml @@ -159,11 +159,8 @@ lanes: webProbe: browserProxyMode: direct defaultOrigin: - mode: k8s-service-cluster-ip - serviceName: hwlab-cloud-web - namespace: hwlab-v03 - port: 8080 - scheme: http + mode: public + baseUrl: https://hwlab.pikapython.com alertThresholds: sameOriginApiSlowMs: 10000 partialApiSlowMs: 10000 @@ -438,9 +435,13 @@ lanes: externalPostgres: provider: PK01 configRef: config/platform-db/postgres-pk01.yaml - serviceName: pk01-platform-postgres + serviceName: d601-pk01-platform-postgres endpointAddress: 82.156.23.220 port: 5432 + runtimeAccess: + routeName: d601-pk01-postgres + endpointAddress: d601-tcp-egress-gateway.unidesk.svc.cluster.local + port: 25432 sslmode: require database: hwlab_d601_v03 cloudApi: diff --git a/config/platform-db/postgres-pk01.yaml b/config/platform-db/postgres-pk01.yaml index 9af4c4c2..be9bbee7 100644 --- a/config/platform-db/postgres-pk01.yaml +++ b/config/platform-db/postgres-pk01.yaml @@ -230,6 +230,36 @@ postgres: user: agentrun_v02 address: 74.48.78.17/32 method: scram-sha-256 + - type: hostssl + database: hwlab_d601_v03 + user: hwlab_d601_v03_app + address: 10.0.8.0/22 + method: scram-sha-256 + - type: hostssl + database: postgres + user: hwlab_d601_v03_app + address: 10.0.8.0/22 + method: scram-sha-256 + - type: hostssl + database: hwlab_d601_v03 + user: hwlab_d601_v03_app + address: 36.49.29.0/24 + method: scram-sha-256 + - type: hostssl + database: postgres + user: hwlab_d601_v03_app + address: 36.49.29.0/24 + method: scram-sha-256 + - type: hostssl + database: hwlab_d601_v03 + user: hwlab_d601_v03_app + address: 74.48.78.17/32 + method: scram-sha-256 + - type: hostssl + database: postgres + user: hwlab_d601_v03_app + address: 74.48.78.17/32 + method: scram-sha-256 secrets: source: master-local @@ -291,6 +321,20 @@ secrets: AGENTRUN_V02_DB_NAME: agentrun_v02 randomHex: AGENTRUN_V02_DB_PASSWORD: 32 + - name: hwlab-d601-v03-db-credentials + sourceRef: platform-db/hwlab-d601-v03-db.env + type: env + requiredKeys: + - HWLAB_D601_V03_DB_USER + - HWLAB_D601_V03_DB_PASSWORD + - HWLAB_D601_V03_DB_NAME + createIfMissing: + enabled: true + values: + HWLAB_D601_V03_DB_USER: hwlab_d601_v03_app + HWLAB_D601_V03_DB_NAME: hwlab_d601_v03 + randomHex: + HWLAB_D601_V03_DB_PASSWORD: 32 objects: roles: - name: sub2api @@ -329,6 +373,15 @@ objects: createdb: false createrole: false superuser: false + - name: hwlab_d601_v03_app + passwordRef: + sourceRef: platform-db/hwlab-d601-v03-db.env + key: HWLAB_D601_V03_DB_PASSWORD + login: true + attributes: + createdb: false + createrole: false + superuser: false databases: - name: sub2api owner: sub2api @@ -350,6 +403,11 @@ objects: encoding: UTF8 locale: C.UTF-8 extensions: [] + - name: hwlab_d601_v03 + owner: hwlab_d601_v03_app + encoding: UTF8 + locale: C.UTF-8 + extensions: [] exports: connectionStrings: @@ -413,6 +471,38 @@ exports: - scope: agentrun-v02 secret: agentrun-v02-mgr-db key: DATABASE_URL + - name: hwlab-d601-v03-cloud-api-database-url + sourceSecretRef: platform-db/hwlab-d601-v03-db.env + render: + envKey: DATABASE_URL + format: postgresql://$(HWLAB_D601_V03_DB_USER):$(HWLAB_D601_V03_DB_PASSWORD)@$(PGHOST):$(PGPORT)/$(HWLAB_D601_V03_DB_NAME)?sslmode=require + variables: + PGHOST: d601-tcp-egress-gateway.unidesk.svc.cluster.local + PGPORT: "25432" + writeToSecretSource: + sourceRef: hwlab/d601-v03-cloud-api-db.env + key: DATABASE_URL + mode: update-or-insert + consumers: + - scope: hwlab-d601-v03 + secret: hwlab-cloud-api-v03-db + key: DATABASE_URL + - name: hwlab-d601-v03-openfga-datastore-uri + sourceSecretRef: platform-db/hwlab-d601-v03-db.env + render: + envKey: DATASTORE_URI + format: postgres://$(HWLAB_D601_V03_DB_USER):$(HWLAB_D601_V03_DB_PASSWORD)@$(PGHOST):$(PGPORT)/$(HWLAB_D601_V03_DB_NAME)?sslmode=require + variables: + PGHOST: d601-tcp-egress-gateway.unidesk.svc.cluster.local + PGPORT: "25432" + writeToSecretSource: + sourceRef: hwlab/d601-v03-openfga-db.env + key: DATASTORE_URI + mode: update-or-insert + consumers: + - scope: hwlab-d601-v03 + secret: hwlab-v03-openfga + key: DATASTORE_URI backup: phase: minimum-restoreable @@ -452,6 +542,9 @@ observability: - kind: psql-app-role database: agentrun_v02 user: agentrun_v02 + - kind: psql-app-role + database: hwlab_d601_v03 + user: hwlab_d601_v03_app - kind: disk-free path: /var/lib/postgresql/16/main minFreeGiB: 10 diff --git a/scripts/src/hwlab-node-lanes.ts b/scripts/src/hwlab-node-lanes.ts index 96a55e3d..78cd64e4 100644 --- a/scripts/src/hwlab-node-lanes.ts +++ b/scripts/src/hwlab-node-lanes.ts @@ -69,12 +69,19 @@ export interface HwlabRuntimeExternalPostgresComponentSpec { readonly schema?: string; } +export interface HwlabRuntimeExternalPostgresRuntimeAccessSpec { + readonly routeName?: string; + readonly endpointAddress: string; + readonly port: number; +} + export interface HwlabRuntimeExternalPostgresSpec { readonly provider: string; readonly configRef: string; readonly serviceName: string; readonly endpointAddress: string; readonly port: number; + readonly runtimeAccess?: HwlabRuntimeExternalPostgresRuntimeAccessSpec; readonly sslmode: "require"; readonly database: string; readonly cloudApi: HwlabRuntimeExternalPostgresComponentSpec; @@ -657,6 +664,16 @@ function externalPostgresComponentConfig(value: unknown, path: string): HwlabRun }; } +function externalPostgresRuntimeAccessConfig(value: unknown, path: string): HwlabRuntimeExternalPostgresRuntimeAccessSpec | undefined { + if (value === undefined) return undefined; + const raw = asRecord(value, path); + return { + routeName: optionalStringField(raw, "routeName", path), + endpointAddress: stringField(raw, "endpointAddress", path), + port: numberField(raw, "port", path), + }; +} + function externalPostgresConfig(value: unknown, path: string): HwlabRuntimeExternalPostgresSpec | undefined { if (value === undefined) return undefined; const raw = asRecord(value, path); @@ -668,6 +685,7 @@ function externalPostgresConfig(value: unknown, path: string): HwlabRuntimeExter serviceName: stringField(raw, "serviceName", path), endpointAddress: stringField(raw, "endpointAddress", path), port: numberField(raw, "port", path), + ...(raw.runtimeAccess === undefined ? {} : { runtimeAccess: externalPostgresRuntimeAccessConfig(raw.runtimeAccess, `${path}.runtimeAccess`) }), sslmode, database: stringField(raw, "database", path), cloudApi: externalPostgresComponentConfig(raw.cloudApi, `${path}.cloudApi`), diff --git a/scripts/src/hwlab-node/plan.ts b/scripts/src/hwlab-node/plan.ts index 949a3467..a9da1bea 100644 --- a/scripts/src/hwlab-node/plan.ts +++ b/scripts/src/hwlab-node/plan.ts @@ -156,6 +156,7 @@ export function nodeRuntimeExpected(spec: HwlabRuntimeLaneSpec): Record !item.__delete)));", " return changed;", "}", "function patchHealthContract() {", @@ -1829,19 +1844,29 @@ export function nodeRuntimePipelinePostprocessScript(): string[] { "if (overlay.externalPostgres && overlay.externalPostgres.sslmode) checks.push('runtime-db-ssl-mode');", "const pg = overlay.externalPostgres;", "if (pg && pg.serviceName) {", + " const access = pg.runtimeAccess || { endpointAddress: pg.endpointAddress, port: pg.port };", " const file = path.join(runtimePath, 'external-postgres.yaml');", " if (!fs.existsSync(file)) fail('external-postgres-missing');", " const items = listItems(readYaml(file)).filter(Boolean);", " const service = items.find((item) => item && item.kind === 'Service' && item.metadata && item.metadata.name === pg.serviceName);", " const endpointSlice = items.find((item) => item && item.kind === 'EndpointSlice' && item.metadata && item.metadata.name === String(pg.serviceName) + '-host');", " if (!service) fail('external-postgres-service-missing', { expected: pg.serviceName });", - " if (!endpointSlice) fail('external-postgres-endpointslice-missing', { expected: String(pg.serviceName) + '-host' });", + " const endpointIsIpv4 = /^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$/.test(String(access.endpointAddress));", " const servicePort = service.spec && Array.isArray(service.spec.ports) && service.spec.ports[0] ? service.spec.ports[0].port : null;", + " if (!endpointIsIpv4) {", + " if (service.spec && service.spec.type !== 'ExternalName') fail('external-postgres-service-type-mismatch', { value: service.spec.type, expected: 'ExternalName' });", + " if (!service.spec || String(service.spec.externalName) !== String(access.endpointAddress)) fail('external-postgres-external-name-mismatch', { value: service.spec && service.spec.externalName, expected: access.endpointAddress });", + " if (Number(servicePort) !== Number(access.port)) fail('external-postgres-port-mismatch', { servicePort, expectedPort: access.port });", + " if (endpointSlice) fail('external-postgres-endpointslice-unexpected', { name: String(pg.serviceName) + '-host' });", + " checks.push('external-postgres-bridge');", + " } else {", + " if (!endpointSlice) fail('external-postgres-endpointslice-missing', { expected: String(pg.serviceName) + '-host' });", " const endpointPort = Array.isArray(endpointSlice.ports) && endpointSlice.ports[0] ? endpointSlice.ports[0].port : null;", " const endpointAddress = Array.isArray(endpointSlice.endpoints) && endpointSlice.endpoints[0] && Array.isArray(endpointSlice.endpoints[0].addresses) ? endpointSlice.endpoints[0].addresses[0] : null;", - " if (Number(servicePort) !== Number(pg.port) || Number(endpointPort) !== Number(pg.port)) fail('external-postgres-port-mismatch', { servicePort, endpointPort, expectedPort: pg.port });", - " if (String(endpointAddress) !== String(pg.endpointAddress)) fail('external-postgres-address-mismatch', { endpointAddress, expectedEndpointAddress: pg.endpointAddress });", + " if (Number(servicePort) !== Number(access.port) || Number(endpointPort) !== Number(access.port)) fail('external-postgres-port-mismatch', { servicePort, endpointPort, expectedPort: access.port });", + " if (String(endpointAddress) !== String(access.endpointAddress)) fail('external-postgres-address-mismatch', { endpointAddress, expectedEndpointAddress: access.endpointAddress });", " checks.push('external-postgres-bridge');", + " }", "}", "const exposure = overlay.publicExposure;", "if (exposure && exposure.enabled) {", diff --git a/scripts/src/hwlab-node/secret-scripts.ts b/scripts/src/hwlab-node/secret-scripts.ts index df2e1a09..ac9bf1e0 100644 --- a/scripts/src/hwlab-node/secret-scripts.ts +++ b/scripts/src/hwlab-node/secret-scripts.ts @@ -578,6 +578,7 @@ export function platformDbSecretStatusScript(options: NodeSecretOptions, spec: R "secret_exists_flag() { kubectl -n \"$namespace\" get secret \"$1\" >/dev/null 2>&1 && printf yes || printf no; }", "resource_exists_flag() { kubectl -n \"$namespace\" get \"$1\" \"$2\" >/dev/null 2>&1 && printf yes || printf no; }", "endpointslice_exists_flag() { kubectl -n \"$namespace\" get endpointslice \"$1\" >/dev/null 2>&1 && printf yes || printf no; }", + "service_jsonpath() { kubectl -n \"$namespace\" get service \"$platform_service\" -o \"jsonpath=$1\" 2>/dev/null || true; }", "secret_b64_key() { kubectl -n \"$namespace\" get secret \"$1\" -o \"go-template={{ index .data \\\"$2\\\" }}\" 2>/dev/null || true; }", "decoded_value() { if [ -n \"$1\" ]; then printf '%s' \"$1\" | base64 -d 2>/dev/null || true; fi; }", "decoded_length() { if [ -n \"$1\" ]; then printf '%s' \"$1\" | base64 -d 2>/dev/null | wc -c | tr -d ' '; else printf '0'; fi; }", @@ -611,6 +612,11 @@ export function platformDbSecretStatusScript(options: NodeSecretOptions, spec: R "platform_service_exists=$(resource_exists_flag service \"$platform_service\")", "platform_endpoints_exists=$(resource_exists_flag endpoints \"$platform_service\")", "platform_endpointslice_exists=$(endpointslice_exists_flag \"$platform_endpointslice\")", + "platform_service_type=$(service_jsonpath '{.spec.type}')", + "platform_external_name=$(service_jsonpath '{.spec.externalName}')", + "platform_service_port=$(service_jsonpath '{.spec.ports[0].port}')", + "platform_external_name_matches=no", + "if [ \"$platform_service_type\" = ExternalName ] && [ \"$platform_external_name\" = \"$db_host\" ]; then platform_external_name_matches=yes; fi", "uri_matches_expected \"$uri_value\"", "printf 'namespace\\t%s\\n' \"$namespace\"", "printf 'secret\\t%s\\n' \"$name\"", @@ -638,6 +644,10 @@ export function platformDbSecretStatusScript(options: NodeSecretOptions, spec: R "printf 'platformEndpointSlice\\t%s\\n' \"$platform_endpointslice\"", "printf 'platformEndpointSliceExists\\t%s\\n' \"$platform_endpointslice_exists\"", "printf 'platformEndpointAddress\\t%s\\n' \"$platform_endpoint_address\"", + "printf 'platformServiceType\\t%s\\n' \"$platform_service_type\"", + "printf 'platformExternalName\\t%s\\n' \"$platform_external_name\"", + "printf 'platformServicePort\\t%s\\n' \"$platform_service_port\"", + "printf 'platformExternalNameMatches\\t%s\\n' \"$platform_external_name_matches\"", "printf 'dbName\\t%s\\n' \"$db_name\"", "printf 'dbUser\\t%s\\n' \"$db_user\"", "printf 'dbHost\\t%s\\n' \"$db_host\"", @@ -1778,7 +1788,7 @@ export function secretStatusFromText(text: string, commandOk: boolean, exitCode: typeof afterUrlBytes === "number" && afterUrlBytes > 0; const platformBridgeHealthy = fields.platformServiceExists === "yes" && fields.platformEndpointsExists !== "yes" && - fields.platformEndpointSliceExists === "yes"; + (fields.platformEndpointSliceExists === "yes" || fields.platformExternalNameMatches === "yes"); const uriHealthy = fields.dbHostMatchesPlatform === "yes" && fields.dbNameMatchesExpected === "yes" && fields.dbUserMatchesExpected === "yes"; @@ -1808,6 +1818,9 @@ export function secretStatusFromText(text: string, commandOk: boolean, exitCode: legacyEndpointsAbsent: fields.platformEndpointsExists !== "yes", endpointSlice: fields.platformEndpointSlice || `${spec.platformPostgresService}-host`, endpointSliceExists: fields.platformEndpointSliceExists === "yes", + serviceType: fields.platformServiceType || null, + externalName: fields.platformExternalName || null, + externalNameMatches: fields.platformExternalNameMatches === "yes", }, dbName: fields.dbName || spec.cloudApiDbName, dbUser: fields.dbUser || spec.cloudApiDbUser, @@ -1917,7 +1930,7 @@ export function secretStatusFromText(text: string, commandOk: boolean, exitCode: typeof afterUriBytes === "number" && afterUriBytes > 0; const platformBridgeHealthy = fields.platformServiceExists === "yes" && fields.platformEndpointsExists !== "yes" && - fields.platformEndpointSliceExists === "yes"; + (fields.platformEndpointSliceExists === "yes" || fields.platformExternalNameMatches === "yes"); const uriHealthy = fields.dbHostMatchesPlatform === "yes" && fields.dbNameMatchesExpected === "yes" && fields.dbUserMatchesExpected === "yes"; @@ -1948,6 +1961,9 @@ export function secretStatusFromText(text: string, commandOk: boolean, exitCode: legacyEndpointsAbsent: fields.platformEndpointsExists !== "yes", endpointSlice: fields.platformEndpointSlice || `${spec.platformPostgresService}-host`, endpointSliceExists: fields.platformEndpointSliceExists === "yes", + serviceType: fields.platformServiceType || null, + externalName: fields.platformExternalName || null, + externalNameMatches: fields.platformExternalNameMatches === "yes", }, dbName: fields.dbName || spec.openFgaDbName, dbUser: fields.dbUser || spec.openFgaDbUser, diff --git a/scripts/src/hwlab-node/web-probe.ts b/scripts/src/hwlab-node/web-probe.ts index 74dcc020..ab763769 100644 --- a/scripts/src/hwlab-node/web-probe.ts +++ b/scripts/src/hwlab-node/web-probe.ts @@ -114,6 +114,7 @@ export function nodeRuntimeRenderOverlay(spec: HwlabRuntimeLaneSpec): Record