iec-builder 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +111 -0
- package/.iec.yaml +5 -0
- package/CLAUDE.md +174 -0
- package/Dockerfile +34 -0
- package/README.md +84 -0
- package/catalog-info.yaml +11 -0
- package/dist/config/env.d.ts +219 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +89 -0
- package/dist/config/env.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +148 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/auth.d.ts +43 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +217 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/org-access.d.ts +28 -0
- package/dist/middleware/org-access.d.ts.map +1 -0
- package/dist/middleware/org-access.js +102 -0
- package/dist/middleware/org-access.js.map +1 -0
- package/dist/models/types.d.ts +254 -0
- package/dist/models/types.d.ts.map +1 -0
- package/dist/models/types.js +2 -0
- package/dist/models/types.js.map +1 -0
- package/dist/routes/ai.d.ts +2 -0
- package/dist/routes/ai.d.ts.map +1 -0
- package/dist/routes/ai.js +77 -0
- package/dist/routes/ai.js.map +1 -0
- package/dist/routes/audit.d.ts +2 -0
- package/dist/routes/audit.d.ts.map +1 -0
- package/dist/routes/audit.js +102 -0
- package/dist/routes/audit.js.map +1 -0
- package/dist/routes/builds.d.ts +2 -0
- package/dist/routes/builds.d.ts.map +1 -0
- package/dist/routes/builds.js +262 -0
- package/dist/routes/builds.js.map +1 -0
- package/dist/routes/cluster.d.ts +2 -0
- package/dist/routes/cluster.d.ts.map +1 -0
- package/dist/routes/cluster.js +181 -0
- package/dist/routes/cluster.js.map +1 -0
- package/dist/routes/config.d.ts +2 -0
- package/dist/routes/config.d.ts.map +1 -0
- package/dist/routes/config.js +291 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/databases.d.ts +2 -0
- package/dist/routes/databases.d.ts.map +1 -0
- package/dist/routes/databases.js +161 -0
- package/dist/routes/databases.js.map +1 -0
- package/dist/routes/db-whitelist.d.ts +2 -0
- package/dist/routes/db-whitelist.d.ts.map +1 -0
- package/dist/routes/db-whitelist.js +148 -0
- package/dist/routes/db-whitelist.js.map +1 -0
- package/dist/routes/domains.d.ts +2 -0
- package/dist/routes/domains.d.ts.map +1 -0
- package/dist/routes/domains.js +449 -0
- package/dist/routes/domains.js.map +1 -0
- package/dist/routes/oauth.d.ts +2 -0
- package/dist/routes/oauth.d.ts.map +1 -0
- package/dist/routes/oauth.js +180 -0
- package/dist/routes/oauth.js.map +1 -0
- package/dist/routes/observability.d.ts +2 -0
- package/dist/routes/observability.d.ts.map +1 -0
- package/dist/routes/observability.js +167 -0
- package/dist/routes/observability.js.map +1 -0
- package/dist/routes/orgs.d.ts +2 -0
- package/dist/routes/orgs.d.ts.map +1 -0
- package/dist/routes/orgs.js +270 -0
- package/dist/routes/orgs.js.map +1 -0
- package/dist/routes/platform.d.ts +2 -0
- package/dist/routes/platform.d.ts.map +1 -0
- package/dist/routes/platform.js +107 -0
- package/dist/routes/platform.js.map +1 -0
- package/dist/routes/push.d.ts +2 -0
- package/dist/routes/push.d.ts.map +1 -0
- package/dist/routes/push.js +233 -0
- package/dist/routes/push.js.map +1 -0
- package/dist/routes/rotation.d.ts +3 -0
- package/dist/routes/rotation.d.ts.map +1 -0
- package/dist/routes/rotation.js +154 -0
- package/dist/routes/rotation.js.map +1 -0
- package/dist/routes/services.d.ts +2 -0
- package/dist/routes/services.d.ts.map +1 -0
- package/dist/routes/services.js +246 -0
- package/dist/routes/services.js.map +1 -0
- package/dist/routes/storage.d.ts +2 -0
- package/dist/routes/storage.d.ts.map +1 -0
- package/dist/routes/storage.js +118 -0
- package/dist/routes/storage.js.map +1 -0
- package/dist/routes/users.d.ts +2 -0
- package/dist/routes/users.d.ts.map +1 -0
- package/dist/routes/users.js +183 -0
- package/dist/routes/users.js.map +1 -0
- package/dist/routes/versions.d.ts +2 -0
- package/dist/routes/versions.d.ts.map +1 -0
- package/dist/routes/versions.js +195 -0
- package/dist/routes/versions.js.map +1 -0
- package/dist/routes/webhooks.d.ts +2 -0
- package/dist/routes/webhooks.d.ts.map +1 -0
- package/dist/routes/webhooks.js +334 -0
- package/dist/routes/webhooks.js.map +1 -0
- package/dist/services/__tests__/deploy-pipeline.integration.test.d.ts +2 -0
- package/dist/services/__tests__/deploy-pipeline.integration.test.d.ts.map +1 -0
- package/dist/services/__tests__/deploy-pipeline.integration.test.js +482 -0
- package/dist/services/__tests__/deploy-pipeline.integration.test.js.map +1 -0
- package/dist/services/bio-client.d.ts +68 -0
- package/dist/services/bio-client.d.ts.map +1 -0
- package/dist/services/bio-client.js +110 -0
- package/dist/services/bio-client.js.map +1 -0
- package/dist/services/build-queue.d.ts +7 -0
- package/dist/services/build-queue.d.ts.map +1 -0
- package/dist/services/build-queue.js +114 -0
- package/dist/services/build-queue.js.map +1 -0
- package/dist/services/builder.d.ts +7 -0
- package/dist/services/builder.d.ts.map +1 -0
- package/dist/services/builder.js +1384 -0
- package/dist/services/builder.js.map +1 -0
- package/dist/services/catalog.d.ts +177 -0
- package/dist/services/catalog.d.ts.map +1 -0
- package/dist/services/catalog.js +805 -0
- package/dist/services/catalog.js.map +1 -0
- package/dist/services/catalog.test.d.ts +2 -0
- package/dist/services/catalog.test.d.ts.map +1 -0
- package/dist/services/catalog.test.js +467 -0
- package/dist/services/catalog.test.js.map +1 -0
- package/dist/services/cloudflare.d.ts +43 -0
- package/dist/services/cloudflare.d.ts.map +1 -0
- package/dist/services/cloudflare.js +182 -0
- package/dist/services/cloudflare.js.map +1 -0
- package/dist/services/config-validator.d.ts +28 -0
- package/dist/services/config-validator.d.ts.map +1 -0
- package/dist/services/config-validator.js +68 -0
- package/dist/services/config-validator.js.map +1 -0
- package/dist/services/config-validator.test.d.ts +2 -0
- package/dist/services/config-validator.test.d.ts.map +1 -0
- package/dist/services/config-validator.test.js +151 -0
- package/dist/services/config-validator.test.js.map +1 -0
- package/dist/services/crypto.d.ts +19 -0
- package/dist/services/crypto.d.ts.map +1 -0
- package/dist/services/crypto.js +63 -0
- package/dist/services/crypto.js.map +1 -0
- package/dist/services/database.d.ts +26 -0
- package/dist/services/database.d.ts.map +1 -0
- package/dist/services/database.js +100 -0
- package/dist/services/database.js.map +1 -0
- package/dist/services/db-credential-manager.d.ts +73 -0
- package/dist/services/db-credential-manager.d.ts.map +1 -0
- package/dist/services/db-credential-manager.js +342 -0
- package/dist/services/db-credential-manager.js.map +1 -0
- package/dist/services/db-provisioner.d.ts +57 -0
- package/dist/services/db-provisioner.d.ts.map +1 -0
- package/dist/services/db-provisioner.js +400 -0
- package/dist/services/db-provisioner.js.map +1 -0
- package/dist/services/db-provisioner.test.d.ts +2 -0
- package/dist/services/db-provisioner.test.d.ts.map +1 -0
- package/dist/services/db-provisioner.test.js +141 -0
- package/dist/services/db-provisioner.test.js.map +1 -0
- package/dist/services/db-whitelist.d.ts +58 -0
- package/dist/services/db-whitelist.d.ts.map +1 -0
- package/dist/services/db-whitelist.js +379 -0
- package/dist/services/db-whitelist.js.map +1 -0
- package/dist/services/dependency-resolver.d.ts +58 -0
- package/dist/services/dependency-resolver.d.ts.map +1 -0
- package/dist/services/dependency-resolver.js +180 -0
- package/dist/services/dependency-resolver.js.map +1 -0
- package/dist/services/dependency-resolver.test.d.ts +2 -0
- package/dist/services/dependency-resolver.test.d.ts.map +1 -0
- package/dist/services/dependency-resolver.test.js +195 -0
- package/dist/services/dependency-resolver.test.js.map +1 -0
- package/dist/services/deploy-gate.d.ts +19 -0
- package/dist/services/deploy-gate.d.ts.map +1 -0
- package/dist/services/deploy-gate.js +56 -0
- package/dist/services/deploy-gate.js.map +1 -0
- package/dist/services/deploy-gate.test.d.ts +2 -0
- package/dist/services/deploy-gate.test.d.ts.map +1 -0
- package/dist/services/deploy-gate.test.js +199 -0
- package/dist/services/deploy-gate.test.js.map +1 -0
- package/dist/services/dockerfile-generator.d.ts +31 -0
- package/dist/services/dockerfile-generator.d.ts.map +1 -0
- package/dist/services/dockerfile-generator.js +544 -0
- package/dist/services/dockerfile-generator.js.map +1 -0
- package/dist/services/dockerfile-generator.test.d.ts +2 -0
- package/dist/services/dockerfile-generator.test.d.ts.map +1 -0
- package/dist/services/dockerfile-generator.test.js +144 -0
- package/dist/services/dockerfile-generator.test.js.map +1 -0
- package/dist/services/forgejo.d.ts +58 -0
- package/dist/services/forgejo.d.ts.map +1 -0
- package/dist/services/forgejo.js +131 -0
- package/dist/services/forgejo.js.map +1 -0
- package/dist/services/koko.d.ts +153 -0
- package/dist/services/koko.d.ts.map +1 -0
- package/dist/services/koko.js +260 -0
- package/dist/services/koko.js.map +1 -0
- package/dist/services/kubernetes.d.ts +16 -0
- package/dist/services/kubernetes.d.ts.map +1 -0
- package/dist/services/kubernetes.js +102 -0
- package/dist/services/kubernetes.js.map +1 -0
- package/dist/services/oauth-provisioner.d.ts +30 -0
- package/dist/services/oauth-provisioner.d.ts.map +1 -0
- package/dist/services/oauth-provisioner.js +182 -0
- package/dist/services/oauth-provisioner.js.map +1 -0
- package/dist/services/oauth-provisioner.test.d.ts +2 -0
- package/dist/services/oauth-provisioner.test.d.ts.map +1 -0
- package/dist/services/oauth-provisioner.test.js +349 -0
- package/dist/services/oauth-provisioner.test.js.map +1 -0
- package/dist/services/pod-diagnostics.d.ts +11 -0
- package/dist/services/pod-diagnostics.d.ts.map +1 -0
- package/dist/services/pod-diagnostics.js +201 -0
- package/dist/services/pod-diagnostics.js.map +1 -0
- package/dist/services/rotation-scheduler.d.ts +2 -0
- package/dist/services/rotation-scheduler.d.ts.map +1 -0
- package/dist/services/rotation-scheduler.js +215 -0
- package/dist/services/rotation-scheduler.js.map +1 -0
- package/dist/services/storage-credential-manager.d.ts +43 -0
- package/dist/services/storage-credential-manager.d.ts.map +1 -0
- package/dist/services/storage-credential-manager.js +159 -0
- package/dist/services/storage-credential-manager.js.map +1 -0
- package/dist/services/storage-provisioner.d.ts +32 -0
- package/dist/services/storage-provisioner.d.ts.map +1 -0
- package/dist/services/storage-provisioner.js +136 -0
- package/dist/services/storage-provisioner.js.map +1 -0
- package/dist/services/storage.d.ts +65 -0
- package/dist/services/storage.d.ts.map +1 -0
- package/dist/services/storage.js +204 -0
- package/dist/services/storage.js.map +1 -0
- package/dist/services/troubleshooter.d.ts +22 -0
- package/dist/services/troubleshooter.d.ts.map +1 -0
- package/dist/services/troubleshooter.js +168 -0
- package/dist/services/troubleshooter.js.map +1 -0
- package/dist/services/vault-client.d.ts +114 -0
- package/dist/services/vault-client.d.ts.map +1 -0
- package/dist/services/vault-client.js +411 -0
- package/dist/services/vault-client.js.map +1 -0
- package/dist/utils/logger.d.ts +2 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +6 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/response.d.ts +13 -0
- package/dist/utils/response.d.ts.map +1 -0
- package/dist/utils/response.js +12 -0
- package/dist/utils/response.js.map +1 -0
- package/docs/registry-migration.md +301 -0
- package/docs/registry-quickstart.md +169 -0
- package/ecosystem.config.cjs +14 -0
- package/findings.md +168 -0
- package/helm/default-service/Chart.yaml +6 -0
- package/helm/default-service/templates/deployment.yaml +97 -0
- package/helm/default-service/templates/ingress.yaml +43 -0
- package/helm/default-service/templates/service.yaml +17 -0
- package/helm/default-service/values.yaml +82 -0
- package/helm/services/iec-builder/Chart.yaml +6 -0
- package/helm/services/iec-builder/templates/_helpers.tpl +61 -0
- package/helm/services/iec-builder/templates/deployment.yaml +73 -0
- package/helm/services/iec-builder/templates/service.yaml +15 -0
- package/helm/services/iec-builder/templates/serviceaccount.yaml +12 -0
- package/helm/services/iec-builder/values.yaml +56 -0
- package/helm/vault-values.yaml +127 -0
- package/package.json +45 -0
- package/progress.md +156 -0
- package/scripts/.vault-init-keys.json +23 -0
- package/scripts/backfill-ownership.ts +113 -0
- package/scripts/finalize-mongo-auth.sh +212 -0
- package/scripts/setup-ipset.sh +107 -0
- package/scripts/setup-mongo-auth.sh +163 -0
- package/scripts/setup-neo4j-auth.sh +62 -0
- package/scripts/setup-redis-auth.sh +55 -0
- package/scripts/setup-registry-secret.sh +71 -0
- package/scripts/setup-vault.sh +308 -0
- package/src/config/env.ts +117 -0
- package/src/index.ts +153 -0
- package/src/middleware/auth.ts +294 -0
- package/src/middleware/org-access.ts +126 -0
- package/src/models/types.ts +288 -0
- package/src/routes/ai.ts +115 -0
- package/src/routes/audit.ts +121 -0
- package/src/routes/builds.ts +320 -0
- package/src/routes/cluster.ts +235 -0
- package/src/routes/config.ts +369 -0
- package/src/routes/databases.ts +201 -0
- package/src/routes/db-whitelist.ts +204 -0
- package/src/routes/domains.ts +547 -0
- package/src/routes/oauth.ts +195 -0
- package/src/routes/observability.ts +205 -0
- package/src/routes/orgs.ts +330 -0
- package/src/routes/platform.ts +134 -0
- package/src/routes/rotation.ts +191 -0
- package/src/routes/services.ts +290 -0
- package/src/routes/storage.ts +153 -0
- package/src/routes/users.ts +235 -0
- package/src/routes/webhooks.ts +384 -0
- package/src/services/__tests__/catalog-storage.test.ts +186 -0
- package/src/services/__tests__/deploy-pipeline.integration.test.ts +624 -0
- package/src/services/__tests__/pod-diagnostics.test.ts +332 -0
- package/src/services/__tests__/storage-credential-manager.test.ts +129 -0
- package/src/services/__tests__/storage-provisioner.test.ts +166 -0
- package/src/services/__tests__/troubleshooter.test.ts +329 -0
- package/src/services/bio-client.ts +189 -0
- package/src/services/build-queue.ts +137 -0
- package/src/services/builder.ts +1800 -0
- package/src/services/catalog.test.ts +1389 -0
- package/src/services/catalog.ts +1187 -0
- package/src/services/cloudflare.ts +259 -0
- package/src/services/config-validator.test.ts +190 -0
- package/src/services/config-validator.ts +108 -0
- package/src/services/crypto.ts +78 -0
- package/src/services/database.ts +122 -0
- package/src/services/db-credential-manager.test.ts +101 -0
- package/src/services/db-credential-manager.ts +447 -0
- package/src/services/db-provisioner.test.ts +602 -0
- package/src/services/db-provisioner.ts +589 -0
- package/src/services/db-whitelist.test.ts +671 -0
- package/src/services/db-whitelist.ts +496 -0
- package/src/services/dependency-resolver.test.ts +677 -0
- package/src/services/dependency-resolver.ts +319 -0
- package/src/services/deploy-gate.test.ts +247 -0
- package/src/services/deploy-gate.ts +75 -0
- package/src/services/dockerfile-generator.test.ts +401 -0
- package/src/services/dockerfile-generator.ts +606 -0
- package/src/services/forgejo.ts +212 -0
- package/src/services/koko.ts +492 -0
- package/src/services/kubernetes.ts +141 -0
- package/src/services/oauth-provisioner.test.ts +477 -0
- package/src/services/oauth-provisioner.ts +286 -0
- package/src/services/pod-diagnostics.ts +261 -0
- package/src/services/rotation-scheduler.ts +293 -0
- package/src/services/storage-credential-manager.ts +223 -0
- package/src/services/storage-provisioner.ts +216 -0
- package/src/services/storage.ts +274 -0
- package/src/services/troubleshooter.ts +208 -0
- package/src/services/vault-client.test.ts +272 -0
- package/src/services/vault-client.ts +587 -0
- package/src/utils/logger.ts +6 -0
- package/src/utils/response.ts +23 -0
- package/task_plan.md +171 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +19 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import { mkdir, writeFile, rm } from 'fs/promises'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
import { tmpdir } from 'os'
|
|
5
|
+
import { randomUUID } from 'crypto'
|
|
6
|
+
|
|
7
|
+
vi.mock('../utils/logger.js', () => ({
|
|
8
|
+
logger: {
|
|
9
|
+
info: vi.fn(),
|
|
10
|
+
warn: vi.fn(),
|
|
11
|
+
error: vi.fn(),
|
|
12
|
+
},
|
|
13
|
+
}))
|
|
14
|
+
|
|
15
|
+
import { generateDockerfile, patchCustomDockerfile } from './dockerfile-generator.js'
|
|
16
|
+
import type { ParsedCatalog } from './catalog.js'
|
|
17
|
+
|
|
18
|
+
function makeCatalog(overrides: Partial<ParsedCatalog> = {}): ParsedCatalog {
|
|
19
|
+
return {
|
|
20
|
+
name: 'test-site',
|
|
21
|
+
description: 'Test static site',
|
|
22
|
+
framework: 'static',
|
|
23
|
+
nodeVersion: '20',
|
|
24
|
+
buildCommand: 'true',
|
|
25
|
+
startCommand: '',
|
|
26
|
+
outputDir: '.',
|
|
27
|
+
port: 80,
|
|
28
|
+
healthEndpoint: '/health',
|
|
29
|
+
envVars: [],
|
|
30
|
+
routes: [],
|
|
31
|
+
cronjobs: [],
|
|
32
|
+
databases: [],
|
|
33
|
+
internalDependencies: [],
|
|
34
|
+
lifecycle: 'production',
|
|
35
|
+
owner: 'test-org',
|
|
36
|
+
type: 'service',
|
|
37
|
+
podTier: 'nano',
|
|
38
|
+
configDeclarations: [],
|
|
39
|
+
copyPaths: [],
|
|
40
|
+
catalogVersion: '0.3.0',
|
|
41
|
+
consumesDatabase: [],
|
|
42
|
+
dependencies: [],
|
|
43
|
+
storage: [],
|
|
44
|
+
modules: [],
|
|
45
|
+
externalDependencies: [],
|
|
46
|
+
...overrides,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function createFixture(files: Record<string, string>): Promise<string> {
|
|
51
|
+
const dir = join(tmpdir(), `iec-builder-test-${randomUUID()}`)
|
|
52
|
+
await mkdir(dir, { recursive: true })
|
|
53
|
+
for (const [name, content] of Object.entries(files)) {
|
|
54
|
+
const filePath = join(dir, name)
|
|
55
|
+
const parentDir = join(filePath, '..')
|
|
56
|
+
await mkdir(parentDir, { recursive: true })
|
|
57
|
+
await writeFile(filePath, content, 'utf-8')
|
|
58
|
+
}
|
|
59
|
+
return dir
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
describe('dockerfile-generator', () => {
|
|
63
|
+
const tempDirs: string[] = []
|
|
64
|
+
|
|
65
|
+
afterEach(async () => {
|
|
66
|
+
await Promise.all(tempDirs.map(d => rm(d, { recursive: true, force: true })))
|
|
67
|
+
tempDirs.splice(0, tempDirs.length)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
async function setup(files: Record<string, string>) {
|
|
71
|
+
const dir = await createFixture(files)
|
|
72
|
+
tempDirs.push(dir)
|
|
73
|
+
return dir
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe('static framework — no package.json (raw HTML)', () => {
|
|
77
|
+
it('should generate a simple nginx Dockerfile without build step', async () => {
|
|
78
|
+
const dir = await setup({
|
|
79
|
+
'index.html': '<html><body>Hello</body></html>',
|
|
80
|
+
'catalog-info.yaml': 'apiVersion: backstage.io/v1alpha1',
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const result = await generateDockerfile(dir, makeCatalog())
|
|
84
|
+
expect(result).toContain('Dockerfile.generated')
|
|
85
|
+
|
|
86
|
+
const { readFile } = await import('fs/promises')
|
|
87
|
+
const content = await readFile(result, 'utf-8')
|
|
88
|
+
|
|
89
|
+
expect(content).toContain('FROM nginx:alpine')
|
|
90
|
+
expect(content).not.toContain('FROM node:')
|
|
91
|
+
expect(content).not.toContain('npm install')
|
|
92
|
+
expect(content).not.toContain('npm run build')
|
|
93
|
+
expect(content).toContain('COPY . /usr/share/nginx/html')
|
|
94
|
+
expect(content).toContain('/health')
|
|
95
|
+
expect(content).toContain('EXPOSE 80')
|
|
96
|
+
expect(content).toContain('rm -rf')
|
|
97
|
+
expect(content).toContain('catalog-info.yaml')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('should clean up non-servable files in raw HTML mode', async () => {
|
|
101
|
+
const dir = await setup({
|
|
102
|
+
'index.html': '<html><body>Hello</body></html>',
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const result = await generateDockerfile(dir, makeCatalog())
|
|
106
|
+
const { readFile } = await import('fs/promises')
|
|
107
|
+
const content = await readFile(result, 'utf-8')
|
|
108
|
+
|
|
109
|
+
expect(content).toContain('.git')
|
|
110
|
+
expect(content).toContain('.iec.yaml')
|
|
111
|
+
expect(content).toContain('helm')
|
|
112
|
+
expect(content).toContain('.gitignore')
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
describe('static framework — with package.json (build step)', () => {
|
|
117
|
+
it('should generate multi-stage Dockerfile with npm build', async () => {
|
|
118
|
+
const dir = await setup({
|
|
119
|
+
'package.json': JSON.stringify({ name: 'test', scripts: { build: 'vite build' } }),
|
|
120
|
+
'index.html': '<html><body>Hello</body></html>',
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const catalog = makeCatalog({
|
|
124
|
+
buildCommand: 'npm run build',
|
|
125
|
+
outputDir: 'dist',
|
|
126
|
+
})
|
|
127
|
+
const result = await generateDockerfile(dir, catalog)
|
|
128
|
+
|
|
129
|
+
const { readFile } = await import('fs/promises')
|
|
130
|
+
const content = await readFile(result, 'utf-8')
|
|
131
|
+
|
|
132
|
+
expect(content).toContain('FROM node:20-alpine AS builder')
|
|
133
|
+
expect(content).toContain('npm install')
|
|
134
|
+
expect(content).toContain('npm run build')
|
|
135
|
+
expect(content).toContain('FROM nginx:alpine AS runner')
|
|
136
|
+
expect(content).toContain('COPY --from=builder /app/dist /usr/share/nginx/html')
|
|
137
|
+
expect(content).toContain('/health')
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
describe('existing Dockerfile', () => {
|
|
142
|
+
it('should always generate Dockerfile.generated even when Dockerfile exists', async () => {
|
|
143
|
+
const dir = await setup({
|
|
144
|
+
'Dockerfile': 'FROM nginx:alpine\nCOPY . /usr/share/nginx/html',
|
|
145
|
+
'index.html': '<html><body>Hello</body></html>',
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
const result = await generateDockerfile(dir, makeCatalog())
|
|
149
|
+
expect(result).toEqual(join(dir, 'Dockerfile.generated'))
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
describe('other frameworks', () => {
|
|
154
|
+
it('should generate express Dockerfile', async () => {
|
|
155
|
+
const dir = await setup({
|
|
156
|
+
'package.json': JSON.stringify({ name: 'test' }),
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
const catalog = makeCatalog({
|
|
160
|
+
framework: 'express',
|
|
161
|
+
port: 3000,
|
|
162
|
+
buildCommand: 'npm run build',
|
|
163
|
+
startCommand: 'npm start',
|
|
164
|
+
outputDir: 'dist',
|
|
165
|
+
})
|
|
166
|
+
const result = await generateDockerfile(dir, catalog)
|
|
167
|
+
|
|
168
|
+
const { readFile } = await import('fs/promises')
|
|
169
|
+
const content = await readFile(result, 'utf-8')
|
|
170
|
+
|
|
171
|
+
expect(content).toContain('Express/Hono/Fastify')
|
|
172
|
+
expect(content).toContain('EXPOSE 3000')
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
describe('patchCustomDockerfile', () => {
|
|
178
|
+
const tempDirs: string[] = []
|
|
179
|
+
|
|
180
|
+
afterEach(async () => {
|
|
181
|
+
await Promise.all(tempDirs.map(d => rm(d, { recursive: true, force: true })))
|
|
182
|
+
tempDirs.splice(0, tempDirs.length)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
async function setupDockerfile(content: string): Promise<string> {
|
|
186
|
+
const dir = join(tmpdir(), `iec-builder-test-${randomUUID()}`)
|
|
187
|
+
await mkdir(dir, { recursive: true })
|
|
188
|
+
const filePath = join(dir, 'Dockerfile')
|
|
189
|
+
await writeFile(filePath, content, 'utf-8')
|
|
190
|
+
tempDirs.push(dir)
|
|
191
|
+
return filePath
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function readPatched(filePath: string): Promise<string> {
|
|
195
|
+
const { readFile } = await import('fs/promises')
|
|
196
|
+
return readFile(filePath, 'utf-8')
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
describe('numeric UID patching', () => {
|
|
200
|
+
it('should replace non-numeric USER with USER 1001', async () => {
|
|
201
|
+
const path = await setupDockerfile([
|
|
202
|
+
'FROM node:20-alpine AS builder',
|
|
203
|
+
'WORKDIR /app',
|
|
204
|
+
'COPY . .',
|
|
205
|
+
'RUN npm run build',
|
|
206
|
+
'FROM node:20-alpine AS runner',
|
|
207
|
+
'USER nextjs',
|
|
208
|
+
'CMD ["node", "server.js"]',
|
|
209
|
+
].join('\n'))
|
|
210
|
+
|
|
211
|
+
const patches = await patchCustomDockerfile(path, false)
|
|
212
|
+
const content = await readPatched(path)
|
|
213
|
+
|
|
214
|
+
expect(patches).toHaveLength(1)
|
|
215
|
+
expect(patches[0].type).toBe('numeric-uid')
|
|
216
|
+
expect(patches[0].message).toContain('USER nextjs')
|
|
217
|
+
expect(content).toContain('USER 1001')
|
|
218
|
+
expect(content).not.toContain('USER nextjs')
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('should leave numeric USER untouched', async () => {
|
|
222
|
+
const path = await setupDockerfile([
|
|
223
|
+
'FROM node:20-alpine',
|
|
224
|
+
'USER 1001',
|
|
225
|
+
'CMD ["node", "server.js"]',
|
|
226
|
+
].join('\n'))
|
|
227
|
+
|
|
228
|
+
const patches = await patchCustomDockerfile(path, false)
|
|
229
|
+
const content = await readPatched(path)
|
|
230
|
+
|
|
231
|
+
expect(patches).toHaveLength(0)
|
|
232
|
+
expect(content).toContain('USER 1001')
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('should not replace root USER (validated separately)', async () => {
|
|
236
|
+
const path = await setupDockerfile([
|
|
237
|
+
'FROM node:20-alpine',
|
|
238
|
+
'USER root',
|
|
239
|
+
'CMD ["node", "server.js"]',
|
|
240
|
+
].join('\n'))
|
|
241
|
+
|
|
242
|
+
const patches = await patchCustomDockerfile(path, false)
|
|
243
|
+
|
|
244
|
+
expect(patches).toHaveLength(0)
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it('should only patch USER in the final stage', async () => {
|
|
248
|
+
const path = await setupDockerfile([
|
|
249
|
+
'FROM node:20-alpine AS builder',
|
|
250
|
+
'USER builduser',
|
|
251
|
+
'RUN npm run build',
|
|
252
|
+
'FROM node:20-alpine AS runner',
|
|
253
|
+
'USER appuser',
|
|
254
|
+
'CMD ["node", "server.js"]',
|
|
255
|
+
].join('\n'))
|
|
256
|
+
|
|
257
|
+
const patches = await patchCustomDockerfile(path, false)
|
|
258
|
+
const content = await readPatched(path)
|
|
259
|
+
|
|
260
|
+
// Only the final stage USER should be patched
|
|
261
|
+
expect(patches).toHaveLength(1)
|
|
262
|
+
expect(content).toContain('USER builduser')
|
|
263
|
+
expect(content).not.toContain('USER appuser')
|
|
264
|
+
expect(content).toMatch(/AS runner[\s\S]*USER 1001/)
|
|
265
|
+
})
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
describe('Vault entrypoint injection', () => {
|
|
269
|
+
it('should inject Vault entrypoint when missing and vault enabled', async () => {
|
|
270
|
+
const path = await setupDockerfile([
|
|
271
|
+
'FROM node:20-alpine',
|
|
272
|
+
'WORKDIR /app',
|
|
273
|
+
'COPY . .',
|
|
274
|
+
'USER 1001',
|
|
275
|
+
'CMD ["node", "server.js"]',
|
|
276
|
+
].join('\n'))
|
|
277
|
+
|
|
278
|
+
const patches = await patchCustomDockerfile(path, true)
|
|
279
|
+
const content = await readPatched(path)
|
|
280
|
+
|
|
281
|
+
expect(patches).toHaveLength(1)
|
|
282
|
+
expect(patches[0].type).toBe('vault-entrypoint')
|
|
283
|
+
expect(content).toContain('/vault/secrets')
|
|
284
|
+
expect(content).toContain('entrypoint.sh')
|
|
285
|
+
expect(content).toContain('ENTRYPOINT')
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
it('should not inject Vault entrypoint when vault disabled', async () => {
|
|
289
|
+
const path = await setupDockerfile([
|
|
290
|
+
'FROM node:20-alpine',
|
|
291
|
+
'USER 1001',
|
|
292
|
+
'CMD ["node", "server.js"]',
|
|
293
|
+
].join('\n'))
|
|
294
|
+
|
|
295
|
+
const patches = await patchCustomDockerfile(path, false)
|
|
296
|
+
|
|
297
|
+
expect(patches).toHaveLength(0)
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
it('should skip injection if Vault entrypoint already present', async () => {
|
|
301
|
+
const path = await setupDockerfile([
|
|
302
|
+
'FROM node:20-alpine',
|
|
303
|
+
'WORKDIR /app',
|
|
304
|
+
'COPY . .',
|
|
305
|
+
'RUN printf \'#!/bin/sh\\nfor f in /vault/secrets/*; do . "$f"; done\\nexec "$@"\\n\' > /usr/local/bin/entrypoint.sh && chmod +x /usr/local/bin/entrypoint.sh',
|
|
306
|
+
'ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]',
|
|
307
|
+
'USER 1001',
|
|
308
|
+
'CMD ["node", "server.js"]',
|
|
309
|
+
].join('\n'))
|
|
310
|
+
|
|
311
|
+
const patches = await patchCustomDockerfile(path, true)
|
|
312
|
+
|
|
313
|
+
expect(patches).toHaveLength(0)
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
it('should inject Vault entrypoint before USER directive', async () => {
|
|
317
|
+
const path = await setupDockerfile([
|
|
318
|
+
'FROM node:20-alpine',
|
|
319
|
+
'WORKDIR /app',
|
|
320
|
+
'COPY . .',
|
|
321
|
+
'USER 1001',
|
|
322
|
+
'CMD ["node", "server.js"]',
|
|
323
|
+
].join('\n'))
|
|
324
|
+
|
|
325
|
+
await patchCustomDockerfile(path, true)
|
|
326
|
+
const content = await readPatched(path)
|
|
327
|
+
|
|
328
|
+
// Vault ENTRYPOINT should appear before USER 1001
|
|
329
|
+
const entrypointIdx = content.indexOf('ENTRYPOINT')
|
|
330
|
+
const userIdx = content.indexOf('USER 1001')
|
|
331
|
+
expect(entrypointIdx).toBeGreaterThan(-1)
|
|
332
|
+
expect(entrypointIdx).toBeLessThan(userIdx)
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
it('should convert existing ENTRYPOINT to CMD when wrapping with Vault', async () => {
|
|
336
|
+
const path = await setupDockerfile([
|
|
337
|
+
'FROM node:20-alpine',
|
|
338
|
+
'WORKDIR /app',
|
|
339
|
+
'COPY . .',
|
|
340
|
+
'USER 1001',
|
|
341
|
+
'ENTRYPOINT ["node", "server.js"]',
|
|
342
|
+
].join('\n'))
|
|
343
|
+
|
|
344
|
+
const patches = await patchCustomDockerfile(path, true)
|
|
345
|
+
const content = await readPatched(path)
|
|
346
|
+
|
|
347
|
+
expect(patches).toHaveLength(1)
|
|
348
|
+
expect(patches[0].type).toBe('vault-entrypoint')
|
|
349
|
+
// Original ENTRYPOINT should be converted to CMD
|
|
350
|
+
expect(content).toContain('CMD ["node", "server.js"]')
|
|
351
|
+
// Vault ENTRYPOINT should be present
|
|
352
|
+
expect(content).toContain('ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]')
|
|
353
|
+
})
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
describe('combined patches', () => {
|
|
357
|
+
it('should apply both UID fix and Vault injection', async () => {
|
|
358
|
+
const path = await setupDockerfile([
|
|
359
|
+
'FROM node:20-alpine AS builder',
|
|
360
|
+
'WORKDIR /app',
|
|
361
|
+
'COPY . .',
|
|
362
|
+
'RUN npm run build',
|
|
363
|
+
'FROM node:20-alpine AS runner',
|
|
364
|
+
'WORKDIR /app',
|
|
365
|
+
'COPY --from=builder /app/dist ./dist',
|
|
366
|
+
'USER nextjs',
|
|
367
|
+
'CMD ["node", "dist/index.js"]',
|
|
368
|
+
].join('\n'))
|
|
369
|
+
|
|
370
|
+
const patches = await patchCustomDockerfile(path, true)
|
|
371
|
+
const content = await readPatched(path)
|
|
372
|
+
|
|
373
|
+
expect(patches).toHaveLength(2)
|
|
374
|
+
const types = patches.map(p => p.type)
|
|
375
|
+
expect(types).toContain('numeric-uid')
|
|
376
|
+
expect(types).toContain('vault-entrypoint')
|
|
377
|
+
|
|
378
|
+
expect(content).toContain('USER 1001')
|
|
379
|
+
expect(content).not.toContain('USER nextjs')
|
|
380
|
+
expect(content).toContain('/vault/secrets')
|
|
381
|
+
expect(content).toContain('ENTRYPOINT')
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
it('should not modify file when no patches needed', async () => {
|
|
385
|
+
const original = [
|
|
386
|
+
'FROM node:20-alpine',
|
|
387
|
+
'WORKDIR /app',
|
|
388
|
+
'COPY . .',
|
|
389
|
+
'USER 1001',
|
|
390
|
+
'CMD ["node", "server.js"]',
|
|
391
|
+
].join('\n')
|
|
392
|
+
|
|
393
|
+
const path = await setupDockerfile(original)
|
|
394
|
+
const patches = await patchCustomDockerfile(path, false)
|
|
395
|
+
const content = await readPatched(path)
|
|
396
|
+
|
|
397
|
+
expect(patches).toHaveLength(0)
|
|
398
|
+
expect(content).toBe(original)
|
|
399
|
+
})
|
|
400
|
+
})
|
|
401
|
+
})
|