@intentius/chant-lexicon-helm 0.45.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.test.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import { describe, test, expect, beforeAll } from "vitest";
|
|
2
|
-
import { mkdirSync, writeFileSync, rmSync, existsSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
1
|
+
import { describe, test, expect, beforeAll, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mkdirSync, writeFileSync, rmSync, existsSync, readFileSync, mkdtempSync } from "node:fs";
|
|
3
|
+
import { delimiter, join } from "node:path";
|
|
4
4
|
import { execFileSync } from "node:child_process";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
|
|
9
|
+
import { HelmRender, getHelmRenderRecords, clearHelmRenderRecords } from "./render";
|
|
10
|
+
import { helmContentDigest, helmInputDigest, renderStability } from "./render-digest";
|
|
11
|
+
import { loadRenderManifest } from "./render-store";
|
|
12
|
+
import { clearValuesProbeRecords, getValuesProbeRecords } from "./values-probe";
|
|
13
|
+
import { whm504 } from "./lint/post-synth/whm504";
|
|
8
14
|
|
|
9
15
|
const FIXTURE_DIR = join(tmpdir(), "chant-helm-render-fixture");
|
|
10
16
|
const CHART_DIR = join(FIXTURE_DIR, "tiny-chart");
|
|
@@ -256,3 +262,376 @@ describe.skipIf(!fixtureAvailable)("HelmRender", () => {
|
|
|
256
262
|
}
|
|
257
263
|
});
|
|
258
264
|
});
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Capability-profile plumbing (#1235) — asserted against a scripted `helm`
|
|
268
|
+
* double that records its argv, so these tests need no real helm binary, no
|
|
269
|
+
* network, and no chart. The double answers any `helm template` with one
|
|
270
|
+
* minimal manifest.
|
|
271
|
+
*/
|
|
272
|
+
describe("HelmRender capability profiles", () => {
|
|
273
|
+
const FAKE_BIN = join(tmpdir(), "chant-helm-render-fake-bin");
|
|
274
|
+
let argvFile: string;
|
|
275
|
+
let origPath: string | undefined;
|
|
276
|
+
let origCwd: string;
|
|
277
|
+
|
|
278
|
+
beforeAll(() => {
|
|
279
|
+
mkdirSync(FAKE_BIN, { recursive: true });
|
|
280
|
+
writeFileSync(
|
|
281
|
+
join(FAKE_BIN, "helm"),
|
|
282
|
+
`#!/bin/sh
|
|
283
|
+
printf '%s\\n' "$@" > "$CHANT_TEST_HELM_ARGV"
|
|
284
|
+
cat <<'EOF'
|
|
285
|
+
apiVersion: v1
|
|
286
|
+
kind: ConfigMap
|
|
287
|
+
metadata:
|
|
288
|
+
name: fake-render
|
|
289
|
+
EOF
|
|
290
|
+
`,
|
|
291
|
+
{ mode: 0o755 },
|
|
292
|
+
);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
beforeEach(() => {
|
|
296
|
+
argvFile = join(mkdtempSync(join(tmpdir(), "chant-helm-argv-")), "argv.txt");
|
|
297
|
+
process.env.CHANT_TEST_HELM_ARGV = argvFile;
|
|
298
|
+
origPath = process.env.PATH;
|
|
299
|
+
process.env.PATH = FAKE_BIN + delimiter + (origPath ?? "");
|
|
300
|
+
origCwd = process.cwd();
|
|
301
|
+
clearHelmRenderRecords();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
afterEach(() => {
|
|
305
|
+
process.env.PATH = origPath;
|
|
306
|
+
delete process.env.CHANT_TEST_HELM_ARGV;
|
|
307
|
+
process.chdir(origCwd);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
function renderedArgv(): string[] {
|
|
311
|
+
return readFileSync(argvFile, "utf8")
|
|
312
|
+
.split("\n")
|
|
313
|
+
.filter((l) => l.length > 0);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
test("an inline profile pins --kube-version and one --api-versions per entry", () => {
|
|
317
|
+
HelmRender({
|
|
318
|
+
name: "rel",
|
|
319
|
+
chart: "/dev/null/some-chart",
|
|
320
|
+
noCache: true,
|
|
321
|
+
capabilityProfile: {
|
|
322
|
+
name: "prod",
|
|
323
|
+
kubeVersion: "1.33.6",
|
|
324
|
+
apiVersions: ["monitoring.coreos.com/v1", "cert-manager.io/v1"],
|
|
325
|
+
},
|
|
326
|
+
} as Parameters<typeof HelmRender>[0]);
|
|
327
|
+
|
|
328
|
+
const argv = renderedArgv();
|
|
329
|
+
const kvIdx = argv.indexOf("--kube-version");
|
|
330
|
+
expect(kvIdx).toBeGreaterThan(-1);
|
|
331
|
+
expect(argv[kvIdx + 1]).toBe("1.33.6");
|
|
332
|
+
const apiIdxs = argv.map((a, i) => (a === "--api-versions" ? i : -1)).filter((i) => i >= 0);
|
|
333
|
+
expect(apiIdxs.length).toBe(2);
|
|
334
|
+
expect(argv[apiIdxs[0] + 1]).toBe("monitoring.coreos.com/v1");
|
|
335
|
+
expect(argv[apiIdxs[1] + 1]).toBe("cert-manager.io/v1");
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("no profile means no capability flags — today's unpinned behavior", () => {
|
|
339
|
+
HelmRender({
|
|
340
|
+
name: "rel",
|
|
341
|
+
chart: "/dev/null/some-chart",
|
|
342
|
+
noCache: true,
|
|
343
|
+
} as Parameters<typeof HelmRender>[0]);
|
|
344
|
+
|
|
345
|
+
const argv = renderedArgv();
|
|
346
|
+
expect(argv).not.toContain("--kube-version");
|
|
347
|
+
expect(argv).not.toContain("--api-versions");
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test("a profile named in chant.config.json resolves and pins the render", () => {
|
|
351
|
+
const projectDir = mkdtempSync(join(tmpdir(), "chant-helm-render-project-"));
|
|
352
|
+
writeFileSync(
|
|
353
|
+
join(projectDir, "chant.config.json"),
|
|
354
|
+
JSON.stringify({
|
|
355
|
+
helm: {
|
|
356
|
+
capabilityProfiles: {
|
|
357
|
+
prod: { kubeVersion: "v1.31.4", apiVersions: ["batch/v1"] },
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
}),
|
|
361
|
+
);
|
|
362
|
+
process.chdir(projectDir);
|
|
363
|
+
|
|
364
|
+
HelmRender({
|
|
365
|
+
name: "rel",
|
|
366
|
+
chart: "/dev/null/some-chart",
|
|
367
|
+
noCache: true,
|
|
368
|
+
capabilityProfile: "prod",
|
|
369
|
+
} as Parameters<typeof HelmRender>[0]);
|
|
370
|
+
|
|
371
|
+
const argv = renderedArgv();
|
|
372
|
+
const kvIdx = argv.indexOf("--kube-version");
|
|
373
|
+
expect(argv[kvIdx + 1]).toBe("v1.31.4");
|
|
374
|
+
const apiIdx = argv.indexOf("--api-versions");
|
|
375
|
+
expect(argv[apiIdx + 1]).toBe("batch/v1");
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
test("an undeclared profile reference is an error naming it, before helm runs", () => {
|
|
379
|
+
const projectDir = mkdtempSync(join(tmpdir(), "chant-helm-render-project-"));
|
|
380
|
+
writeFileSync(
|
|
381
|
+
join(projectDir, "chant.config.json"),
|
|
382
|
+
JSON.stringify({ helm: { capabilityProfiles: { staging: { kubeVersion: "1.31.4" } } } }),
|
|
383
|
+
);
|
|
384
|
+
process.chdir(projectDir);
|
|
385
|
+
|
|
386
|
+
expect(() =>
|
|
387
|
+
HelmRender({
|
|
388
|
+
name: "rel",
|
|
389
|
+
chart: "/dev/null/some-chart",
|
|
390
|
+
noCache: true,
|
|
391
|
+
capabilityProfile: "prod",
|
|
392
|
+
} as Parameters<typeof HelmRender>[0]),
|
|
393
|
+
).toThrow(/capability profile "prod" is not declared/);
|
|
394
|
+
// Failed before any render: the double never ran.
|
|
395
|
+
expect(existsSync(argvFile)).toBe(false);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test("an invalid inline profile is an error naming the field", () => {
|
|
399
|
+
expect(() =>
|
|
400
|
+
HelmRender({
|
|
401
|
+
name: "rel",
|
|
402
|
+
chart: "/dev/null/some-chart",
|
|
403
|
+
noCache: true,
|
|
404
|
+
capabilityProfile: { name: "prod", kubeVersion: "latest" },
|
|
405
|
+
} as Parameters<typeof HelmRender>[0]),
|
|
406
|
+
).toThrow(/kubeVersion/);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test("the render record carries the profile identity for pinned renders, and none for unpinned", () => {
|
|
410
|
+
HelmRender({
|
|
411
|
+
name: "pinned",
|
|
412
|
+
chart: "/dev/null/some-chart",
|
|
413
|
+
noCache: true,
|
|
414
|
+
capabilityProfile: { name: "prod", kubeVersion: "1.33.6", apiVersions: ["batch/v1"] },
|
|
415
|
+
} as Parameters<typeof HelmRender>[0]);
|
|
416
|
+
HelmRender({
|
|
417
|
+
name: "unpinned",
|
|
418
|
+
chart: "/dev/null/some-chart",
|
|
419
|
+
noCache: true,
|
|
420
|
+
} as Parameters<typeof HelmRender>[0]);
|
|
421
|
+
|
|
422
|
+
const records = getHelmRenderRecords();
|
|
423
|
+
expect(records.length).toBe(2);
|
|
424
|
+
expect(records[0].name).toBe("pinned");
|
|
425
|
+
expect(records[0].capabilityProfile).toEqual({
|
|
426
|
+
name: "prod",
|
|
427
|
+
kubeVersion: "1.33.6",
|
|
428
|
+
apiVersions: ["batch/v1"],
|
|
429
|
+
});
|
|
430
|
+
expect(records[1].name).toBe("unpinned");
|
|
431
|
+
expect(records[1].capabilityProfile).toBeUndefined();
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test("digests are recorded only for pinned renders — unpinned renders record neither (#1237)", () => {
|
|
435
|
+
HelmRender({
|
|
436
|
+
name: "pinned",
|
|
437
|
+
chart: "/dev/null/some-chart",
|
|
438
|
+
noCache: true,
|
|
439
|
+
capabilityProfile: { name: "prod", kubeVersion: "1.33.6" },
|
|
440
|
+
} as Parameters<typeof HelmRender>[0]);
|
|
441
|
+
HelmRender({
|
|
442
|
+
name: "unpinned",
|
|
443
|
+
chart: "/dev/null/some-chart",
|
|
444
|
+
noCache: true,
|
|
445
|
+
} as Parameters<typeof HelmRender>[0]);
|
|
446
|
+
|
|
447
|
+
const [pinned, unpinned] = getHelmRenderRecords();
|
|
448
|
+
expect(pinned.contentDigest).toMatch(/^sha256:[0-9a-f]{64}$/);
|
|
449
|
+
expect(pinned.inputDigest).toMatch(/^sha256:[0-9a-f]{64}$/);
|
|
450
|
+
// The double's output is fixed, so the content digest is the canonical
|
|
451
|
+
// digest of that one ConfigMap — assert it against the canonicalizer.
|
|
452
|
+
expect(pinned.contentDigest).toBe(
|
|
453
|
+
helmContentDigest("apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: fake-render\n"),
|
|
454
|
+
);
|
|
455
|
+
expect(unpinned.contentDigest).toBeUndefined();
|
|
456
|
+
expect(unpinned.inputDigest).toBeUndefined();
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
test("a pinned render's inputDigest is #1243's input digest for the same inputs", () => {
|
|
460
|
+
HelmRender({
|
|
461
|
+
name: "rel",
|
|
462
|
+
chart: "/dev/null/some-chart",
|
|
463
|
+
noCache: true,
|
|
464
|
+
values: { replicaCount: 3 },
|
|
465
|
+
capabilityProfile: { name: "prod", kubeVersion: "1.33.6", apiVersions: ["batch/v1"] },
|
|
466
|
+
} as Parameters<typeof HelmRender>[0]);
|
|
467
|
+
|
|
468
|
+
const [record] = getHelmRenderRecords();
|
|
469
|
+
expect(record.inputDigest).toBe(
|
|
470
|
+
helmInputDigest({
|
|
471
|
+
chart: "/dev/null/some-chart",
|
|
472
|
+
chartVersion: undefined,
|
|
473
|
+
values: { replicaCount: 3 },
|
|
474
|
+
capabilityProfile: { kubeVersion: "1.33.6", apiVersions: ["batch/v1"] },
|
|
475
|
+
}),
|
|
476
|
+
);
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Digest behavior against real helm renders of the deterministic fixture
|
|
482
|
+
* (#1237). Pinning uses an inline profile so `helm template` runs with
|
|
483
|
+
* `--kube-version` — the same invocation shape a config-declared profile
|
|
484
|
+
* produces.
|
|
485
|
+
*/
|
|
486
|
+
describe.skipIf(!fixtureAvailable)("HelmRender digests (real helm)", () => {
|
|
487
|
+
const PROFILE = { name: "test", kubeVersion: "1.33.6" };
|
|
488
|
+
|
|
489
|
+
beforeEach(() => {
|
|
490
|
+
clearHelmRenderRecords();
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
test("two pinned renders of the deterministic fixture agree on both digests", () => {
|
|
494
|
+
for (const name of ["rel", "rel"]) {
|
|
495
|
+
HelmRender({
|
|
496
|
+
name,
|
|
497
|
+
chart: CHART_DIR,
|
|
498
|
+
noCache: true,
|
|
499
|
+
capabilityProfile: PROFILE,
|
|
500
|
+
} as Parameters<typeof HelmRender>[0]);
|
|
501
|
+
}
|
|
502
|
+
const [first, second] = getHelmRenderRecords();
|
|
503
|
+
expect(first.contentDigest).toMatch(/^sha256:[0-9a-f]{64}$/);
|
|
504
|
+
expect(first.contentDigest).toBe(second.contentDigest);
|
|
505
|
+
expect(first.inputDigest).toBe(second.inputDigest);
|
|
506
|
+
|
|
507
|
+
const report = renderStability(getHelmRenderRecords());
|
|
508
|
+
expect(report.unstable).toEqual([]);
|
|
509
|
+
expect(report.stable.length).toBe(1);
|
|
510
|
+
expect(report.stable[0].names).toEqual(["rel", "rel"]);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
test("a values change moves contentDigest and inputDigest together", () => {
|
|
514
|
+
for (const replicaCount of [1, 3]) {
|
|
515
|
+
HelmRender({
|
|
516
|
+
name: "rel",
|
|
517
|
+
chart: CHART_DIR,
|
|
518
|
+
noCache: true,
|
|
519
|
+
values: { replicaCount },
|
|
520
|
+
capabilityProfile: PROFILE,
|
|
521
|
+
} as Parameters<typeof HelmRender>[0]);
|
|
522
|
+
}
|
|
523
|
+
const [one, three] = getHelmRenderRecords();
|
|
524
|
+
expect(one.contentDigest).not.toBe(three.contentDigest);
|
|
525
|
+
expect(one.inputDigest).not.toBe(three.inputDigest);
|
|
526
|
+
// Different inputs — no stability claim to make, and no false alarm.
|
|
527
|
+
expect(renderStability(getHelmRenderRecords()).unstable).toEqual([]);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
test("same inputs, different bytes — renderStability names the divergence", () => {
|
|
531
|
+
HelmRender({
|
|
532
|
+
name: "rel",
|
|
533
|
+
chart: CHART_DIR,
|
|
534
|
+
noCache: true,
|
|
535
|
+
capabilityProfile: PROFILE,
|
|
536
|
+
} as Parameters<typeof HelmRender>[0]);
|
|
537
|
+
const [genuine] = getHelmRenderRecords();
|
|
538
|
+
// A second record with the same inputs but different bytes — the shape an
|
|
539
|
+
// unstable chart (randAlphaNum, timestamps) produces. The fixture is
|
|
540
|
+
// deliberately deterministic, so fabricate the divergent twin.
|
|
541
|
+
const divergent = { ...genuine, contentDigest: "sha256:" + "0".repeat(64) };
|
|
542
|
+
const report = renderStability([genuine, divergent]);
|
|
543
|
+
expect(report.stable).toEqual([]);
|
|
544
|
+
expect(report.unstable.length).toBe(1);
|
|
545
|
+
expect(report.unstable[0].inputDigest).toBe(genuine.inputDigest);
|
|
546
|
+
expect(report.unstable[0].contentDigests).toEqual([genuine.contentDigest, divergent.contentDigest]);
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* End-to-end coalesced-values wiring (#1251, #1252): a real pinned build of
|
|
552
|
+
* the fixture chart carries the probe's digest and valueSources into its
|
|
553
|
+
* persisted `RenderManifest` (issue #1252 AC1), and a dead assignment in
|
|
554
|
+
* the supplied values produces a WHM504 finding through the same real
|
|
555
|
+
* build — not a hand-built probe record (AC3).
|
|
556
|
+
*/
|
|
557
|
+
describe.skipIf(!fixtureAvailable)("coalesced-values probe wired into the render path (#1251, #1252)", () => {
|
|
558
|
+
const PROFILE = { name: "test", kubeVersion: "1.33.6" };
|
|
559
|
+
let storeRoot: string;
|
|
560
|
+
let origRoot: string | undefined;
|
|
561
|
+
|
|
562
|
+
function makeCtx(): PostSynthContext {
|
|
563
|
+
const outputs = new Map<string, string>();
|
|
564
|
+
return {
|
|
565
|
+
outputs,
|
|
566
|
+
entities: new Map(),
|
|
567
|
+
buildResult: { outputs, entities: new Map(), warnings: [], errors: [], sourceFileCount: 1 },
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
beforeEach(() => {
|
|
572
|
+
clearHelmRenderRecords();
|
|
573
|
+
clearValuesProbeRecords();
|
|
574
|
+
storeRoot = mkdtempSync(join(tmpdir(), "chant-helm-render-store-"));
|
|
575
|
+
origRoot = process.env.CHANT_HELM_RENDER_ROOT;
|
|
576
|
+
process.env.CHANT_HELM_RENDER_ROOT = storeRoot;
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
afterEach(() => {
|
|
580
|
+
if (origRoot === undefined) delete process.env.CHANT_HELM_RENDER_ROOT;
|
|
581
|
+
else process.env.CHANT_HELM_RENDER_ROOT = origRoot;
|
|
582
|
+
rmSync(storeRoot, { recursive: true, force: true });
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
test("a pinned build of a clean chart records a digest and valueSources in the persisted RenderManifest", () => {
|
|
586
|
+
HelmRender({
|
|
587
|
+
name: "rel",
|
|
588
|
+
chart: CHART_DIR,
|
|
589
|
+
persist: true,
|
|
590
|
+
values: { replicaCount: 3 },
|
|
591
|
+
capabilityProfile: PROFILE,
|
|
592
|
+
} as Parameters<typeof HelmRender>[0]);
|
|
593
|
+
|
|
594
|
+
const [record] = getHelmRenderRecords();
|
|
595
|
+
expect(record.coalescedValuesDigest).toMatch(/^sha256:[0-9a-f]{64}$/);
|
|
596
|
+
|
|
597
|
+
const manifest = loadRenderManifest(record.contentDigest!, { root: storeRoot });
|
|
598
|
+
expect(manifest).toBeDefined();
|
|
599
|
+
expect(manifest!.coalescedValuesDigest).toBe(record.coalescedValuesDigest);
|
|
600
|
+
expect(manifest!.valueSources).toBeTruthy();
|
|
601
|
+
// The supplied override is attributed to the values layer that won it.
|
|
602
|
+
expect(manifest!.valueSources!["replicaCount"]).toBe("supplied file");
|
|
603
|
+
|
|
604
|
+
// No dead assignments in this build — WHM504 has nothing to say.
|
|
605
|
+
expect(getValuesProbeRecords()).toHaveLength(1);
|
|
606
|
+
expect(whm504.check(makeCtx())).toEqual([]);
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
test("a dead assignment in a real pinned build fires WHM504 end-to-end", () => {
|
|
610
|
+
HelmRender({
|
|
611
|
+
name: "rel",
|
|
612
|
+
chart: CHART_DIR,
|
|
613
|
+
persist: true,
|
|
614
|
+
// "totallyUnknownSubchart" names no dependency of the fixture chart
|
|
615
|
+
// (which has the "tiny-sub" subchart, so it does have dependencies)
|
|
616
|
+
// and no root default — the classic silently-ignored subchart typo.
|
|
617
|
+
values: { totallyUnknownSubchart: { replicas: 9 } },
|
|
618
|
+
capabilityProfile: PROFILE,
|
|
619
|
+
} as Parameters<typeof HelmRender>[0]);
|
|
620
|
+
|
|
621
|
+
const [record] = getHelmRenderRecords();
|
|
622
|
+
expect(record.coalescedValuesDigest).toMatch(/^sha256:[0-9a-f]{64}$/);
|
|
623
|
+
|
|
624
|
+
const manifest = loadRenderManifest(record.contentDigest!, { root: storeRoot });
|
|
625
|
+
expect(manifest!.coalescedValuesDigest).toBe(record.coalescedValuesDigest);
|
|
626
|
+
|
|
627
|
+
// The real build path recorded the probe — WHM504 fires without any
|
|
628
|
+
// test constructing a probe record by hand.
|
|
629
|
+
expect(getValuesProbeRecords()).toHaveLength(1);
|
|
630
|
+
const diags = whm504.check(makeCtx());
|
|
631
|
+
expect(diags).toHaveLength(1);
|
|
632
|
+
expect(diags[0].checkId).toBe("WHM504");
|
|
633
|
+
expect(diags[0].entity).toBe("rel");
|
|
634
|
+
expect(diags[0].message).toContain("totallyUnknownSubchart");
|
|
635
|
+
expect(diags[0].message).toContain("target no subchart");
|
|
636
|
+
});
|
|
637
|
+
});
|