@intentius/chant-lexicon-docker 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/README.md +24 -0
- package/dist/integrity.json +19 -0
- package/dist/manifest.json +15 -0
- package/dist/meta.json +222 -0
- package/dist/rules/apt-no-recommends.ts +43 -0
- package/dist/rules/docker-helpers.ts +114 -0
- package/dist/rules/no-latest-image.ts +36 -0
- package/dist/rules/no-latest-tag.ts +63 -0
- package/dist/rules/no-root-user.ts +36 -0
- package/dist/rules/prefer-copy.ts +53 -0
- package/dist/rules/ssh-port-exposed.ts +68 -0
- package/dist/rules/unused-volume.ts +49 -0
- package/dist/skills/chant-docker-patterns.md +153 -0
- package/dist/skills/chant-docker.md +129 -0
- package/dist/types/index.d.ts +93 -0
- package/package.json +53 -0
- package/src/codegen/docs-cli.ts +10 -0
- package/src/codegen/docs.ts +12 -0
- package/src/codegen/generate-cli.ts +36 -0
- package/src/codegen/generate-compose.ts +21 -0
- package/src/codegen/generate-dockerfile.ts +21 -0
- package/src/codegen/generate.test.ts +105 -0
- package/src/codegen/generate.ts +158 -0
- package/src/codegen/naming.test.ts +81 -0
- package/src/codegen/naming.ts +54 -0
- package/src/codegen/package.ts +65 -0
- package/src/codegen/patches.ts +42 -0
- package/src/codegen/versions.ts +15 -0
- package/src/composites/index.ts +12 -0
- package/src/coverage.test.ts +33 -0
- package/src/coverage.ts +54 -0
- package/src/default-labels.test.ts +85 -0
- package/src/default-labels.ts +72 -0
- package/src/generated/index.d.ts +93 -0
- package/src/generated/index.ts +10 -0
- package/src/generated/lexicon-docker.json +222 -0
- package/src/generated/runtime.ts +4 -0
- package/src/import/generator.test.ts +133 -0
- package/src/import/generator.ts +127 -0
- package/src/import/parser.test.ts +137 -0
- package/src/import/parser.ts +190 -0
- package/src/import/roundtrip.test.ts +49 -0
- package/src/import/testdata/full.yaml +43 -0
- package/src/import/testdata/simple.yaml +9 -0
- package/src/import/testdata/webapp.yaml +41 -0
- package/src/index.ts +29 -0
- package/src/interpolation.test.ts +41 -0
- package/src/interpolation.ts +76 -0
- package/src/lint/post-synth/apt-no-recommends.ts +43 -0
- package/src/lint/post-synth/docker-helpers.ts +114 -0
- package/src/lint/post-synth/no-latest-image.ts +36 -0
- package/src/lint/post-synth/no-root-user.ts +36 -0
- package/src/lint/post-synth/post-synth.test.ts +181 -0
- package/src/lint/post-synth/prefer-copy.ts +53 -0
- package/src/lint/post-synth/ssh-port-exposed.ts +68 -0
- package/src/lint/post-synth/unused-volume.ts +49 -0
- package/src/lint/rules/data/deprecated-images.ts +28 -0
- package/src/lint/rules/data/known-base-images.ts +20 -0
- package/src/lint/rules/index.ts +5 -0
- package/src/lint/rules/no-latest-tag.ts +63 -0
- package/src/lint/rules/rules.test.ts +82 -0
- package/src/lsp/completions.test.ts +34 -0
- package/src/lsp/completions.ts +20 -0
- package/src/lsp/hover.test.ts +34 -0
- package/src/lsp/hover.ts +38 -0
- package/src/package-cli.ts +42 -0
- package/src/plugin.test.ts +117 -0
- package/src/plugin.ts +250 -0
- package/src/serializer.test.ts +294 -0
- package/src/serializer.ts +322 -0
- package/src/skills/chant-docker-patterns.md +153 -0
- package/src/skills/chant-docker.md +129 -0
- package/src/spec/fetch-compose.ts +35 -0
- package/src/spec/fetch-engine.ts +25 -0
- package/src/spec/parse-compose.ts +110 -0
- package/src/spec/parse-engine.ts +47 -0
- package/src/validate-cli.ts +19 -0
- package/src/validate.test.ts +16 -0
- package/src/validate.ts +44 -0
- package/src/variables.test.ts +32 -0
- package/src/variables.ts +47 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DKRD003: SSH Port Exposed
|
|
3
|
+
*
|
|
4
|
+
* Detects services exposing port 22 (SSH) on the host.
|
|
5
|
+
* Exposing SSH externally is a security risk.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
9
|
+
import { getPrimaryOutput, extractServices } from "./docker-helpers";
|
|
10
|
+
|
|
11
|
+
function exposesSshPort(port: string): boolean {
|
|
12
|
+
// Formats: "22", "22/tcp", "0.0.0.0:22:22", "127.0.0.1:22:22", "host:container"
|
|
13
|
+
const normalized = port.trim();
|
|
14
|
+
const parts = normalized.split(":");
|
|
15
|
+
|
|
16
|
+
if (parts.length === 1) {
|
|
17
|
+
// Just a port number: "22" or "22/tcp"
|
|
18
|
+
const portNum = parts[0].split("/")[0];
|
|
19
|
+
return portNum === "22";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (parts.length === 2) {
|
|
23
|
+
// "hostPort:containerPort" e.g. "22:22" — no IP binding, accessible on all interfaces
|
|
24
|
+
const hostPort = parts[0].split("/")[0];
|
|
25
|
+
return hostPort === "22";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (parts.length >= 3) {
|
|
29
|
+
// "ip:hostPort:containerPort" e.g. "127.0.0.1:22:22" or "0.0.0.0:22:22"
|
|
30
|
+
const ip = parts[0];
|
|
31
|
+
const hostPort = parts[1].split("/")[0];
|
|
32
|
+
// Only flag if port is 22 AND not bound to loopback
|
|
33
|
+
if (hostPort !== "22") return false;
|
|
34
|
+
return ip !== "127.0.0.1" && ip !== "::1" && ip !== "localhost";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const dkrd003: PostSynthCheck = {
|
|
41
|
+
id: "DKRD003",
|
|
42
|
+
description: "Service exposes SSH port (22) externally — this is a security risk",
|
|
43
|
+
|
|
44
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
45
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
46
|
+
|
|
47
|
+
for (const [_outputName, output] of ctx.outputs) {
|
|
48
|
+
const yaml = getPrimaryOutput(output);
|
|
49
|
+
if (!yaml) continue;
|
|
50
|
+
|
|
51
|
+
const services = extractServices(yaml);
|
|
52
|
+
for (const [name, svc] of services) {
|
|
53
|
+
for (const port of svc.ports ?? []) {
|
|
54
|
+
if (exposesSshPort(port)) {
|
|
55
|
+
diagnostics.push({
|
|
56
|
+
checkId: "DKRD003",
|
|
57
|
+
severity: "error",
|
|
58
|
+
message: `Service "${name}" exposes SSH port 22 externally (port mapping: "${port}"). This is a security risk.`,
|
|
59
|
+
lexicon: "docker",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return diagnostics;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DKRD002: Unused Named Volume
|
|
3
|
+
*
|
|
4
|
+
* Detects top-level named volumes that are not mounted by any service.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { getPrimaryOutput, extractServices, extractNamedVolumes } from "./docker-helpers";
|
|
9
|
+
|
|
10
|
+
export const dkrd002: PostSynthCheck = {
|
|
11
|
+
id: "DKRD002",
|
|
12
|
+
description: "Named volume is declared but not mounted by any service",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
16
|
+
|
|
17
|
+
for (const [_outputName, output] of ctx.outputs) {
|
|
18
|
+
const yaml = getPrimaryOutput(output);
|
|
19
|
+
if (!yaml) continue;
|
|
20
|
+
|
|
21
|
+
const namedVolumes = extractNamedVolumes(yaml);
|
|
22
|
+
if (namedVolumes.size === 0) continue;
|
|
23
|
+
|
|
24
|
+
const services = extractServices(yaml);
|
|
25
|
+
const mountedVolumes = new Set<string>();
|
|
26
|
+
|
|
27
|
+
for (const svc of services.values()) {
|
|
28
|
+
for (const vol of svc.volumes ?? []) {
|
|
29
|
+
// Volume mount format: "volname:/container/path" or just "volname"
|
|
30
|
+
const volumeName = vol.split(":")[0].trim();
|
|
31
|
+
mountedVolumes.add(volumeName);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const volName of namedVolumes) {
|
|
36
|
+
if (!mountedVolumes.has(volName)) {
|
|
37
|
+
diagnostics.push({
|
|
38
|
+
checkId: "DKRD002",
|
|
39
|
+
severity: "warning",
|
|
40
|
+
message: `Named volume "${volName}" is declared but not mounted by any service.`,
|
|
41
|
+
lexicon: "docker",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return diagnostics;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# chant-docker-patterns
|
|
2
|
+
|
|
3
|
+
Common Docker Compose and Dockerfile patterns using `@intentius/chant-lexicon-docker`.
|
|
4
|
+
|
|
5
|
+
## Database service with volume
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { Service, Volume } from "@intentius/chant-lexicon-docker";
|
|
9
|
+
|
|
10
|
+
export const postgres = new Service({
|
|
11
|
+
image: "postgres:16-alpine",
|
|
12
|
+
environment: {
|
|
13
|
+
POSTGRES_DB: "myapp",
|
|
14
|
+
POSTGRES_USER: "myapp",
|
|
15
|
+
POSTGRES_PASSWORD: env("POSTGRES_PASSWORD", { required: true }),
|
|
16
|
+
},
|
|
17
|
+
volumes: ["pgdata:/var/lib/postgresql/data"],
|
|
18
|
+
restart: "unless-stopped",
|
|
19
|
+
healthcheck: {
|
|
20
|
+
test: ["CMD-SHELL", "pg_isready -U myapp"],
|
|
21
|
+
interval: "10s",
|
|
22
|
+
timeout: "5s",
|
|
23
|
+
retries: 5,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const pgdata = new Volume({});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Redis cache
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
export const redis = new Service({
|
|
34
|
+
image: "redis:7-alpine",
|
|
35
|
+
volumes: ["redisdata:/data"],
|
|
36
|
+
command: "redis-server --appendonly yes",
|
|
37
|
+
restart: "unless-stopped",
|
|
38
|
+
});
|
|
39
|
+
export const redisdata = new Volume({});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Reverse proxy (nginx)
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
export const nginx = new Service({
|
|
46
|
+
image: "nginx:1.25-alpine",
|
|
47
|
+
ports: ["80:80", "443:443"],
|
|
48
|
+
volumes: ["./nginx.conf:/etc/nginx/nginx.conf:ro", "./certs:/etc/ssl/certs:ro"],
|
|
49
|
+
depends_on: ["api"],
|
|
50
|
+
restart: "unless-stopped",
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Multi-stage Node.js Dockerfile
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
export const nodeApp = new Dockerfile({
|
|
58
|
+
stages: [
|
|
59
|
+
{
|
|
60
|
+
from: "node:20-alpine",
|
|
61
|
+
as: "deps",
|
|
62
|
+
workdir: "/app",
|
|
63
|
+
copy: ["package*.json ./"],
|
|
64
|
+
run: ["npm ci --only=production"],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
from: "node:20-alpine",
|
|
68
|
+
as: "build",
|
|
69
|
+
workdir: "/app",
|
|
70
|
+
copy: ["--from=deps /app/node_modules ./node_modules", ". ."],
|
|
71
|
+
run: ["npm run build"],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
from: "node:20-alpine",
|
|
75
|
+
workdir: "/app",
|
|
76
|
+
copy: [
|
|
77
|
+
"--from=deps /app/node_modules ./node_modules",
|
|
78
|
+
"--from=build /app/dist ./dist",
|
|
79
|
+
],
|
|
80
|
+
user: "node",
|
|
81
|
+
expose: ["3000"],
|
|
82
|
+
cmd: `["node", "dist/index.js"]`,
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Python service with virtualenv
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
export const pythonApp = new Dockerfile({
|
|
92
|
+
from: "python:3.12-slim",
|
|
93
|
+
workdir: "/app",
|
|
94
|
+
env: ["PYTHONUNBUFFERED=1", "PYTHONDONTWRITEBYTECODE=1"],
|
|
95
|
+
copy: ["requirements.txt ."],
|
|
96
|
+
run: [
|
|
97
|
+
"pip install --no-cache-dir --no-deps -r requirements.txt",
|
|
98
|
+
],
|
|
99
|
+
copy: [". ."],
|
|
100
|
+
user: "1000:1000",
|
|
101
|
+
cmd: `["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]`,
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Network isolation
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { Service, Network } from "@intentius/chant-lexicon-docker";
|
|
109
|
+
|
|
110
|
+
export const internal = new Network({ driver: "bridge", internal: true });
|
|
111
|
+
export const external = new Network({ driver: "bridge" });
|
|
112
|
+
|
|
113
|
+
export const api = new Service({
|
|
114
|
+
image: "myapi:1.0",
|
|
115
|
+
networks: ["external", "internal"],
|
|
116
|
+
});
|
|
117
|
+
export const db = new Service({
|
|
118
|
+
image: "postgres:16-alpine",
|
|
119
|
+
networks: ["internal"], // only reachable via internal network
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Secrets and configs
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { Service, DockerSecret, DockerConfig } from "@intentius/chant-lexicon-docker";
|
|
127
|
+
|
|
128
|
+
export const dbPassword = new DockerSecret({ file: "./secrets/db-password.txt" });
|
|
129
|
+
export const appConfig = new DockerConfig({ file: "./config/app.yaml" });
|
|
130
|
+
|
|
131
|
+
export const app = new Service({
|
|
132
|
+
image: "myapp:1.0",
|
|
133
|
+
secrets: ["dbPassword"],
|
|
134
|
+
configs: ["appConfig"],
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Environment interpolation patterns
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { env } from "@intentius/chant-lexicon-docker";
|
|
142
|
+
|
|
143
|
+
// Dev vs prod toggle:
|
|
144
|
+
export const api = new Service({
|
|
145
|
+
image: env("API_IMAGE", { default: "myapp:latest" }),
|
|
146
|
+
environment: {
|
|
147
|
+
LOG_LEVEL: env("LOG_LEVEL", { default: "info" }),
|
|
148
|
+
WORKERS: env("WORKERS", { default: "4" }),
|
|
149
|
+
SECRET_KEY: env("SECRET_KEY", { required: true }),
|
|
150
|
+
SENTRY_DSN: env("SENTRY_DSN", { default: "" }),
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
```
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# chant-docker
|
|
2
|
+
|
|
3
|
+
You are helping a developer define Docker Compose services and Dockerfiles using the `@intentius/chant-lexicon-docker` library.
|
|
4
|
+
|
|
5
|
+
## Core types
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { Service, Volume, Network, Dockerfile, env, defaultLabels } from "@intentius/chant-lexicon-docker";
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Service — docker-compose.yml services:
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
export const api = new Service({
|
|
15
|
+
image: "myapp:1.0",
|
|
16
|
+
ports: ["8080:8080"],
|
|
17
|
+
environment: {
|
|
18
|
+
NODE_ENV: "production",
|
|
19
|
+
DB_URL: env("DATABASE_URL", { required: true }),
|
|
20
|
+
},
|
|
21
|
+
depends_on: ["db"],
|
|
22
|
+
restart: "unless-stopped",
|
|
23
|
+
healthcheck: {
|
|
24
|
+
test: ["CMD", "curl", "-f", "http://localhost:8080/health"],
|
|
25
|
+
interval: "30s",
|
|
26
|
+
timeout: "10s",
|
|
27
|
+
retries: 3,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Volume — top-level named volume:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
export const pgdata = new Volume({});
|
|
36
|
+
// With driver options:
|
|
37
|
+
export const nfsdata = new Volume({ driver: "local", driver_opts: { type: "nfs", o: "addr=..." } });
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Dockerfile — generates Dockerfile.{name}:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// Single-stage:
|
|
44
|
+
export const builder = new Dockerfile({
|
|
45
|
+
from: "node:20-alpine",
|
|
46
|
+
workdir: "/app",
|
|
47
|
+
copy: ["package*.json ./"],
|
|
48
|
+
run: ["npm ci --only=production"],
|
|
49
|
+
user: "node",
|
|
50
|
+
cmd: `["node", "dist/index.js"]`,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Multi-stage build:
|
|
54
|
+
export const app = new Dockerfile({
|
|
55
|
+
stages: [
|
|
56
|
+
{
|
|
57
|
+
from: "node:20-alpine",
|
|
58
|
+
as: "build",
|
|
59
|
+
workdir: "/app",
|
|
60
|
+
copy: ["package*.json ./"],
|
|
61
|
+
run: ["npm ci", "npm run build"],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
from: "node:20-alpine",
|
|
65
|
+
workdir: "/app",
|
|
66
|
+
copy: ["--from=build /app/dist ./dist", "--from=build /app/node_modules ./node_modules"],
|
|
67
|
+
user: "node",
|
|
68
|
+
cmd: `["node", "dist/index.js"]`,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Variable interpolation with env():
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { env } from "@intentius/chant-lexicon-docker";
|
|
78
|
+
|
|
79
|
+
// ${VAR} — required, no default:
|
|
80
|
+
env("DATABASE_URL")
|
|
81
|
+
|
|
82
|
+
// ${VAR:-default} — use default if unset:
|
|
83
|
+
env("LOG_LEVEL", { default: "info" })
|
|
84
|
+
|
|
85
|
+
// ${VAR:?message} — fail with error if unset:
|
|
86
|
+
env("API_SECRET", { required: true })
|
|
87
|
+
env("API_SECRET", { errorMessage: "API_SECRET must be set in .env" })
|
|
88
|
+
|
|
89
|
+
// ${VAR:+value} — substitute value if VAR is set:
|
|
90
|
+
env("DEBUG", { ifSet: "verbose" })
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Default labels (injected into all services):
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
export const labels = defaultLabels({
|
|
97
|
+
"com.example.team": "platform",
|
|
98
|
+
"com.example.managed-by": "chant",
|
|
99
|
+
"com.example.version": "1.0.0",
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Service → Dockerfile cross-reference:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// The serializer emits "Dockerfile.builder" as the filename.
|
|
107
|
+
// Reference it in the service build config:
|
|
108
|
+
export const api = new Service({
|
|
109
|
+
build: {
|
|
110
|
+
context: ".",
|
|
111
|
+
dockerfile: "Dockerfile.builder", // matches the Dockerfile entity name
|
|
112
|
+
},
|
|
113
|
+
ports: ["8080:8080"],
|
|
114
|
+
});
|
|
115
|
+
export const builder = new Dockerfile({ from: "node:20-alpine", ... });
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Output structure
|
|
119
|
+
|
|
120
|
+
- All Compose entities → `docker-compose.yml`
|
|
121
|
+
- Each Dockerfile entity → `Dockerfile.{name}`
|
|
122
|
+
|
|
123
|
+
## Key rules
|
|
124
|
+
|
|
125
|
+
- `DKRS001`: Never use `:latest` image tags in source — use explicit versions
|
|
126
|
+
- `DKRD001`: Post-synth: no `:latest` images in generated YAML
|
|
127
|
+
- `DKRD002`: Post-synth: no unused named volumes
|
|
128
|
+
- `DKRD003`: Post-synth: SSH port 22 not exposed externally
|
|
129
|
+
- `DKRD010–012`: Dockerfile best practices (apt recommends, COPY vs ADD, USER)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Code generated by chant generate. DO NOT EDIT.
|
|
2
|
+
import type { Declarable } from "@intentius/chant/declarable";
|
|
3
|
+
|
|
4
|
+
export interface ServiceProps {
|
|
5
|
+
image?: string;
|
|
6
|
+
build?: object;
|
|
7
|
+
command?: string | string[];
|
|
8
|
+
entrypoint?: string | string[];
|
|
9
|
+
environment?: Record<string, string>;
|
|
10
|
+
ports?: string[];
|
|
11
|
+
volumes?: string[];
|
|
12
|
+
networks?: string[];
|
|
13
|
+
depends_on?: string[];
|
|
14
|
+
restart?: string;
|
|
15
|
+
labels?: Record<string, string>;
|
|
16
|
+
healthcheck?: object;
|
|
17
|
+
deploy?: object;
|
|
18
|
+
secrets?: string[];
|
|
19
|
+
configs?: string[];
|
|
20
|
+
}
|
|
21
|
+
export declare const Service: new (props?: ServiceProps) => Declarable;
|
|
22
|
+
|
|
23
|
+
export interface VolumeProps {
|
|
24
|
+
driver?: string;
|
|
25
|
+
driver_opts?: Record<string, string>;
|
|
26
|
+
external?: boolean;
|
|
27
|
+
labels?: Record<string, string>;
|
|
28
|
+
name?: string;
|
|
29
|
+
}
|
|
30
|
+
export declare const Volume: new (props?: VolumeProps) => Declarable;
|
|
31
|
+
|
|
32
|
+
export interface NetworkProps {
|
|
33
|
+
driver?: string;
|
|
34
|
+
driver_opts?: Record<string, string>;
|
|
35
|
+
external?: boolean;
|
|
36
|
+
labels?: Record<string, string>;
|
|
37
|
+
internal?: boolean;
|
|
38
|
+
ipam?: object;
|
|
39
|
+
}
|
|
40
|
+
export declare const Network: new (props?: NetworkProps) => Declarable;
|
|
41
|
+
|
|
42
|
+
export interface DockerConfigProps {
|
|
43
|
+
file?: string;
|
|
44
|
+
external?: boolean;
|
|
45
|
+
labels?: Record<string, string>;
|
|
46
|
+
name?: string;
|
|
47
|
+
}
|
|
48
|
+
export declare const DockerConfig: new (props?: DockerConfigProps) => Declarable;
|
|
49
|
+
|
|
50
|
+
export interface DockerSecretProps {
|
|
51
|
+
file?: string;
|
|
52
|
+
external?: boolean;
|
|
53
|
+
labels?: Record<string, string>;
|
|
54
|
+
name?: string;
|
|
55
|
+
}
|
|
56
|
+
export declare const DockerSecret: new (props?: DockerSecretProps) => Declarable;
|
|
57
|
+
|
|
58
|
+
export interface DockerfileStage {
|
|
59
|
+
from: string;
|
|
60
|
+
as?: string;
|
|
61
|
+
arg?: string[];
|
|
62
|
+
env?: string[];
|
|
63
|
+
run?: string[];
|
|
64
|
+
copy?: string[];
|
|
65
|
+
add?: string[];
|
|
66
|
+
workdir?: string;
|
|
67
|
+
user?: string;
|
|
68
|
+
expose?: string[];
|
|
69
|
+
volume?: string[];
|
|
70
|
+
label?: string[];
|
|
71
|
+
entrypoint?: string;
|
|
72
|
+
cmd?: string;
|
|
73
|
+
healthcheck?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface DockerfileProps {
|
|
76
|
+
name?: string;
|
|
77
|
+
from?: string;
|
|
78
|
+
stages?: DockerfileStage[];
|
|
79
|
+
arg?: string[];
|
|
80
|
+
env?: string[];
|
|
81
|
+
run?: string[];
|
|
82
|
+
copy?: string[];
|
|
83
|
+
add?: string[];
|
|
84
|
+
workdir?: string;
|
|
85
|
+
user?: string;
|
|
86
|
+
expose?: string[];
|
|
87
|
+
volume?: string[];
|
|
88
|
+
label?: string[];
|
|
89
|
+
entrypoint?: string;
|
|
90
|
+
cmd?: string;
|
|
91
|
+
healthcheck?: string;
|
|
92
|
+
}
|
|
93
|
+
export declare const Dockerfile: new (props?: DockerfileProps) => Declarable;
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@intentius/chant-lexicon-docker",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Docker lexicon for chant — declarative IaC in TypeScript",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"homepage": "https://intentius.io/chant",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/intentius/chant.git",
|
|
10
|
+
"directory": "lexicons/docker"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/intentius/chant/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"infrastructure-as-code",
|
|
17
|
+
"iac",
|
|
18
|
+
"typescript",
|
|
19
|
+
"docker",
|
|
20
|
+
"docker-compose",
|
|
21
|
+
"dockerfile",
|
|
22
|
+
"chant"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"files": [
|
|
26
|
+
"src/",
|
|
27
|
+
"dist/"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"exports": {
|
|
33
|
+
".": "./src/index.ts",
|
|
34
|
+
"./*": "./src/*.ts",
|
|
35
|
+
"./manifest": "./dist/manifest.json",
|
|
36
|
+
"./meta": "./dist/meta.json",
|
|
37
|
+
"./types": "./dist/types/index.d.ts"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"generate": "bun run src/codegen/generate-cli.ts",
|
|
41
|
+
"bundle": "bun run src/package-cli.ts",
|
|
42
|
+
"validate": "bun run src/validate-cli.ts",
|
|
43
|
+
"docs": "bun run src/codegen/docs-cli.ts",
|
|
44
|
+
"prepack": "bun run generate && bun run bundle && bun run validate"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@intentius/chant": "0.1.0",
|
|
48
|
+
"typescript": "^5.9.3"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@intentius/chant": "^0.1.0"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker lexicon docs generation.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export async function generateDocs(opts?: { verbose?: boolean }): Promise<void> {
|
|
6
|
+
if (opts?.verbose) {
|
|
7
|
+
console.error("Generating Docker lexicon docs...");
|
|
8
|
+
}
|
|
9
|
+
// Docs are currently hand-authored in docs/src/content/docs/
|
|
10
|
+
// Future: auto-generate entity reference pages from lexicon-docker.json
|
|
11
|
+
console.error("Docker docs: see docs/src/content/docs/overview.mdx");
|
|
12
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* CLI entry point for Docker lexicon generation.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { dirname } from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import { generate, writeGeneratedFiles } from "./generate";
|
|
9
|
+
|
|
10
|
+
const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
11
|
+
|
|
12
|
+
async function main() {
|
|
13
|
+
const verbose = process.argv.includes("--verbose") || process.argv.includes("-v");
|
|
14
|
+
const force = process.argv.includes("--force") || process.argv.includes("-f");
|
|
15
|
+
|
|
16
|
+
console.error("Generating Docker lexicon...");
|
|
17
|
+
|
|
18
|
+
const result = await generate({ verbose, force });
|
|
19
|
+
writeGeneratedFiles(result, pkgDir);
|
|
20
|
+
|
|
21
|
+
console.error(
|
|
22
|
+
`Generated ${result.resources} entities, ${result.properties} property types, ${result.enums} enums`,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (result.warnings.length > 0) {
|
|
26
|
+
console.error(`${result.warnings.length} warnings:`);
|
|
27
|
+
for (const w of result.warnings) {
|
|
28
|
+
console.error(` ${w.file}: ${w.error}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
main().catch((err) => {
|
|
34
|
+
console.error("Generation failed:", err);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate Compose entity types from the Compose Spec JSON Schema.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { fetchComposeSpec } from "../spec/fetch-compose";
|
|
6
|
+
import { parseComposeSpec, type ComposeParseResult } from "../spec/parse-compose";
|
|
7
|
+
|
|
8
|
+
export interface ComposeGenerateResult {
|
|
9
|
+
results: ComposeParseResult[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function generateComposePipeline(opts: { force?: boolean; verbose?: boolean } = {}): Promise<ComposeGenerateResult> {
|
|
13
|
+
if (opts.verbose) console.error("Fetching Compose Spec...");
|
|
14
|
+
const data = await fetchComposeSpec(opts.force);
|
|
15
|
+
|
|
16
|
+
if (opts.verbose) console.error("Parsing Compose Spec...");
|
|
17
|
+
const results = parseComposeSpec(data);
|
|
18
|
+
|
|
19
|
+
if (opts.verbose) console.error(`Parsed ${results.length} Compose entity types`);
|
|
20
|
+
return { results };
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate Dockerfile entity types from the Docker Engine API OpenAPI spec.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { fetchEngineApi } from "../spec/fetch-engine";
|
|
6
|
+
import { parseEngineApi, type DockerfileParseResult } from "../spec/parse-engine";
|
|
7
|
+
|
|
8
|
+
export interface DockerfileGenerateResult {
|
|
9
|
+
result: DockerfileParseResult;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function generateDockerfilePipeline(opts: { force?: boolean; verbose?: boolean } = {}): Promise<DockerfileGenerateResult> {
|
|
13
|
+
if (opts.verbose) console.error("Fetching Docker Engine API...");
|
|
14
|
+
const data = await fetchEngineApi(opts.force);
|
|
15
|
+
|
|
16
|
+
if (opts.verbose) console.error("Parsing Engine API for Dockerfile types...");
|
|
17
|
+
const result = parseEngineApi(data);
|
|
18
|
+
|
|
19
|
+
if (opts.verbose) console.error(`Parsed ${result.instructions.length} Dockerfile instructions`);
|
|
20
|
+
return { result };
|
|
21
|
+
}
|