@intentius/chant-lexicon-helm 0.53.1 → 0.54.1
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/integrity.json +3 -3
- package/dist/lint/post-synth/whm503.d.ts +1 -1
- package/dist/lint/post-synth/whm503.d.ts.map +1 -1
- package/dist/list-artifacts.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/render-records.d.ts +67 -0
- package/dist/render-records.d.ts.map +1 -0
- package/dist/render.d.ts +2 -48
- package/dist/render.d.ts.map +1 -1
- package/dist/rules/whm503.ts +5 -2
- package/package.json +2 -2
- package/src/lint/post-synth/whm503.ts +5 -2
- package/src/list-artifacts.test.ts +101 -0
- package/src/list-artifacts.ts +58 -0
- package/src/render-records.ts +79 -0
- package/src/render.test.ts +34 -1
- package/src/render.ts +23 -60
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "2dafdf3357b31ae4e71eb1a9d231ab393023c53d6a8c418b6eb2d4f4653e44c9",
|
|
5
5
|
"meta.json": "14243c5730a07c6a6edc35ddd351438547d58df5cf345f2233a355b0c7611ccc",
|
|
6
6
|
"types/index.d.ts": "5377696ca8698cd2999e4680feb8e8e4b54a7b49fb603a87b2f27356114d1794",
|
|
7
7
|
"rules/chart-metadata.ts": "8f3377e893d5e2828460b7fe5924fca098334245a9a2fdb90f6b67e490eaf091",
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
"rules/whm407.ts": "200190f5de2d86d2bcc5aee0b5ce4807919ec727b1fc14576ea17b9b3073838b",
|
|
31
31
|
"rules/whm501.ts": "e6afa7c0eface9820380bf189fe8be58f4a6997dd5b74f76335022650616d080",
|
|
32
32
|
"rules/whm502.ts": "c3ed7b4b5215d96e46eba758aca2171928d8f8fb1eb20452ea6b4393672755b0",
|
|
33
|
-
"rules/whm503.ts": "
|
|
33
|
+
"rules/whm503.ts": "84c2c4d3e261d12a1dbeee974af02d512ccdc5c0495119c0d6f997619db019dd",
|
|
34
34
|
"rules/whm504.ts": "15c03b47c271270eee93de45310ff6dfc2665dfe221d457665a638cfd9637005",
|
|
35
35
|
"skills/chant-helm.md": "828a169f9275c58df0a079287befb14c450ad8a92257e3cb6960450221941973",
|
|
36
36
|
"skills/chant-helm-patterns.md": "9e79e6a46391da46709d8aa57e2825a7cd9eb981cd923f02ad60836c49b2561e",
|
|
37
37
|
"skills/chant-helm-security.md": "bfc367eabceed2e84f1cf94501b407df78aeed963cec104f24a321d0962063c9"
|
|
38
38
|
},
|
|
39
|
-
"composite": "
|
|
39
|
+
"composite": "35076abd609245fabdb6d5e6067e82654eba0b56b705c5b0c0eb96de8d9c403f"
|
|
40
40
|
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* non-empty.
|
|
20
20
|
*/
|
|
21
21
|
import type { PostSynthCheck, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
22
|
-
import type { HelmRenderRecord } from "../../render.js";
|
|
22
|
+
import type { HelmRenderRecord } from "../../render-records.js";
|
|
23
23
|
/**
|
|
24
24
|
* Scan a set of `HelmRenderRecord`s for pinned renders whose stored artifact
|
|
25
25
|
* carries a `Secret` document with populated `data`/`stringData`. Exported
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"whm503.d.ts","sourceRoot":"","sources":["../../../src/lint/post-synth/whm503.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAoB,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"whm503.d.ts","sourceRoot":"","sources":["../../../src/lint/post-synth/whm503.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAoB,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAM9G,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAc7D;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,SAAS,gBAAgB,EAAE,EACpC,IAAI,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACvB,mBAAmB,EAAE,CAwCvB;AAED,eAAO,MAAM,MAAM,EAAE,cAUpB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-artifacts.d.ts","sourceRoot":"","sources":["../src/list-artifacts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"list-artifacts.d.ts","sourceRoot":"","sources":["../src/list-artifacts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AA4DjE,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;CAC/E,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CA0E5C"}
|
package/dist/manifest.json
CHANGED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The in-process ledger of `HelmRender` invocations — split out of
|
|
3
|
+
* `./render.ts` so a post-synth check can read it without importing the
|
|
4
|
+
* render machinery (#1979 follow-up, found by the hosted audit's bump).
|
|
5
|
+
*
|
|
6
|
+
* WHM503 (`./lint/post-synth/whm503.ts`) scans these records, and post-synth
|
|
7
|
+
* barrels are edge-imported by the hosted audit service: everything they
|
|
8
|
+
* reach through static imports must be safe to initialize on workerd.
|
|
9
|
+
* `render.ts` is not — it imports the full `@intentius/chant` barrel, whose
|
|
10
|
+
* static graph reaches the TypeScript compiler (fold-import), and
|
|
11
|
+
* TypeScript's own module init touches `__filename`, which crashes the
|
|
12
|
+
* worker at startup. This module carries only the record shape and the
|
|
13
|
+
* ledger, imports nothing but a type, and keeps the barrel edge-clean;
|
|
14
|
+
* `render.ts` re-exports it all, so the lexicon's public surface is
|
|
15
|
+
* unchanged.
|
|
16
|
+
*/
|
|
17
|
+
import type { HelmCapabilityProfile } from "./config.js";
|
|
18
|
+
/**
|
|
19
|
+
* What one `HelmRender` invocation recorded about itself. `capabilityProfile`
|
|
20
|
+
* is the profile identity the render was pinned against; `undefined` means
|
|
21
|
+
* the render was unpinned and its bytes depend on the local helm binary's
|
|
22
|
+
* defaults.
|
|
23
|
+
*
|
|
24
|
+
* Pinned renders (profile present — the v1 gate, see #1237) also carry the
|
|
25
|
+
* digest pair:
|
|
26
|
+
*
|
|
27
|
+
* - `inputDigest` — `sha256:` over the canonical JSON of the declared inputs
|
|
28
|
+
* (chart reference, version, values, capability facts). Shared with the
|
|
29
|
+
* release-ledger digest #1243 records on deploy, via `helmInputDigest`.
|
|
30
|
+
* Answers "same inputs?" without touching any bytes.
|
|
31
|
+
* - `contentDigest` — `sha256:` over the canonical rendered bytes
|
|
32
|
+
* (`canonicalizeRender`). The artifact identity: answers "same bytes on
|
|
33
|
+
* the cluster?".
|
|
34
|
+
*
|
|
35
|
+
* They diverge exactly when the render is not a function of its declared
|
|
36
|
+
* inputs — `renderStability` in ./render-digest.ts names that.
|
|
37
|
+
*
|
|
38
|
+
* Unpinned renders record neither digest. Their bytes are a function of the
|
|
39
|
+
* local helm binary's defaulted capabilities, so a digest over them would
|
|
40
|
+
* assert an identity the render does not have — it would differ across
|
|
41
|
+
* machines that did nothing differently, and equal digests would still
|
|
42
|
+
* prove nothing about a cluster. No digest is the honest record.
|
|
43
|
+
*/
|
|
44
|
+
export interface HelmRenderRecord {
|
|
45
|
+
/** The render's logical name (`HelmRenderProps.name`) — also the helm release name baked into the bytes. */
|
|
46
|
+
name: string;
|
|
47
|
+
chart: string;
|
|
48
|
+
version?: string;
|
|
49
|
+
capabilityProfile?: HelmCapabilityProfile;
|
|
50
|
+
/** Input-side identity (#1237/#1243). Present only for pinned renders. */
|
|
51
|
+
inputDigest?: string;
|
|
52
|
+
/** Content-side identity over canonical rendered bytes (#1237). Present only for pinned renders. */
|
|
53
|
+
contentDigest?: string;
|
|
54
|
+
/**
|
|
55
|
+
* The build-time coalesced-values probe's digest (#1251), present only
|
|
56
|
+
* when the probe ran — a pinned render of a local chart (the probe needs
|
|
57
|
+
* the chart source on disk, so a repo-fetched chart never gets one).
|
|
58
|
+
*/
|
|
59
|
+
coalescedValuesDigest?: string;
|
|
60
|
+
}
|
|
61
|
+
/** Append one invocation's record (called by `HelmRender`, ./render.ts). */
|
|
62
|
+
export declare function recordHelmRender(record: HelmRenderRecord): void;
|
|
63
|
+
/** Every render recorded in this process, in invocation order. */
|
|
64
|
+
export declare function getHelmRenderRecords(): readonly HelmRenderRecord[];
|
|
65
|
+
/** Reset the record list (test isolation). */
|
|
66
|
+
export declare function clearHelmRenderRecords(): void;
|
|
67
|
+
//# sourceMappingURL=render-records.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-records.d.ts","sourceRoot":"","sources":["../src/render-records.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4GAA4G;IAC5G,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB,CAAC,EAAE,qBAAqB,CAAC;IAC1C,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oGAAoG;IACpG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAID,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAE/D;AAED,kEAAkE;AAClE,wBAAgB,oBAAoB,IAAI,SAAS,gBAAgB,EAAE,CAElE;AAED,8CAA8C;AAC9C,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C"}
|
package/dist/render.d.ts
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* values: { installCRDs: true },
|
|
31
31
|
* });
|
|
32
32
|
*/
|
|
33
|
-
import { type
|
|
33
|
+
import { type HelmCapabilityProfileRef } from "./config.js";
|
|
34
34
|
export interface HelmRenderProps {
|
|
35
35
|
/** Logical name for the render (used in cache key + composite name). */
|
|
36
36
|
name: string;
|
|
@@ -85,52 +85,6 @@ export interface HelmRenderProps {
|
|
|
85
85
|
*/
|
|
86
86
|
sourceRef?: string;
|
|
87
87
|
}
|
|
88
|
-
|
|
89
|
-
* What one `HelmRender` invocation recorded about itself. `capabilityProfile`
|
|
90
|
-
* is the profile identity the render was pinned against; `undefined` means
|
|
91
|
-
* the render was unpinned and its bytes depend on the local helm binary's
|
|
92
|
-
* defaults.
|
|
93
|
-
*
|
|
94
|
-
* Pinned renders (profile present — the v1 gate, see #1237) also carry the
|
|
95
|
-
* digest pair:
|
|
96
|
-
*
|
|
97
|
-
* - `inputDigest` — `sha256:` over the canonical JSON of the declared inputs
|
|
98
|
-
* (chart reference, version, values, capability facts). Shared with the
|
|
99
|
-
* release-ledger digest #1243 records on deploy, via `helmInputDigest`.
|
|
100
|
-
* Answers "same inputs?" without touching any bytes.
|
|
101
|
-
* - `contentDigest` — `sha256:` over the canonical rendered bytes
|
|
102
|
-
* (`canonicalizeRender`). The artifact identity: answers "same bytes on
|
|
103
|
-
* the cluster?".
|
|
104
|
-
*
|
|
105
|
-
* They diverge exactly when the render is not a function of its declared
|
|
106
|
-
* inputs — `renderStability` in ./render-digest.ts names that.
|
|
107
|
-
*
|
|
108
|
-
* Unpinned renders record neither digest. Their bytes are a function of the
|
|
109
|
-
* local helm binary's defaulted capabilities, so a digest over them would
|
|
110
|
-
* assert an identity the render does not have — it would differ across
|
|
111
|
-
* machines that did nothing differently, and equal digests would still
|
|
112
|
-
* prove nothing about a cluster. No digest is the honest record.
|
|
113
|
-
*/
|
|
114
|
-
export interface HelmRenderRecord {
|
|
115
|
-
/** The render's logical name (`HelmRenderProps.name`) — also the helm release name baked into the bytes. */
|
|
116
|
-
name: string;
|
|
117
|
-
chart: string;
|
|
118
|
-
version?: string;
|
|
119
|
-
capabilityProfile?: HelmCapabilityProfile;
|
|
120
|
-
/** Input-side identity (#1237/#1243). Present only for pinned renders. */
|
|
121
|
-
inputDigest?: string;
|
|
122
|
-
/** Content-side identity over canonical rendered bytes (#1237). Present only for pinned renders. */
|
|
123
|
-
contentDigest?: string;
|
|
124
|
-
/**
|
|
125
|
-
* The build-time coalesced-values probe's digest (#1251), present only
|
|
126
|
-
* when the probe ran — a pinned render of a local chart (the probe needs
|
|
127
|
-
* the chart source on disk, so a repo-fetched chart never gets one).
|
|
128
|
-
*/
|
|
129
|
-
coalescedValuesDigest?: string;
|
|
130
|
-
}
|
|
131
|
-
/** Every render recorded in this process, in invocation order. */
|
|
132
|
-
export declare function getHelmRenderRecords(): readonly HelmRenderRecord[];
|
|
133
|
-
/** Reset the record list (test isolation). */
|
|
134
|
-
export declare function clearHelmRenderRecords(): void;
|
|
88
|
+
export { getHelmRenderRecords, clearHelmRenderRecords, type HelmRenderRecord } from "./render-records.js";
|
|
135
89
|
export declare const HelmRender: import("@intentius/chant").CompositeDefinition<HelmRenderProps, import("@intentius/chant").CompositeMembers>;
|
|
136
90
|
//# sourceMappingURL=render.d.ts.map
|
package/dist/render.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAeH,OAAO,
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAeH,OAAO,EAAwD,KAAK,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAY/G,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;;;;;OAWG;IACH,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;IAC7C;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AA8RvG,eAAO,MAAM,UAAU,8GAsFP,CAAC"}
|
package/dist/rules/whm503.ts
CHANGED
|
@@ -22,8 +22,11 @@
|
|
|
22
22
|
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
23
23
|
import yaml from "js-yaml";
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
// From ./render-records, NOT ./render: this barrel is edge-imported by the
|
|
26
|
+
// hosted audit, and render.ts's static graph reaches the TypeScript compiler
|
|
27
|
+
// through the @intentius/chant barrel — whose module init crashes workerd.
|
|
28
|
+
import type { HelmRenderRecord } from "../../render-records";
|
|
29
|
+
import { getHelmRenderRecords } from "../../render-records";
|
|
27
30
|
import { loadRenderManifest, readRenderDocument } from "../../render-store";
|
|
28
31
|
|
|
29
32
|
interface ParsedSecretDoc {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-helm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.54.1",
|
|
4
4
|
"description": "Helm chart lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"@types/js-yaml": "^4.0.9"
|
|
87
87
|
},
|
|
88
88
|
"peerDependencies": {
|
|
89
|
-
"@intentius/chant": "^0.
|
|
89
|
+
"@intentius/chant": "^0.54.1",
|
|
90
90
|
"typescript": "^5.9.3",
|
|
91
91
|
"zod": "^4.3.6"
|
|
92
92
|
}
|
|
@@ -22,8 +22,11 @@
|
|
|
22
22
|
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
23
23
|
import yaml from "js-yaml";
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
// From ./render-records, NOT ./render: this barrel is edge-imported by the
|
|
26
|
+
// hosted audit, and render.ts's static graph reaches the TypeScript compiler
|
|
27
|
+
// through the @intentius/chant barrel — whose module init crashes workerd.
|
|
28
|
+
import type { HelmRenderRecord } from "../../render-records";
|
|
29
|
+
import { getHelmRenderRecords } from "../../render-records";
|
|
27
30
|
import { loadRenderManifest, readRenderDocument } from "../../render-store";
|
|
28
31
|
|
|
29
32
|
interface ParsedSecretDoc {
|
|
@@ -11,11 +11,37 @@ vi.mock("node:child_process", async () => {
|
|
|
11
11
|
} };
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
+
const ledgerMock = vi.fn();
|
|
15
|
+
vi.mock("@intentius/chant/lifecycle/release-ledger", async () => {
|
|
16
|
+
const actual = await vi.importActual<typeof import("@intentius/chant/lifecycle/release-ledger")>(
|
|
17
|
+
"@intentius/chant/lifecycle/release-ledger",
|
|
18
|
+
);
|
|
19
|
+
return { ...actual, readReleaseLedger: (env: string) => ledgerMock(env) };
|
|
20
|
+
});
|
|
21
|
+
|
|
14
22
|
const { listArtifacts } = await import("./list-artifacts");
|
|
15
23
|
|
|
24
|
+
/** One ledger line as the helm deploy activity records it (#1243). */
|
|
25
|
+
function ledgerRecord(overrides: Record<string, unknown>): Record<string, unknown> {
|
|
26
|
+
return {
|
|
27
|
+
version: 1,
|
|
28
|
+
component: "web",
|
|
29
|
+
env: "prod",
|
|
30
|
+
digest: "sha256:input-digest",
|
|
31
|
+
gitSha: "deadbeef",
|
|
32
|
+
runId: "run-1",
|
|
33
|
+
timestamp: "2026-08-30T00:00:00.000Z",
|
|
34
|
+
actor: "ci-bot",
|
|
35
|
+
...overrides,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
16
39
|
describe("helm listArtifacts", () => {
|
|
17
40
|
beforeEach(() => {
|
|
18
41
|
execMock.mockReset();
|
|
42
|
+
ledgerMock.mockReset();
|
|
43
|
+
// Default: no ledger readable — the join contributes nothing.
|
|
44
|
+
ledgerMock.mockRejectedValue(new Error("no lifecycle branch"));
|
|
19
45
|
});
|
|
20
46
|
|
|
21
47
|
test("queries `helm list -A -o json` and maps releases to artifacts", async () => {
|
|
@@ -91,4 +117,79 @@ describe("helm listArtifacts", () => {
|
|
|
91
117
|
const result = await listArtifacts({ environment: "prod", entities: new Map() });
|
|
92
118
|
expect(result).toEqual({});
|
|
93
119
|
});
|
|
120
|
+
|
|
121
|
+
describe("render identity read back from the release ledger (#2031)", () => {
|
|
122
|
+
const webRelease = { name: "web", namespace: "default", revision: "1", status: "deployed", chart: "web-1.0.0", app_version: "1.0" };
|
|
123
|
+
|
|
124
|
+
test("an unpinned chant deploy reports its inputDigest", async () => {
|
|
125
|
+
execMock.mockResolvedValue({ stdout: JSON.stringify([webRelease]), stderr: "" });
|
|
126
|
+
ledgerMock.mockResolvedValue({ records: [ledgerRecord({})], malformed: 0 });
|
|
127
|
+
|
|
128
|
+
const result = await listArtifacts({ environment: "prod", entities: new Map() });
|
|
129
|
+
expect(ledgerMock).toHaveBeenCalledWith("prod");
|
|
130
|
+
expect(result["release/default/web"].attributes).toMatchObject({ inputDigest: "sha256:input-digest" });
|
|
131
|
+
expect(result["release/default/web"].attributes!.contentDigest).toBeUndefined();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("a pinned deploy reports contentDigest and inputDigest — joinable to `helm renders`", async () => {
|
|
135
|
+
execMock.mockResolvedValue({ stdout: JSON.stringify([webRelease]), stderr: "" });
|
|
136
|
+
ledgerMock.mockResolvedValue({
|
|
137
|
+
records: [ledgerRecord({ digest: "sha256:content-digest", inputDigest: "sha256:input-digest" })],
|
|
138
|
+
malformed: 0,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const result = await listArtifacts({ environment: "prod", entities: new Map() });
|
|
142
|
+
expect(result["release/default/web"].attributes).toMatchObject({
|
|
143
|
+
inputDigest: "sha256:input-digest",
|
|
144
|
+
contentDigest: "sha256:content-digest",
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("the latest record per component wins", async () => {
|
|
149
|
+
execMock.mockResolvedValue({ stdout: JSON.stringify([webRelease]), stderr: "" });
|
|
150
|
+
ledgerMock.mockResolvedValue({
|
|
151
|
+
records: [
|
|
152
|
+
ledgerRecord({ digest: "sha256:older", timestamp: "2026-08-01T00:00:00.000Z" }),
|
|
153
|
+
ledgerRecord({ digest: "sha256:newer", timestamp: "2026-08-30T00:00:00.000Z" }),
|
|
154
|
+
],
|
|
155
|
+
malformed: 0,
|
|
156
|
+
});
|
|
157
|
+
const result = await listArtifacts({ environment: "prod", entities: new Map() });
|
|
158
|
+
expect(result["release/default/web"].attributes!.inputDigest).toBe("sha256:newer");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("a release deployed outside chant reports nothing — absent is the honest answer", async () => {
|
|
162
|
+
execMock.mockResolvedValue({
|
|
163
|
+
stdout: JSON.stringify([webRelease, { ...webRelease, name: "manual", chart: "manual-0.1.0" }]),
|
|
164
|
+
stderr: "",
|
|
165
|
+
});
|
|
166
|
+
ledgerMock.mockResolvedValue({ records: [ledgerRecord({})], malformed: 0 });
|
|
167
|
+
|
|
168
|
+
const result = await listArtifacts({ environment: "prod", entities: new Map() });
|
|
169
|
+
expect(result["release/default/web"].attributes!.inputDigest).toBe("sha256:input-digest");
|
|
170
|
+
expect(result["release/default/manual"].attributes!.inputDigest).toBeUndefined();
|
|
171
|
+
expect(result["release/default/manual"].attributes!.contentDigest).toBeUndefined();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("one name in several namespaces is ambiguous against a namespace-less record — neither joins", async () => {
|
|
175
|
+
execMock.mockResolvedValue({
|
|
176
|
+
stdout: JSON.stringify([webRelease, { ...webRelease, namespace: "staging-ns" }]),
|
|
177
|
+
stderr: "",
|
|
178
|
+
});
|
|
179
|
+
ledgerMock.mockResolvedValue({ records: [ledgerRecord({})], malformed: 0 });
|
|
180
|
+
|
|
181
|
+
const result = await listArtifacts({ environment: "prod", entities: new Map() });
|
|
182
|
+
expect(result["release/default/web"].attributes!.inputDigest).toBeUndefined();
|
|
183
|
+
expect(result["release/staging-ns/web"].attributes!.inputDigest).toBeUndefined();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("an unreadable ledger joins nothing rather than failing the snapshot", async () => {
|
|
187
|
+
execMock.mockResolvedValue({ stdout: JSON.stringify([webRelease]), stderr: "" });
|
|
188
|
+
ledgerMock.mockRejectedValue(new Error("not a git repository"));
|
|
189
|
+
|
|
190
|
+
const result = await listArtifacts({ environment: "prod", entities: new Map() });
|
|
191
|
+
expect(result["release/default/web"]).toBeDefined();
|
|
192
|
+
expect(result["release/default/web"].attributes!.inputDigest).toBeUndefined();
|
|
193
|
+
});
|
|
194
|
+
});
|
|
94
195
|
});
|
package/src/list-artifacts.ts
CHANGED
|
@@ -17,9 +17,45 @@ import { promisify } from "node:util";
|
|
|
17
17
|
import type { ArtifactMetadata } from "@intentius/chant/lexicon";
|
|
18
18
|
import { loadChantConfigUpward } from "@intentius/chant/config";
|
|
19
19
|
import { resolveClusterTarget } from "@intentius/chant/kubectl-context";
|
|
20
|
+
import { readReleaseLedger, latestPerComponent, type ReleaseRecord } from "@intentius/chant/lifecycle/release-ledger";
|
|
20
21
|
|
|
21
22
|
const execAsync = promisify(exec);
|
|
22
23
|
|
|
24
|
+
/**
|
|
25
|
+
* The render identity a chant deploy recorded for a release (#2031). `helm
|
|
26
|
+
* list` cannot know which bytes a release was installed from — but the
|
|
27
|
+
* release ledger already records it on deploy (#1243): the input digest for
|
|
28
|
+
* an unpinned deploy, contentDigest + inputDigest for a pinned one. This
|
|
29
|
+
* reads that record back, so an observed release joins `helm renders` /
|
|
30
|
+
* `helm diff` by digest instead of by chart-name prefix matching.
|
|
31
|
+
*
|
|
32
|
+
* The join key is the ledger record's `component`, which the helm deploy
|
|
33
|
+
* activity defaults to the release name (`args.component ?? args.name`,
|
|
34
|
+
* ./op/activities/helm.ts). Deliberately best-effort and honest about
|
|
35
|
+
* absence:
|
|
36
|
+
*
|
|
37
|
+
* - a release deployed outside chant has no record, and reports nothing;
|
|
38
|
+
* - a ledger that cannot be read (no repo, no lifecycle branch) joins
|
|
39
|
+
* nothing rather than failing the snapshot;
|
|
40
|
+
* - two observed releases sharing one name (different namespaces) are
|
|
41
|
+
* ambiguous — the record does not carry a namespace — so neither joins;
|
|
42
|
+
* - a record whose `component` was overridden away from the release name
|
|
43
|
+
* does not join (the honest miss, same as today).
|
|
44
|
+
*/
|
|
45
|
+
function renderIdentityOf(
|
|
46
|
+
record: ReleaseRecord | undefined,
|
|
47
|
+
): { inputDigest?: string; contentDigest?: string } {
|
|
48
|
+
if (!record) return {};
|
|
49
|
+
if (record.inputDigest) {
|
|
50
|
+
// Pinned deploy (#1242): `digest` is the render's contentDigest, the
|
|
51
|
+
// input identity rides alongside.
|
|
52
|
+
return { inputDigest: record.inputDigest, contentDigest: record.digest };
|
|
53
|
+
}
|
|
54
|
+
// Unpinned deploy: the record is keyed by the input digest itself
|
|
55
|
+
// (`ReleaseRecord.inputDigest` is "absent when digest is already input-side").
|
|
56
|
+
return { inputDigest: record.digest };
|
|
57
|
+
}
|
|
58
|
+
|
|
23
59
|
interface HelmListEntry {
|
|
24
60
|
name?: string;
|
|
25
61
|
namespace?: string;
|
|
@@ -75,9 +111,29 @@ export async function listArtifacts(options: {
|
|
|
75
111
|
return result;
|
|
76
112
|
}
|
|
77
113
|
|
|
114
|
+
// The environment's latest release record per component (#2031) — the
|
|
115
|
+
// deploy-side identity read back. Best-effort: an unreadable ledger joins
|
|
116
|
+
// nothing rather than blocking the snapshot.
|
|
117
|
+
let recorded: Map<string, ReleaseRecord> = new Map();
|
|
118
|
+
try {
|
|
119
|
+
const { records } = await readReleaseLedger(options.environment);
|
|
120
|
+
recorded = latestPerComponent(records);
|
|
121
|
+
} catch {
|
|
122
|
+
recorded = new Map();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// A name observed in several namespaces is ambiguous against a record that
|
|
126
|
+
// carries no namespace — neither joins.
|
|
127
|
+
const nameCounts = new Map<string, number>();
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
if (entry.name) nameCounts.set(entry.name, (nameCounts.get(entry.name) ?? 0) + 1);
|
|
130
|
+
}
|
|
131
|
+
|
|
78
132
|
for (const entry of entries) {
|
|
79
133
|
if (!entry.name || !entry.namespace) continue;
|
|
80
134
|
const key = `release/${entry.namespace}/${entry.name}`;
|
|
135
|
+
const record = nameCounts.get(entry.name) === 1 ? recorded.get(entry.name) : undefined;
|
|
136
|
+
const { inputDigest, contentDigest } = renderIdentityOf(record);
|
|
81
137
|
result[key] = {
|
|
82
138
|
type: "Helm::Release",
|
|
83
139
|
physicalId: `${entry.namespace}/${entry.name}`,
|
|
@@ -88,6 +144,8 @@ export async function listArtifacts(options: {
|
|
|
88
144
|
revision: entry.revision,
|
|
89
145
|
appVersion: entry.app_version,
|
|
90
146
|
namespace: entry.namespace,
|
|
147
|
+
inputDigest,
|
|
148
|
+
contentDigest,
|
|
91
149
|
}),
|
|
92
150
|
};
|
|
93
151
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The in-process ledger of `HelmRender` invocations — split out of
|
|
3
|
+
* `./render.ts` so a post-synth check can read it without importing the
|
|
4
|
+
* render machinery (#1979 follow-up, found by the hosted audit's bump).
|
|
5
|
+
*
|
|
6
|
+
* WHM503 (`./lint/post-synth/whm503.ts`) scans these records, and post-synth
|
|
7
|
+
* barrels are edge-imported by the hosted audit service: everything they
|
|
8
|
+
* reach through static imports must be safe to initialize on workerd.
|
|
9
|
+
* `render.ts` is not — it imports the full `@intentius/chant` barrel, whose
|
|
10
|
+
* static graph reaches the TypeScript compiler (fold-import), and
|
|
11
|
+
* TypeScript's own module init touches `__filename`, which crashes the
|
|
12
|
+
* worker at startup. This module carries only the record shape and the
|
|
13
|
+
* ledger, imports nothing but a type, and keeps the barrel edge-clean;
|
|
14
|
+
* `render.ts` re-exports it all, so the lexicon's public surface is
|
|
15
|
+
* unchanged.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { HelmCapabilityProfile } from "./config";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* What one `HelmRender` invocation recorded about itself. `capabilityProfile`
|
|
22
|
+
* is the profile identity the render was pinned against; `undefined` means
|
|
23
|
+
* the render was unpinned and its bytes depend on the local helm binary's
|
|
24
|
+
* defaults.
|
|
25
|
+
*
|
|
26
|
+
* Pinned renders (profile present — the v1 gate, see #1237) also carry the
|
|
27
|
+
* digest pair:
|
|
28
|
+
*
|
|
29
|
+
* - `inputDigest` — `sha256:` over the canonical JSON of the declared inputs
|
|
30
|
+
* (chart reference, version, values, capability facts). Shared with the
|
|
31
|
+
* release-ledger digest #1243 records on deploy, via `helmInputDigest`.
|
|
32
|
+
* Answers "same inputs?" without touching any bytes.
|
|
33
|
+
* - `contentDigest` — `sha256:` over the canonical rendered bytes
|
|
34
|
+
* (`canonicalizeRender`). The artifact identity: answers "same bytes on
|
|
35
|
+
* the cluster?".
|
|
36
|
+
*
|
|
37
|
+
* They diverge exactly when the render is not a function of its declared
|
|
38
|
+
* inputs — `renderStability` in ./render-digest.ts names that.
|
|
39
|
+
*
|
|
40
|
+
* Unpinned renders record neither digest. Their bytes are a function of the
|
|
41
|
+
* local helm binary's defaulted capabilities, so a digest over them would
|
|
42
|
+
* assert an identity the render does not have — it would differ across
|
|
43
|
+
* machines that did nothing differently, and equal digests would still
|
|
44
|
+
* prove nothing about a cluster. No digest is the honest record.
|
|
45
|
+
*/
|
|
46
|
+
export interface HelmRenderRecord {
|
|
47
|
+
/** The render's logical name (`HelmRenderProps.name`) — also the helm release name baked into the bytes. */
|
|
48
|
+
name: string;
|
|
49
|
+
chart: string;
|
|
50
|
+
version?: string;
|
|
51
|
+
capabilityProfile?: HelmCapabilityProfile;
|
|
52
|
+
/** Input-side identity (#1237/#1243). Present only for pinned renders. */
|
|
53
|
+
inputDigest?: string;
|
|
54
|
+
/** Content-side identity over canonical rendered bytes (#1237). Present only for pinned renders. */
|
|
55
|
+
contentDigest?: string;
|
|
56
|
+
/**
|
|
57
|
+
* The build-time coalesced-values probe's digest (#1251), present only
|
|
58
|
+
* when the probe ran — a pinned render of a local chart (the probe needs
|
|
59
|
+
* the chart source on disk, so a repo-fetched chart never gets one).
|
|
60
|
+
*/
|
|
61
|
+
coalescedValuesDigest?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const renderRecords: HelmRenderRecord[] = [];
|
|
65
|
+
|
|
66
|
+
/** Append one invocation's record (called by `HelmRender`, ./render.ts). */
|
|
67
|
+
export function recordHelmRender(record: HelmRenderRecord): void {
|
|
68
|
+
renderRecords.push(record);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Every render recorded in this process, in invocation order. */
|
|
72
|
+
export function getHelmRenderRecords(): readonly HelmRenderRecord[] {
|
|
73
|
+
return renderRecords;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Reset the record list (test isolation). */
|
|
77
|
+
export function clearHelmRenderRecords(): void {
|
|
78
|
+
renderRecords.length = 0;
|
|
79
|
+
}
|
package/src/render.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, test, expect, beforeAll, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { mkdirSync, writeFileSync, rmSync, existsSync, readFileSync, mkdtempSync } from "node:fs";
|
|
2
|
+
import { mkdirSync, writeFileSync, rmSync, existsSync, readFileSync, readdirSync, mkdtempSync } from "node:fs";
|
|
3
3
|
import { delimiter, join } from "node:path";
|
|
4
4
|
import { execFileSync } from "node:child_process";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
@@ -237,6 +237,39 @@ describe.skipIf(!fixtureAvailable)("HelmRender", () => {
|
|
|
237
237
|
expect(dep.props.spec.replicas).toBe(3);
|
|
238
238
|
});
|
|
239
239
|
|
|
240
|
+
test("CHANT_HELM_RENDER_ROOT redirects the unpinned cache — the air-gapped handoff (#2035)", () => {
|
|
241
|
+
// Before #2035 the unprofiled path hardcoded ~/.chant/helm-renders and
|
|
242
|
+
// honoured no environment variable, so a hermetic build had no way to be
|
|
243
|
+
// handed a pre-rendered chart short of pre-warming $HOME.
|
|
244
|
+
const root = mkdtempSync(join(tmpdir(), "chant-helm-render-root-"));
|
|
245
|
+
const origRoot = process.env.CHANT_HELM_RENDER_ROOT;
|
|
246
|
+
process.env.CHANT_HELM_RENDER_ROOT = root;
|
|
247
|
+
try {
|
|
248
|
+
const first = HelmRender({ name: "rel-rooted", chart: CHART_DIR });
|
|
249
|
+
expect(Object.keys(first.members as Record<string, unknown>).length).toBeGreaterThanOrEqual(2);
|
|
250
|
+
// The cache landed under the override, not under $HOME.
|
|
251
|
+
const cached = readdirSync(root).filter((d) => !d.startsWith("_"));
|
|
252
|
+
expect(cached.length).toBe(1);
|
|
253
|
+
expect(existsSync(join(root, cached[0], "manifests.yaml"))).toBe(true);
|
|
254
|
+
|
|
255
|
+
// A machine with no helm at all renders from the handed-over store.
|
|
256
|
+
const emptyDir = join(tmpdir(), "chant-helm-render-empty-path");
|
|
257
|
+
if (!existsSync(emptyDir)) mkdirSync(emptyDir);
|
|
258
|
+
const origPath = process.env.PATH;
|
|
259
|
+
process.env.PATH = emptyDir;
|
|
260
|
+
try {
|
|
261
|
+
const second = HelmRender({ name: "rel-rooted", chart: CHART_DIR });
|
|
262
|
+
expect(Object.keys(second.members as Record<string, unknown>).length).toBeGreaterThanOrEqual(2);
|
|
263
|
+
} finally {
|
|
264
|
+
process.env.PATH = origPath;
|
|
265
|
+
}
|
|
266
|
+
} finally {
|
|
267
|
+
if (origRoot === undefined) delete process.env.CHANT_HELM_RENDER_ROOT;
|
|
268
|
+
else process.env.CHANT_HELM_RENDER_ROOT = origRoot;
|
|
269
|
+
rmSync(root, { recursive: true, force: true });
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
240
273
|
test("cache reuse: second render with same args skips helm CLI", () => {
|
|
241
274
|
// First, render with cache enabled.
|
|
242
275
|
const first = HelmRender({
|
package/src/render.ts
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
import { execFileSync } from "node:child_process";
|
|
35
35
|
import { createHash } from "node:crypto";
|
|
36
36
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
37
|
-
import {
|
|
37
|
+
import { tmpdir } from "node:os";
|
|
38
38
|
import { join } from "node:path";
|
|
39
39
|
|
|
40
40
|
import { Composite } from "@intentius/chant";
|
|
@@ -51,8 +51,10 @@ import {
|
|
|
51
51
|
loadRenderContent,
|
|
52
52
|
persistHelmRender,
|
|
53
53
|
renderCacheKey,
|
|
54
|
+
renderStoreRoot,
|
|
54
55
|
} from "./render-store";
|
|
55
56
|
import { probeCoalescedValues, type CoalescedValuesProbe } from "./values-probe";
|
|
57
|
+
import { recordHelmRender } from "./render-records";
|
|
56
58
|
|
|
57
59
|
export interface HelmRenderProps {
|
|
58
60
|
/** Logical name for the render (used in cache key + composite name). */
|
|
@@ -109,61 +111,11 @@ export interface HelmRenderProps {
|
|
|
109
111
|
sourceRef?: string;
|
|
110
112
|
}
|
|
111
113
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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;
|
|
166
|
-
}
|
|
114
|
+
// The invocation-record ledger lives in ./render-records.ts — a module with
|
|
115
|
+
// no runtime imports — so WHM503 (edge-imported via the post-synth barrel)
|
|
116
|
+
// can read it without pulling this module's heavy static graph. Re-exported
|
|
117
|
+
// here to keep the lexicon's public surface unchanged.
|
|
118
|
+
export { getHelmRenderRecords, clearHelmRenderRecords, type HelmRenderRecord } from "./render-records";
|
|
167
119
|
|
|
168
120
|
interface RenderedDoc {
|
|
169
121
|
apiVersion?: string;
|
|
@@ -172,7 +124,18 @@ interface RenderedDoc {
|
|
|
172
124
|
[k: string]: unknown;
|
|
173
125
|
}
|
|
174
126
|
|
|
175
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Root of the legacy unpinned cache — the same directory (and the same
|
|
129
|
+
* `CHANT_HELM_RENDER_ROOT` override) as the content-addressed render store
|
|
130
|
+
* (#2035). Before this the unprofiled path hardcoded `~/.chant/helm-renders`
|
|
131
|
+
* and honoured no environment variable, so a hermetic or air-gapped build
|
|
132
|
+
* had no way to hand it a pre-rendered chart short of pre-warming `$HOME`.
|
|
133
|
+
* A function, not a module-level constant, so the override is read when a
|
|
134
|
+
* render happens rather than when this module loaded.
|
|
135
|
+
*/
|
|
136
|
+
function cacheRoot(): string {
|
|
137
|
+
return renderStoreRoot();
|
|
138
|
+
}
|
|
176
139
|
|
|
177
140
|
/**
|
|
178
141
|
* Legacy cache key — truncated, unprefixed, input-derived. Since #1238 this
|
|
@@ -251,7 +214,7 @@ function renderViaHelm(props: HelmRenderProps, profile?: HelmCapabilityProfile):
|
|
|
251
214
|
"--repository-config",
|
|
252
215
|
"/dev/null",
|
|
253
216
|
"--repository-cache",
|
|
254
|
-
join(
|
|
217
|
+
join(cacheRoot(), "_helm-repo-cache"),
|
|
255
218
|
]
|
|
256
219
|
: [];
|
|
257
220
|
|
|
@@ -302,7 +265,7 @@ function loadOrRender(props: HelmRenderProps, profile?: HelmCapabilityProfile):
|
|
|
302
265
|
if (props.noCache) {
|
|
303
266
|
return renderViaHelm(props, profile);
|
|
304
267
|
}
|
|
305
|
-
const cacheDir = join(
|
|
268
|
+
const cacheDir = join(cacheRoot(), cacheKey(props, profile));
|
|
306
269
|
const cachePath = join(cacheDir, "manifests.yaml");
|
|
307
270
|
if (existsSync(cachePath)) {
|
|
308
271
|
return readFileSync(cachePath, "utf8");
|
|
@@ -490,7 +453,7 @@ export const HelmRender = Composite<HelmRenderProps>((props) => {
|
|
|
490
453
|
}
|
|
491
454
|
: {};
|
|
492
455
|
|
|
493
|
-
|
|
456
|
+
recordHelmRender({
|
|
494
457
|
name: props.name,
|
|
495
458
|
chart: props.chart,
|
|
496
459
|
version: props.version,
|