@intentius/chant-lexicon-helm 0.0.16
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 +22 -0
- package/dist/integrity.json +36 -0
- package/dist/manifest.json +37 -0
- package/dist/meta.json +208 -0
- package/dist/rules/chart-metadata.ts +64 -0
- package/dist/rules/helm-helpers.ts +64 -0
- package/dist/rules/no-hardcoded-image.ts +62 -0
- package/dist/rules/values-no-secrets.ts +82 -0
- package/dist/rules/whm101.ts +46 -0
- package/dist/rules/whm102.ts +33 -0
- package/dist/rules/whm103.ts +59 -0
- package/dist/rules/whm104.ts +35 -0
- package/dist/rules/whm105.ts +30 -0
- package/dist/rules/whm201.ts +36 -0
- package/dist/rules/whm202.ts +50 -0
- package/dist/rules/whm203.ts +39 -0
- package/dist/rules/whm204.ts +60 -0
- package/dist/rules/whm301.ts +41 -0
- package/dist/rules/whm302.ts +40 -0
- package/dist/rules/whm401.ts +57 -0
- package/dist/rules/whm402.ts +45 -0
- package/dist/rules/whm403.ts +45 -0
- package/dist/rules/whm404.ts +36 -0
- package/dist/rules/whm405.ts +53 -0
- package/dist/rules/whm406.ts +34 -0
- package/dist/rules/whm407.ts +83 -0
- package/dist/rules/whm501.ts +103 -0
- package/dist/rules/whm502.ts +94 -0
- package/dist/skills/chant-helm-chart-patterns.md +229 -0
- package/dist/skills/chant-helm-chart-security-patterns.md +192 -0
- package/dist/skills/chant-helm-create-chart.md +211 -0
- package/dist/types/index.d.ts +132 -0
- package/package.json +34 -0
- package/src/codegen/docs-cli.ts +4 -0
- package/src/codegen/docs.ts +483 -0
- package/src/codegen/generate-cli.ts +28 -0
- package/src/codegen/generate.ts +249 -0
- package/src/codegen/naming.ts +38 -0
- package/src/codegen/package.ts +64 -0
- package/src/composites/composites.test.ts +1050 -0
- package/src/composites/helm-batch-job.ts +209 -0
- package/src/composites/helm-crd-lifecycle.ts +184 -0
- package/src/composites/helm-cron-job.ts +177 -0
- package/src/composites/helm-daemon-set.ts +169 -0
- package/src/composites/helm-external-secret.ts +93 -0
- package/src/composites/helm-library.ts +51 -0
- package/src/composites/helm-microservice.ts +331 -0
- package/src/composites/helm-monitored-service.ts +252 -0
- package/src/composites/helm-namespace-env.ts +154 -0
- package/src/composites/helm-secure-ingress.ts +114 -0
- package/src/composites/helm-stateful-service.ts +213 -0
- package/src/composites/helm-web-app.ts +264 -0
- package/src/composites/helm-worker.ts +207 -0
- package/src/composites/index.ts +38 -0
- package/src/coverage.test.ts +21 -0
- package/src/coverage.ts +50 -0
- package/src/generated/index.d.ts +132 -0
- package/src/generated/index.ts +13 -0
- package/src/generated/lexicon-helm.json +208 -0
- package/src/helpers.test.ts +51 -0
- package/src/helpers.ts +100 -0
- package/src/import/generator.ts +285 -0
- package/src/import/import.test.ts +224 -0
- package/src/import/parser.ts +160 -0
- package/src/import/template-stripper.ts +123 -0
- package/src/index.ts +108 -0
- package/src/intrinsics.test.ts +380 -0
- package/src/intrinsics.ts +484 -0
- package/src/lint/post-synth/helm-helpers.ts +64 -0
- package/src/lint/post-synth/post-synth.test.ts +504 -0
- package/src/lint/post-synth/whm101.ts +46 -0
- package/src/lint/post-synth/whm102.ts +33 -0
- package/src/lint/post-synth/whm103.ts +59 -0
- package/src/lint/post-synth/whm104.ts +35 -0
- package/src/lint/post-synth/whm105.ts +30 -0
- package/src/lint/post-synth/whm201.ts +36 -0
- package/src/lint/post-synth/whm202.ts +50 -0
- package/src/lint/post-synth/whm203.ts +39 -0
- package/src/lint/post-synth/whm204.ts +60 -0
- package/src/lint/post-synth/whm301.ts +41 -0
- package/src/lint/post-synth/whm302.ts +40 -0
- package/src/lint/post-synth/whm401.ts +57 -0
- package/src/lint/post-synth/whm402.ts +45 -0
- package/src/lint/post-synth/whm403.ts +45 -0
- package/src/lint/post-synth/whm404.ts +36 -0
- package/src/lint/post-synth/whm405.ts +53 -0
- package/src/lint/post-synth/whm406.ts +34 -0
- package/src/lint/post-synth/whm407.ts +83 -0
- package/src/lint/post-synth/whm501.ts +103 -0
- package/src/lint/post-synth/whm502.ts +94 -0
- package/src/lint/rules/chart-metadata.ts +64 -0
- package/src/lint/rules/lint-rules.test.ts +97 -0
- package/src/lint/rules/no-hardcoded-image.ts +62 -0
- package/src/lint/rules/values-no-secrets.ts +82 -0
- package/src/lsp/completions.test.ts +72 -0
- package/src/lsp/completions.ts +20 -0
- package/src/lsp/hover.test.ts +46 -0
- package/src/lsp/hover.ts +46 -0
- package/src/package-cli.ts +28 -0
- package/src/plugin.test.ts +71 -0
- package/src/plugin.ts +206 -0
- package/src/resources.ts +77 -0
- package/src/serializer.test.ts +930 -0
- package/src/serializer.ts +835 -0
- package/src/skills/chart-patterns.md +229 -0
- package/src/skills/chart-security-patterns.md +192 -0
- package/src/skills/create-chart.md +211 -0
- package/src/validate-cli.ts +21 -0
- package/src/validate.test.ts +37 -0
- package/src/validate.ts +36 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: chant-helm-patterns
|
|
3
|
+
description: Common Helm chart patterns and best practices using chant
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Common Helm Chart Patterns
|
|
8
|
+
|
|
9
|
+
## Standard Chart Layout
|
|
10
|
+
|
|
11
|
+
A chant Helm project compiles TypeScript into the standard Helm directory structure:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
my-chart/
|
|
15
|
+
Chart.yaml ← chart metadata (name, version, apiVersion: v2)
|
|
16
|
+
values.yaml ← default configuration values
|
|
17
|
+
templates/
|
|
18
|
+
deployment.yaml
|
|
19
|
+
service.yaml
|
|
20
|
+
ingress.yaml
|
|
21
|
+
_helpers.tpl ← named templates (fullname, labels, etc.)
|
|
22
|
+
NOTES.txt ← post-install instructions
|
|
23
|
+
charts/ ← subcharts (dependencies)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The source of truth is `src/`. Never edit generated files in `templates/` by hand.
|
|
27
|
+
|
|
28
|
+
## Parameterization via Values Proxy
|
|
29
|
+
|
|
30
|
+
The `values` proxy turns property access into `{{ .Values.x }}` template directives:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { values } from "@intentius/chant-lexicon-helm";
|
|
34
|
+
|
|
35
|
+
// Simple access
|
|
36
|
+
values.replicaCount // → {{ .Values.replicaCount }}
|
|
37
|
+
|
|
38
|
+
// Nested access
|
|
39
|
+
values.image.repository // → {{ .Values.image.repository }}
|
|
40
|
+
values.image.tag // → {{ .Values.image.tag }}
|
|
41
|
+
|
|
42
|
+
// Use in resource definitions
|
|
43
|
+
export const deployment = new Deployment({
|
|
44
|
+
spec: {
|
|
45
|
+
replicas: values.replicaCount,
|
|
46
|
+
template: {
|
|
47
|
+
spec: {
|
|
48
|
+
containers: [{
|
|
49
|
+
name: "app",
|
|
50
|
+
image: printf("%s:%s", values.image.repository, values.image.tag),
|
|
51
|
+
}],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Default values are set in the `Values` resource, which becomes `values.yaml`.
|
|
59
|
+
|
|
60
|
+
## Conditional Resources with If()
|
|
61
|
+
|
|
62
|
+
Wrap resources in `If()` to gate them on a values flag:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { If, values } from "@intentius/chant-lexicon-helm";
|
|
66
|
+
import { Ingress } from "@intentius/chant-lexicon-k8s";
|
|
67
|
+
|
|
68
|
+
// Only rendered when .Values.ingress.enabled is true
|
|
69
|
+
export const ingress = If(values.ingress.enabled, new Ingress({
|
|
70
|
+
metadata: { name: include("my-app.fullname") },
|
|
71
|
+
spec: {
|
|
72
|
+
rules: [{ host: values.ingress.hostname }],
|
|
73
|
+
},
|
|
74
|
+
}));
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`If()` wraps the entire template output in `{{- if .Values.ingress.enabled }}...{{- end }}`.
|
|
78
|
+
|
|
79
|
+
## Helper Templates with include()
|
|
80
|
+
|
|
81
|
+
Reference named templates from `_helpers.tpl`:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { include } from "@intentius/chant-lexicon-helm";
|
|
85
|
+
|
|
86
|
+
export const deployment = new Deployment({
|
|
87
|
+
metadata: {
|
|
88
|
+
name: include("my-app.fullname"),
|
|
89
|
+
labels: include("my-app.labels"),
|
|
90
|
+
},
|
|
91
|
+
spec: {
|
|
92
|
+
selector: {
|
|
93
|
+
matchLabels: include("my-app.selectorLabels"),
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The composites (`HelmWebApp`, `HelmMicroservice`, etc.) generate `_helpers.tpl` automatically with standard named templates for fullname, labels, and selector labels.
|
|
100
|
+
|
|
101
|
+
## Dependency Management with HelmDependency
|
|
102
|
+
|
|
103
|
+
Declare subchart dependencies that go into `Chart.yaml`:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { HelmDependency } from "@intentius/chant-lexicon-helm";
|
|
107
|
+
|
|
108
|
+
export const redisDep = HelmDependency({
|
|
109
|
+
name: "redis",
|
|
110
|
+
version: "17.x",
|
|
111
|
+
repository: "https://charts.bitnami.com/bitnami",
|
|
112
|
+
condition: "redis.enabled",
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
export const postgresqlDep = HelmDependency({
|
|
116
|
+
name: "postgresql",
|
|
117
|
+
version: "12.x",
|
|
118
|
+
repository: "https://charts.bitnami.com/bitnami",
|
|
119
|
+
condition: "postgresql.enabled",
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
After building, run `helm dependency update` to fetch the subcharts.
|
|
124
|
+
|
|
125
|
+
## Multi-Environment Configuration
|
|
126
|
+
|
|
127
|
+
Use separate values files per environment and compose them at deploy time:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// src/infra.ts — base chart with parameterized defaults
|
|
131
|
+
export const app = HelmWebApp({
|
|
132
|
+
name: "my-app",
|
|
133
|
+
port: 3000,
|
|
134
|
+
replicas: 1, // default for dev
|
|
135
|
+
imageTag: "latest", // overridden per env
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# Deploy with environment-specific overrides
|
|
141
|
+
helm install my-app . -f values.yaml -f values-staging.yaml
|
|
142
|
+
helm install my-app . -f values.yaml -f values-production.yaml
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Structure values files as overlays: `values.yaml` (defaults) -> `values-staging.yaml` (overrides) -> `values-production.yaml` (overrides). Later files win.
|
|
146
|
+
|
|
147
|
+
## Library Charts with HelmLibrary
|
|
148
|
+
|
|
149
|
+
Create reusable chart libraries that other charts can import:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
import { HelmLibrary } from "@intentius/chant-lexicon-helm";
|
|
153
|
+
|
|
154
|
+
export const library = HelmLibrary({
|
|
155
|
+
name: "common-templates",
|
|
156
|
+
version: "1.0.0",
|
|
157
|
+
templates: {
|
|
158
|
+
"common.labels": labelsTemplate,
|
|
159
|
+
"common.annotations": annotationsTemplate,
|
|
160
|
+
"common.fullname": fullnameTemplate,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Library charts produce no templates of their own. They are included as dependencies by application charts, which use `include()` to reference the shared named templates.
|
|
166
|
+
|
|
167
|
+
## Testing Patterns with HelmTest
|
|
168
|
+
|
|
169
|
+
Add Helm test pods that run on `helm test`:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { HelmTest } from "@intentius/chant-lexicon-helm";
|
|
173
|
+
|
|
174
|
+
export const connectivityTest = HelmTest({
|
|
175
|
+
name: "test-connection",
|
|
176
|
+
image: "busybox:1.36",
|
|
177
|
+
command: ["wget", "--spider", "http://my-app:3000/healthz"],
|
|
178
|
+
});
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
This generates a pod in `templates/tests/` with the `helm.sh/hook: test` annotation. Run tests after install:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
helm install my-app .
|
|
185
|
+
helm test my-app
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Lint rule WHM301** fires when an application chart has no test resources.
|
|
189
|
+
|
|
190
|
+
## Composites Quick Reference
|
|
191
|
+
|
|
192
|
+
| Composite | Resources created |
|
|
193
|
+
|---------------------|-------------------------------------------------------------|
|
|
194
|
+
| `HelmWebApp` | Chart, Values, Deployment, Service, Ingress?, HPA?, SA? |
|
|
195
|
+
| `HelmMicroservice` | Chart, Values, Deployment, Service, SA, ConfigMap?, PDB?, HPA?, Ingress? |
|
|
196
|
+
| `HelmWorker` | Chart, Values, Deployment, SA, HPA?, PDB? |
|
|
197
|
+
| `HelmCronJob` | Chart, Values, CronJob |
|
|
198
|
+
| `HelmStatefulSet` | Chart, Values, StatefulSet, Service |
|
|
199
|
+
| `HelmDaemonSet` | Chart, Values, DaemonSet, SA? |
|
|
200
|
+
| `HelmLibrary` | Chart (type: library), named templates |
|
|
201
|
+
|
|
202
|
+
All composites accept optional `podSecurityContext`, `securityContext`, `nodeSelector`, `tolerations`, `affinity`, and `strategy` fields.
|
|
203
|
+
|
|
204
|
+
## Built-in Objects
|
|
205
|
+
|
|
206
|
+
Access Helm's built-in objects in templates:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { Release, ChartRef, Capabilities } from "@intentius/chant-lexicon-helm";
|
|
210
|
+
|
|
211
|
+
Release.Name // {{ .Release.Name }}
|
|
212
|
+
Release.Namespace // {{ .Release.Namespace }}
|
|
213
|
+
Release.IsUpgrade // {{ .Release.IsUpgrade }}
|
|
214
|
+
ChartRef.Name // {{ .Chart.Name }}
|
|
215
|
+
ChartRef.Version // {{ .Chart.Version }}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Template Functions
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import { include, printf, toYaml, quote, required, helmDefault } from "@intentius/chant-lexicon-helm";
|
|
222
|
+
|
|
223
|
+
include("my-app.fullname") // {{ include "my-app.fullname" . }}
|
|
224
|
+
printf("%s-%s", Release.Name, "worker") // {{ printf "%s-%s" .Release.Name "worker" }}
|
|
225
|
+
toYaml(values.resources, 12) // {{ toYaml .Values.resources | nindent 12 }}
|
|
226
|
+
quote(values.annotations) // {{ quote .Values.annotations }}
|
|
227
|
+
required("image.tag is required", values.image.tag) // {{ required "..." .Values.image.tag }}
|
|
228
|
+
helmDefault(values.replicas, 1) // {{ default 1 .Values.replicas }}
|
|
229
|
+
```
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: chant-helm-security
|
|
3
|
+
description: Security best practices for Helm charts built with chant
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Helm Chart Security Patterns
|
|
8
|
+
|
|
9
|
+
## Pod Security Context
|
|
10
|
+
|
|
11
|
+
Always set pod-level security constraints. The `podSecurityContext` field applies to all containers in the pod.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { HelmWebApp } from "@intentius/chant-lexicon-helm";
|
|
15
|
+
|
|
16
|
+
const app = HelmWebApp({
|
|
17
|
+
name: "secure-app",
|
|
18
|
+
port: 3000,
|
|
19
|
+
podSecurityContext: {
|
|
20
|
+
runAsNonRoot: true,
|
|
21
|
+
runAsUser: 1000,
|
|
22
|
+
runAsGroup: 1000,
|
|
23
|
+
fsGroup: 1000,
|
|
24
|
+
seccompProfile: { type: "RuntimeDefault" },
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Lint rule WHM402** fires when `runAsNonRoot` is not set on any pod spec.
|
|
30
|
+
|
|
31
|
+
## Container Security Context
|
|
32
|
+
|
|
33
|
+
Set per-container restrictions to minimize attack surface:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
const app = HelmWebApp({
|
|
37
|
+
name: "hardened-app",
|
|
38
|
+
port: 8080,
|
|
39
|
+
securityContext: {
|
|
40
|
+
readOnlyRootFilesystem: true,
|
|
41
|
+
allowPrivilegeEscalation: false,
|
|
42
|
+
capabilities: { drop: ["ALL"] },
|
|
43
|
+
runAsUser: 1000,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
- **WHM403**: `readOnlyRootFilesystem` not set
|
|
49
|
+
- **WHM404**: `privileged: true` detected
|
|
50
|
+
|
|
51
|
+
If the application needs to write temporary files, mount a writable `emptyDir` volume at the specific path rather than disabling `readOnlyRootFilesystem`.
|
|
52
|
+
|
|
53
|
+
## Image Tagging
|
|
54
|
+
|
|
55
|
+
Never use `:latest` or omit the tag entirely. Use semver tags or digests.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// Bad — WHM401 fires
|
|
59
|
+
HelmWebApp({ name: "app", imageTag: "latest", port: 3000 });
|
|
60
|
+
|
|
61
|
+
// Good — pinned semver
|
|
62
|
+
HelmWebApp({ name: "app", imageTag: "v2.4.1", port: 3000 });
|
|
63
|
+
|
|
64
|
+
// Best — pinned digest
|
|
65
|
+
HelmWebApp({ name: "app", imageTag: "sha256:abc123...", port: 3000 });
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Lint rule WHM401** fires when the image uses `:latest` or has no tag at all.
|
|
69
|
+
|
|
70
|
+
## Secret Management
|
|
71
|
+
|
|
72
|
+
Never inline secrets in `values.yaml`. Use External Secrets Operator or Sealed Secrets.
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { HelmExternalSecret } from "@intentius/chant-lexicon-helm";
|
|
76
|
+
|
|
77
|
+
// External Secrets Operator — fetches secrets from a remote store at runtime
|
|
78
|
+
const secrets = HelmExternalSecret({
|
|
79
|
+
name: "app-secrets",
|
|
80
|
+
secretStoreName: "aws-secretsmanager",
|
|
81
|
+
data: {
|
|
82
|
+
DB_PASSWORD: "prod/db-password",
|
|
83
|
+
API_KEY: "prod/api-key",
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Lint rule WHM407** fires when a Secret resource contains inline `data` or `stringData` values. The fix is always to use an external secret provider.
|
|
89
|
+
|
|
90
|
+
### What NOT to do
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// Bad — WHM407 fires, secret value is in source control
|
|
94
|
+
new Secret({
|
|
95
|
+
metadata: { name: "db-creds" },
|
|
96
|
+
stringData: { password: "hunter2" },
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## RBAC and ServiceAccount
|
|
101
|
+
|
|
102
|
+
Every workload should run under a dedicated ServiceAccount with minimal RBAC:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
const app = HelmMicroservice({
|
|
106
|
+
name: "payment-api",
|
|
107
|
+
port: 8080,
|
|
108
|
+
serviceAccount: true, // creates a dedicated SA
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
When the workload needs API access, bind only the required verbs and resources:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { Role, RoleBinding } from "@intentius/chant-lexicon-k8s";
|
|
116
|
+
|
|
117
|
+
export const role = new Role({
|
|
118
|
+
metadata: { name: include("payment-api.fullname") },
|
|
119
|
+
rules: [{
|
|
120
|
+
apiGroups: [""],
|
|
121
|
+
resources: ["configmaps"],
|
|
122
|
+
verbs: ["get", "watch"],
|
|
123
|
+
}],
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Avoid `ClusterRole` with wildcard resources or verbs. Prefer namespace-scoped `Role` bindings.
|
|
128
|
+
|
|
129
|
+
## Network Policies
|
|
130
|
+
|
|
131
|
+
Restrict pod-to-pod traffic to only what is required:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { NetworkPolicy } from "@intentius/chant-lexicon-k8s";
|
|
135
|
+
|
|
136
|
+
export const netpol = new NetworkPolicy({
|
|
137
|
+
metadata: { name: "allow-frontend-to-api" },
|
|
138
|
+
spec: {
|
|
139
|
+
podSelector: { matchLabels: { app: "payment-api" } },
|
|
140
|
+
policyTypes: ["Ingress"],
|
|
141
|
+
ingress: [{
|
|
142
|
+
from: [{ podSelector: { matchLabels: { app: "frontend" } } }],
|
|
143
|
+
ports: [{ protocol: "TCP", port: 8080 }],
|
|
144
|
+
}],
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Start with a default-deny policy per namespace, then add allow rules for known traffic patterns.
|
|
150
|
+
|
|
151
|
+
## Resource Limits and Requests
|
|
152
|
+
|
|
153
|
+
Always set both requests and limits. Without them, a single pod can starve others.
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const app = HelmMicroservice({
|
|
157
|
+
name: "api",
|
|
158
|
+
port: 8080,
|
|
159
|
+
// HelmMicroservice sets sensible defaults:
|
|
160
|
+
// requests: { cpu: "250m", memory: "128Mi" }
|
|
161
|
+
// limits: { cpu: "500m", memory: "256Mi" }
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Lint rule WHM405** fires when a container spec is missing `cpu` or `memory` in resources. **WHM302** fires when resource limits are not set at all.
|
|
166
|
+
|
|
167
|
+
## Security Lint Rules Reference
|
|
168
|
+
|
|
169
|
+
| Rule | What it checks | Severity |
|
|
170
|
+
|--------|---------------------------------------------|----------|
|
|
171
|
+
| WHM401 | Image uses `:latest` tag or no tag | Warning |
|
|
172
|
+
| WHM402 | `runAsNonRoot` not set on pod | Warning |
|
|
173
|
+
| WHM403 | `readOnlyRootFilesystem` not set | Warning |
|
|
174
|
+
| WHM404 | `privileged: true` on a container | Error |
|
|
175
|
+
| WHM405 | Missing cpu/memory in resource spec | Warning |
|
|
176
|
+
| WHM406 | CRD lifecycle limitation (no auto-upgrade) | Info |
|
|
177
|
+
| WHM407 | Secret with inline data in source | Error |
|
|
178
|
+
|
|
179
|
+
## Checklist
|
|
180
|
+
|
|
181
|
+
When reviewing or building a Helm chart, verify:
|
|
182
|
+
|
|
183
|
+
1. Pod runs as non-root with a numeric UID (`runAsNonRoot: true`, `runAsUser: 1000`)
|
|
184
|
+
2. Containers drop all capabilities (`capabilities: { drop: ["ALL"] }`)
|
|
185
|
+
3. Root filesystem is read-only (`readOnlyRootFilesystem: true`)
|
|
186
|
+
4. Privilege escalation is blocked (`allowPrivilegeEscalation: false`)
|
|
187
|
+
5. Images use pinned semver or digest tags, never `:latest`
|
|
188
|
+
6. Secrets come from an external provider, never inline
|
|
189
|
+
7. Each workload has a dedicated ServiceAccount
|
|
190
|
+
8. RBAC is namespace-scoped with minimal verbs
|
|
191
|
+
9. Network policies restrict ingress/egress to known peers
|
|
192
|
+
10. CPU and memory requests and limits are set on every container
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: chant-helm
|
|
3
|
+
description: Build, validate, and deploy Helm charts from a chant project
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Helm Chart Operational Playbook
|
|
8
|
+
|
|
9
|
+
## How chant and Helm relate
|
|
10
|
+
|
|
11
|
+
chant is a **synthesis-only** tool — it compiles TypeScript source files into a complete Helm chart directory (Chart.yaml, values.yaml, templates/, etc.). chant does NOT call Helm CLI. Your job as an agent is to bridge the two:
|
|
12
|
+
|
|
13
|
+
- Use **chant** for: build, lint, diff (local chart comparison)
|
|
14
|
+
- Use **helm** for: install, upgrade, rollback, test, dependency update
|
|
15
|
+
|
|
16
|
+
The source of truth is the TypeScript in `src/`. The generated chart directory is an intermediate artifact — never edit it by hand.
|
|
17
|
+
|
|
18
|
+
## Scaffolding a new project
|
|
19
|
+
|
|
20
|
+
### Initialize with a template
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
chant init --lexicon helm # default: Deployment + Service chart
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Project structure after init
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
my-chart/
|
|
30
|
+
src/
|
|
31
|
+
chart.ts ← Chart metadata, Values, K8s resources with Helm intrinsics
|
|
32
|
+
chant.json ← project configuration
|
|
33
|
+
package.json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Key concepts
|
|
37
|
+
|
|
38
|
+
### Values proxy
|
|
39
|
+
|
|
40
|
+
The `values` proxy creates `{{ .Values.x }}` template directives:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { values } from "@intentius/chant-lexicon-helm";
|
|
44
|
+
|
|
45
|
+
// values.replicaCount → {{ .Values.replicaCount }}
|
|
46
|
+
// values.image.repository → {{ .Values.image.repository }}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Template functions
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { include, printf, toYaml, quote, required, helmDefault } from "@intentius/chant-lexicon-helm";
|
|
53
|
+
|
|
54
|
+
include("my-app.fullname") // {{ include "my-app.fullname" . }}
|
|
55
|
+
printf("%s:%s", values.image.repo, values.image.tag) // {{ printf "%s:%s" ... }}
|
|
56
|
+
toYaml(values.resources, 12) // {{ toYaml .Values.resources | nindent 12 }}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Conditional resources
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { If, values } from "@intentius/chant-lexicon-helm";
|
|
63
|
+
|
|
64
|
+
// Wrap an entire resource in {{- if .Values.ingress.enabled }}
|
|
65
|
+
export const ingress = If(values.ingress.enabled, new Ingress({ ... }));
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Built-in objects
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { Release, ChartRef } from "@intentius/chant-lexicon-helm";
|
|
72
|
+
|
|
73
|
+
Release.Name // {{ .Release.Name }}
|
|
74
|
+
Release.Namespace // {{ .Release.Namespace }}
|
|
75
|
+
ChartRef.Version // {{ .Chart.Version }}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Build and validate workflow
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Build the chart
|
|
82
|
+
chant build
|
|
83
|
+
|
|
84
|
+
# Lint the TypeScript (pre-synth rules)
|
|
85
|
+
chant lint
|
|
86
|
+
|
|
87
|
+
# Validate the generated chart (post-synth checks)
|
|
88
|
+
chant check
|
|
89
|
+
|
|
90
|
+
# Validate with helm CLI
|
|
91
|
+
helm lint dist/
|
|
92
|
+
helm template test dist/
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Common patterns
|
|
96
|
+
|
|
97
|
+
### Deployment with parameterized image
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
export const deployment = new Deployment({
|
|
101
|
+
metadata: {
|
|
102
|
+
name: include("my-app.fullname"),
|
|
103
|
+
labels: include("my-app.labels"),
|
|
104
|
+
},
|
|
105
|
+
spec: {
|
|
106
|
+
replicas: values.replicaCount,
|
|
107
|
+
template: {
|
|
108
|
+
spec: {
|
|
109
|
+
containers: [{
|
|
110
|
+
name: "my-app",
|
|
111
|
+
image: printf("%s:%s", values.image.repository, values.image.tag),
|
|
112
|
+
resources: toYaml(values.resources),
|
|
113
|
+
}],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Composites for common patterns
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { HelmWebApp, HelmMicroservice, HelmDaemonSet, HelmWorker } from "@intentius/chant-lexicon-helm";
|
|
124
|
+
|
|
125
|
+
// Quick scaffold: Deployment + Service + Ingress + HPA + ServiceAccount
|
|
126
|
+
const result = HelmWebApp({ name: "my-app", port: 3000, replicas: 3 });
|
|
127
|
+
|
|
128
|
+
// Full microservice: + PDB + ConfigMap + health probes + resource limits
|
|
129
|
+
const msvc = HelmMicroservice({ name: "api", port: 8080 });
|
|
130
|
+
|
|
131
|
+
// DaemonSet for node-level agents (logging, monitoring)
|
|
132
|
+
const agent = HelmDaemonSet({ name: "log-agent", imageRepository: "fluent/fluent-bit" });
|
|
133
|
+
|
|
134
|
+
// Worker for background processors (no Service, exec probes, queue config)
|
|
135
|
+
const worker = HelmWorker({ name: "job-processor", replicas: 4 });
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Secret management with ExternalSecret
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { HelmExternalSecret } from "@intentius/chant-lexicon-helm";
|
|
142
|
+
|
|
143
|
+
const secrets = HelmExternalSecret({
|
|
144
|
+
name: "app-secrets",
|
|
145
|
+
secretStoreName: "vault",
|
|
146
|
+
data: {
|
|
147
|
+
DB_PASSWORD: "secret/data/db-password",
|
|
148
|
+
API_KEY: "secret/data/api-key",
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Resource ordering
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { withOrder, argoWave } from "@intentius/chant-lexicon-helm";
|
|
157
|
+
|
|
158
|
+
// Helm hook ordering (lower weight = runs first)
|
|
159
|
+
metadata: { annotations: { ...withOrder(-5) } }
|
|
160
|
+
|
|
161
|
+
// Argo CD sync waves
|
|
162
|
+
metadata: { annotations: { ...argoWave(2) } }
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### CRD lifecycle management
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { HelmCRDLifecycle } from "@intentius/chant-lexicon-helm";
|
|
169
|
+
|
|
170
|
+
// Managed CRD lifecycle via Helm hooks (solves Helm's CRD limitation)
|
|
171
|
+
const lifecycle = HelmCRDLifecycle({
|
|
172
|
+
name: "my-operator",
|
|
173
|
+
crdContent: crdYaml,
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Lint rules
|
|
178
|
+
|
|
179
|
+
| Rule | Description |
|
|
180
|
+
|------|-------------|
|
|
181
|
+
| WHM001 | Chart must have name, version, apiVersion |
|
|
182
|
+
| WHM002 | Values should not contain bare secrets |
|
|
183
|
+
| WHM003 | Container images should use values references |
|
|
184
|
+
| WHM101 | Chart.yaml has valid apiVersion (v2) |
|
|
185
|
+
| WHM102 | values.schema.json present when Values used |
|
|
186
|
+
| WHM103 | Go template syntax valid (balanced braces) |
|
|
187
|
+
| WHM104 | NOTES.txt exists for application charts |
|
|
188
|
+
| WHM105 | _helpers.tpl exists |
|
|
189
|
+
| WHM201 | Resources have standard Helm labels |
|
|
190
|
+
| WHM301 | At least one test for application charts |
|
|
191
|
+
| WHM302 | Resource limits set |
|
|
192
|
+
| WHM401 | Image uses :latest tag or no tag |
|
|
193
|
+
| WHM402 | runAsNonRoot not set |
|
|
194
|
+
| WHM403 | readOnlyRootFilesystem not set |
|
|
195
|
+
| WHM404 | privileged: true detected |
|
|
196
|
+
| WHM405 | Resource spec missing cpu/memory |
|
|
197
|
+
| WHM406 | CRD lifecycle limitation |
|
|
198
|
+
| WHM407 | Secret with inline data |
|
|
199
|
+
| WHM501 | Unused values keys |
|
|
200
|
+
| WHM502 | Deprecated K8s API versions |
|
|
201
|
+
|
|
202
|
+
## Troubleshooting
|
|
203
|
+
|
|
204
|
+
### "apiVersion must be v2"
|
|
205
|
+
Helm 3 requires `apiVersion: v2` in Chart.yaml. Update your Chart metadata.
|
|
206
|
+
|
|
207
|
+
### "unbalanced template braces"
|
|
208
|
+
A Go template expression has mismatched `{{` / `}}`. Check your intrinsic usage.
|
|
209
|
+
|
|
210
|
+
### "hardcoded image tag"
|
|
211
|
+
Use `printf("%s:%s", values.image.repository, values.image.tag)` instead of literal strings for container images.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* CLI entry point for Helm lexicon validation.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { validate } from "./validate";
|
|
7
|
+
import { printValidationResult } from "@intentius/chant/codegen/validate";
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
const result = await validate();
|
|
11
|
+
printValidationResult(result);
|
|
12
|
+
|
|
13
|
+
if (!result.success) {
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
main().catch((err) => {
|
|
19
|
+
console.error("Validation failed:", err);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { validate } from "./validate";
|
|
6
|
+
|
|
7
|
+
const basePath = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
8
|
+
const generatedDir = join(basePath, "src", "generated");
|
|
9
|
+
const hasGenerated = existsSync(join(generatedDir, "lexicon-helm.json"));
|
|
10
|
+
|
|
11
|
+
describe("validate", () => {
|
|
12
|
+
test("runs validation checks on current artifacts", async () => {
|
|
13
|
+
const result = await validate({ basePath });
|
|
14
|
+
expect(result.checks.length).toBeGreaterThan(0);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test.skipIf(!hasGenerated)("checks lexicon JSON exists and parses", async () => {
|
|
18
|
+
const result = await validate({ basePath });
|
|
19
|
+
const jsonCheck = result.checks.find((c) => c.name === "lexicon-json-exists");
|
|
20
|
+
expect(jsonCheck).toBeDefined();
|
|
21
|
+
expect(jsonCheck?.ok).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test.skipIf(!hasGenerated)("checks types exist", async () => {
|
|
25
|
+
const result = await validate({ basePath });
|
|
26
|
+
const typesCheck = result.checks.find((c) => c.name === "types-exist");
|
|
27
|
+
expect(typesCheck).toBeDefined();
|
|
28
|
+
expect(typesCheck?.ok).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test.skipIf(!hasGenerated)("checks required names are present", async () => {
|
|
32
|
+
const result = await validate({ basePath });
|
|
33
|
+
const requiredCheck = result.checks.find((c) => c.name === "required-names");
|
|
34
|
+
expect(requiredCheck).toBeDefined();
|
|
35
|
+
expect(requiredCheck?.ok).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
});
|