@intentius/chant-lexicon-helm 0.124.31 → 0.124.33
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/dist/archive-render.js +76 -0
- package/dist/codegen/docs-cli.js +3 -0
- package/dist/codegen/docs.js +113 -0
- package/dist/codegen/generate-cli.js +19 -0
- package/dist/codegen/generate.js +210 -0
- package/dist/codegen/naming.js +29 -0
- package/dist/codegen/package.js +44 -0
- package/dist/codegen/values-type.js +87 -0
- package/dist/commands.js +413 -0
- package/dist/components/builders.js +9 -0
- package/dist/components/capability-plugin.js +17 -0
- package/dist/components/helm-upgrade.js +109 -0
- package/dist/components/index.js +11 -0
- package/dist/composites/catalog.js +1119 -0
- package/dist/composites/helm-batch-job.js +148 -0
- package/dist/composites/helm-crd-lifecycle.js +145 -0
- package/dist/composites/helm-cron-job.js +130 -0
- package/dist/composites/helm-daemon-set.js +125 -0
- package/dist/composites/helm-external-secret.js +63 -0
- package/dist/composites/helm-library.js +24 -0
- package/dist/composites/helm-microservice.js +260 -0
- package/dist/composites/helm-monitored-service.js +188 -0
- package/dist/composites/helm-namespace-env.js +116 -0
- package/dist/composites/helm-secure-ingress.js +86 -0
- package/dist/composites/helm-stateful-service.js +160 -0
- package/dist/composites/helm-web-app.js +201 -0
- package/dist/composites/helm-worker.js +164 -0
- package/dist/composites/index.js +13 -0
- package/dist/config.js +161 -0
- package/dist/coverage.js +37 -0
- package/dist/deep-observe-hooks.js +19 -0
- package/dist/deep-observe.js +55 -0
- package/dist/describe-resources.js +119 -0
- package/dist/describe-stack-status.js +66 -0
- package/dist/detect.js +11 -0
- package/dist/generated/index.js +11 -0
- package/dist/helpers.js +84 -0
- package/dist/import/generator.js +292 -0
- package/dist/import/parser.js +141 -0
- package/dist/import/template-stripper.js +86 -0
- package/dist/index.js +55 -0
- package/dist/integrity.json +2 -2
- package/dist/intrinsics.js +482 -0
- package/dist/lint/audit-catalog.js +33 -0
- package/dist/lint/audit-lineage.js +53 -0
- package/dist/lint/post-synth/helm-helpers.js +60 -0
- package/dist/lint/post-synth/index.js +48 -0
- package/dist/lint/post-synth/whm005-no-empty-wrapper.js +47 -0
- package/dist/lint/post-synth/whm101.js +38 -0
- package/dist/lint/post-synth/whm102.js +26 -0
- package/dist/lint/post-synth/whm103.js +51 -0
- package/dist/lint/post-synth/whm104.js +29 -0
- package/dist/lint/post-synth/whm105.js +23 -0
- package/dist/lint/post-synth/whm201.js +29 -0
- package/dist/lint/post-synth/whm202.js +42 -0
- package/dist/lint/post-synth/whm203.js +32 -0
- package/dist/lint/post-synth/whm204.js +51 -0
- package/dist/lint/post-synth/whm301.js +31 -0
- package/dist/lint/post-synth/whm302.js +33 -0
- package/dist/lint/post-synth/whm401.js +52 -0
- package/dist/lint/post-synth/whm402.js +38 -0
- package/dist/lint/post-synth/whm403.js +38 -0
- package/dist/lint/post-synth/whm404.js +30 -0
- package/dist/lint/post-synth/whm405.js +47 -0
- package/dist/lint/post-synth/whm406.js +27 -0
- package/dist/lint/post-synth/whm407.js +72 -0
- package/dist/lint/post-synth/whm501.js +89 -0
- package/dist/lint/post-synth/whm502.js +85 -0
- package/dist/lint/post-synth/whm503.js +82 -0
- package/dist/lint/post-synth/whm504.js +47 -0
- package/dist/lint/rules/chart-metadata.js +53 -0
- package/dist/lint/rules/no-hardcoded-image.js +50 -0
- package/dist/lint/rules/values-no-helm-tpl.js +78 -0
- package/dist/lint/rules/values-no-secrets.js +65 -0
- package/dist/list-artifacts.js +134 -0
- package/dist/lsp/completions.d.ts.map +1 -1
- package/dist/lsp/completions.js +18 -0
- package/dist/lsp/hover.d.ts.map +1 -1
- package/dist/lsp/hover.js +40 -0
- package/dist/manifest.json +1 -1
- package/dist/op/activities/cluster-probe.js +116 -0
- package/dist/op/activities/helm.js +449 -0
- package/dist/op/activities/index.js +10 -0
- package/dist/op/builders.js +45 -0
- package/dist/package-cli.js +21 -0
- package/dist/pinnability/actions.js +207 -0
- package/dist/pinnability/classify.js +416 -0
- package/dist/pinnability/conditions.js +0 -0
- package/dist/pinnability/index.js +16 -0
- package/dist/pinnability/localize.js +518 -0
- package/dist/pinnability/render-stream.js +53 -0
- package/dist/pinnability/values.js +151 -0
- package/dist/plugin.js +458 -0
- package/dist/project-codegen.js +215 -0
- package/dist/release-observe.js +287 -0
- package/dist/render-diff.js +279 -0
- package/dist/render-digest.js +0 -0
- package/dist/render-records.js +29 -0
- package/dist/render-store.js +401 -0
- package/dist/render-wrapper.js +164 -0
- package/dist/render.js +394 -0
- package/dist/resources.js +109 -0
- package/dist/serializer.js +903 -0
- package/dist/validate-cli.js +17 -0
- package/dist/validate.js +30 -0
- package/dist/values-probe.js +504 -0
- package/dist/wrapper-chart.js +95 -0
- package/package.json +11 -11
- package/src/codegen/package.ts +2 -2
- package/src/lsp/completions.ts +2 -1
- package/src/lsp/hover.ts +2 -1
package/dist/render.js
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HelmRender — render an upstream Helm chart at chant build time.
|
|
3
|
+
*
|
|
4
|
+
* Most chant projects that want to install third-party operators (ESO,
|
|
5
|
+
* cert-manager, ingress-nginx, etc.) ran `helm template` or `helm install`
|
|
6
|
+
* as a separate deploy phase. That meant the chant build output was
|
|
7
|
+
* incomplete — `kubectl apply -f dist/...yaml` didn't carry those operators.
|
|
8
|
+
*
|
|
9
|
+
* `HelmRender({ repo, chart, version, values })` resolves at synth time:
|
|
10
|
+
* 1. Shells out to `helm template` (requires the `helm` binary in PATH).
|
|
11
|
+
* 2. Parses the resulting multi-document YAML.
|
|
12
|
+
* 3. Emits each rendered K8s manifest as a Declarable in the build output.
|
|
13
|
+
* 4. Caches the rendered output under `~/.chant/helm-renders/<hash>/`
|
|
14
|
+
* keyed by (repo, chart, version, values) so subsequent builds skip
|
|
15
|
+
* network access.
|
|
16
|
+
*
|
|
17
|
+
* The lexicon must include both `helm` and `k8s` (since rendered manifests
|
|
18
|
+
* are k8s resources).
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* import { HelmRender } from "@intentius/chant-lexicon-helm";
|
|
22
|
+
*
|
|
23
|
+
* export const eso = HelmRender({
|
|
24
|
+
* name: "external-secrets",
|
|
25
|
+
* repo: "https://charts.external-secrets.io",
|
|
26
|
+
* chart: "external-secrets",
|
|
27
|
+
* version: "0.10.4",
|
|
28
|
+
* namespace: "external-secrets",
|
|
29
|
+
* createNamespace: true,
|
|
30
|
+
* values: { installCRDs: true },
|
|
31
|
+
* });
|
|
32
|
+
*/
|
|
33
|
+
import { execFileSync } from "node:child_process";
|
|
34
|
+
import { createHash } from "node:crypto";
|
|
35
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
36
|
+
import { tmpdir } from "node:os";
|
|
37
|
+
import { join } from "node:path";
|
|
38
|
+
import { Composite } from "@intentius/chant";
|
|
39
|
+
// Use the k8s lexicon's Deployment as a generic Declarable wrapper for
|
|
40
|
+
// arbitrary K8s manifests. The k8s serializer reads props.apiVersion and
|
|
41
|
+
// props.kind verbatim when set, so the actual class doesn't matter.
|
|
42
|
+
import { Deployment } from "@intentius/chant-lexicon-k8s/generated/index";
|
|
43
|
+
import { applyRenderedPatches } from "@intentius/chant-lexicon-k8s/patch/rendered-patch";
|
|
44
|
+
import yaml from "js-yaml";
|
|
45
|
+
import { resolveCapabilityProfile } from "./config.js";
|
|
46
|
+
import { helmContentDigest, helmInputDigest } from "./render-digest.js";
|
|
47
|
+
import { findRenderByCacheKey, loadRenderContent, persistHelmRender, renderCacheKey, renderStoreRoot, } from "./render-store.js";
|
|
48
|
+
import { probeCoalescedValues } from "./values-probe.js";
|
|
49
|
+
import { recordHelmRender } from "./render-records.js";
|
|
50
|
+
// The invocation-record ledger lives in ./render-records.ts — a module with
|
|
51
|
+
// no runtime imports — so WHM503 (edge-imported via the post-synth barrel)
|
|
52
|
+
// can read it without pulling this module's heavy static graph. Re-exported
|
|
53
|
+
// here to keep the lexicon's public surface unchanged.
|
|
54
|
+
export { getHelmRenderRecords, clearHelmRenderRecords } from "./render-records.js";
|
|
55
|
+
/**
|
|
56
|
+
* Root of the legacy unpinned cache — the same directory (and the same
|
|
57
|
+
* `CHANT_HELM_RENDER_ROOT` override) as the content-addressed render store
|
|
58
|
+
* (#2035). Before this the unprofiled path hardcoded `~/.chant/helm-renders`
|
|
59
|
+
* and honoured no environment variable, so a hermetic or air-gapped build
|
|
60
|
+
* had no way to hand it a pre-rendered chart short of pre-warming `$HOME`.
|
|
61
|
+
* A function, not a module-level constant, so the override is read when a
|
|
62
|
+
* render happens rather than when this module loaded.
|
|
63
|
+
*/
|
|
64
|
+
function cacheRoot() {
|
|
65
|
+
return renderStoreRoot();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Legacy cache key — truncated, unprefixed, input-derived. Since #1238 this
|
|
69
|
+
* keys only *unpinned* renders (and names the values tempfile): pinned
|
|
70
|
+
* renders live in the content-addressed store (./render-store.ts) under
|
|
71
|
+
* their full `sha256:` contentDigest, with the inputs index providing cache
|
|
72
|
+
* hits. Existing truncated-hash entries stay where they are — a new root,
|
|
73
|
+
* not a migration.
|
|
74
|
+
*/
|
|
75
|
+
function cacheKey(props, profile) {
|
|
76
|
+
const stable = JSON.stringify({
|
|
77
|
+
repo: props.repo,
|
|
78
|
+
chart: props.chart,
|
|
79
|
+
version: props.version,
|
|
80
|
+
namespace: props.namespace ?? null,
|
|
81
|
+
values: props.values ?? null,
|
|
82
|
+
// Only present for pinned renders (which no longer read this cache but
|
|
83
|
+
// still name their values tempfile with this key). A pinned render must
|
|
84
|
+
// never reuse an unpinned render's bytes (or another profile's) — the
|
|
85
|
+
// profile is a real render input (#1235).
|
|
86
|
+
...(profile
|
|
87
|
+
? {
|
|
88
|
+
capabilityProfile: {
|
|
89
|
+
name: profile.name,
|
|
90
|
+
kubeVersion: profile.kubeVersion,
|
|
91
|
+
apiVersions: profile.apiVersions ?? [],
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
: {}),
|
|
95
|
+
});
|
|
96
|
+
return createHash("sha256").update(stable).digest("hex").slice(0, 16);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Version of the local helm binary, for the persisted `RenderManifest`.
|
|
100
|
+
* Load-bearing there — the defaulted kube version is a property of the
|
|
101
|
+
* binary, so this field explains a digest mismatch between machines. Never
|
|
102
|
+
* fails a build: `"unknown"` when helm cannot answer.
|
|
103
|
+
*/
|
|
104
|
+
function helmBinaryVersion() {
|
|
105
|
+
try {
|
|
106
|
+
const out = execFileSync("helm", ["version", "--template", "{{.Version}}"], {
|
|
107
|
+
encoding: "utf8",
|
|
108
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
109
|
+
});
|
|
110
|
+
return out.trim().split("\n")[0] || "unknown";
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return "unknown";
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function renderViaHelm(props, profile) {
|
|
117
|
+
// Write values overrides to a tempfile if any.
|
|
118
|
+
let valuesArgs = [];
|
|
119
|
+
if (props.values && Object.keys(props.values).length > 0) {
|
|
120
|
+
const valuesPath = join(tmpdir(), `chant-helm-values-${cacheKey(props, profile)}.yaml`);
|
|
121
|
+
writeFileSync(valuesPath, yaml.dump(props.values));
|
|
122
|
+
valuesArgs = ["--values", valuesPath];
|
|
123
|
+
}
|
|
124
|
+
// When `repo` is set, helm fetches the chart by name+version from the repo.
|
|
125
|
+
// When `repo` is absent, treat `chart` as a local path.
|
|
126
|
+
const fetchArgs = [];
|
|
127
|
+
if (props.repo) {
|
|
128
|
+
fetchArgs.push("--repo", props.repo);
|
|
129
|
+
if (props.version)
|
|
130
|
+
fetchArgs.push("--version", props.version);
|
|
131
|
+
}
|
|
132
|
+
// When --repo is set, isolate helm's repository config + cache to a
|
|
133
|
+
// chant-private directory. Without this, helm tries to refresh ALL of the
|
|
134
|
+
// user's existing repo indexes (eks, jetstack, etc.) and fails with
|
|
135
|
+
// "no cached repo found" if any one has gone stale — even though we
|
|
136
|
+
// only care about the single repo the consumer asked for.
|
|
137
|
+
const isolationArgs = props.repo
|
|
138
|
+
? [
|
|
139
|
+
"--repository-config",
|
|
140
|
+
"/dev/null",
|
|
141
|
+
"--repository-cache",
|
|
142
|
+
join(cacheRoot(), "_helm-repo-cache"),
|
|
143
|
+
]
|
|
144
|
+
: [];
|
|
145
|
+
const args = [
|
|
146
|
+
"template",
|
|
147
|
+
props.name,
|
|
148
|
+
props.chart,
|
|
149
|
+
// Without this, `helm template` silently drops manifests shipped in the
|
|
150
|
+
// chart's (or a subchart's) crds/ directory.
|
|
151
|
+
"--include-crds",
|
|
152
|
+
// Pin .Capabilities to the declared cluster profile. Without these, the
|
|
153
|
+
// kube version defaults to one baked into the helm binary and APIVersions
|
|
154
|
+
// is empty — both silently, both making the rendered bytes a function of
|
|
155
|
+
// the local toolchain (#1235).
|
|
156
|
+
...(profile
|
|
157
|
+
? [
|
|
158
|
+
"--kube-version",
|
|
159
|
+
profile.kubeVersion,
|
|
160
|
+
...(profile.apiVersions ?? []).flatMap((v) => ["--api-versions", v]),
|
|
161
|
+
]
|
|
162
|
+
: []),
|
|
163
|
+
...fetchArgs,
|
|
164
|
+
...isolationArgs,
|
|
165
|
+
...(props.namespace ? ["--namespace", props.namespace] : []),
|
|
166
|
+
...valuesArgs,
|
|
167
|
+
];
|
|
168
|
+
try {
|
|
169
|
+
const out = execFileSync("helm", args, {
|
|
170
|
+
encoding: "utf8",
|
|
171
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
172
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
173
|
+
});
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
const stderr = err && typeof err === "object" && "stderr" in err
|
|
178
|
+
? String(err.stderr)
|
|
179
|
+
: String(err);
|
|
180
|
+
throw new Error(`HelmRender failed for ${props.repo}/${props.chart}@${props.version}:\n${stderr}\n` +
|
|
181
|
+
`Hint: ensure the 'helm' CLI is on PATH (helm version) and the chart is reachable.`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function loadOrRender(props, profile) {
|
|
185
|
+
if (props.noCache) {
|
|
186
|
+
return renderViaHelm(props, profile);
|
|
187
|
+
}
|
|
188
|
+
const cacheDir = join(cacheRoot(), cacheKey(props, profile));
|
|
189
|
+
const cachePath = join(cacheDir, "manifests.yaml");
|
|
190
|
+
if (existsSync(cachePath)) {
|
|
191
|
+
return readFileSync(cachePath, "utf8");
|
|
192
|
+
}
|
|
193
|
+
const out = renderViaHelm(props, profile);
|
|
194
|
+
try {
|
|
195
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
196
|
+
writeFileSync(cachePath, out);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// Cache write failure is non-fatal — the render is still in memory.
|
|
200
|
+
}
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Best-effort coalesced-values probe (#1251) for a `HelmRender` invocation.
|
|
205
|
+
*
|
|
206
|
+
* The probe (`./values-probe.ts`) needs the chart source on disk — the same
|
|
207
|
+
* requirement the pinnability classifier (#1234) has — so it only runs for
|
|
208
|
+
* a local chart (`props.repo` unset, `props.chart` a directory with a
|
|
209
|
+
* `Chart.yaml`). A repo-fetched chart's source is never kept on disk here,
|
|
210
|
+
* so it gets no probe, the same v1 gate `render.ts`'s digest recording
|
|
211
|
+
* already documents.
|
|
212
|
+
*
|
|
213
|
+
* Never fatal: a probe failure (a chart the probe's template injection
|
|
214
|
+
* can't render, `helm` briefly unavailable, ...) must not break the actual
|
|
215
|
+
* render it is only observing. `undefined` means "no probe result" — never
|
|
216
|
+
* fabricated, and WHM504 simply has nothing to report for this render.
|
|
217
|
+
*/
|
|
218
|
+
function runValuesProbeBestEffort(props) {
|
|
219
|
+
if (props.repo)
|
|
220
|
+
return undefined;
|
|
221
|
+
if (!existsSync(join(props.chart, "Chart.yaml")))
|
|
222
|
+
return undefined;
|
|
223
|
+
try {
|
|
224
|
+
return probeCoalescedValues({
|
|
225
|
+
chartDir: props.chart,
|
|
226
|
+
name: props.name,
|
|
227
|
+
releaseName: props.name,
|
|
228
|
+
namespace: props.namespace,
|
|
229
|
+
supplied: props.values && Object.keys(props.values).length > 0
|
|
230
|
+
? [{ origin: "supplied file", values: props.values }]
|
|
231
|
+
: [],
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
// Non-fatal — see the docstring above. The main render already ran (or
|
|
236
|
+
// is about to) via its own `helm template` invocation; this extra,
|
|
237
|
+
// probe-only render observing the same chart is never load-bearing.
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* The pinned render path (#1238): the content-addressed store is both the
|
|
243
|
+
* cache and the durable record.
|
|
244
|
+
*
|
|
245
|
+
* Read: unless `noCache` (or `persist: false`) is set, the full-inputs
|
|
246
|
+
* cache key resolves through the store's inputs index to canonical bytes a
|
|
247
|
+
* previous identical render stored — semantically identical to helm's
|
|
248
|
+
* output (canonicalization only normalizes render noise) and byte-stable
|
|
249
|
+
* under re-canonicalization, so digests recompute identically.
|
|
250
|
+
*
|
|
251
|
+
* Write: a fresh render persists { canonical bytes, RenderManifest } unless
|
|
252
|
+
* the caller opted out — the manifest is written alongside every pinned
|
|
253
|
+
* render by default. `persist: true` with `noCache` still persists (a
|
|
254
|
+
* forced-fresh render is still a durable artifact); a persist failure on
|
|
255
|
+
* the default path is non-fatal like a legacy cache-write failure, but an
|
|
256
|
+
* explicit `persist: true` surfaces it.
|
|
257
|
+
*/
|
|
258
|
+
function loadOrRenderPinned(props, profile, valuesProbe) {
|
|
259
|
+
const chartRef = props.repo ? `${props.repo}/${props.chart}` : props.chart;
|
|
260
|
+
const storeRead = !props.noCache && props.persist !== false;
|
|
261
|
+
const storeWrite = props.persist === true || (!props.noCache && props.persist !== false);
|
|
262
|
+
const key = renderCacheKey({
|
|
263
|
+
chart: chartRef,
|
|
264
|
+
chartVersion: props.version,
|
|
265
|
+
releaseName: props.name,
|
|
266
|
+
namespace: props.namespace,
|
|
267
|
+
values: props.values,
|
|
268
|
+
capabilityProfile: profile,
|
|
269
|
+
});
|
|
270
|
+
if (storeRead) {
|
|
271
|
+
const hit = findRenderByCacheKey(key);
|
|
272
|
+
if (hit) {
|
|
273
|
+
const content = loadRenderContent(hit.contentDigest);
|
|
274
|
+
if (content !== undefined)
|
|
275
|
+
return content;
|
|
276
|
+
// Index points at pruned/corrupt content — fall through and re-render.
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const out = renderViaHelm(props, profile);
|
|
280
|
+
if (storeWrite) {
|
|
281
|
+
try {
|
|
282
|
+
persistHelmRender({
|
|
283
|
+
rendered: out,
|
|
284
|
+
releaseName: props.name,
|
|
285
|
+
chart: props.chart,
|
|
286
|
+
repo: props.repo || undefined,
|
|
287
|
+
chartVersion: props.version,
|
|
288
|
+
namespace: props.namespace,
|
|
289
|
+
values: props.values,
|
|
290
|
+
capabilityProfile: profile,
|
|
291
|
+
helmVersion: helmBinaryVersion(),
|
|
292
|
+
sourceRef: props.sourceRef,
|
|
293
|
+
coalescedValuesDigest: valuesProbe?.digest,
|
|
294
|
+
valueSources: valuesProbe?.valueSources,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
catch (err) {
|
|
298
|
+
if (props.persist === true)
|
|
299
|
+
throw err;
|
|
300
|
+
// Default-on persistence degrades like a legacy cache-write failure:
|
|
301
|
+
// the render is still in memory and the build goes on.
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return out;
|
|
305
|
+
}
|
|
306
|
+
function parseMultiDoc(text) {
|
|
307
|
+
const docs = yaml.loadAll(text);
|
|
308
|
+
return docs
|
|
309
|
+
.filter((d) => d !== null && typeof d === "object")
|
|
310
|
+
.filter((d) => d.kind && d.apiVersion);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Sanitize an arbitrary string into a valid TS/JS identifier suffix.
|
|
314
|
+
* Used to derive Composite Members keys from manifest kind+name pairs.
|
|
315
|
+
*/
|
|
316
|
+
function safeKey(input) {
|
|
317
|
+
return input.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
318
|
+
}
|
|
319
|
+
export const HelmRender = Composite((props) => {
|
|
320
|
+
// Resolve first: a named profile the config does not declare must fail the
|
|
321
|
+
// build here, before any helm invocation could silently render against the
|
|
322
|
+
// binary's default capabilities.
|
|
323
|
+
const profile = props.capabilityProfile !== undefined ? resolveCapabilityProfile(props.capabilityProfile) : undefined;
|
|
324
|
+
if (!profile && props.persist === true) {
|
|
325
|
+
throw new Error(`HelmRender "${props.name}": persist requires a pinned render, and this one is unpinned ` +
|
|
326
|
+
`(no capabilityProfile declared). An unpinned render's bytes are a function of the local ` +
|
|
327
|
+
`helm binary's defaulted capabilities, so they have no stable content identity to store ` +
|
|
328
|
+
`under. Declare capabilityProfile to pin the render (#1235), or drop persist.`);
|
|
329
|
+
}
|
|
330
|
+
// Build-time coalesced-values probe (#1251/#1252). Runs (best-effort) for
|
|
331
|
+
// a pinned render of a local chart — the same gate the digests below use,
|
|
332
|
+
// since the probe needs the chart source on disk and only a pinned render
|
|
333
|
+
// has a RenderManifest to carry its digest. `probeCoalescedValues` records
|
|
334
|
+
// itself into the WHM504 surface (`getValuesProbeRecords`) as part of
|
|
335
|
+
// running — no separate `recordValuesProbe` call needed here, and any
|
|
336
|
+
// dead assignment it found is worth reporting whether or not this render
|
|
337
|
+
// ultimately persists.
|
|
338
|
+
const valuesProbe = profile ? runValuesProbeBestEffort(props) : undefined;
|
|
339
|
+
const yamlText = profile ? loadOrRenderPinned(props, profile, valuesProbe) : loadOrRender(props, undefined);
|
|
340
|
+
const docs = applyRenderedPatches(parseMultiDoc(yamlText), props.patches, `HelmRender "${props.name}" (${props.chart}${props.version ? `@${props.version}` : ""})`);
|
|
341
|
+
// Digests are recorded only for pinned renders (#1237). Profile presence
|
|
342
|
+
// is the v1 gate: the classifier (#1234) needs the chart source on disk,
|
|
343
|
+
// which a repo-fetched render does not keep, so the gate here is the
|
|
344
|
+
// declared-inputs property, not a template analysis. An unpinned render's
|
|
345
|
+
// digest would be a function of the local helm binary — meaningless as an
|
|
346
|
+
// identity — so unpinned renders record neither digest.
|
|
347
|
+
const digests = profile
|
|
348
|
+
? {
|
|
349
|
+
inputDigest: helmInputDigest({
|
|
350
|
+
// Same chart-reference convention helmInstall digests: a local
|
|
351
|
+
// path stays itself; a repo-fetched chart is `<repo-url>/<chart>`.
|
|
352
|
+
chart: props.repo ? `${props.repo}/${props.chart}` : props.chart,
|
|
353
|
+
chartVersion: props.version,
|
|
354
|
+
values: props.values ?? {},
|
|
355
|
+
capabilityProfile: {
|
|
356
|
+
kubeVersion: profile.kubeVersion,
|
|
357
|
+
apiVersions: profile.apiVersions,
|
|
358
|
+
},
|
|
359
|
+
}),
|
|
360
|
+
contentDigest: helmContentDigest(yamlText),
|
|
361
|
+
...(valuesProbe ? { coalescedValuesDigest: valuesProbe.digest } : {}),
|
|
362
|
+
}
|
|
363
|
+
: {};
|
|
364
|
+
recordHelmRender({
|
|
365
|
+
name: props.name,
|
|
366
|
+
chart: props.chart,
|
|
367
|
+
version: props.version,
|
|
368
|
+
capabilityProfile: profile,
|
|
369
|
+
...digests,
|
|
370
|
+
});
|
|
371
|
+
const out = {};
|
|
372
|
+
if (props.createNamespace && props.namespace) {
|
|
373
|
+
out["__namespace"] = new Deployment({
|
|
374
|
+
apiVersion: "v1",
|
|
375
|
+
kind: "Namespace",
|
|
376
|
+
metadata: { name: props.namespace },
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
const usedKeys = new Set();
|
|
380
|
+
for (let i = 0; i < docs.length; i++) {
|
|
381
|
+
const doc = docs[i];
|
|
382
|
+
const kind = doc.kind ?? "Unknown";
|
|
383
|
+
const name = doc.metadata?.name ?? `doc${i}`;
|
|
384
|
+
let key = safeKey(`${kind}_${name}`);
|
|
385
|
+
// Disambiguate on collision (e.g. same kind+name across docs).
|
|
386
|
+
let collisionN = 2;
|
|
387
|
+
while (usedKeys.has(key)) {
|
|
388
|
+
key = `${safeKey(`${kind}_${name}`)}_${collisionN++}`;
|
|
389
|
+
}
|
|
390
|
+
usedKeys.add(key);
|
|
391
|
+
out[key] = new Deployment(doc);
|
|
392
|
+
}
|
|
393
|
+
return out;
|
|
394
|
+
}, "HelmRender");
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helm-specific resource and property types.
|
|
3
|
+
*
|
|
4
|
+
* Created via createResource/createProperty from @intentius/chant/runtime.
|
|
5
|
+
* These are static types (no upstream schema to fetch).
|
|
6
|
+
*/
|
|
7
|
+
import { createResource, createProperty } from "@intentius/chant/runtime";
|
|
8
|
+
const LEXICON = "helm";
|
|
9
|
+
// ── Resources ─────────────────────────────────────────────
|
|
10
|
+
/**
|
|
11
|
+
* Helm::Chart — Chart.yaml metadata.
|
|
12
|
+
*
|
|
13
|
+
* Defines the chart's identity: apiVersion, name, version, appVersion,
|
|
14
|
+
* description, type (application|library), keywords, maintainers, dependencies.
|
|
15
|
+
*/
|
|
16
|
+
export const Chart = createResource("Helm::Chart", LEXICON, {});
|
|
17
|
+
/**
|
|
18
|
+
* Helm::Values — Typed values definition.
|
|
19
|
+
*
|
|
20
|
+
* The props passed to the constructor become the default values emitted
|
|
21
|
+
* in values.yaml. The serializer also generates values.schema.json from
|
|
22
|
+
* the value types.
|
|
23
|
+
*/
|
|
24
|
+
export const Values = createResource("Helm::Values", LEXICON, {});
|
|
25
|
+
/**
|
|
26
|
+
* Helm::Test — Helm test pod.
|
|
27
|
+
*
|
|
28
|
+
* Annotated with `helm.sh/hook: test` in the serialized output.
|
|
29
|
+
* Props should contain a K8s Pod spec.
|
|
30
|
+
*/
|
|
31
|
+
export const HelmTest = createResource("Helm::Test", LEXICON, {});
|
|
32
|
+
/**
|
|
33
|
+
* Helm::Notes — NOTES.txt template content.
|
|
34
|
+
*
|
|
35
|
+
* The `content` prop is emitted as templates/NOTES.txt.
|
|
36
|
+
*/
|
|
37
|
+
export const HelmNotes = createResource("Helm::Notes", LEXICON, {});
|
|
38
|
+
// ── Property types ────────────────────────────────────────
|
|
39
|
+
/**
|
|
40
|
+
* Helm::Hook — Lifecycle hook annotation.
|
|
41
|
+
*
|
|
42
|
+
* Wraps a K8s resource with helm.sh/hook annotations.
|
|
43
|
+
* Props: { hook: string, weight?: number, deletePolicy?: string, resource: Declarable }
|
|
44
|
+
*/
|
|
45
|
+
export const HelmHook = createProperty("Helm::Hook", LEXICON);
|
|
46
|
+
/**
|
|
47
|
+
* Helm::Dependency — Chart dependency entry.
|
|
48
|
+
*
|
|
49
|
+
* Props: { name, version, repository, condition?, tags?, enabled?, importValues?, alias? }
|
|
50
|
+
*/
|
|
51
|
+
export const HelmDependency = createProperty("Helm::Dependency", LEXICON);
|
|
52
|
+
/**
|
|
53
|
+
* Helm::Maintainer — Chart maintainer entry.
|
|
54
|
+
*
|
|
55
|
+
* Props: { name, email?, url? }
|
|
56
|
+
*/
|
|
57
|
+
export const HelmMaintainer = createProperty("Helm::Maintainer", LEXICON);
|
|
58
|
+
/**
|
|
59
|
+
* Helm::ValuesOverride — Generate a named YAML override file.
|
|
60
|
+
*
|
|
61
|
+
* Emits a standalone YAML file inside the chart directory, intended to be
|
|
62
|
+
* passed as `helm upgrade -f chart-dir/values-base.yaml`. Use this for
|
|
63
|
+
* static configuration shared across all deployments (disabled bundled
|
|
64
|
+
* services, shared secret references, etc.).
|
|
65
|
+
*
|
|
66
|
+
* Props: { filename: string, values: Record<string, unknown> }
|
|
67
|
+
*
|
|
68
|
+
* ```ts
|
|
69
|
+
* new ValuesOverride({
|
|
70
|
+
* filename: "values-base",
|
|
71
|
+
* values: { postgresql: { install: false }, redis: { install: false } },
|
|
72
|
+
* })
|
|
73
|
+
* // → generates chart-dir/values-base.yaml
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export const ValuesOverride = createResource("Helm::ValuesOverride", LEXICON, {});
|
|
77
|
+
// ── CRD ──────────────────────────────────────────────────
|
|
78
|
+
/**
|
|
79
|
+
* Helm::CRD — Custom Resource Definition placed in the crds/ directory.
|
|
80
|
+
*
|
|
81
|
+
* Props: { content: string, filename?: string }
|
|
82
|
+
*/
|
|
83
|
+
export const HelmCRD = createResource("Helm::CRD", LEXICON, {});
|
|
84
|
+
// ── K8s resource types used by composites ────────────────
|
|
85
|
+
// These are thin wrappers so composite members are Declarable.
|
|
86
|
+
const K8S = "k8s";
|
|
87
|
+
export const Deployment = createResource("K8s::Apps::Deployment", K8S, {});
|
|
88
|
+
export const StatefulSet = createResource("K8s::Apps::StatefulSet", K8S, {});
|
|
89
|
+
export const DaemonSet = createResource("K8s::Apps::DaemonSet", K8S, {});
|
|
90
|
+
export const Service = createResource("K8s::Core::Service", K8S, {});
|
|
91
|
+
export const ServiceAccount = createResource("K8s::Core::ServiceAccount", K8S, {});
|
|
92
|
+
export const ConfigMap = createResource("K8s::Core::ConfigMap", K8S, {});
|
|
93
|
+
export const Namespace = createResource("K8s::Core::Namespace", K8S, {});
|
|
94
|
+
export const Job = createResource("K8s::Batch::Job", K8S, {});
|
|
95
|
+
export const CronJob = createResource("K8s::Batch::CronJob", K8S, {});
|
|
96
|
+
export const Ingress = createResource("K8s::Networking::Ingress", K8S, {});
|
|
97
|
+
export const NetworkPolicy = createResource("K8s::Networking::NetworkPolicy", K8S, {});
|
|
98
|
+
export const HPA = createResource("K8s::Autoscaling::HorizontalPodAutoscaler", K8S, {});
|
|
99
|
+
export const PDB = createResource("K8s::Policy::PodDisruptionBudget", K8S, {});
|
|
100
|
+
export const ResourceQuota = createResource("K8s::Core::ResourceQuota", K8S, {});
|
|
101
|
+
export const LimitRange = createResource("K8s::Core::LimitRange", K8S, {});
|
|
102
|
+
export const ClusterRole = createResource("K8s::Rbac::ClusterRole", K8S, {});
|
|
103
|
+
export const ClusterRoleBinding = createResource("K8s::Rbac::ClusterRoleBinding", K8S, {});
|
|
104
|
+
export const Role = createResource("K8s::Rbac::Role", K8S, {});
|
|
105
|
+
export const RoleBinding = createResource("K8s::Rbac::RoleBinding", K8S, {});
|
|
106
|
+
export const ExternalSecret = createResource("K8s::ExternalSecrets::ExternalSecret", K8S, {});
|
|
107
|
+
export const ServiceMonitor = createResource("K8s::Monitoring::ServiceMonitor", K8S, {});
|
|
108
|
+
export const PrometheusRule = createResource("K8s::Monitoring::PrometheusRule", K8S, {});
|
|
109
|
+
export const Certificate = createResource("K8s::CertManager::Certificate", K8S, {});
|