Compare commits

..

7 commits

8 changed files with 85 additions and 21 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
dist/
node_modules/

View file

@ -1,11 +0,0 @@
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: maidn-e2e-secret
namespace: staging
labels:
maidn.io/e2e: "true"
spec:
instances: 1
storage:
size: 1Gi

View file

@ -1,4 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- database.yaml

17
Dockerfile Normal file
View file

@ -0,0 +1,17 @@
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json .
RUN npm install --package-lock=false
COPY tsconfig.json .
COPY src ./src
RUN npm run build
FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production PORT=8080
COPY package.json .
RUN npm install --omit=dev --package-lock=false && npm cache clean --force
COPY --from=build /app/dist ./dist
USER node
EXPOSE 8080
CMD ["npm", "start"]

View file

@ -3,14 +3,17 @@
"private": true, "private": true,
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"start": "node dist/index.js" "start": "node dist/index.js",
"test": "npm run build && node --test test/health.test.js"
}, },
"dependencies": { "dependencies": {
"express": "^5.1.0" "express": "^5.1.0",
"pg": "^8.16.3"
}, },
"devDependencies": { "devDependencies": {
"@types/express": "^5.0.1", "@types/express": "^5.0.1",
"@types/node": "^22.15.0", "@types/node": "^22.15.0",
"@types/pg": "^8.15.5",
"typescript": "~5.8.0" "typescript": "~5.8.0"
} }
} }

23
src/health.ts Normal file
View file

@ -0,0 +1,23 @@
import { Client } from 'pg';
export async function isReady(selectOne = databaseSelectOne) {
if (process.env.MAIDN_RUNTIME_REQUIRED !== 'true') return true;
if (!process.env.E2E_SECRET || !process.env.DATABASE_URL) return false;
try {
await selectOne();
return true;
} catch {
return false;
}
}
async function databaseSelectOne() {
const client = new Client({ connectionString: process.env.DATABASE_URL });
await client.connect();
try {
await client.query('SELECT 1');
} finally {
await client.end();
}
}

View file

@ -1,10 +1,11 @@
import express from 'express'; import express from 'express';
import { isReady } from './health.js';
const secretConfigured = Boolean(process.env.E2E_SECRET);
const app = express(); const app = express();
app.get('/', (_request, response) => { app.get('/', async (_request, response) => {
response.status(secretConfigured ? 200 : 503).send(secretConfigured ? 'maidn-e2e-secret-ready' : 'maidn-e2e-secret-missing'); const ready = await isReady();
response.status(ready ? 200 : 503).send(ready ? 'maidn-e2e-secret-ready' : 'maidn-e2e-secret-not-ready');
}); });
app.listen(process.env.PORT ?? 3000); app.listen(process.env.PORT ?? 8080);

33
test/health.test.js Normal file
View file

@ -0,0 +1,33 @@
const assert = require('node:assert/strict');
const test = require('node:test');
const { isReady } = require('../dist/health.js');
test('readiness requires the secret and a successful database query', async () => {
const secret = process.env.E2E_SECRET;
const databaseUrl = process.env.DATABASE_URL;
const runtimeRequired = process.env.MAIDN_RUNTIME_REQUIRED;
try {
delete process.env.MAIDN_RUNTIME_REQUIRED;
assert.equal(await isReady(async () => assert.fail('query should not run')), true);
process.env.MAIDN_RUNTIME_REQUIRED = 'true';
delete process.env.E2E_SECRET;
assert.equal(await isReady(async () => assert.fail('query should not run')), false);
process.env.E2E_SECRET = 'configured';
delete process.env.DATABASE_URL;
assert.equal(await isReady(async () => assert.fail('query should not run')), false);
process.env.DATABASE_URL = 'postgres://example';
assert.equal(await isReady(async () => {}), true);
assert.equal(await isReady(async () => { throw new Error('unavailable'); }), false);
} finally {
if (secret === undefined) delete process.env.E2E_SECRET;
else process.env.E2E_SECRET = secret;
if (databaseUrl === undefined) delete process.env.DATABASE_URL;
else process.env.DATABASE_URL = databaseUrl;
if (runtimeRequired === undefined) delete process.env.MAIDN_RUNTIME_REQUIRED;
else process.env.MAIDN_RUNTIME_REQUIRED = runtimeRequired;
}
});