@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,484 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helm template intrinsics.
|
|
3
|
+
*
|
|
4
|
+
* All intrinsics implement INTRINSIC_MARKER and produce `toJSON()` output
|
|
5
|
+
* containing `__helm_tpl` markers that the serializer detects and emits
|
|
6
|
+
* as raw Go template expressions instead of YAML-quoting them.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { INTRINSIC_MARKER, type Intrinsic } from "@intentius/chant/intrinsic";
|
|
10
|
+
|
|
11
|
+
// ── Marker key ────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
/** JSON marker key used by the serializer to detect Helm template expressions. */
|
|
14
|
+
export const HELM_TPL_KEY = "__helm_tpl";
|
|
15
|
+
|
|
16
|
+
/** JSON marker key for conditional resource wrappers. */
|
|
17
|
+
export const HELM_IF_KEY = "__helm_if";
|
|
18
|
+
|
|
19
|
+
/** JSON marker key for range loops. */
|
|
20
|
+
export const HELM_RANGE_KEY = "__helm_range";
|
|
21
|
+
|
|
22
|
+
/** JSON marker key for with scopes. */
|
|
23
|
+
export const HELM_WITH_KEY = "__helm_with";
|
|
24
|
+
|
|
25
|
+
// ── HelmTpl base class ────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Base class for all Helm template expressions.
|
|
29
|
+
*
|
|
30
|
+
* `toJSON()` returns `{ __helm_tpl: "{{ expr }}" }` which the serializer
|
|
31
|
+
* detects and emits as a raw Go template directive.
|
|
32
|
+
*/
|
|
33
|
+
export class HelmTpl implements Intrinsic {
|
|
34
|
+
readonly [INTRINSIC_MARKER] = true as const;
|
|
35
|
+
readonly expr: string;
|
|
36
|
+
|
|
37
|
+
constructor(expr: string) {
|
|
38
|
+
this.expr = expr;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
toJSON(): { __helm_tpl: string } {
|
|
42
|
+
return { [HELM_TPL_KEY]: this.expr };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Pipe this expression through a Go template function.
|
|
47
|
+
* `values.x.pipe("upper").pipe("quote")` → `{{ .Values.x | upper | quote }}`
|
|
48
|
+
*/
|
|
49
|
+
pipe(fn: string): HelmTpl {
|
|
50
|
+
// Strip outer {{ }} if present, append pipe, re-wrap
|
|
51
|
+
const inner = this.expr.replace(/^\{\{-?\s*/, "").replace(/\s*-?\}\}$/, "");
|
|
52
|
+
return new HelmTpl(`{{ ${inner} | ${fn} }}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
toString(): string {
|
|
56
|
+
return this.expr;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Values proxy ──────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Create a Proxy-based accessor that records property paths and returns
|
|
64
|
+
* HelmTpl instances for Go template expressions.
|
|
65
|
+
*
|
|
66
|
+
* `values.replicas` → HelmTpl("{{ .Values.replicas }}")
|
|
67
|
+
* `values.image.repository` → HelmTpl("{{ .Values.image.repository }}")
|
|
68
|
+
*/
|
|
69
|
+
function createValuesProxy(path: string = ".Values"): HelmTpl & Record<string, unknown> {
|
|
70
|
+
const tpl = new HelmTpl(`{{ ${path} }}`);
|
|
71
|
+
|
|
72
|
+
return new Proxy(tpl, {
|
|
73
|
+
get(target, prop, receiver) {
|
|
74
|
+
if (prop === INTRINSIC_MARKER) return true;
|
|
75
|
+
if (prop === "toJSON") return () => target.toJSON();
|
|
76
|
+
if (prop === "expr") return target.expr;
|
|
77
|
+
if (prop === "toString") return () => target.toString();
|
|
78
|
+
if (prop === Symbol.toPrimitive) return () => target.expr;
|
|
79
|
+
|
|
80
|
+
if (prop === "pipe") {
|
|
81
|
+
return (fn: string) => {
|
|
82
|
+
const piped = new HelmTpl(`{{ ${path} | ${fn} }}`);
|
|
83
|
+
return createPipedProxy(piped, `${path} | ${fn}`);
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (typeof prop === "string") {
|
|
88
|
+
return createValuesProxy(`${path}.${prop}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return Reflect.get(target, prop, receiver);
|
|
92
|
+
},
|
|
93
|
+
}) as HelmTpl & Record<string, unknown>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Create a proxy for piped expressions that allows further chaining.
|
|
98
|
+
*/
|
|
99
|
+
function createPipedProxy(tpl: HelmTpl, pipedExpr: string): HelmTpl {
|
|
100
|
+
return new Proxy(tpl, {
|
|
101
|
+
get(target, prop, receiver) {
|
|
102
|
+
if (prop === INTRINSIC_MARKER) return true;
|
|
103
|
+
if (prop === "toJSON") return () => target.toJSON();
|
|
104
|
+
if (prop === "expr") return target.expr;
|
|
105
|
+
if (prop === "toString") return () => target.toString();
|
|
106
|
+
if (prop === Symbol.toPrimitive) return () => target.expr;
|
|
107
|
+
|
|
108
|
+
if (prop === "pipe") {
|
|
109
|
+
return (fn: string) => {
|
|
110
|
+
const newExpr = `${pipedExpr} | ${fn}`;
|
|
111
|
+
const newTpl = new HelmTpl(`{{ ${newExpr} }}`);
|
|
112
|
+
return createPipedProxy(newTpl, newExpr);
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return Reflect.get(target, prop, receiver);
|
|
117
|
+
},
|
|
118
|
+
}) as HelmTpl;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Values proxy — the primary way to reference Helm values in templates.
|
|
123
|
+
*
|
|
124
|
+
* ```ts
|
|
125
|
+
* values.replicas // → {{ .Values.replicas }}
|
|
126
|
+
* values.image.tag // → {{ .Values.image.tag }}
|
|
127
|
+
* values.x.pipe("quote") // → {{ .Values.x | quote }}
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export const values: Record<string, any> = createValuesProxy();
|
|
131
|
+
|
|
132
|
+
// ── Built-in objects ──────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
function createBuiltinObject(prefix: string, fields: string[]): Record<string, HelmTpl> {
|
|
135
|
+
const obj: Record<string, HelmTpl> = {};
|
|
136
|
+
for (const field of fields) {
|
|
137
|
+
obj[field] = new HelmTpl(`{{ ${prefix}.${field} }}`);
|
|
138
|
+
}
|
|
139
|
+
return obj;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Release built-in object.
|
|
144
|
+
*
|
|
145
|
+
* `Release.Name` → `{{ .Release.Name }}`
|
|
146
|
+
*/
|
|
147
|
+
export const Release = createBuiltinObject(".Release", [
|
|
148
|
+
"Name", "Namespace", "Service", "IsUpgrade", "IsInstall", "Revision",
|
|
149
|
+
]);
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* ChartRef built-in object (named ChartRef to avoid conflict with Chart resource).
|
|
153
|
+
*
|
|
154
|
+
* `ChartRef.Name` → `{{ .Chart.Name }}`
|
|
155
|
+
*/
|
|
156
|
+
export const ChartRef = createBuiltinObject(".Chart", [
|
|
157
|
+
"Name", "Home", "Sources", "Version", "Description", "Keywords",
|
|
158
|
+
"Maintainers", "Icon", "APIVersion", "Condition", "Tags",
|
|
159
|
+
"AppVersion", "Deprecated", "Annotations", "KubeVersion", "Type",
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
// ── Nested builtin proxy ─────────────────────────────────
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Create a Proxy-based accessor for built-in objects with arbitrarily nested
|
|
166
|
+
* property access. Each level returns a new proxy that extends the path.
|
|
167
|
+
*
|
|
168
|
+
* `createNestedBuiltinProxy(".Capabilities").KubeVersion.Version`
|
|
169
|
+
* → HelmTpl("{{ .Capabilities.KubeVersion.Version }}")
|
|
170
|
+
*/
|
|
171
|
+
function createNestedBuiltinProxy(prefix: string): HelmTpl & Record<string, any> {
|
|
172
|
+
const tpl = new HelmTpl(`{{ ${prefix} }}`);
|
|
173
|
+
return new Proxy(tpl, {
|
|
174
|
+
get(target, prop, receiver) {
|
|
175
|
+
if (prop === INTRINSIC_MARKER) return true;
|
|
176
|
+
if (prop === "toJSON") return () => target.toJSON();
|
|
177
|
+
if (prop === "expr") return target.expr;
|
|
178
|
+
if (prop === "toString") return () => target.toString();
|
|
179
|
+
if (prop === Symbol.toPrimitive) return () => target.expr;
|
|
180
|
+
if (prop === "pipe") {
|
|
181
|
+
return (fn: string) => {
|
|
182
|
+
const inner = prefix;
|
|
183
|
+
const piped = new HelmTpl(`{{ ${inner} | ${fn} }}`);
|
|
184
|
+
return createPipedProxy(piped, `${inner} | ${fn}`);
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (typeof prop === "string") {
|
|
188
|
+
return createNestedBuiltinProxy(`${prefix}.${prop}`);
|
|
189
|
+
}
|
|
190
|
+
return Reflect.get(target, prop, receiver);
|
|
191
|
+
},
|
|
192
|
+
}) as HelmTpl & Record<string, any>;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Capabilities built-in object — supports arbitrarily nested property access.
|
|
197
|
+
*
|
|
198
|
+
* `Capabilities.KubeVersion.Version` → `{{ .Capabilities.KubeVersion.Version }}`
|
|
199
|
+
* `Capabilities.APIVersions` → `{{ .Capabilities.APIVersions }}`
|
|
200
|
+
* `Capabilities.HelmVersion.Version` → `{{ .Capabilities.HelmVersion.Version }}`
|
|
201
|
+
*/
|
|
202
|
+
export const Capabilities: Record<string, any> = createNestedBuiltinProxy(".Capabilities");
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Template built-in object.
|
|
206
|
+
*
|
|
207
|
+
* `Template.Name` → `{{ .Template.Name }}`
|
|
208
|
+
* `Template.BasePath` → `{{ .Template.BasePath }}`
|
|
209
|
+
*/
|
|
210
|
+
export const Template = createBuiltinObject(".Template", ["Name", "BasePath"]);
|
|
211
|
+
|
|
212
|
+
// ── Files helpers ────────────────────────────────────────
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* `.Files.Get` — Read a file from the chart directory.
|
|
216
|
+
*
|
|
217
|
+
* `filesGet("config.ini")` → `{{ .Files.Get "config.ini" }}`
|
|
218
|
+
*/
|
|
219
|
+
export function filesGet(path: string): HelmTpl {
|
|
220
|
+
return new HelmTpl(`{{ .Files.Get "${path}" }}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* `.Files.Glob` — Match files by glob pattern.
|
|
225
|
+
*
|
|
226
|
+
* `filesGlob("conf/*")` → `{{ .Files.Glob "conf/*" }}`
|
|
227
|
+
*/
|
|
228
|
+
export function filesGlob(pattern: string): HelmTpl {
|
|
229
|
+
return new HelmTpl(`{{ .Files.Glob "${pattern}" }}`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* `.Files.Glob.AsConfig` — Render matched files as ConfigMap data.
|
|
234
|
+
*
|
|
235
|
+
* `filesAsConfig("conf/*")` → `{{ (.Files.Glob "conf/*").AsConfig }}`
|
|
236
|
+
*/
|
|
237
|
+
export function filesAsConfig(pattern: string): HelmTpl {
|
|
238
|
+
return new HelmTpl(`{{ (.Files.Glob "${pattern}").AsConfig }}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* `.Files.Glob.AsSecrets` — Render matched files as Secret data.
|
|
243
|
+
*
|
|
244
|
+
* `filesAsSecrets("conf/*")` → `{{ (.Files.Glob "conf/*").AsSecrets }}`
|
|
245
|
+
*/
|
|
246
|
+
export function filesAsSecrets(pattern: string): HelmTpl {
|
|
247
|
+
return new HelmTpl(`{{ (.Files.Glob "${pattern}").AsSecrets }}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ── Template functions ────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* `include` — Include a named template.
|
|
254
|
+
*
|
|
255
|
+
* `include("my-app.fullname")` → `{{ include "my-app.fullname" . }}`
|
|
256
|
+
*/
|
|
257
|
+
export function include(name: string, ctx: string = "."): HelmTpl {
|
|
258
|
+
return new HelmTpl(`{{ include "${name}" ${ctx} }}`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* `required` — Require a value to be set.
|
|
263
|
+
*
|
|
264
|
+
* `required("msg", values.x)` → `{{ required "msg" .Values.x }}`
|
|
265
|
+
*/
|
|
266
|
+
export function required(msg: string, val: HelmTpl): HelmTpl {
|
|
267
|
+
const inner = extractExpr(val);
|
|
268
|
+
return new HelmTpl(`{{ required "${msg}" ${inner} }}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* `helmDefault` — Provide a default value.
|
|
273
|
+
*
|
|
274
|
+
* `helmDefault("nginx", values.image.repository)` → `{{ default "nginx" .Values.image.repository }}`
|
|
275
|
+
*/
|
|
276
|
+
export function helmDefault(def: string | number | boolean, val: HelmTpl): HelmTpl {
|
|
277
|
+
const inner = extractExpr(val);
|
|
278
|
+
const defStr = typeof def === "string" ? `"${def}"` : String(def);
|
|
279
|
+
return new HelmTpl(`{{ default ${defStr} ${inner} }}`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* `toYaml` — Convert a value to YAML.
|
|
284
|
+
*
|
|
285
|
+
* `toYaml(values.resources)` → `{{ toYaml .Values.resources | nindent 12 }}`
|
|
286
|
+
*/
|
|
287
|
+
export function toYaml(val: HelmTpl, indent?: number): HelmTpl {
|
|
288
|
+
const inner = extractExpr(val);
|
|
289
|
+
if (indent !== undefined) {
|
|
290
|
+
return new HelmTpl(`{{ toYaml ${inner} | nindent ${indent} }}`);
|
|
291
|
+
}
|
|
292
|
+
return new HelmTpl(`{{ toYaml ${inner} }}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* `quote` — Quote a value.
|
|
297
|
+
*
|
|
298
|
+
* `quote(values.x)` → `{{ quote .Values.x }}`
|
|
299
|
+
*/
|
|
300
|
+
export function quote(val: HelmTpl): HelmTpl {
|
|
301
|
+
const inner = extractExpr(val);
|
|
302
|
+
return new HelmTpl(`{{ ${inner} | quote }}`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* `printf` — Format a string.
|
|
307
|
+
*
|
|
308
|
+
* `printf("%s:%s", values.image.repository, values.image.tag)`
|
|
309
|
+
* → `{{ printf "%s:%s" .Values.image.repository .Values.image.tag }}`
|
|
310
|
+
*/
|
|
311
|
+
export function printf(fmt: string, ...args: HelmTpl[]): HelmTpl {
|
|
312
|
+
const argExprs = args.map(extractExpr).join(" ");
|
|
313
|
+
return new HelmTpl(`{{ printf "${fmt}" ${argExprs} }}`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* `tpl` — Evaluate a string as a template.
|
|
318
|
+
*
|
|
319
|
+
* `tpl(values.someTemplate)` → `{{ tpl .Values.someTemplate . }}`
|
|
320
|
+
*/
|
|
321
|
+
export function tpl(template: HelmTpl, ctx: string = "."): HelmTpl {
|
|
322
|
+
const inner = extractExpr(template);
|
|
323
|
+
return new HelmTpl(`{{ tpl ${inner} ${ctx} }}`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* `lookup` — Look up a resource at deploy time.
|
|
328
|
+
*
|
|
329
|
+
* `lookup("v1", "Secret", "ns", "name")` → `{{ lookup "v1" "Secret" "ns" "name" }}`
|
|
330
|
+
*/
|
|
331
|
+
export function lookup(apiVersion: string, kind: string, namespace: string, name: string): HelmTpl {
|
|
332
|
+
return new HelmTpl(`{{ lookup "${apiVersion}" "${kind}" "${namespace}" "${name}" }}`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ── Control flow ──────────────────────────────────────────
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Marker interface for conditional resource wrappers.
|
|
339
|
+
*/
|
|
340
|
+
export interface HelmConditional extends Intrinsic {
|
|
341
|
+
readonly condition: string;
|
|
342
|
+
readonly body: unknown;
|
|
343
|
+
readonly elseBody?: unknown;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* `If` — Conditional resource or value.
|
|
348
|
+
*
|
|
349
|
+
* When used at the resource level (wrapping a Declarable), the serializer
|
|
350
|
+
* wraps the entire YAML document in `{{- if }}` / `{{- end }}`.
|
|
351
|
+
*
|
|
352
|
+
* When used at the value level, it emits inline conditionals.
|
|
353
|
+
*
|
|
354
|
+
* ```ts
|
|
355
|
+
* If(values.ingress.enabled, new Ingress({ ... }))
|
|
356
|
+
* ```
|
|
357
|
+
*/
|
|
358
|
+
export function If(condition: HelmTpl | string, then: unknown, elseBody?: unknown): HelmConditional & { toJSON(): unknown } {
|
|
359
|
+
const condExpr = typeof condition === "string" ? condition : extractExpr(condition);
|
|
360
|
+
return {
|
|
361
|
+
[INTRINSIC_MARKER]: true as const,
|
|
362
|
+
condition: condExpr,
|
|
363
|
+
body: then,
|
|
364
|
+
elseBody,
|
|
365
|
+
toJSON() {
|
|
366
|
+
// Resolve nested ElseIf objects by calling their toJSON()
|
|
367
|
+
const resolvedElse = elseBody !== undefined && typeof elseBody === "object" && elseBody !== null && "toJSON" in elseBody
|
|
368
|
+
? (elseBody as { toJSON(): unknown }).toJSON()
|
|
369
|
+
: elseBody;
|
|
370
|
+
return {
|
|
371
|
+
[HELM_IF_KEY]: condExpr,
|
|
372
|
+
body: then,
|
|
373
|
+
...(resolvedElse !== undefined ? { else: resolvedElse } : {}),
|
|
374
|
+
};
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* `ElseIf` — Chained else-if condition for use inside an `If` else body.
|
|
381
|
+
*
|
|
382
|
+
* The serializer detects when an else body contains a `__helm_if` marker
|
|
383
|
+
* and emits `{{- else if <cond> }}` instead of a nested `{{- else }}\n{{- if }}`.
|
|
384
|
+
*
|
|
385
|
+
* ```ts
|
|
386
|
+
* If(values.tier, "gold",
|
|
387
|
+
* ElseIf(values.backup, "silver", "bronze"))
|
|
388
|
+
* ```
|
|
389
|
+
* → `{{- if .Values.tier }}gold{{- else if .Values.backup }}silver{{- else }}bronze{{- end }}`
|
|
390
|
+
*/
|
|
391
|
+
export function ElseIf(
|
|
392
|
+
condition: HelmTpl | string,
|
|
393
|
+
then: unknown,
|
|
394
|
+
elseBody?: unknown,
|
|
395
|
+
): { toJSON(): unknown } {
|
|
396
|
+
const condExpr = typeof condition === "string" ? condition : extractExpr(condition);
|
|
397
|
+
return {
|
|
398
|
+
toJSON() {
|
|
399
|
+
return {
|
|
400
|
+
[HELM_IF_KEY]: condExpr,
|
|
401
|
+
body: then,
|
|
402
|
+
...(elseBody !== undefined ? { else: elseBody } : {}),
|
|
403
|
+
};
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* `Range` — Iterate over a list at deploy time.
|
|
410
|
+
*
|
|
411
|
+
* ```ts
|
|
412
|
+
* Range(values.hosts, (item) => ({ host: item }))
|
|
413
|
+
* ```
|
|
414
|
+
*/
|
|
415
|
+
export function Range(list: HelmTpl, body: unknown): Intrinsic {
|
|
416
|
+
const listExpr = extractExpr(list);
|
|
417
|
+
return {
|
|
418
|
+
[INTRINSIC_MARKER]: true as const,
|
|
419
|
+
toJSON() {
|
|
420
|
+
return {
|
|
421
|
+
[HELM_RANGE_KEY]: listExpr,
|
|
422
|
+
body,
|
|
423
|
+
};
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* `With` — Scope into a value at deploy time.
|
|
430
|
+
*
|
|
431
|
+
* ```ts
|
|
432
|
+
* With(values.nodeSelector, (ctx) => ctx)
|
|
433
|
+
* ```
|
|
434
|
+
*/
|
|
435
|
+
export function With(scope: HelmTpl, body: unknown): Intrinsic {
|
|
436
|
+
const scopeExpr = extractExpr(scope);
|
|
437
|
+
return {
|
|
438
|
+
[INTRINSIC_MARKER]: true as const,
|
|
439
|
+
toJSON() {
|
|
440
|
+
return {
|
|
441
|
+
[HELM_WITH_KEY]: scopeExpr,
|
|
442
|
+
body,
|
|
443
|
+
};
|
|
444
|
+
},
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ── Resource ordering helpers ─────────────────────────────
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Helm hook annotations for resource ordering.
|
|
452
|
+
*
|
|
453
|
+
* Returns an annotation map for pre-install/pre-upgrade hooks
|
|
454
|
+
* with a numeric weight for ordering.
|
|
455
|
+
*
|
|
456
|
+
* Usage: `metadata: { annotations: { ...withOrder(1) } }`
|
|
457
|
+
*/
|
|
458
|
+
export function withOrder(weight: number): Record<string, string> {
|
|
459
|
+
return {
|
|
460
|
+
"helm.sh/hook": "pre-install,pre-upgrade",
|
|
461
|
+
"helm.sh/hook-weight": String(weight),
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Argo CD sync wave annotation.
|
|
467
|
+
*
|
|
468
|
+
* Returns an annotation map with the Argo CD sync wave number.
|
|
469
|
+
*
|
|
470
|
+
* Usage: `metadata: { annotations: { ...argoWave(2) } }`
|
|
471
|
+
*/
|
|
472
|
+
export function argoWave(wave: number): Record<string, string> {
|
|
473
|
+
return { "argocd.argoproj.io/sync-wave": String(wave) };
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// ── Helpers ───────────────────────────────────────────────
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Extract the Go template expression from a HelmTpl, stripping `{{ }}` wrappers.
|
|
480
|
+
*/
|
|
481
|
+
function extractExpr(val: HelmTpl | string): string {
|
|
482
|
+
if (typeof val === "string") return val;
|
|
483
|
+
return val.expr.replace(/^\{\{-?\s*/, "").replace(/\s*-?\}\}$/, "");
|
|
484
|
+
}
|
|
@@ -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
|
+
}
|