@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
package/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# @intentius/chant-lexicon-helm
|
|
2
|
+
|
|
3
|
+
Helm lexicon for [chant](https://intentius.io/chant/) — declare Helm charts, releases, and values as typed TypeScript.
|
|
4
|
+
|
|
5
|
+
This package provides generated constructors for Helm resource types, composites for grouping related resources, and Helm-specific lint rules. It builds on the Kubernetes lexicon for base resource types. It also includes LSP and MCP server support for editor completions and hover.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install --save-dev @intentius/chant @intentius/chant-lexicon-helm
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**[Documentation →](https://intentius.io/chant/lexicons/helm/)**
|
|
12
|
+
|
|
13
|
+
## Related Packages
|
|
14
|
+
|
|
15
|
+
| Package | Role |
|
|
16
|
+
|---------|------|
|
|
17
|
+
| [@intentius/chant](https://www.npmjs.com/package/@intentius/chant) | Core type system, CLI, build pipeline |
|
|
18
|
+
| [@intentius/chant-lexicon-k8s](https://www.npmjs.com/package/@intentius/chant-lexicon-k8s) | Kubernetes lexicon (base dependency) |
|
|
19
|
+
|
|
20
|
+
## License
|
|
21
|
+
|
|
22
|
+
See the main project LICENSE file.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"algorithm": "xxhash64",
|
|
3
|
+
"artifacts": {
|
|
4
|
+
"manifest.json": "9d47d59c10c219ca",
|
|
5
|
+
"meta.json": "b7aab243e162dfaf",
|
|
6
|
+
"types/index.d.ts": "e8011da51963f058",
|
|
7
|
+
"rules/no-hardcoded-image.ts": "c75aa9c33016c3b9",
|
|
8
|
+
"rules/values-no-secrets.ts": "213276bfe1fb8ff7",
|
|
9
|
+
"rules/chart-metadata.ts": "d0cff2b78fed78d3",
|
|
10
|
+
"rules/whm103.ts": "a777a13f2eaf1831",
|
|
11
|
+
"rules/whm104.ts": "e2d644da2a9dc605",
|
|
12
|
+
"rules/whm201.ts": "f263fe69901552eb",
|
|
13
|
+
"rules/whm101.ts": "533ae8f65cb2227b",
|
|
14
|
+
"rules/whm402.ts": "f722014a723654af",
|
|
15
|
+
"rules/whm204.ts": "7b57de71795fc102",
|
|
16
|
+
"rules/whm406.ts": "c76ac5a44c3e9dcd",
|
|
17
|
+
"rules/whm301.ts": "f3ed3a269093527",
|
|
18
|
+
"rules/whm203.ts": "2bb546d268c84232",
|
|
19
|
+
"rules/whm404.ts": "8847425e930d41c1",
|
|
20
|
+
"rules/whm102.ts": "4c0c494c253df56a",
|
|
21
|
+
"rules/helm-helpers.ts": "2e0cc249268227ed",
|
|
22
|
+
"rules/whm302.ts": "44e58ce621ae6d1a",
|
|
23
|
+
"rules/whm502.ts": "2c9901ecbfaf92cb",
|
|
24
|
+
"rules/whm407.ts": "f1fbc6942aff8e96",
|
|
25
|
+
"rules/whm401.ts": "fe72c3c450a12f93",
|
|
26
|
+
"rules/whm105.ts": "8977ec834713b90c",
|
|
27
|
+
"rules/whm403.ts": "729d10edb48b0d03",
|
|
28
|
+
"rules/whm405.ts": "ec82442f9120e5e0",
|
|
29
|
+
"rules/whm202.ts": "dbe1cbb3237be84f",
|
|
30
|
+
"rules/whm501.ts": "e9a9f2b4e034a51f",
|
|
31
|
+
"skills/chant-helm-chart-patterns.md": "7a2163247f44bf6d",
|
|
32
|
+
"skills/chant-helm-chart-security-patterns.md": "a7b950513dac7d37",
|
|
33
|
+
"skills/chant-helm-create-chart.md": "d2cb222caa70736e"
|
|
34
|
+
},
|
|
35
|
+
"composite": "e4128d38dbdb1bbb"
|
|
36
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "helm",
|
|
3
|
+
"version": "0.0.16",
|
|
4
|
+
"chantVersion": ">=0.1.0",
|
|
5
|
+
"namespace": "Helm",
|
|
6
|
+
"intrinsics": [
|
|
7
|
+
{
|
|
8
|
+
"name": "values",
|
|
9
|
+
"description": "Proxy accessor for {{ .Values.x }} references"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"name": "Release",
|
|
13
|
+
"description": "Built-in Release object"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "ChartRef",
|
|
17
|
+
"description": "Built-in Chart object"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "include",
|
|
21
|
+
"description": "Include a named template"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "If",
|
|
25
|
+
"description": "Conditional resource/value"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "Range",
|
|
29
|
+
"description": "Range loop"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"name": "With",
|
|
33
|
+
"description": "With scope"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"pseudoParameters": {}
|
|
37
|
+
}
|
package/dist/meta.json
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Chart": {
|
|
3
|
+
"resourceType": "Helm::Chart",
|
|
4
|
+
"kind": "resource",
|
|
5
|
+
"description": "Chart.yaml metadata — defines the chart identity, version, and type.",
|
|
6
|
+
"props": {
|
|
7
|
+
"apiVersion": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"description": "Chart API version (v2)",
|
|
10
|
+
"required": true
|
|
11
|
+
},
|
|
12
|
+
"name": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "Chart name",
|
|
15
|
+
"required": true
|
|
16
|
+
},
|
|
17
|
+
"version": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Chart version (SemVer)",
|
|
20
|
+
"required": true
|
|
21
|
+
},
|
|
22
|
+
"appVersion": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "Version of the app deployed by this chart"
|
|
25
|
+
},
|
|
26
|
+
"description": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "A single-sentence description of this chart"
|
|
29
|
+
},
|
|
30
|
+
"type": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "Chart type: application or library"
|
|
33
|
+
},
|
|
34
|
+
"keywords": {
|
|
35
|
+
"type": "string[]",
|
|
36
|
+
"description": "Keywords for chart search"
|
|
37
|
+
},
|
|
38
|
+
"home": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "URL of the project home page"
|
|
41
|
+
},
|
|
42
|
+
"icon": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "URL to an SVG or PNG image for the chart"
|
|
45
|
+
},
|
|
46
|
+
"deprecated": {
|
|
47
|
+
"type": "boolean",
|
|
48
|
+
"description": "Whether this chart is deprecated"
|
|
49
|
+
},
|
|
50
|
+
"sources": {
|
|
51
|
+
"type": "string[]",
|
|
52
|
+
"description": "URLs to source code for this chart"
|
|
53
|
+
},
|
|
54
|
+
"maintainers": {
|
|
55
|
+
"type": "HelmMaintainerProps[]",
|
|
56
|
+
"description": "List of chart maintainers"
|
|
57
|
+
},
|
|
58
|
+
"annotations": {
|
|
59
|
+
"type": "Record<string, string>",
|
|
60
|
+
"description": "Arbitrary key-value annotations"
|
|
61
|
+
},
|
|
62
|
+
"kubeVersion": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"description": "SemVer range of compatible Kubernetes versions"
|
|
65
|
+
},
|
|
66
|
+
"condition": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"description": "YAML path for chart enablement (subcharts)"
|
|
69
|
+
},
|
|
70
|
+
"tags": {
|
|
71
|
+
"type": "string",
|
|
72
|
+
"description": "Tags for grouping charts for enabling/disabling"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"Values": {
|
|
77
|
+
"resourceType": "Helm::Values",
|
|
78
|
+
"kind": "resource",
|
|
79
|
+
"description": "Typed values definition — emits values.yaml and values.schema.json.",
|
|
80
|
+
"props": {}
|
|
81
|
+
},
|
|
82
|
+
"HelmTest": {
|
|
83
|
+
"resourceType": "Helm::Test",
|
|
84
|
+
"kind": "resource",
|
|
85
|
+
"description": "Helm test pod — annotated with helm.sh/hook: test.",
|
|
86
|
+
"props": {
|
|
87
|
+
"resource": {
|
|
88
|
+
"type": "object",
|
|
89
|
+
"description": "K8s Pod resource to use as the test"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"HelmNotes": {
|
|
94
|
+
"resourceType": "Helm::Notes",
|
|
95
|
+
"kind": "resource",
|
|
96
|
+
"description": "NOTES.txt template content — displayed after helm install.",
|
|
97
|
+
"props": {
|
|
98
|
+
"content": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"description": "NOTES.txt content (may contain Go template expressions)",
|
|
101
|
+
"required": true
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"HelmHook": {
|
|
106
|
+
"resourceType": "Helm::Hook",
|
|
107
|
+
"kind": "property",
|
|
108
|
+
"description": "Lifecycle hook annotation — wraps a K8s resource with helm.sh/hook annotations.",
|
|
109
|
+
"props": {
|
|
110
|
+
"hook": {
|
|
111
|
+
"type": "string",
|
|
112
|
+
"description": "Hook type: pre-install, post-install, pre-upgrade, post-upgrade, pre-delete, post-delete, pre-rollback, post-rollback, test",
|
|
113
|
+
"required": true
|
|
114
|
+
},
|
|
115
|
+
"weight": {
|
|
116
|
+
"type": "number",
|
|
117
|
+
"description": "Hook execution order weight"
|
|
118
|
+
},
|
|
119
|
+
"deletePolicy": {
|
|
120
|
+
"type": "string",
|
|
121
|
+
"description": "Hook delete policy: before-hook-creation, hook-succeeded, hook-failed"
|
|
122
|
+
},
|
|
123
|
+
"resource": {
|
|
124
|
+
"type": "object",
|
|
125
|
+
"description": "K8s resource to annotate with the hook",
|
|
126
|
+
"required": true
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"HelmDependency": {
|
|
131
|
+
"resourceType": "Helm::Dependency",
|
|
132
|
+
"kind": "property",
|
|
133
|
+
"description": "Chart dependency entry for Chart.yaml dependencies.",
|
|
134
|
+
"props": {
|
|
135
|
+
"name": {
|
|
136
|
+
"type": "string",
|
|
137
|
+
"description": "Dependency chart name",
|
|
138
|
+
"required": true
|
|
139
|
+
},
|
|
140
|
+
"version": {
|
|
141
|
+
"type": "string",
|
|
142
|
+
"description": "Dependency version range (SemVer)",
|
|
143
|
+
"required": true
|
|
144
|
+
},
|
|
145
|
+
"repository": {
|
|
146
|
+
"type": "string",
|
|
147
|
+
"description": "Repository URL",
|
|
148
|
+
"required": true
|
|
149
|
+
},
|
|
150
|
+
"condition": {
|
|
151
|
+
"type": "string",
|
|
152
|
+
"description": "YAML path that enables/disables this dependency"
|
|
153
|
+
},
|
|
154
|
+
"tags": {
|
|
155
|
+
"type": "string[]",
|
|
156
|
+
"description": "Tags for grouping dependencies"
|
|
157
|
+
},
|
|
158
|
+
"enabled": {
|
|
159
|
+
"type": "boolean",
|
|
160
|
+
"description": "Whether this dependency is enabled"
|
|
161
|
+
},
|
|
162
|
+
"importValues": {
|
|
163
|
+
"type": "unknown[]",
|
|
164
|
+
"description": "Values to import from dependency (import-values in Chart.yaml)"
|
|
165
|
+
},
|
|
166
|
+
"alias": {
|
|
167
|
+
"type": "string",
|
|
168
|
+
"description": "Alias for the dependency"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"HelmMaintainer": {
|
|
173
|
+
"resourceType": "Helm::Maintainer",
|
|
174
|
+
"kind": "property",
|
|
175
|
+
"description": "Chart maintainer entry for Chart.yaml maintainers.",
|
|
176
|
+
"props": {
|
|
177
|
+
"name": {
|
|
178
|
+
"type": "string",
|
|
179
|
+
"description": "Maintainer name",
|
|
180
|
+
"required": true
|
|
181
|
+
},
|
|
182
|
+
"email": {
|
|
183
|
+
"type": "string",
|
|
184
|
+
"description": "Maintainer email"
|
|
185
|
+
},
|
|
186
|
+
"url": {
|
|
187
|
+
"type": "string",
|
|
188
|
+
"description": "Maintainer URL"
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
"HelmCRD": {
|
|
193
|
+
"resourceType": "Helm::CRD",
|
|
194
|
+
"kind": "resource",
|
|
195
|
+
"description": "Custom Resource Definition — placed in the crds/ directory.",
|
|
196
|
+
"props": {
|
|
197
|
+
"content": {
|
|
198
|
+
"type": "string",
|
|
199
|
+
"description": "CRD YAML content",
|
|
200
|
+
"required": true
|
|
201
|
+
},
|
|
202
|
+
"filename": {
|
|
203
|
+
"type": "string",
|
|
204
|
+
"description": "CRD filename (e.g. mycrd.yaml)"
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHM001: Chart Metadata Required
|
|
3
|
+
*
|
|
4
|
+
* Detects Chart constructors missing required fields: name, version, apiVersion.
|
|
5
|
+
*
|
|
6
|
+
* Bad: new Chart({})
|
|
7
|
+
* Good: new Chart({ apiVersion: "v2", name: "my-app", version: "0.1.0" })
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
11
|
+
import * as ts from "typescript";
|
|
12
|
+
|
|
13
|
+
const REQUIRED_FIELDS = ["name", "version", "apiVersion"];
|
|
14
|
+
|
|
15
|
+
export const chartMetadataRule: LintRule = {
|
|
16
|
+
id: "WHM001",
|
|
17
|
+
severity: "error",
|
|
18
|
+
category: "correctness",
|
|
19
|
+
description:
|
|
20
|
+
"Chart must have name, version, and apiVersion fields",
|
|
21
|
+
|
|
22
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
23
|
+
const { sourceFile } = context;
|
|
24
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
25
|
+
|
|
26
|
+
function visit(node: ts.Node): void {
|
|
27
|
+
// Look for `new Chart({ ... })`
|
|
28
|
+
if (
|
|
29
|
+
ts.isNewExpression(node) &&
|
|
30
|
+
ts.isIdentifier(node.expression) &&
|
|
31
|
+
node.expression.text === "Chart" &&
|
|
32
|
+
node.arguments &&
|
|
33
|
+
node.arguments.length > 0
|
|
34
|
+
) {
|
|
35
|
+
const arg = node.arguments[0];
|
|
36
|
+
if (ts.isObjectLiteralExpression(arg)) {
|
|
37
|
+
const presentKeys = new Set<string>();
|
|
38
|
+
for (const prop of arg.properties) {
|
|
39
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
40
|
+
presentKeys.add(prop.name.text);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const missing = REQUIRED_FIELDS.filter((f) => !presentKeys.has(f));
|
|
45
|
+
if (missing.length > 0) {
|
|
46
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
47
|
+
diagnostics.push({
|
|
48
|
+
file: sourceFile.fileName,
|
|
49
|
+
line: line + 1,
|
|
50
|
+
column: character + 1,
|
|
51
|
+
ruleId: "WHM001",
|
|
52
|
+
severity: "error",
|
|
53
|
+
message: `Chart is missing required fields: ${missing.join(", ")}`,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
ts.forEachChild(node, visit);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
visit(sourceFile);
|
|
62
|
+
return diagnostics;
|
|
63
|
+
},
|
|
64
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for Helm post-synth checks.
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities to extract and parse the Helm chart files
|
|
5
|
+
* from the serializer's SerializerResult output.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
9
|
+
import { getPrimaryOutput } from "@intentius/chant/lint/post-synth";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Extract the files map from a serializer output.
|
|
13
|
+
* Returns empty object if output is a plain string.
|
|
14
|
+
*/
|
|
15
|
+
export function getChartFiles(output: string | SerializerResult): Record<string, string> {
|
|
16
|
+
if (typeof output === "string") return {};
|
|
17
|
+
return output.files ?? {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Parse Chart.yaml content into key-value pairs.
|
|
22
|
+
* Lightweight parser — not a full YAML parser.
|
|
23
|
+
*/
|
|
24
|
+
export function parseChartYaml(content: string): Record<string, string> {
|
|
25
|
+
const result: Record<string, string> = {};
|
|
26
|
+
for (const line of content.split("\n")) {
|
|
27
|
+
const match = line.match(/^([a-zA-Z]+):\s*(.+)$/);
|
|
28
|
+
if (match) {
|
|
29
|
+
result[match[1]] = match[2].replace(/^'(.*)'$/, "$1").trim();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Check if a string contains balanced Go template braces.
|
|
37
|
+
*/
|
|
38
|
+
export function hasBalancedBraces(content: string): boolean {
|
|
39
|
+
let depth = 0;
|
|
40
|
+
for (let i = 0; i < content.length - 1; i++) {
|
|
41
|
+
if (content[i] === "{" && content[i + 1] === "{") {
|
|
42
|
+
depth++;
|
|
43
|
+
i++; // skip next char
|
|
44
|
+
} else if (content[i] === "}" && content[i + 1] === "}") {
|
|
45
|
+
depth--;
|
|
46
|
+
i++;
|
|
47
|
+
if (depth < 0) return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return depth === 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Extract all Go template expressions from content.
|
|
55
|
+
*/
|
|
56
|
+
export function extractTemplateExpressions(content: string): string[] {
|
|
57
|
+
const regex = /\{\{-?\s*(.*?)\s*-?\}\}/g;
|
|
58
|
+
const matches: string[] = [];
|
|
59
|
+
let match;
|
|
60
|
+
while ((match = regex.exec(content)) !== null) {
|
|
61
|
+
matches.push(match[1]);
|
|
62
|
+
}
|
|
63
|
+
return matches;
|
|
64
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHM003: Container Images Should Use Values References
|
|
3
|
+
*
|
|
4
|
+
* Detects container image strings that are hardcoded in K8s resource
|
|
5
|
+
* constructors instead of using values references. In Helm charts, images
|
|
6
|
+
* should be parameterized via values.yaml so they can be overridden at
|
|
7
|
+
* install time.
|
|
8
|
+
*
|
|
9
|
+
* Bad: image: "nginx:1.19"
|
|
10
|
+
* Good: image: printf("%s:%s", values.image.repository, values.image.tag)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
14
|
+
import * as ts from "typescript";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Pattern matching common container image references.
|
|
18
|
+
* Matches strings like "nginx:1.19", "registry.io/app:latest", etc.
|
|
19
|
+
*/
|
|
20
|
+
const IMAGE_PATTERN = /^[a-z0-9][a-z0-9._/-]*:[a-z0-9][a-z0-9._-]*$/i;
|
|
21
|
+
|
|
22
|
+
export const noHardcodedImageRule: LintRule = {
|
|
23
|
+
id: "WHM003",
|
|
24
|
+
severity: "warning",
|
|
25
|
+
category: "correctness",
|
|
26
|
+
description:
|
|
27
|
+
"Container images should use values references, not hardcoded tags",
|
|
28
|
+
|
|
29
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
30
|
+
const { sourceFile } = context;
|
|
31
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
32
|
+
|
|
33
|
+
function visit(node: ts.Node): void {
|
|
34
|
+
// Look for property assignments: `image: "nginx:1.19"`
|
|
35
|
+
if (
|
|
36
|
+
ts.isPropertyAssignment(node) &&
|
|
37
|
+
ts.isIdentifier(node.name) &&
|
|
38
|
+
node.name.text === "image" &&
|
|
39
|
+
ts.isStringLiteral(node.initializer)
|
|
40
|
+
) {
|
|
41
|
+
const value = node.initializer.text;
|
|
42
|
+
if (IMAGE_PATTERN.test(value)) {
|
|
43
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(
|
|
44
|
+
node.initializer.getStart(),
|
|
45
|
+
);
|
|
46
|
+
diagnostics.push({
|
|
47
|
+
file: sourceFile.fileName,
|
|
48
|
+
line: line + 1,
|
|
49
|
+
column: character + 1,
|
|
50
|
+
ruleId: "WHM003",
|
|
51
|
+
severity: "warning",
|
|
52
|
+
message: `Hardcoded image "${value}" — use values references (e.g. printf("%s:%s", values.image.repository, values.image.tag)) for Helm chart parameterization`,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
ts.forEachChild(node, visit);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
visit(sourceFile);
|
|
60
|
+
return diagnostics;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHM002: Values Should Not Contain Bare Secrets
|
|
3
|
+
*
|
|
4
|
+
* Detects Values constructor props that contain keys suggesting sensitive
|
|
5
|
+
* data (password, token, key, secret, apiKey) without an `existingSecret`
|
|
6
|
+
* pattern — hardcoded secrets in values.yaml are a security anti-pattern.
|
|
7
|
+
*
|
|
8
|
+
* Bad: new Values({ dbPassword: "hunter2" })
|
|
9
|
+
* Good: new Values({ existingSecret: "", dbPasswordKey: "password" })
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
13
|
+
import * as ts from "typescript";
|
|
14
|
+
|
|
15
|
+
const SENSITIVE_KEY_PATTERN = /^(password|secret|token|apiKey|api_key|private_key|privateKey)$/i;
|
|
16
|
+
const SAFE_PATTERNS = /existing|ref|key_name|keyName|secretName/i;
|
|
17
|
+
|
|
18
|
+
export const valuesNoSecretsRule: LintRule = {
|
|
19
|
+
id: "WHM002",
|
|
20
|
+
severity: "warning",
|
|
21
|
+
category: "security",
|
|
22
|
+
description:
|
|
23
|
+
"Values should not contain bare secrets — use existingSecret pattern instead",
|
|
24
|
+
|
|
25
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
26
|
+
const { sourceFile } = context;
|
|
27
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
28
|
+
|
|
29
|
+
function visit(node: ts.Node): void {
|
|
30
|
+
// Look for `new Values({ ... })`
|
|
31
|
+
if (
|
|
32
|
+
ts.isNewExpression(node) &&
|
|
33
|
+
ts.isIdentifier(node.expression) &&
|
|
34
|
+
node.expression.text === "Values" &&
|
|
35
|
+
node.arguments &&
|
|
36
|
+
node.arguments.length > 0
|
|
37
|
+
) {
|
|
38
|
+
const arg = node.arguments[0];
|
|
39
|
+
if (ts.isObjectLiteralExpression(arg)) {
|
|
40
|
+
checkObjectLiteral(arg, sourceFile, diagnostics);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
ts.forEachChild(node, visit);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
visit(sourceFile);
|
|
47
|
+
return diagnostics;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function checkObjectLiteral(
|
|
52
|
+
obj: ts.ObjectLiteralExpression,
|
|
53
|
+
sourceFile: ts.SourceFile,
|
|
54
|
+
diagnostics: LintDiagnostic[],
|
|
55
|
+
): void {
|
|
56
|
+
for (const prop of obj.properties) {
|
|
57
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
58
|
+
const key = prop.name.text;
|
|
59
|
+
|
|
60
|
+
// Check if the key looks sensitive
|
|
61
|
+
if (SENSITIVE_KEY_PATTERN.test(key) && !SAFE_PATTERNS.test(key)) {
|
|
62
|
+
// Check if the value is a string literal (hardcoded secret)
|
|
63
|
+
if (ts.isStringLiteral(prop.initializer) && prop.initializer.text !== "") {
|
|
64
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(prop.getStart());
|
|
65
|
+
diagnostics.push({
|
|
66
|
+
file: sourceFile.fileName,
|
|
67
|
+
line: line + 1,
|
|
68
|
+
column: character + 1,
|
|
69
|
+
ruleId: "WHM002",
|
|
70
|
+
severity: "warning",
|
|
71
|
+
message: `Values key "${key}" contains a hardcoded secret — use existingSecret pattern or leave empty as default`,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Recurse into nested objects
|
|
77
|
+
if (ts.isObjectLiteralExpression(prop.initializer)) {
|
|
78
|
+
checkObjectLiteral(prop.initializer, sourceFile, diagnostics);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHM101: Chart.yaml has required fields and valid apiVersion (v2).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
6
|
+
import { getChartFiles, parseChartYaml } from "./helm-helpers";
|
|
7
|
+
|
|
8
|
+
export const whm101: PostSynthCheck = {
|
|
9
|
+
id: "WHM101",
|
|
10
|
+
description: "Chart.yaml must have required fields (apiVersion v2, name, version)",
|
|
11
|
+
|
|
12
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
13
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
14
|
+
|
|
15
|
+
for (const [, output] of ctx.outputs) {
|
|
16
|
+
const files = getChartFiles(output);
|
|
17
|
+
const chartYaml = files["Chart.yaml"];
|
|
18
|
+
if (!chartYaml) {
|
|
19
|
+
diagnostics.push({
|
|
20
|
+
checkId: "WHM101",
|
|
21
|
+
severity: "error",
|
|
22
|
+
message: "Chart.yaml is missing from serializer output",
|
|
23
|
+
lexicon: "helm",
|
|
24
|
+
});
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const parsed = parseChartYaml(chartYaml);
|
|
29
|
+
|
|
30
|
+
if (!parsed.apiVersion) {
|
|
31
|
+
diagnostics.push({ checkId: "WHM101", severity: "error", message: "Chart.yaml missing apiVersion", lexicon: "helm" });
|
|
32
|
+
} else if (parsed.apiVersion !== "v2") {
|
|
33
|
+
diagnostics.push({ checkId: "WHM101", severity: "error", message: `Chart.yaml apiVersion should be "v2", got "${parsed.apiVersion}"`, lexicon: "helm" });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!parsed.name) {
|
|
37
|
+
diagnostics.push({ checkId: "WHM101", severity: "error", message: "Chart.yaml missing name", lexicon: "helm" });
|
|
38
|
+
}
|
|
39
|
+
if (!parsed.version) {
|
|
40
|
+
diagnostics.push({ checkId: "WHM101", severity: "error", message: "Chart.yaml missing version", lexicon: "helm" });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return diagnostics;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHM102: values.schema.json present when Values type is used.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
6
|
+
import { getChartFiles } from "./helm-helpers";
|
|
7
|
+
|
|
8
|
+
export const whm102: PostSynthCheck = {
|
|
9
|
+
id: "WHM102",
|
|
10
|
+
description: "values.schema.json should be present when Values are non-empty",
|
|
11
|
+
|
|
12
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
13
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
14
|
+
|
|
15
|
+
for (const [, output] of ctx.outputs) {
|
|
16
|
+
const files = getChartFiles(output);
|
|
17
|
+
const valuesYaml = files["values.yaml"];
|
|
18
|
+
const valuesSchema = files["values.schema.json"];
|
|
19
|
+
|
|
20
|
+
// If values.yaml has content (not just "{}"), schema should exist
|
|
21
|
+
if (valuesYaml && valuesYaml.trim() !== "{}" && !valuesSchema) {
|
|
22
|
+
diagnostics.push({
|
|
23
|
+
checkId: "WHM102",
|
|
24
|
+
severity: "warning",
|
|
25
|
+
message: "values.schema.json is missing — consider adding typed Values to generate a schema",
|
|
26
|
+
lexicon: "helm",
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return diagnostics;
|
|
32
|
+
},
|
|
33
|
+
};
|