Compare commits

...

No commits in common. "feat/phase3-https-listener" and "main" have entirely different histories.

17 changed files with 123 additions and 125 deletions

2
.gitignore vendored Normal file
View file

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

8
AGENTS.md Normal file
View file

@ -0,0 +1,8 @@
# Fixture Review Contract
- Canonical E2E source lives in `Maidn/maidn-e2e-secret`; `origin` must target that repository.
- `test-org-2` is disposable-only and must never be treated as the test-suite source or delivery owner.
- Keep fixture changes on one scoped branch and open a Forgejo pull request for review.
- Before review, run `git diff --check` and the applicable build or test command.
- Return review requests as: PR URL, branch, purpose, checks, risk, and merge action.
- Do not commit credentials, token files, generated workspaces, or Kubernetes Secret values.

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

@ -1,4 +0,0 @@
apiVersion: v2
name: maidn-e2e-secret
description: Maidn E2E secret runtime fixture
version: 0.1.0

View file

@ -1,48 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Chart.Name }}
labels:
app: {{ .Chart.Name }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Chart.Name }}
template:
metadata:
labels:
app: {{ .Chart.Name }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- range . }}
- name: {{ . | quote }}
{{- end }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 8080
{{- if or (eq .Release.Namespace "staging") (eq .Release.Namespace "production") }}
env:
- name: E2E_SECRET
valueFrom:
secretKeyRef:
name: {{ printf "%s-runtime-%s" .Chart.Name .Release.Namespace }}
key: {{ .Values.runtimeSecret.key | quote }}
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ printf "%s-postgres-app" .Release.Namespace }}
key: uri
- name: MAIDN_RUNTIME_REQUIRED
value: "true"
{{- end }}
readinessProbe:
httpGet:
path: /
port: http

View file

@ -1,20 +0,0 @@
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: forgejo-registry-credentials
spec:
secretStoreRef:
kind: ClusterSecretStore
name: openbao
target:
name: forgejo-registry-credentials
creationPolicy: Owner
template:
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: "{{ `{{ .dockerconfigjson }}` }}"
data:
- secretKey: dockerconfigjson
remoteRef:
key: cicd/forgejo-registry
property: dockerconfigjson

View file

@ -1,20 +0,0 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: {{ .Chart.Name }}
spec:
parentRefs:
- name: {{ .Values.gateway.name }}
namespace: {{ .Values.gateway.namespace }}
sectionName: http
- name: {{ .Values.gateway.name }}
namespace: {{ .Values.gateway.namespace }}
sectionName: https-wildcard
{{- with .Values.gateway.hostname }}
hostnames:
- {{ . | quote }}
{{- end }}
rules:
- backendRefs:
- name: {{ .Chart.Name }}
port: 8080

View file

@ -1,12 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ .Chart.Name }}
spec:
type: ClusterIP
selector:
app: {{ .Chart.Name }}
ports:
- name: http
port: 8080
targetPort: http

View file

@ -1,16 +0,0 @@
replicaCount: 1
image:
repository: ""
tag: dev
pullPolicy: IfNotPresent
imagePullSecrets:
- forgejo-registry-credentials
runtimeSecret:
key: e2e-secret
gateway:
name: public
namespace: gateway-system
hostname: ""

19
package.json Normal file
View file

@ -0,0 +1,19 @@
{
"name": "maidn-e2e-secret",
"private": true,
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"test": "npm run build && node --test test/health.test.js"
},
"dependencies": {
"express": "^5.1.0",
"pg": "^8.16.3"
},
"devDependencies": {
"@types/express": "^5.0.1",
"@types/node": "^22.15.0",
"@types/pg": "^8.15.5",
"typescript": "~5.8.0"
}
}

View file

@ -1 +0,0 @@
replicaCount: 1

View file

@ -1,2 +0,0 @@
gateway:
hostname: maidn-e2e-secret.dev02.nid3.com

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();
}
}

11
src/index.ts Normal file
View file

@ -0,0 +1,11 @@
import express from 'express';
import { isReady } from './health.js';
const app = express();
app.get('/', async (_request, response) => {
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 ?? 8080);

View file

@ -1,2 +0,0 @@
gateway:
hostname: maidn-e2e-secret-staging.dev02.nid3.com

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;
}
});

10
tsconfig.json Normal file
View file

@ -0,0 +1,10 @@
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "dist",
"strict": true,
"target": "ES2022"
},
"include": ["src"]
}