Skip to content

Commit a760ae6

Browse files
committed
fix(api): normalize pg sslmode and add runtime tsconfig for jobs
1 parent d0a1696 commit a760ae6

11 files changed

Lines changed: 99 additions & 9 deletions

File tree

src/helm/openerrata/templates/selector-cronjob.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ spec:
2121
- name: selector
2222
image: {{ include "openerrata.image" . | quote }}
2323
imagePullPolicy: {{ .Values.image.pullPolicy }}
24-
command: ["node", "node_modules/tsx/dist/cli.mjs", "src/lib/services/selector-entrypoint.ts"]
24+
command: ["node", "node_modules/tsx/dist/cli.mjs", "--tsconfig", "tsconfig.runtime.json", "src/lib/services/selector-entrypoint.ts"]
2525
envFrom:
2626
- secretRef:
2727
name: {{ include "openerrata.fullname" . }}

src/helm/openerrata/templates/worker-deployment.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ spec:
2121
- name: worker
2222
image: {{ include "openerrata.image" . | quote }}
2323
imagePullPolicy: {{ .Values.image.pullPolicy }}
24-
command: ["node", "node_modules/tsx/dist/cli.mjs", "src/lib/services/worker-entrypoint.ts"]
24+
command: ["node", "node_modules/tsx/dist/cli.mjs", "--tsconfig", "tsconfig.runtime.json", "src/lib/services/worker-entrypoint.ts"]
2525
envFrom:
2626
- secretRef:
2727
name: {{ include "openerrata.fullname" . }}

src/typescript/api/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ COPY --from=build /app/api/prisma /app/api/prisma
2828
COPY --from=build /app/api/prisma.config.ts /app/api/prisma.config.ts
2929
COPY --from=build /app/api/src /app/api/src
3030
COPY --from=build /app/api/package.json /app/api/package.json
31+
COPY --from=build /app/api/tsconfig.runtime.json /app/api/tsconfig.runtime.json
3132

3233
EXPOSE 3000
3334
ENV PORT=3000

src/typescript/api/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
"test:unit": "tsx --test \"test/unit/**/*.test.ts\"",
1010
"test:integration": "tsx test/integration/run-integration-tests.ts",
1111
"test:integration:raw": "tsx --test test/integration/api-endpoints.integration.test.ts",
12-
"worker": "tsx src/lib/services/worker-entrypoint.ts",
13-
"selector": "tsx src/lib/services/selector-entrypoint.ts",
14-
"instance-api-key": "tsx src/lib/services/instance-api-key-entrypoint.ts",
12+
"worker": "tsx --tsconfig tsconfig.runtime.json src/lib/services/worker-entrypoint.ts",
13+
"selector": "tsx --tsconfig tsconfig.runtime.json src/lib/services/selector-entrypoint.ts",
14+
"instance-api-key": "tsx --tsconfig tsconfig.runtime.json src/lib/services/instance-api-key-entrypoint.ts",
1515
"prisma:generate": "svelte-kit sync && prisma generate",
1616
"prisma:migrate:dev": "prisma migrate dev",
1717
"prisma:migrate:deploy": "prisma migrate deploy",

src/typescript/api/src/lib/db/client.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import "./prisma-enum-compat.js";
22
import { PrismaPg } from "@prisma/adapter-pg";
3+
import { normalizePgConnectionStringForNode } from "$lib/db/connection-string.js";
34
import { PrismaClient } from "$lib/generated/prisma/client";
45
import { getEnv } from "$lib/config/env.js";
56
import { Pool } from "pg";
@@ -13,7 +14,7 @@ declare global {
1314

1415
function createPrismaClient(): PrismaClient {
1516
const env = getEnv();
16-
const databaseUrl = env.DATABASE_URL;
17+
const databaseUrl = normalizePgConnectionStringForNode(env.DATABASE_URL);
1718

1819
const pool = new Pool({
1920
connectionString: databaseUrl,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const libpqCompatSslModes = new Set(["allow", "prefer", "require"]);
2+
3+
export function normalizePgConnectionStringForNode(
4+
databaseUrl: string,
5+
): string {
6+
const parsed = new URL(databaseUrl);
7+
const sslMode = parsed.searchParams.get("sslmode");
8+
if (sslMode === null) {
9+
return databaseUrl;
10+
}
11+
12+
if (!libpqCompatSslModes.has(sslMode.toLowerCase())) {
13+
return databaseUrl;
14+
}
15+
16+
if (parsed.searchParams.has("uselibpqcompat")) {
17+
return databaseUrl;
18+
}
19+
20+
parsed.searchParams.set("uselibpqcompat", "true");
21+
return parsed.toString();
22+
}

src/typescript/api/src/lib/services/instance-api-key-entrypoint.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { config as loadDotenv } from "dotenv";
22
import { PrismaPg } from "@prisma/adapter-pg";
3+
import { normalizePgConnectionStringForNode } from "$lib/db/connection-string.js";
34
import { PrismaClient, type Prisma } from "$lib/generated/prisma/client";
45
import { Pool } from "pg";
56
import { hashInstanceApiKey } from "$lib/services/instance-api-key.js";
@@ -173,7 +174,7 @@ function requireDatabaseUrl(): string {
173174
if (typeof value !== "string" || value.trim().length === 0) {
174175
throw new Error("DATABASE_URL is required.");
175176
}
176-
return value.trim();
177+
return normalizePgConnectionStringForNode(value.trim());
177178
}
178179

179180
function createScriptPrismaClient(): PrismaClient {

src/typescript/api/src/lib/services/queue.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import { makeWorkerUtils } from "graphile-worker";
22
import { getEnv } from "$lib/config/env.js";
3+
import { normalizePgConnectionStringForNode } from "$lib/db/connection-string.js";
34
import { createQueueManager } from "./queue-lifecycle.js";
45

6+
const databaseUrl = normalizePgConnectionStringForNode(getEnv().DATABASE_URL);
7+
58
const manager = createQueueManager(() =>
6-
makeWorkerUtils({ connectionString: getEnv().DATABASE_URL }),
9+
makeWorkerUtils({ connectionString: databaseUrl }),
710
);
811

912
export async function enqueueInvestigationRun(

src/typescript/api/src/lib/services/worker-runner.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { run } from "graphile-worker";
22
import { getEnv } from "$lib/config/env.js";
3+
import { normalizePgConnectionStringForNode } from "$lib/db/connection-string.js";
34
import { orchestrateInvestigation } from "./orchestrator.js";
45

5-
const databaseUrl = getEnv().DATABASE_URL;
6+
const databaseUrl = normalizePgConnectionStringForNode(getEnv().DATABASE_URL);
67

78
function isInvestigatePayload(
89
payload: unknown,
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import assert from "node:assert/strict";
2+
import { test } from "node:test";
3+
import { normalizePgConnectionStringForNode } from "../../src/lib/db/connection-string.js";
4+
5+
test("adds uselibpqcompat for sslmode=require", () => {
6+
const input = "postgresql://user:pass@db.example.com:5432/openerrata?sslmode=require";
7+
const output = normalizePgConnectionStringForNode(input);
8+
const url = new URL(output);
9+
10+
assert.equal(url.searchParams.get("sslmode"), "require");
11+
assert.equal(url.searchParams.get("uselibpqcompat"), "true");
12+
});
13+
14+
test("adds uselibpqcompat for sslmode=prefer and sslmode=allow", () => {
15+
const prefer = normalizePgConnectionStringForNode(
16+
"postgresql://user:pass@db.example.com/openerrata?sslmode=prefer",
17+
);
18+
const allow = normalizePgConnectionStringForNode(
19+
"postgresql://user:pass@db.example.com/openerrata?sslmode=allow",
20+
);
21+
22+
assert.equal(new URL(prefer).searchParams.get("uselibpqcompat"), "true");
23+
assert.equal(new URL(allow).searchParams.get("uselibpqcompat"), "true");
24+
});
25+
26+
test("leaves connection strings unchanged when sslmode is absent", () => {
27+
const input = "postgresql://user:pass@db.example.com:5432/openerrata";
28+
assert.equal(normalizePgConnectionStringForNode(input), input);
29+
});
30+
31+
test("leaves connection strings unchanged when uselibpqcompat is already present", () => {
32+
const input =
33+
"postgresql://user:pass@db.example.com/openerrata?sslmode=require&uselibpqcompat=false";
34+
assert.equal(normalizePgConnectionStringForNode(input), input);
35+
});
36+
37+
test("leaves strict sslmodes unchanged", () => {
38+
const verifyFull =
39+
"postgresql://user:pass@db.example.com/openerrata?sslmode=verify-full";
40+
const verifyCa =
41+
"postgresql://user:pass@db.example.com/openerrata?sslmode=verify-ca";
42+
const disable =
43+
"postgresql://user:pass@db.example.com/openerrata?sslmode=disable";
44+
45+
assert.equal(normalizePgConnectionStringForNode(verifyFull), verifyFull);
46+
assert.equal(normalizePgConnectionStringForNode(verifyCa), verifyCa);
47+
assert.equal(normalizePgConnectionStringForNode(disable), disable);
48+
});

0 commit comments

Comments
 (0)