@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
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coalesced-values probe (#1251) and its provenance products (#1252).
|
|
3
|
+
*
|
|
4
|
+
* Two layers:
|
|
5
|
+
*
|
|
6
|
+
* 1. Real-helm tests, gated on the binary like render.test.ts — the
|
|
7
|
+
* committed umbrella fixture (alias, globals, disabled dependency) plus
|
|
8
|
+
* a built provenance fixture with a parent override, a supplied file and
|
|
9
|
+
* a --set layer.
|
|
10
|
+
* 2. Pure tests over `computeValueSources` / `findDeadAssignments` /
|
|
11
|
+
* `coalescedValuesDigest` — no helm, no fs.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { describe, test, expect, beforeAll, beforeEach } from "vitest";
|
|
15
|
+
import { execFileSync } from "node:child_process";
|
|
16
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { tmpdir } from "node:os";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
probeCoalescedValues,
|
|
22
|
+
coalescedValuesDigest,
|
|
23
|
+
computeValueSources,
|
|
24
|
+
findDeadAssignments,
|
|
25
|
+
rootCoalescedValues,
|
|
26
|
+
getValuesProbeRecords,
|
|
27
|
+
clearValuesProbeRecords,
|
|
28
|
+
type CoalescedChartValues,
|
|
29
|
+
type SuppliedValuesLayer,
|
|
30
|
+
type ValuesAttributionInput,
|
|
31
|
+
} from "./values-probe";
|
|
32
|
+
|
|
33
|
+
const UMBRELLA = join(import.meta.dirname, "..", "test", "survey", "fixtures", "umbrella-fixture");
|
|
34
|
+
|
|
35
|
+
function helmAvailable(): boolean {
|
|
36
|
+
try {
|
|
37
|
+
execFileSync("helm", ["version"], { stdio: "ignore" });
|
|
38
|
+
return true;
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const hasHelm = helmAvailable();
|
|
44
|
+
|
|
45
|
+
/** A parent chart overriding its child, with a global, a disabled dep, and a child default. */
|
|
46
|
+
const PROV_DIR = join(tmpdir(), "chant-values-probe-prov-fixture");
|
|
47
|
+
|
|
48
|
+
function setupProvenanceFixture(): void {
|
|
49
|
+
rmSync(PROV_DIR, { recursive: true, force: true });
|
|
50
|
+
mkdirSync(join(PROV_DIR, "templates"), { recursive: true });
|
|
51
|
+
mkdirSync(join(PROV_DIR, "charts", "web", "templates"), { recursive: true });
|
|
52
|
+
mkdirSync(join(PROV_DIR, "charts", "opt", "templates"), { recursive: true });
|
|
53
|
+
writeFileSync(
|
|
54
|
+
join(PROV_DIR, "Chart.yaml"),
|
|
55
|
+
[
|
|
56
|
+
"apiVersion: v2",
|
|
57
|
+
"name: prov",
|
|
58
|
+
"version: 0.1.0",
|
|
59
|
+
"dependencies:",
|
|
60
|
+
" - name: web",
|
|
61
|
+
" version: 0.1.0",
|
|
62
|
+
" - name: opt",
|
|
63
|
+
" version: 0.1.0",
|
|
64
|
+
" condition: opt.enabled",
|
|
65
|
+
"",
|
|
66
|
+
].join("\n"),
|
|
67
|
+
);
|
|
68
|
+
writeFileSync(
|
|
69
|
+
join(PROV_DIR, "values.yaml"),
|
|
70
|
+
["web:", " replicas: 2", "global:", " region: eu", "opt:", " enabled: false", ""].join("\n"),
|
|
71
|
+
);
|
|
72
|
+
writeFileSync(
|
|
73
|
+
join(PROV_DIR, "templates", "cm.yaml"),
|
|
74
|
+
'apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: {{ .Release.Name }}-prov\ndata:\n who: prov\n',
|
|
75
|
+
);
|
|
76
|
+
writeFileSync(join(PROV_DIR, "charts", "web", "Chart.yaml"), "apiVersion: v2\nname: web\nversion: 0.1.0\n");
|
|
77
|
+
writeFileSync(join(PROV_DIR, "charts", "web", "values.yaml"), "replicas: 1\nport: 8080\ntag: default\n");
|
|
78
|
+
writeFileSync(
|
|
79
|
+
join(PROV_DIR, "charts", "web", "templates", "cm.yaml"),
|
|
80
|
+
'apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: {{ .Release.Name }}-web\ndata:\n who: web\n',
|
|
81
|
+
);
|
|
82
|
+
writeFileSync(join(PROV_DIR, "charts", "opt", "Chart.yaml"), "apiVersion: v2\nname: opt\nversion: 0.1.0\n");
|
|
83
|
+
writeFileSync(join(PROV_DIR, "charts", "opt", "values.yaml"), "size: small\n");
|
|
84
|
+
writeFileSync(
|
|
85
|
+
join(PROV_DIR, "charts", "opt", "templates", "cm.yaml"),
|
|
86
|
+
'apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: {{ .Release.Name }}-opt\ndata:\n who: opt\n',
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const PROV_SUPPLIED: SuppliedValuesLayer[] = [
|
|
91
|
+
{
|
|
92
|
+
origin: "supplied file",
|
|
93
|
+
name: "values-prod.yaml",
|
|
94
|
+
values: {
|
|
95
|
+
web: { tag: "from-file" },
|
|
96
|
+
opt: { size: "big" },
|
|
97
|
+
notachart: { image: "typo" },
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{ origin: "--set", values: { web: { tag: "from-set" } } },
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
describe.skipIf(!hasHelm)("probeCoalescedValues (real helm)", () => {
|
|
104
|
+
beforeAll(() => {
|
|
105
|
+
setupProvenanceFixture();
|
|
106
|
+
});
|
|
107
|
+
beforeEach(() => {
|
|
108
|
+
clearValuesProbeRecords();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("umbrella fixture: one instance per chart, alias included, root first", () => {
|
|
112
|
+
const probe = probeCoalescedValues({ chartDir: UMBRELLA });
|
|
113
|
+
expect(probe.instances.map((i) => i.scope)).toEqual([[], ["kid"], ["kidtwo"]]);
|
|
114
|
+
expect(probe.instances.map((i) => i.chartName)).toEqual(["umbrella-fixture", "kid", "kidtwo"]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("umbrella fixture: disabled dependency is reported with its condition, not as an instance", () => {
|
|
118
|
+
const probe = probeCoalescedValues({ chartDir: UMBRELLA });
|
|
119
|
+
expect(probe.disabled).toEqual([{ scope: ["opt"], name: "opt", condition: "opt.enabled" }]);
|
|
120
|
+
// The root document still carries the disabled dependency's subtree
|
|
121
|
+
// (epic findings 14, 15) — an installed release would omit it.
|
|
122
|
+
expect(rootCoalescedValues(probe)).toMatchObject({ opt: { enabled: false } });
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("umbrella fixture: supplied values reach the right scopes, globals propagate everywhere", () => {
|
|
126
|
+
const probe = probeCoalescedValues({
|
|
127
|
+
chartDir: UMBRELLA,
|
|
128
|
+
supplied: [
|
|
129
|
+
{
|
|
130
|
+
origin: "supplied file",
|
|
131
|
+
values: { kidtwo: { extra: "fromparent" }, global: { g1: "rootg" }, kid: { unused: "dead" } },
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
});
|
|
135
|
+
const byScope = new Map(probe.instances.map((i) => [i.scope.join("."), i.values]));
|
|
136
|
+
expect(byScope.get("kid")).toEqual({ unused: "dead", global: { g1: "rootg" } });
|
|
137
|
+
expect(byScope.get("kidtwo")).toEqual({ extra: "fromparent", global: { g1: "rootg" } });
|
|
138
|
+
expect(byScope.get("")).toMatchObject({ global: { g1: "rootg" } });
|
|
139
|
+
expect(probe.valueSources["kid.unused"]).toBe("supplied file");
|
|
140
|
+
expect(probe.valueSources["kid.global.g1"]).toBe("supplied file");
|
|
141
|
+
expect(probe.valueSources["kidtwo.extra"]).toBe("supplied file");
|
|
142
|
+
expect(probe.valueSources["opt.enabled"]).toBe("chart default");
|
|
143
|
+
// Enabled child scopes are attributed through their own instances, not
|
|
144
|
+
// through the copies helm pushes back into the parent tree.
|
|
145
|
+
expect(probe.valueSources["kid.global.g1"]).toBeDefined();
|
|
146
|
+
expect(probe.valueSources["kidtwo.global.g1"]).toBe("supplied file");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("provenance fixture: valueSources attributes all four layer kinds", () => {
|
|
150
|
+
const probe = probeCoalescedValues({ chartDir: PROV_DIR, supplied: PROV_SUPPLIED });
|
|
151
|
+
expect(probe.valueSources["web.replicas"]).toBe("parent override");
|
|
152
|
+
expect(probe.valueSources["web.port"]).toBe("chart default");
|
|
153
|
+
expect(probe.valueSources["web.tag"]).toBe("--set");
|
|
154
|
+
expect(probe.valueSources["web.global.region"]).toBe("parent override");
|
|
155
|
+
expect(probe.valueSources["global.region"]).toBe("chart default");
|
|
156
|
+
expect(probe.valueSources["opt.size"]).toBe("supplied file");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("provenance fixture: dead assignments — shadowed, disabled subchart, unknown subchart", () => {
|
|
160
|
+
const probe = probeCoalescedValues({ chartDir: PROV_DIR, supplied: PROV_SUPPLIED });
|
|
161
|
+
const byPath = new Map(probe.deadAssignments.map((d) => [d.path, d]));
|
|
162
|
+
expect(byPath.get("web.tag")).toMatchObject({
|
|
163
|
+
reason: "shadowed",
|
|
164
|
+
origin: "supplied file (values-prod.yaml)",
|
|
165
|
+
shadowedBy: "--set",
|
|
166
|
+
});
|
|
167
|
+
expect(byPath.get("opt.size")).toMatchObject({
|
|
168
|
+
reason: "disabled-subchart",
|
|
169
|
+
shadowedBy: expect.stringContaining("opt.enabled"),
|
|
170
|
+
});
|
|
171
|
+
expect(byPath.get("notachart")).toMatchObject({ reason: "unknown-subchart" });
|
|
172
|
+
expect(probe.deadAssignments).toHaveLength(3);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("provenance fixture: no supplied values, no dead assignments", () => {
|
|
176
|
+
const probe = probeCoalescedValues({ chartDir: PROV_DIR });
|
|
177
|
+
expect(probe.deadAssignments).toEqual([]);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("digest is stable across runs and moves with supplied values", () => {
|
|
181
|
+
const a = probeCoalescedValues({ chartDir: PROV_DIR, supplied: PROV_SUPPLIED });
|
|
182
|
+
const b = probeCoalescedValues({ chartDir: PROV_DIR, supplied: PROV_SUPPLIED });
|
|
183
|
+
const c = probeCoalescedValues({
|
|
184
|
+
chartDir: PROV_DIR,
|
|
185
|
+
supplied: [{ origin: "supplied file", values: { web: { replicas: 9 } } }],
|
|
186
|
+
});
|
|
187
|
+
expect(a.digest).toMatch(/^sha256:[0-9a-f]{64}$/);
|
|
188
|
+
expect(a.digest).toBe(b.digest);
|
|
189
|
+
expect(a.digest).not.toBe(c.digest);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("root coalesced values match helm's file-merge semantics on shared keys", () => {
|
|
193
|
+
const probe = probeCoalescedValues({ chartDir: PROV_DIR, supplied: PROV_SUPPLIED });
|
|
194
|
+
const root = rootCoalescedValues(probe);
|
|
195
|
+
// Supplied over defaults, later layer over earlier — what
|
|
196
|
+
// `helm get values --all` reports for an installed release.
|
|
197
|
+
expect(root).toMatchObject({
|
|
198
|
+
global: { region: "eu" },
|
|
199
|
+
opt: { enabled: false, size: "big" },
|
|
200
|
+
notachart: { image: "typo" },
|
|
201
|
+
web: { replicas: 2, port: 8080, tag: "from-set" },
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("the source chart is never touched and no probe file survives", () => {
|
|
206
|
+
probeCoalescedValues({ chartDir: PROV_DIR, supplied: PROV_SUPPLIED });
|
|
207
|
+
expect(existsSync(join(PROV_DIR, "templates", "chant-values-probe.yaml"))).toBe(false);
|
|
208
|
+
expect(existsSync(join(PROV_DIR, "charts", "web", "templates", "chant-values-probe.yaml"))).toBe(false);
|
|
209
|
+
// The real templates are intact — the probe worked on a copy.
|
|
210
|
+
expect(existsSync(join(PROV_DIR, "templates", "cm.yaml"))).toBe(true);
|
|
211
|
+
// A plain render of the source chart carries no probe document.
|
|
212
|
+
const rendered = execFileSync("helm", ["template", "check", PROV_DIR], { encoding: "utf8" });
|
|
213
|
+
expect(rendered).not.toContain("chantValuesProbe");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("every probe run is recorded for WHM504", () => {
|
|
217
|
+
expect(getValuesProbeRecords()).toHaveLength(0);
|
|
218
|
+
probeCoalescedValues({ chartDir: PROV_DIR, name: "prov-probe" });
|
|
219
|
+
const records = getValuesProbeRecords();
|
|
220
|
+
expect(records).toHaveLength(1);
|
|
221
|
+
expect(records[0].name).toBe("prov-probe");
|
|
222
|
+
expect(records[0].chartDir).toBe(PROV_DIR);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe("probeCoalescedValues (fake helm)", () => {
|
|
227
|
+
beforeAll(() => {
|
|
228
|
+
setupProvenanceFixture();
|
|
229
|
+
});
|
|
230
|
+
beforeEach(() => {
|
|
231
|
+
clearValuesProbeRecords();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("parses probe documents, maps scopes from Source paths, reports pruned dependencies", () => {
|
|
235
|
+
const fakeOut = [
|
|
236
|
+
"---",
|
|
237
|
+
"# Source: prov/charts/web/templates/chant-values-probe.yaml",
|
|
238
|
+
"chantValuesProbe: v1",
|
|
239
|
+
"chart: web",
|
|
240
|
+
"values:",
|
|
241
|
+
" replicas: 2",
|
|
242
|
+
"---",
|
|
243
|
+
"# Source: prov/templates/chant-values-probe.yaml",
|
|
244
|
+
"chantValuesProbe: v1",
|
|
245
|
+
"chart: prov",
|
|
246
|
+
"values:",
|
|
247
|
+
" web:",
|
|
248
|
+
" replicas: 2",
|
|
249
|
+
" opt:",
|
|
250
|
+
" enabled: false",
|
|
251
|
+
" global:",
|
|
252
|
+
" region: eu",
|
|
253
|
+
"",
|
|
254
|
+
].join("\n");
|
|
255
|
+
const seen: string[][] = [];
|
|
256
|
+
const probe = probeCoalescedValues({
|
|
257
|
+
chartDir: PROV_DIR,
|
|
258
|
+
runHelm: (args) => {
|
|
259
|
+
seen.push(args);
|
|
260
|
+
return fakeOut;
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
expect(seen).toHaveLength(1);
|
|
264
|
+
expect(seen[0][0]).toBe("template");
|
|
265
|
+
expect(probe.instances.map((i) => i.scope)).toEqual([[], ["web"]]);
|
|
266
|
+
expect(probe.instances[1].chartName).toBe("web");
|
|
267
|
+
expect(probe.disabled).toEqual([{ scope: ["opt"], name: "opt", condition: "opt.enabled" }]);
|
|
268
|
+
expect(probe.digest).toMatch(/^sha256:[0-9a-f]{64}$/);
|
|
269
|
+
expect(probe.valueSources["web.replicas"]).toBe("parent override");
|
|
270
|
+
expect(getValuesProbeRecords()).toHaveLength(1);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("a render with no probe document is an error", () => {
|
|
274
|
+
expect(() => probeCoalescedValues({ chartDir: PROV_DIR, runHelm: () => "---\nkind: ConfigMap\n" })).toThrow(
|
|
275
|
+
/no probe document/,
|
|
276
|
+
);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe("coalescedValuesDigest (pure)", () => {
|
|
281
|
+
test("independent of instance order and of key order inside values", () => {
|
|
282
|
+
const a: CoalescedChartValues[] = [
|
|
283
|
+
{ scope: [], chartName: "root", values: { x: 1, y: { z: 2 } } },
|
|
284
|
+
{ scope: ["kid"], chartName: "kid", values: { p: true } },
|
|
285
|
+
];
|
|
286
|
+
const b: CoalescedChartValues[] = [
|
|
287
|
+
{ scope: ["kid"], chartName: "kid", values: { p: true } },
|
|
288
|
+
{ scope: [], chartName: "root", values: { y: { z: 2 }, x: 1 } },
|
|
289
|
+
];
|
|
290
|
+
expect(coalescedValuesDigest(a)).toBe(coalescedValuesDigest(b));
|
|
291
|
+
const c: CoalescedChartValues[] = [{ scope: [], chartName: "root", values: { x: 1 } }];
|
|
292
|
+
expect(coalescedValuesDigest(a)).not.toBe(coalescedValuesDigest(c));
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
describe("computeValueSources / findDeadAssignments (pure)", () => {
|
|
297
|
+
const defaults: Record<string, Record<string, unknown>> = {
|
|
298
|
+
"": { web: { replicas: 2 }, global: { region: "eu" }, opt: { enabled: false } },
|
|
299
|
+
web: { replicas: 1, port: 8080 },
|
|
300
|
+
};
|
|
301
|
+
const input = (supplied: SuppliedValuesLayer[]): ValuesAttributionInput => ({
|
|
302
|
+
instances: [
|
|
303
|
+
{
|
|
304
|
+
scope: [],
|
|
305
|
+
chartName: "root",
|
|
306
|
+
values: { web: { replicas: 2, port: 8080 }, global: { region: "eu" }, opt: { enabled: false } },
|
|
307
|
+
},
|
|
308
|
+
{ scope: ["web"], chartName: "web", values: { replicas: 2, port: 8080, global: { region: "eu" } } },
|
|
309
|
+
],
|
|
310
|
+
supplied,
|
|
311
|
+
defaultsFor: (scope) => defaults[scope.join(".")],
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("attributes chart default, parent override, and propagated global", () => {
|
|
315
|
+
const sources = computeValueSources(input([]));
|
|
316
|
+
expect(sources).toEqual({
|
|
317
|
+
"global.region": "chart default",
|
|
318
|
+
"opt.enabled": "chart default",
|
|
319
|
+
"web.replicas": "parent override",
|
|
320
|
+
"web.port": "chart default",
|
|
321
|
+
"web.global.region": "parent override",
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test("a supplied layer wins over authored layers, last supplied layer first", () => {
|
|
326
|
+
const supplied: SuppliedValuesLayer[] = [
|
|
327
|
+
{ origin: "supplied file", values: { web: { replicas: 5 } } },
|
|
328
|
+
{ origin: "--set", values: { web: { replicas: 7 } } },
|
|
329
|
+
];
|
|
330
|
+
const attribution = input(supplied);
|
|
331
|
+
// Coalesced tree reflects the winning --set value.
|
|
332
|
+
(attribution.instances[1].values as Record<string, unknown>).replicas = 7;
|
|
333
|
+
((attribution.instances[0].values as Record<string, Record<string, unknown>>).web).replicas = 7;
|
|
334
|
+
const sources = computeValueSources(attribution);
|
|
335
|
+
expect(sources["web.replicas"]).toBe("--set");
|
|
336
|
+
const dead = findDeadAssignments(attribution, []);
|
|
337
|
+
expect(dead).toEqual([
|
|
338
|
+
{ path: "web.replicas", origin: "supplied file", reason: "shadowed", shadowedBy: "--set" },
|
|
339
|
+
]);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test("a path no layer explains reports as computed", () => {
|
|
343
|
+
const attribution = input([]);
|
|
344
|
+
(attribution.instances[0].values as Record<string, unknown>).imported = "by-import-values";
|
|
345
|
+
const sources = computeValueSources(attribution);
|
|
346
|
+
expect(sources["imported"]).toBe("computed");
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("map-into-map across supplied layers merges — not a shadow", () => {
|
|
350
|
+
const supplied: SuppliedValuesLayer[] = [
|
|
351
|
+
{ origin: "supplied file", values: { web: { extra: { a: 1 } } } },
|
|
352
|
+
{ origin: "--set", values: { web: { extra: { b: 2 } } } },
|
|
353
|
+
];
|
|
354
|
+
expect(findDeadAssignments(input(supplied), [])).toEqual([]);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("a supplied value under a disabled dependency scope is dead", () => {
|
|
358
|
+
const supplied: SuppliedValuesLayer[] = [
|
|
359
|
+
{ origin: "supplied file", name: "values.yaml", values: { opt: { size: "big" } } },
|
|
360
|
+
];
|
|
361
|
+
const dead = findDeadAssignments(input(supplied), [{ scope: ["opt"], name: "opt", condition: "opt.enabled" }]);
|
|
362
|
+
expect(dead).toEqual([
|
|
363
|
+
{
|
|
364
|
+
path: "opt.size",
|
|
365
|
+
origin: "supplied file (values.yaml)",
|
|
366
|
+
reason: "disabled-subchart",
|
|
367
|
+
shadowedBy: "disabled subchart opt (condition opt.enabled is false)",
|
|
368
|
+
},
|
|
369
|
+
]);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
test("a values map under a key naming no subchart is dead — once per key", () => {
|
|
373
|
+
const supplied: SuppliedValuesLayer[] = [
|
|
374
|
+
{ origin: "supplied file", values: { wbe: { replicas: 3, tag: "x" } } },
|
|
375
|
+
];
|
|
376
|
+
const dead = findDeadAssignments(input(supplied), []);
|
|
377
|
+
expect(dead).toEqual([
|
|
378
|
+
{
|
|
379
|
+
path: "wbe",
|
|
380
|
+
origin: "supplied file",
|
|
381
|
+
reason: "unknown-subchart",
|
|
382
|
+
shadowedBy: 'no dependency, chart default, or global named "wbe"',
|
|
383
|
+
},
|
|
384
|
+
]);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
test("a scalar under an unknown key, or a chart with no dependencies, is not flagged", () => {
|
|
388
|
+
// Scalar top-level key: templates may read arbitrary .Values — only a
|
|
389
|
+
// MAP under an unknown key reads as a mistyped subchart name.
|
|
390
|
+
const scalar: SuppliedValuesLayer[] = [{ origin: "supplied file", values: { extraFlag: true } }];
|
|
391
|
+
expect(findDeadAssignments(input(scalar), [])).toEqual([]);
|
|
392
|
+
// No dependencies at all: nothing to mistarget.
|
|
393
|
+
const noDeps: ValuesAttributionInput = {
|
|
394
|
+
instances: [{ scope: [], chartName: "solo", values: { anything: { nested: 1 } } }],
|
|
395
|
+
supplied: [{ origin: "supplied file", values: { anything: { nested: 1 } } }],
|
|
396
|
+
defaultsFor: () => ({}),
|
|
397
|
+
};
|
|
398
|
+
expect(findDeadAssignments(noDeps, [])).toEqual([]);
|
|
399
|
+
});
|
|
400
|
+
});
|