test: enforce production docker network invariants
This commit is contained in:
@@ -21,5 +21,8 @@
|
|||||||
"msgpackr-extract",
|
"msgpackr-extract",
|
||||||
"unrs-resolver"
|
"unrs-resolver"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"yaml": "^2.9.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
pnpm-lock.yaml
generated
42
pnpm-lock.yaml
generated
@@ -6,7 +6,11 @@ settings:
|
|||||||
|
|
||||||
importers:
|
importers:
|
||||||
|
|
||||||
.: {}
|
.:
|
||||||
|
devDependencies:
|
||||||
|
yaml:
|
||||||
|
specifier: ^2.9.0
|
||||||
|
version: 2.9.0
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -137,7 +141,7 @@ importers:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
'@angular/build':
|
'@angular/build':
|
||||||
specifier: ^21.2.6
|
specifier: ^21.2.6
|
||||||
version: 21.2.21(@angular/compiler-cli@21.2.20(@angular/compiler@21.2.20)(typescript@5.9.3))(@angular/compiler@21.2.20)(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(@angular/platform-browser@21.2.20(@angular/common@21.2.20(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2)))(@angular/service-worker@21.2.20(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(rxjs@7.8.2))(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.13.3)(chokidar@5.0.0)(postcss@8.5.26)(terser@5.50.0)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.1.10(@types/node@24.13.3)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)))
|
version: 21.2.21(@angular/compiler-cli@21.2.20(@angular/compiler@21.2.20)(typescript@5.9.3))(@angular/compiler@21.2.20)(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(@angular/platform-browser@21.2.20(@angular/common@21.2.20(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2)))(@angular/service-worker@21.2.20(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(rxjs@7.8.2))(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.13.3)(chokidar@5.0.0)(postcss@8.5.26)(terser@5.50.0)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.1.10(@types/node@24.13.3)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)))(yaml@2.9.0)
|
||||||
'@angular/cli':
|
'@angular/cli':
|
||||||
specifier: ^21.2.6
|
specifier: ^21.2.6
|
||||||
version: 21.2.21(@types/node@24.13.3)(chokidar@5.0.0)
|
version: 21.2.21(@types/node@24.13.3)(chokidar@5.0.0)
|
||||||
@@ -155,7 +159,7 @@ importers:
|
|||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^4.0.8
|
specifier: ^4.0.8
|
||||||
version: 4.1.10(@types/node@24.13.3)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0))
|
version: 4.1.10(@types/node@24.13.3)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0))
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -4887,6 +4891,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
|
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
yaml@2.9.0:
|
||||||
|
resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==}
|
||||||
|
engines: {node: '>= 14.6'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
yargs-parser@21.1.1:
|
yargs-parser@21.1.1:
|
||||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -5106,7 +5115,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- chokidar
|
- chokidar
|
||||||
|
|
||||||
'@angular/build@21.2.21(@angular/compiler-cli@21.2.20(@angular/compiler@21.2.20)(typescript@5.9.3))(@angular/compiler@21.2.20)(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(@angular/platform-browser@21.2.20(@angular/common@21.2.20(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2)))(@angular/service-worker@21.2.20(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(rxjs@7.8.2))(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.13.3)(chokidar@5.0.0)(postcss@8.5.26)(terser@5.50.0)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.1.10(@types/node@24.13.3)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)))':
|
'@angular/build@21.2.21(@angular/compiler-cli@21.2.20(@angular/compiler@21.2.20)(typescript@5.9.3))(@angular/compiler@21.2.20)(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(@angular/platform-browser@21.2.20(@angular/common@21.2.20(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2)))(@angular/service-worker@21.2.20(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(rxjs@7.8.2))(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.13.3)(chokidar@5.0.0)(postcss@8.5.26)(terser@5.50.0)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.1.10(@types/node@24.13.3)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)))(yaml@2.9.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ampproject/remapping': 2.3.0
|
'@ampproject/remapping': 2.3.0
|
||||||
'@angular-devkit/architect': 0.2102.21(chokidar@5.0.0)
|
'@angular-devkit/architect': 0.2102.21(chokidar@5.0.0)
|
||||||
@@ -5116,7 +5125,7 @@ snapshots:
|
|||||||
'@babel/helper-annotate-as-pure': 7.27.3
|
'@babel/helper-annotate-as-pure': 7.27.3
|
||||||
'@babel/helper-split-export-declaration': 7.24.7
|
'@babel/helper-split-export-declaration': 7.24.7
|
||||||
'@inquirer/confirm': 5.1.21(@types/node@24.13.3)
|
'@inquirer/confirm': 5.1.21(@types/node@24.13.3)
|
||||||
'@vitejs/plugin-basic-ssl': 2.1.4(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0))
|
'@vitejs/plugin-basic-ssl': 2.1.4(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0))
|
||||||
beasties: 0.4.1
|
beasties: 0.4.1
|
||||||
browserslist: 4.28.8
|
browserslist: 4.28.8
|
||||||
esbuild: 0.28.1
|
esbuild: 0.28.1
|
||||||
@@ -5137,7 +5146,7 @@ snapshots:
|
|||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
undici: 7.29.0
|
undici: 7.29.0
|
||||||
vite: 7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)
|
vite: 7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)
|
||||||
watchpack: 2.5.1
|
watchpack: 2.5.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@angular/core': 21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2)
|
'@angular/core': 21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2)
|
||||||
@@ -5145,7 +5154,7 @@ snapshots:
|
|||||||
'@angular/service-worker': 21.2.20(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(rxjs@7.8.2)
|
'@angular/service-worker': 21.2.20(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(rxjs@7.8.2)
|
||||||
lmdb: 3.5.1
|
lmdb: 3.5.1
|
||||||
postcss: 8.5.26
|
postcss: 8.5.26
|
||||||
vitest: 4.1.10(@types/node@24.13.3)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0))
|
vitest: 4.1.10(@types/node@24.13.3)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@emnapi/core'
|
- '@emnapi/core'
|
||||||
- '@emnapi/runtime'
|
- '@emnapi/runtime'
|
||||||
@@ -6910,9 +6919,9 @@ snapshots:
|
|||||||
'@unrs/resolver-binding-win32-x64-msvc@1.12.2':
|
'@unrs/resolver-binding-win32-x64-msvc@1.12.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@vitejs/plugin-basic-ssl@2.1.4(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0))':
|
'@vitejs/plugin-basic-ssl@2.1.4(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
vite: 7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)
|
vite: 7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)
|
||||||
|
|
||||||
'@vitest/expect@4.1.10':
|
'@vitest/expect@4.1.10':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6923,13 +6932,13 @@ snapshots:
|
|||||||
chai: 6.2.2
|
chai: 6.2.2
|
||||||
tinyrainbow: 3.1.1
|
tinyrainbow: 3.1.1
|
||||||
|
|
||||||
'@vitest/mocker@4.1.10(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0))':
|
'@vitest/mocker@4.1.10(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/spy': 4.1.10
|
'@vitest/spy': 4.1.10
|
||||||
estree-walker: 3.0.3
|
estree-walker: 3.0.3
|
||||||
magic-string: 0.30.21
|
magic-string: 0.30.21
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
vite: 7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)
|
vite: 7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)
|
||||||
|
|
||||||
'@vitest/pretty-format@4.1.10':
|
'@vitest/pretty-format@4.1.10':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -9850,7 +9859,7 @@ snapshots:
|
|||||||
|
|
||||||
vary@1.1.2: {}
|
vary@1.1.2: {}
|
||||||
|
|
||||||
vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0):
|
vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.28.1
|
esbuild: 0.28.1
|
||||||
fdir: 6.5.0(picomatch@4.0.4)
|
fdir: 6.5.0(picomatch@4.0.4)
|
||||||
@@ -9863,11 +9872,12 @@ snapshots:
|
|||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
sass: 1.97.3
|
sass: 1.97.3
|
||||||
terser: 5.50.0
|
terser: 5.50.0
|
||||||
|
yaml: 2.9.0
|
||||||
|
|
||||||
vitest@4.1.10(@types/node@24.13.3)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)):
|
vitest@4.1.10(@types/node@24.13.3)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/expect': 4.1.10
|
'@vitest/expect': 4.1.10
|
||||||
'@vitest/mocker': 4.1.10(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0))
|
'@vitest/mocker': 4.1.10(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0))
|
||||||
'@vitest/pretty-format': 4.1.10
|
'@vitest/pretty-format': 4.1.10
|
||||||
'@vitest/runner': 4.1.10
|
'@vitest/runner': 4.1.10
|
||||||
'@vitest/snapshot': 4.1.10
|
'@vitest/snapshot': 4.1.10
|
||||||
@@ -9884,7 +9894,7 @@ snapshots:
|
|||||||
tinyexec: 1.3.0
|
tinyexec: 1.3.0
|
||||||
tinyglobby: 0.2.17
|
tinyglobby: 0.2.17
|
||||||
tinyrainbow: 3.1.1
|
tinyrainbow: 3.1.1
|
||||||
vite: 7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)
|
vite: 7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)
|
||||||
why-is-node-running: 2.3.0
|
why-is-node-running: 2.3.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 24.13.3
|
'@types/node': 24.13.3
|
||||||
@@ -10034,6 +10044,8 @@ snapshots:
|
|||||||
|
|
||||||
yallist@5.0.0: {}
|
yallist@5.0.0: {}
|
||||||
|
|
||||||
|
yaml@2.9.0: {}
|
||||||
|
|
||||||
yargs-parser@21.1.1: {}
|
yargs-parser@21.1.1: {}
|
||||||
|
|
||||||
yargs-parser@22.0.0: {}
|
yargs-parser@22.0.0: {}
|
||||||
|
|||||||
18
scripts/verify-compose-invariants.mjs
Normal file
18
scripts/verify-compose-invariants.mjs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export function verifyComposeInvariants(compose) {
|
||||||
|
const services = compose?.services ?? {};
|
||||||
|
|
||||||
|
for (const [name, service] of Object.entries(services)) {
|
||||||
|
if (name !== 'edge' && Array.isArray(service.ports) && service.ports.length > 0) {
|
||||||
|
throw new Error(`${name} must not define ports`);
|
||||||
|
}
|
||||||
|
if (service.network_mode === 'host') {
|
||||||
|
throw new Error(`${name} must not use network_mode: host`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const edge = services.edge;
|
||||||
|
const edgePorts = Array.isArray(edge?.ports) ? edge.ports : [];
|
||||||
|
if (edgePorts.length !== 1) {
|
||||||
|
throw new Error(`edge must publish exactly one port; found ${edgePorts.length}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
tests/compose-invariants.test.mjs
Normal file
20
tests/compose-invariants.test.mjs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import { parse } from 'yaml';
|
||||||
|
import { verifyComposeInvariants } from '../scripts/verify-compose-invariants.mjs';
|
||||||
|
|
||||||
|
test('production compose publishes exactly one edge port', () => {
|
||||||
|
const compose = parse(fs.readFileSync(new URL('../compose.yml', import.meta.url), 'utf8'));
|
||||||
|
assert.doesNotThrow(() => verifyComposeInvariants(compose));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('invariant rejects a host port on postgres', () => {
|
||||||
|
const invalid = {
|
||||||
|
services: {
|
||||||
|
edge: { ports: ['443:443'] },
|
||||||
|
postgres: { ports: ['5432:5432'] },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert.throws(() => verifyComposeInvariants(invalid), /postgres.*ports/i);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user