@intentius/chant-lexicon-helm 0.46.0 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/archive-render.d.ts +94 -0
- package/dist/archive-render.d.ts.map +1 -0
- package/dist/commands.d.ts +54 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/config.d.ts +135 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/deep-observe-hooks.d.ts +2 -0
- package/dist/deep-observe-hooks.d.ts.map +1 -0
- package/dist/deep-observe.d.ts +54 -0
- package/dist/deep-observe.d.ts.map +1 -0
- package/dist/describe-resources.d.ts +17 -0
- package/dist/describe-resources.d.ts.map +1 -0
- package/dist/index.d.ts +18 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +5 -3
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/whm503.d.ts +34 -0
- package/dist/lint/post-synth/whm503.d.ts.map +1 -0
- package/dist/lint/post-synth/whm504.d.ts +20 -0
- package/dist/lint/post-synth/whm504.d.ts.map +1 -0
- package/dist/manifest.json +1 -1
- package/dist/okf/index.md +2 -0
- package/dist/okf/rules/WHM503.md +15 -0
- package/dist/okf/rules/WHM504.md +11 -0
- package/dist/okf/types/Values.md +1 -0
- package/dist/op/activities/cluster-probe.d.ts +44 -0
- package/dist/op/activities/cluster-probe.d.ts.map +1 -0
- package/dist/op/activities/helm.d.ts +179 -5
- package/dist/op/activities/helm.d.ts.map +1 -1
- package/dist/op/activities/index.d.ts +4 -2
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/pinnability/actions.d.ts +76 -0
- package/dist/pinnability/actions.d.ts.map +1 -0
- package/dist/pinnability/classify.d.ts +129 -0
- package/dist/pinnability/classify.d.ts.map +1 -0
- package/dist/pinnability/conditions.d.ts +109 -0
- package/dist/pinnability/conditions.d.ts.map +1 -0
- package/dist/pinnability/index.d.ts +17 -0
- package/dist/pinnability/index.d.ts.map +1 -0
- package/dist/pinnability/localize.d.ts +124 -0
- package/dist/pinnability/localize.d.ts.map +1 -0
- package/dist/pinnability/render-stream.d.ts +27 -0
- package/dist/pinnability/render-stream.d.ts.map +1 -0
- package/dist/pinnability/values.d.ts +69 -0
- package/dist/pinnability/values.d.ts.map +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/release-observe.d.ts +109 -0
- package/dist/release-observe.d.ts.map +1 -0
- package/dist/render-diff.d.ts +192 -0
- package/dist/render-diff.d.ts.map +1 -0
- package/dist/render-digest.d.ts +135 -0
- package/dist/render-digest.d.ts.map +1 -0
- package/dist/render-store.d.ts +310 -0
- package/dist/render-store.d.ts.map +1 -0
- package/dist/render-wrapper.d.ts +140 -0
- package/dist/render-wrapper.d.ts.map +1 -0
- package/dist/render.d.ts +81 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/rules/whm503.ts +101 -0
- package/dist/rules/whm504.ts +52 -0
- package/dist/skills/chant-helm.md +7 -0
- package/dist/values-probe.d.ts +177 -0
- package/dist/values-probe.d.ts.map +1 -0
- package/dist/wrapper-chart.d.ts +61 -0
- package/dist/wrapper-chart.d.ts.map +1 -0
- package/package.json +10 -4
- package/src/archive-render.test.ts +201 -0
- package/src/archive-render.ts +119 -0
- package/src/commands.test.ts +487 -0
- package/src/commands.ts +445 -0
- package/src/config.test.ts +130 -0
- package/src/config.ts +220 -0
- package/src/deep-observe-hooks.ts +20 -0
- package/src/deep-observe.test.ts +165 -0
- package/src/deep-observe.ts +121 -0
- package/src/describe-resources.test.ts +255 -0
- package/src/describe-resources.ts +198 -0
- package/src/index.ts +98 -2
- package/src/lifecycle-integration.test.ts +289 -0
- package/src/lint/audit-catalog.ts +2 -0
- package/src/lint/post-synth/index.ts +4 -0
- package/src/lint/post-synth/whm503.test.ts +134 -0
- package/src/lint/post-synth/whm503.ts +101 -0
- package/src/lint/post-synth/whm504.test.ts +105 -0
- package/src/lint/post-synth/whm504.ts +52 -0
- package/src/op/activities/cluster-probe.test.ts +66 -0
- package/src/op/activities/cluster-probe.ts +148 -0
- package/src/op/activities/helm.test.ts +824 -0
- package/src/op/activities/helm.ts +628 -5
- package/src/op/activities/index.ts +17 -2
- package/src/pinnability/actions.ts +263 -0
- package/src/pinnability/classify.ts +579 -0
- package/src/pinnability/conditions.ts +0 -0
- package/src/pinnability/index.ts +77 -0
- package/src/pinnability/localize.test.ts +234 -0
- package/src/pinnability/localize.ts +644 -0
- package/src/pinnability/pinnability.test.ts +560 -0
- package/src/pinnability/render-stream.ts +59 -0
- package/src/pinnability/values.ts +205 -0
- package/src/plugin.test.ts +3 -1
- package/src/plugin.ts +47 -0
- package/src/release-observe.ts +392 -0
- package/src/render-diff.test.ts +366 -0
- package/src/render-diff.ts +407 -0
- package/src/render-digest.test.ts +186 -0
- package/src/render-digest.ts +207 -0
- package/src/render-store.test.ts +412 -0
- package/src/render-store.ts +621 -0
- package/src/render-wrapper.test.ts +259 -0
- package/src/render-wrapper.ts +274 -0
- package/src/render.test.ts +383 -4
- package/src/render.ts +319 -8
- package/src/skills/chant-helm.md +7 -0
- package/src/values-probe.test.ts +400 -0
- package/src/values-probe.ts +627 -0
- package/src/wrapper-chart.test.ts +110 -0
- package/src/wrapper-chart.ts +124 -0
package/src/render.ts
CHANGED
|
@@ -44,6 +44,16 @@ import { Composite } from "@intentius/chant";
|
|
|
44
44
|
import { Deployment } from "@intentius/chant-lexicon-k8s/generated/index";
|
|
45
45
|
import yaml from "js-yaml";
|
|
46
46
|
|
|
47
|
+
import { resolveCapabilityProfile, type HelmCapabilityProfile, type HelmCapabilityProfileRef } from "./config";
|
|
48
|
+
import { helmContentDigest, helmInputDigest } from "./render-digest";
|
|
49
|
+
import {
|
|
50
|
+
findRenderByCacheKey,
|
|
51
|
+
loadRenderContent,
|
|
52
|
+
persistHelmRender,
|
|
53
|
+
renderCacheKey,
|
|
54
|
+
} from "./render-store";
|
|
55
|
+
import { probeCoalescedValues, type CoalescedValuesProbe } from "./values-probe";
|
|
56
|
+
|
|
47
57
|
export interface HelmRenderProps {
|
|
48
58
|
/** Logical name for the render (used in cache key + composite name). */
|
|
49
59
|
name: string;
|
|
@@ -64,6 +74,95 @@ export interface HelmRenderProps {
|
|
|
64
74
|
* fresh render.
|
|
65
75
|
*/
|
|
66
76
|
noCache?: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Capability profile the render is pinned against (#1235, epic #1228).
|
|
79
|
+
*
|
|
80
|
+
* A string names a profile declared in `chant.config.ts`'s
|
|
81
|
+
* `helm.capabilityProfiles` (per cluster — see `./config.ts`); an inline
|
|
82
|
+
* object carries the same facts directly. When set, `helm template` runs
|
|
83
|
+
* with `--kube-version` and `--api-versions` from the profile, so
|
|
84
|
+
* `.Capabilities` reflects the declared cluster instead of whatever the
|
|
85
|
+
* helm binary defaults to. A named profile the config does not declare is
|
|
86
|
+
* an error at synth, never a silent fallback. Absent, rendering is
|
|
87
|
+
* unpinned — exactly today's behavior.
|
|
88
|
+
*/
|
|
89
|
+
capabilityProfile?: HelmCapabilityProfileRef;
|
|
90
|
+
/**
|
|
91
|
+
* Whether this render persists to the content-addressed render store
|
|
92
|
+
* (#1238) — canonical bytes under their `contentDigest` plus a
|
|
93
|
+
* `RenderManifest`, in `~/.chant/helm-renders/sha256-<hex>/` (see
|
|
94
|
+
* ./render-store.ts).
|
|
95
|
+
*
|
|
96
|
+
* Only a pinned render (capabilityProfile present) can persist — an
|
|
97
|
+
* unpinned render has no content identity, and `persist: true` on one is
|
|
98
|
+
* a synth error naming that reason. For pinned renders the default
|
|
99
|
+
* follows the cache knob: persistence is on unless `noCache` is set
|
|
100
|
+
* (`noCache: true` + `persist: true` forces a fresh render that is still
|
|
101
|
+
* persisted; `persist: false` turns the store off entirely).
|
|
102
|
+
*/
|
|
103
|
+
persist?: boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Source ref/commit to record in the persisted `RenderManifest`, when the
|
|
106
|
+
* caller has one. Never resolved implicitly and never fabricated — absent
|
|
107
|
+
* means the manifest records `sourceRef: null`.
|
|
108
|
+
*/
|
|
109
|
+
sourceRef?: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* What one `HelmRender` invocation recorded about itself. `capabilityProfile`
|
|
114
|
+
* is the profile identity the render was pinned against; `undefined` means
|
|
115
|
+
* the render was unpinned and its bytes depend on the local helm binary's
|
|
116
|
+
* defaults.
|
|
117
|
+
*
|
|
118
|
+
* Pinned renders (profile present — the v1 gate, see #1237) also carry the
|
|
119
|
+
* digest pair:
|
|
120
|
+
*
|
|
121
|
+
* - `inputDigest` — `sha256:` over the canonical JSON of the declared inputs
|
|
122
|
+
* (chart reference, version, values, capability facts). Shared with the
|
|
123
|
+
* release-ledger digest #1243 records on deploy, via `helmInputDigest`.
|
|
124
|
+
* Answers "same inputs?" without touching any bytes.
|
|
125
|
+
* - `contentDigest` — `sha256:` over the canonical rendered bytes
|
|
126
|
+
* (`canonicalizeRender`). The artifact identity: answers "same bytes on
|
|
127
|
+
* the cluster?".
|
|
128
|
+
*
|
|
129
|
+
* They diverge exactly when the render is not a function of its declared
|
|
130
|
+
* inputs — `renderStability` in ./render-digest.ts names that.
|
|
131
|
+
*
|
|
132
|
+
* Unpinned renders record neither digest. Their bytes are a function of the
|
|
133
|
+
* local helm binary's defaulted capabilities, so a digest over them would
|
|
134
|
+
* assert an identity the render does not have — it would differ across
|
|
135
|
+
* machines that did nothing differently, and equal digests would still
|
|
136
|
+
* prove nothing about a cluster. No digest is the honest record.
|
|
137
|
+
*/
|
|
138
|
+
export interface HelmRenderRecord {
|
|
139
|
+
/** The render's logical name (`HelmRenderProps.name`) — also the helm release name baked into the bytes. */
|
|
140
|
+
name: string;
|
|
141
|
+
chart: string;
|
|
142
|
+
version?: string;
|
|
143
|
+
capabilityProfile?: HelmCapabilityProfile;
|
|
144
|
+
/** Input-side identity (#1237/#1243). Present only for pinned renders. */
|
|
145
|
+
inputDigest?: string;
|
|
146
|
+
/** Content-side identity over canonical rendered bytes (#1237). Present only for pinned renders. */
|
|
147
|
+
contentDigest?: string;
|
|
148
|
+
/**
|
|
149
|
+
* The build-time coalesced-values probe's digest (#1251), present only
|
|
150
|
+
* when the probe ran — a pinned render of a local chart (the probe needs
|
|
151
|
+
* the chart source on disk, so a repo-fetched chart never gets one).
|
|
152
|
+
*/
|
|
153
|
+
coalescedValuesDigest?: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const renderRecords: HelmRenderRecord[] = [];
|
|
157
|
+
|
|
158
|
+
/** Every render recorded in this process, in invocation order. */
|
|
159
|
+
export function getHelmRenderRecords(): readonly HelmRenderRecord[] {
|
|
160
|
+
return renderRecords;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Reset the record list (test isolation). */
|
|
164
|
+
export function clearHelmRenderRecords(): void {
|
|
165
|
+
renderRecords.length = 0;
|
|
67
166
|
}
|
|
68
167
|
|
|
69
168
|
interface RenderedDoc {
|
|
@@ -75,22 +174,61 @@ interface RenderedDoc {
|
|
|
75
174
|
|
|
76
175
|
const CACHE_ROOT = join(homedir(), ".chant", "helm-renders");
|
|
77
176
|
|
|
78
|
-
|
|
177
|
+
/**
|
|
178
|
+
* Legacy cache key — truncated, unprefixed, input-derived. Since #1238 this
|
|
179
|
+
* keys only *unpinned* renders (and names the values tempfile): pinned
|
|
180
|
+
* renders live in the content-addressed store (./render-store.ts) under
|
|
181
|
+
* their full `sha256:` contentDigest, with the inputs index providing cache
|
|
182
|
+
* hits. Existing truncated-hash entries stay where they are — a new root,
|
|
183
|
+
* not a migration.
|
|
184
|
+
*/
|
|
185
|
+
function cacheKey(props: HelmRenderProps, profile?: HelmCapabilityProfile): string {
|
|
79
186
|
const stable = JSON.stringify({
|
|
80
187
|
repo: props.repo,
|
|
81
188
|
chart: props.chart,
|
|
82
189
|
version: props.version,
|
|
83
190
|
namespace: props.namespace ?? null,
|
|
84
191
|
values: props.values ?? null,
|
|
192
|
+
// Only present for pinned renders (which no longer read this cache but
|
|
193
|
+
// still name their values tempfile with this key). A pinned render must
|
|
194
|
+
// never reuse an unpinned render's bytes (or another profile's) — the
|
|
195
|
+
// profile is a real render input (#1235).
|
|
196
|
+
...(profile
|
|
197
|
+
? {
|
|
198
|
+
capabilityProfile: {
|
|
199
|
+
name: profile.name,
|
|
200
|
+
kubeVersion: profile.kubeVersion,
|
|
201
|
+
apiVersions: profile.apiVersions ?? [],
|
|
202
|
+
},
|
|
203
|
+
}
|
|
204
|
+
: {}),
|
|
85
205
|
});
|
|
86
206
|
return createHash("sha256").update(stable).digest("hex").slice(0, 16);
|
|
87
207
|
}
|
|
88
208
|
|
|
89
|
-
|
|
209
|
+
/**
|
|
210
|
+
* Version of the local helm binary, for the persisted `RenderManifest`.
|
|
211
|
+
* Load-bearing there — the defaulted kube version is a property of the
|
|
212
|
+
* binary, so this field explains a digest mismatch between machines. Never
|
|
213
|
+
* fails a build: `"unknown"` when helm cannot answer.
|
|
214
|
+
*/
|
|
215
|
+
function helmBinaryVersion(): string {
|
|
216
|
+
try {
|
|
217
|
+
const out = execFileSync("helm", ["version", "--template", "{{.Version}}"], {
|
|
218
|
+
encoding: "utf8",
|
|
219
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
220
|
+
});
|
|
221
|
+
return out.trim().split("\n")[0] || "unknown";
|
|
222
|
+
} catch {
|
|
223
|
+
return "unknown";
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function renderViaHelm(props: HelmRenderProps, profile?: HelmCapabilityProfile): string {
|
|
90
228
|
// Write values overrides to a tempfile if any.
|
|
91
229
|
let valuesArgs: string[] = [];
|
|
92
230
|
if (props.values && Object.keys(props.values).length > 0) {
|
|
93
|
-
const valuesPath = join(tmpdir(), `chant-helm-values-${cacheKey(props)}.yaml`);
|
|
231
|
+
const valuesPath = join(tmpdir(), `chant-helm-values-${cacheKey(props, profile)}.yaml`);
|
|
94
232
|
writeFileSync(valuesPath, yaml.dump(props.values));
|
|
95
233
|
valuesArgs = ["--values", valuesPath];
|
|
96
234
|
}
|
|
@@ -124,6 +262,17 @@ function renderViaHelm(props: HelmRenderProps): string {
|
|
|
124
262
|
// Without this, `helm template` silently drops manifests shipped in the
|
|
125
263
|
// chart's (or a subchart's) crds/ directory.
|
|
126
264
|
"--include-crds",
|
|
265
|
+
// Pin .Capabilities to the declared cluster profile. Without these, the
|
|
266
|
+
// kube version defaults to one baked into the helm binary and APIVersions
|
|
267
|
+
// is empty — both silently, both making the rendered bytes a function of
|
|
268
|
+
// the local toolchain (#1235).
|
|
269
|
+
...(profile
|
|
270
|
+
? [
|
|
271
|
+
"--kube-version",
|
|
272
|
+
profile.kubeVersion,
|
|
273
|
+
...(profile.apiVersions ?? []).flatMap((v) => ["--api-versions", v]),
|
|
274
|
+
]
|
|
275
|
+
: []),
|
|
127
276
|
...fetchArgs,
|
|
128
277
|
...isolationArgs,
|
|
129
278
|
...(props.namespace ? ["--namespace", props.namespace] : []),
|
|
@@ -149,16 +298,16 @@ function renderViaHelm(props: HelmRenderProps): string {
|
|
|
149
298
|
}
|
|
150
299
|
}
|
|
151
300
|
|
|
152
|
-
function loadOrRender(props: HelmRenderProps): string {
|
|
301
|
+
function loadOrRender(props: HelmRenderProps, profile?: HelmCapabilityProfile): string {
|
|
153
302
|
if (props.noCache) {
|
|
154
|
-
return renderViaHelm(props);
|
|
303
|
+
return renderViaHelm(props, profile);
|
|
155
304
|
}
|
|
156
|
-
const cacheDir = join(CACHE_ROOT, cacheKey(props));
|
|
305
|
+
const cacheDir = join(CACHE_ROOT, cacheKey(props, profile));
|
|
157
306
|
const cachePath = join(cacheDir, "manifests.yaml");
|
|
158
307
|
if (existsSync(cachePath)) {
|
|
159
308
|
return readFileSync(cachePath, "utf8");
|
|
160
309
|
}
|
|
161
|
-
const out = renderViaHelm(props);
|
|
310
|
+
const out = renderViaHelm(props, profile);
|
|
162
311
|
try {
|
|
163
312
|
mkdirSync(cacheDir, { recursive: true });
|
|
164
313
|
writeFileSync(cachePath, out);
|
|
@@ -168,6 +317,112 @@ function loadOrRender(props: HelmRenderProps): string {
|
|
|
168
317
|
return out;
|
|
169
318
|
}
|
|
170
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Best-effort coalesced-values probe (#1251) for a `HelmRender` invocation.
|
|
322
|
+
*
|
|
323
|
+
* The probe (`./values-probe.ts`) needs the chart source on disk — the same
|
|
324
|
+
* requirement the pinnability classifier (#1234) has — so it only runs for
|
|
325
|
+
* a local chart (`props.repo` unset, `props.chart` a directory with a
|
|
326
|
+
* `Chart.yaml`). A repo-fetched chart's source is never kept on disk here,
|
|
327
|
+
* so it gets no probe, the same v1 gate `render.ts`'s digest recording
|
|
328
|
+
* already documents.
|
|
329
|
+
*
|
|
330
|
+
* Never fatal: a probe failure (a chart the probe's template injection
|
|
331
|
+
* can't render, `helm` briefly unavailable, ...) must not break the actual
|
|
332
|
+
* render it is only observing. `undefined` means "no probe result" — never
|
|
333
|
+
* fabricated, and WHM504 simply has nothing to report for this render.
|
|
334
|
+
*/
|
|
335
|
+
function runValuesProbeBestEffort(props: HelmRenderProps): CoalescedValuesProbe | undefined {
|
|
336
|
+
if (props.repo) return undefined;
|
|
337
|
+
if (!existsSync(join(props.chart, "Chart.yaml"))) return undefined;
|
|
338
|
+
try {
|
|
339
|
+
return probeCoalescedValues({
|
|
340
|
+
chartDir: props.chart,
|
|
341
|
+
name: props.name,
|
|
342
|
+
releaseName: props.name,
|
|
343
|
+
namespace: props.namespace,
|
|
344
|
+
supplied:
|
|
345
|
+
props.values && Object.keys(props.values).length > 0
|
|
346
|
+
? [{ origin: "supplied file", values: props.values }]
|
|
347
|
+
: [],
|
|
348
|
+
});
|
|
349
|
+
} catch {
|
|
350
|
+
// Non-fatal — see the docstring above. The main render already ran (or
|
|
351
|
+
// is about to) via its own `helm template` invocation; this extra,
|
|
352
|
+
// probe-only render observing the same chart is never load-bearing.
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* The pinned render path (#1238): the content-addressed store is both the
|
|
359
|
+
* cache and the durable record.
|
|
360
|
+
*
|
|
361
|
+
* Read: unless `noCache` (or `persist: false`) is set, the full-inputs
|
|
362
|
+
* cache key resolves through the store's inputs index to canonical bytes a
|
|
363
|
+
* previous identical render stored — semantically identical to helm's
|
|
364
|
+
* output (canonicalization only normalizes render noise) and byte-stable
|
|
365
|
+
* under re-canonicalization, so digests recompute identically.
|
|
366
|
+
*
|
|
367
|
+
* Write: a fresh render persists { canonical bytes, RenderManifest } unless
|
|
368
|
+
* the caller opted out — the manifest is written alongside every pinned
|
|
369
|
+
* render by default. `persist: true` with `noCache` still persists (a
|
|
370
|
+
* forced-fresh render is still a durable artifact); a persist failure on
|
|
371
|
+
* the default path is non-fatal like a legacy cache-write failure, but an
|
|
372
|
+
* explicit `persist: true` surfaces it.
|
|
373
|
+
*/
|
|
374
|
+
function loadOrRenderPinned(
|
|
375
|
+
props: HelmRenderProps,
|
|
376
|
+
profile: HelmCapabilityProfile,
|
|
377
|
+
valuesProbe?: CoalescedValuesProbe,
|
|
378
|
+
): string {
|
|
379
|
+
const chartRef = props.repo ? `${props.repo}/${props.chart}` : props.chart;
|
|
380
|
+
const storeRead = !props.noCache && props.persist !== false;
|
|
381
|
+
const storeWrite = props.persist === true || (!props.noCache && props.persist !== false);
|
|
382
|
+
const key = renderCacheKey({
|
|
383
|
+
chart: chartRef,
|
|
384
|
+
chartVersion: props.version,
|
|
385
|
+
releaseName: props.name,
|
|
386
|
+
namespace: props.namespace,
|
|
387
|
+
values: props.values,
|
|
388
|
+
capabilityProfile: profile,
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
if (storeRead) {
|
|
392
|
+
const hit = findRenderByCacheKey(key);
|
|
393
|
+
if (hit) {
|
|
394
|
+
const content = loadRenderContent(hit.contentDigest);
|
|
395
|
+
if (content !== undefined) return content;
|
|
396
|
+
// Index points at pruned/corrupt content — fall through and re-render.
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const out = renderViaHelm(props, profile);
|
|
401
|
+
if (storeWrite) {
|
|
402
|
+
try {
|
|
403
|
+
persistHelmRender({
|
|
404
|
+
rendered: out,
|
|
405
|
+
releaseName: props.name,
|
|
406
|
+
chart: props.chart,
|
|
407
|
+
repo: props.repo || undefined,
|
|
408
|
+
chartVersion: props.version,
|
|
409
|
+
namespace: props.namespace,
|
|
410
|
+
values: props.values,
|
|
411
|
+
capabilityProfile: profile,
|
|
412
|
+
helmVersion: helmBinaryVersion(),
|
|
413
|
+
sourceRef: props.sourceRef,
|
|
414
|
+
coalescedValuesDigest: valuesProbe?.digest,
|
|
415
|
+
valueSources: valuesProbe?.valueSources,
|
|
416
|
+
});
|
|
417
|
+
} catch (err) {
|
|
418
|
+
if (props.persist === true) throw err;
|
|
419
|
+
// Default-on persistence degrades like a legacy cache-write failure:
|
|
420
|
+
// the render is still in memory and the build goes on.
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return out;
|
|
424
|
+
}
|
|
425
|
+
|
|
171
426
|
function parseMultiDoc(text: string): RenderedDoc[] {
|
|
172
427
|
const docs = yaml.loadAll(text);
|
|
173
428
|
return docs
|
|
@@ -184,9 +439,65 @@ function safeKey(input: string): string {
|
|
|
184
439
|
}
|
|
185
440
|
|
|
186
441
|
export const HelmRender = Composite<HelmRenderProps>((props) => {
|
|
187
|
-
|
|
442
|
+
// Resolve first: a named profile the config does not declare must fail the
|
|
443
|
+
// build here, before any helm invocation could silently render against the
|
|
444
|
+
// binary's default capabilities.
|
|
445
|
+
const profile = props.capabilityProfile !== undefined ? resolveCapabilityProfile(props.capabilityProfile) : undefined;
|
|
446
|
+
|
|
447
|
+
if (!profile && props.persist === true) {
|
|
448
|
+
throw new Error(
|
|
449
|
+
`HelmRender "${props.name}": persist requires a pinned render, and this one is unpinned ` +
|
|
450
|
+
`(no capabilityProfile declared). An unpinned render's bytes are a function of the local ` +
|
|
451
|
+
`helm binary's defaulted capabilities, so they have no stable content identity to store ` +
|
|
452
|
+
`under. Declare capabilityProfile to pin the render (#1235), or drop persist.`,
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Build-time coalesced-values probe (#1251/#1252). Runs (best-effort) for
|
|
457
|
+
// a pinned render of a local chart — the same gate the digests below use,
|
|
458
|
+
// since the probe needs the chart source on disk and only a pinned render
|
|
459
|
+
// has a RenderManifest to carry its digest. `probeCoalescedValues` records
|
|
460
|
+
// itself into the WHM504 surface (`getValuesProbeRecords`) as part of
|
|
461
|
+
// running — no separate `recordValuesProbe` call needed here, and any
|
|
462
|
+
// dead assignment it found is worth reporting whether or not this render
|
|
463
|
+
// ultimately persists.
|
|
464
|
+
const valuesProbe = profile ? runValuesProbeBestEffort(props) : undefined;
|
|
465
|
+
|
|
466
|
+
const yamlText = profile ? loadOrRenderPinned(props, profile, valuesProbe) : loadOrRender(props, undefined);
|
|
188
467
|
const docs = parseMultiDoc(yamlText);
|
|
189
468
|
|
|
469
|
+
// Digests are recorded only for pinned renders (#1237). Profile presence
|
|
470
|
+
// is the v1 gate: the classifier (#1234) needs the chart source on disk,
|
|
471
|
+
// which a repo-fetched render does not keep, so the gate here is the
|
|
472
|
+
// declared-inputs property, not a template analysis. An unpinned render's
|
|
473
|
+
// digest would be a function of the local helm binary — meaningless as an
|
|
474
|
+
// identity — so unpinned renders record neither digest.
|
|
475
|
+
const digests = profile
|
|
476
|
+
? {
|
|
477
|
+
inputDigest: helmInputDigest({
|
|
478
|
+
// Same chart-reference convention helmInstall digests: a local
|
|
479
|
+
// path stays itself; a repo-fetched chart is `<repo-url>/<chart>`.
|
|
480
|
+
chart: props.repo ? `${props.repo}/${props.chart}` : props.chart,
|
|
481
|
+
chartVersion: props.version,
|
|
482
|
+
values: props.values ?? {},
|
|
483
|
+
capabilityProfile: {
|
|
484
|
+
kubeVersion: profile.kubeVersion,
|
|
485
|
+
apiVersions: profile.apiVersions,
|
|
486
|
+
},
|
|
487
|
+
}),
|
|
488
|
+
contentDigest: helmContentDigest(yamlText),
|
|
489
|
+
...(valuesProbe ? { coalescedValuesDigest: valuesProbe.digest } : {}),
|
|
490
|
+
}
|
|
491
|
+
: {};
|
|
492
|
+
|
|
493
|
+
renderRecords.push({
|
|
494
|
+
name: props.name,
|
|
495
|
+
chart: props.chart,
|
|
496
|
+
version: props.version,
|
|
497
|
+
capabilityProfile: profile,
|
|
498
|
+
...digests,
|
|
499
|
+
});
|
|
500
|
+
|
|
190
501
|
const out: Record<string, InstanceType<typeof Deployment>> = {};
|
|
191
502
|
|
|
192
503
|
if (props.createNamespace && props.namespace) {
|
package/src/skills/chant-helm.md
CHANGED
|
@@ -289,6 +289,13 @@ const lifecycle = HelmCRDLifecycle({
|
|
|
289
289
|
| WHM407 | post-synth | Secret with inline data |
|
|
290
290
|
| WHM501 | post-synth | Unused values keys |
|
|
291
291
|
| WHM502 | post-synth | Deprecated K8s API versions |
|
|
292
|
+
| WHM503 | pinned-render | Pinned render artifact carries populated Secret data/stringData — use `runtimeSlot()` or `HelmExternalSecret` |
|
|
293
|
+
|
|
294
|
+
WHM503 is a different stage from the rest: it scans the STORED, rendered
|
|
295
|
+
artifact a pinned `HelmRender` writes to the render store (#1238), not the
|
|
296
|
+
chart source `WHM005`-`WHM502` validate. `WHM407` catches an inline literal
|
|
297
|
+
in a chart TEMPLATE before rendering; WHM503 catches a resolved credential
|
|
298
|
+
that made it into a durable, promotable artifact.
|
|
292
299
|
|
|
293
300
|
## OCI registry workflow
|
|
294
301
|
|