@intentius/chant-lexicon-helm 0.0.18 → 0.0.24
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 +5 -5
- package/dist/manifest.json +1 -1
- package/dist/skills/chant-helm.md +447 -0
- package/package.json +21 -3
- package/src/codegen/docs.test.ts +14 -0
- package/src/codegen/generate.test.ts +71 -0
- package/src/codegen/package.test.ts +36 -0
- package/src/composites/composites.test.ts +116 -110
- package/src/composites/helm-batch-job.ts +33 -19
- package/src/composites/helm-crd-lifecycle.ts +37 -24
- package/src/composites/helm-cron-job.ts +25 -13
- package/src/composites/helm-daemon-set.ts +26 -14
- package/src/composites/helm-external-secret.ts +21 -12
- package/src/composites/helm-library.ts +21 -7
- package/src/composites/helm-microservice.ts +46 -29
- package/src/composites/helm-monitored-service.ts +75 -51
- package/src/composites/helm-namespace-env.ts +80 -52
- package/src/composites/helm-secure-ingress.ts +66 -50
- package/src/composites/helm-stateful-service.ts +29 -16
- package/src/composites/helm-web-app.ts +37 -22
- package/src/composites/helm-worker.ts +34 -20
- package/src/import/roundtrip.test.ts +144 -0
- package/src/lint/post-synth/whm101.test.ts +69 -0
- package/src/lint/post-synth/whm102.test.ts +57 -0
- package/src/lint/post-synth/whm103.test.ts +58 -0
- package/src/lint/post-synth/whm104.test.ts +57 -0
- package/src/lint/post-synth/whm105.test.ts +41 -0
- package/src/lint/post-synth/whm201.test.ts +59 -0
- package/src/lint/post-synth/whm202.test.ts +62 -0
- package/src/lint/post-synth/whm203.test.ts +58 -0
- package/src/lint/post-synth/whm204.test.ts +56 -0
- package/src/lint/post-synth/whm301.test.ts +49 -0
- package/src/lint/post-synth/whm302.test.ts +58 -0
- package/src/lint/post-synth/whm401.test.ts +59 -0
- package/src/lint/post-synth/whm402.test.ts +58 -0
- package/src/lint/post-synth/whm403.test.ts +50 -0
- package/src/lint/post-synth/whm404.test.ts +50 -0
- package/src/lint/post-synth/whm405.test.ts +60 -0
- package/src/lint/post-synth/whm406.test.ts +43 -0
- package/src/lint/post-synth/whm407.test.ts +60 -0
- package/src/lint/post-synth/whm501.test.ts +70 -0
- package/src/lint/post-synth/whm502.test.ts +72 -0
- package/src/lint/rules/chart-metadata.test.ts +45 -0
- package/src/lint/rules/no-hardcoded-image.test.ts +41 -0
- package/src/lint/rules/values-no-secrets.test.ts +48 -0
- package/src/plugin.test.ts +3 -3
- package/src/plugin.ts +190 -19
- package/src/resources.ts +29 -0
- package/src/skills/chant-helm.md +447 -0
- package/dist/skills/chant-helm-create-chart.md +0 -211
- package/src/skills/create-chart.md +0 -211
- /package/dist/skills/{chant-helm-chart-patterns.md → chant-helm-patterns.md} +0 -0
- /package/dist/skills/{chant-helm-chart-security-patterns.md → chant-helm-security.md} +0 -0
- /package/src/skills/{chart-patterns.md → chant-helm-patterns.md} +0 -0
- /package/src/skills/{chart-security-patterns.md → chant-helm-security.md} +0 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { HelmParser } from "./parser";
|
|
3
|
+
import { HelmGenerator } from "./generator";
|
|
4
|
+
|
|
5
|
+
const parser = new HelmParser();
|
|
6
|
+
const generator = new HelmGenerator();
|
|
7
|
+
|
|
8
|
+
describe("roundtrip: parse -> generate", () => {
|
|
9
|
+
test("minimal chart roundtrip", () => {
|
|
10
|
+
const files = {
|
|
11
|
+
"Chart.yaml": "apiVersion: v2\nname: my-app\nversion: 0.1.0\n",
|
|
12
|
+
};
|
|
13
|
+
const ir = parser.parseFiles(files);
|
|
14
|
+
const generated = generator.generate(ir);
|
|
15
|
+
|
|
16
|
+
expect(generated).toHaveLength(1);
|
|
17
|
+
expect(generated[0].path).toBe("chart.ts");
|
|
18
|
+
expect(generated[0].content).toContain("import");
|
|
19
|
+
expect(generated[0].content).toContain("export const chart = new Chart(");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("chart with values roundtrip", () => {
|
|
23
|
+
const files = {
|
|
24
|
+
"Chart.yaml": "apiVersion: v2\nname: my-app\nversion: 0.1.0\n",
|
|
25
|
+
"values.yaml": "replicaCount: 2\nimage:\n repository: nginx\n tag: latest\n",
|
|
26
|
+
};
|
|
27
|
+
const ir = parser.parseFiles(files);
|
|
28
|
+
expect(ir.parameters.length).toBeGreaterThan(0);
|
|
29
|
+
|
|
30
|
+
const generated = generator.generate(ir);
|
|
31
|
+
const content = generated[0].content;
|
|
32
|
+
expect(content).toContain("Chart");
|
|
33
|
+
expect(content).toContain("Values");
|
|
34
|
+
expect(content).toContain("export const valuesSchema = new Values(");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("Deployment template roundtrip", () => {
|
|
38
|
+
const files = {
|
|
39
|
+
"Chart.yaml": "apiVersion: v2\nname: my-app\nversion: 0.1.0\n",
|
|
40
|
+
"templates/deploy.yaml": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: my-app\nspec:\n replicas: 1\n",
|
|
41
|
+
};
|
|
42
|
+
const ir = parser.parseFiles(files);
|
|
43
|
+
const deploy = ir.resources.find((r) => r.type === "K8s::Apps::Deployment");
|
|
44
|
+
expect(deploy).toBeDefined();
|
|
45
|
+
|
|
46
|
+
const generated = generator.generate(ir);
|
|
47
|
+
const content = generated[0].content;
|
|
48
|
+
expect(content).toContain("@intentius/chant-lexicon-k8s");
|
|
49
|
+
expect(content).toContain("Deployment");
|
|
50
|
+
expect(content).toContain("export const");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("Service template roundtrip", () => {
|
|
54
|
+
const files = {
|
|
55
|
+
"Chart.yaml": "apiVersion: v2\nname: my-app\nversion: 0.1.0\n",
|
|
56
|
+
"templates/svc.yaml": "apiVersion: v1\nkind: Service\nmetadata:\n name: my-app\nspec:\n type: ClusterIP\n ports:\n - port: 80\n",
|
|
57
|
+
};
|
|
58
|
+
const ir = parser.parseFiles(files);
|
|
59
|
+
const svc = ir.resources.find((r) => r.type === "K8s::Core::Service");
|
|
60
|
+
expect(svc).toBeDefined();
|
|
61
|
+
|
|
62
|
+
const generated = generator.generate(ir);
|
|
63
|
+
const content = generated[0].content;
|
|
64
|
+
expect(content).toContain("Service");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("template expressions produce intrinsic imports", () => {
|
|
68
|
+
const files = {
|
|
69
|
+
"Chart.yaml": "apiVersion: v2\nname: my-app\nversion: 0.1.0\n",
|
|
70
|
+
"templates/deploy.yaml": 'apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: {{ include "my-app.fullname" . }}\nspec:\n replicas: {{ .Values.replicaCount }}\n',
|
|
71
|
+
};
|
|
72
|
+
const ir = parser.parseFiles(files);
|
|
73
|
+
const generated = generator.generate(ir);
|
|
74
|
+
const content = generated[0].content;
|
|
75
|
+
expect(content).toContain("include");
|
|
76
|
+
expect(content).toContain("values");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("NOTES.txt roundtrip", () => {
|
|
80
|
+
const files = {
|
|
81
|
+
"Chart.yaml": "apiVersion: v2\nname: my-app\nversion: 0.1.0\n",
|
|
82
|
+
"templates/NOTES.txt": "Thank you for installing {{ .Chart.Name }}",
|
|
83
|
+
};
|
|
84
|
+
const ir = parser.parseFiles(files);
|
|
85
|
+
const notes = ir.resources.find((r) => r.type === "Helm::Notes");
|
|
86
|
+
expect(notes).toBeDefined();
|
|
87
|
+
|
|
88
|
+
const generated = generator.generate(ir);
|
|
89
|
+
expect(generated[0].content).toContain("HelmNotes");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("full chart with multiple resources roundtrip", () => {
|
|
93
|
+
const files = {
|
|
94
|
+
"Chart.yaml": "apiVersion: v2\nname: my-app\nversion: 0.1.0\n",
|
|
95
|
+
"values.yaml": "replicaCount: 1\nimage:\n repository: nginx\n tag: stable\n",
|
|
96
|
+
"templates/deploy.yaml": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: my-app\nspec:\n replicas: {{ .Values.replicaCount }}\n",
|
|
97
|
+
"templates/svc.yaml": "apiVersion: v1\nkind: Service\nmetadata:\n name: my-app\nspec:\n type: ClusterIP\n",
|
|
98
|
+
"templates/NOTES.txt": "Thank you for installing my-app!",
|
|
99
|
+
};
|
|
100
|
+
const ir = parser.parseFiles(files);
|
|
101
|
+
expect(ir.resources.length).toBeGreaterThanOrEqual(4);
|
|
102
|
+
|
|
103
|
+
const generated = generator.generate(ir);
|
|
104
|
+
const content = generated[0].content;
|
|
105
|
+
expect(content).toContain("Chart");
|
|
106
|
+
expect(content).toContain("Values");
|
|
107
|
+
expect(content).toContain("Deployment");
|
|
108
|
+
expect(content).toContain("Service");
|
|
109
|
+
expect(content).toContain("HelmNotes");
|
|
110
|
+
expect(content).toContain("export const");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("IR metadata captures chart name", () => {
|
|
114
|
+
const files = {
|
|
115
|
+
"Chart.yaml": "apiVersion: v2\nname: cool-app\nversion: 1.0.0\n",
|
|
116
|
+
};
|
|
117
|
+
const ir = parser.parseFiles(files);
|
|
118
|
+
expect(ir.metadata?.chartName).toBe("cool-app");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("ConfigMap template roundtrip", () => {
|
|
122
|
+
const files = {
|
|
123
|
+
"Chart.yaml": "apiVersion: v2\nname: my-app\nversion: 0.1.0\n",
|
|
124
|
+
"templates/configmap.yaml": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: my-config\ndata:\n key: value\n",
|
|
125
|
+
};
|
|
126
|
+
const ir = parser.parseFiles(files);
|
|
127
|
+
const cm = ir.resources.find((r) => r.type === "K8s::Core::ConfigMap");
|
|
128
|
+
expect(cm).toBeDefined();
|
|
129
|
+
|
|
130
|
+
const generated = generator.generate(ir);
|
|
131
|
+
expect(generated[0].content).toContain("ConfigMap");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("skips _helpers.tpl files", () => {
|
|
135
|
+
const files = {
|
|
136
|
+
"Chart.yaml": "apiVersion: v2\nname: my-app\nversion: 0.1.0\n",
|
|
137
|
+
"templates/_helpers.tpl": '{{- define "my-app.fullname" -}}\n{{ .Release.Name }}\n{{- end -}}\n',
|
|
138
|
+
};
|
|
139
|
+
const ir = parser.parseFiles(files);
|
|
140
|
+
// Only the chart resource, no template from _helpers.tpl
|
|
141
|
+
expect(ir.resources).toHaveLength(1);
|
|
142
|
+
expect(ir.resources[0].type).toBe("Helm::Chart");
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
3
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
4
|
+
import { whm101 } from "./whm101";
|
|
5
|
+
|
|
6
|
+
function makeCtx(files: Record<string, string>): PostSynthContext {
|
|
7
|
+
const result: SerializerResult = { primary: files["Chart.yaml"] ?? "", files };
|
|
8
|
+
const outputs = new Map<string, string | SerializerResult>();
|
|
9
|
+
outputs.set("helm", result);
|
|
10
|
+
return {
|
|
11
|
+
outputs,
|
|
12
|
+
entities: new Map(),
|
|
13
|
+
buildResult: {
|
|
14
|
+
outputs,
|
|
15
|
+
entities: new Map(),
|
|
16
|
+
warnings: [],
|
|
17
|
+
errors: [],
|
|
18
|
+
sourceFileCount: 1,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("WHM101: Chart.yaml validation", () => {
|
|
24
|
+
test("passes with valid Chart.yaml", () => {
|
|
25
|
+
const ctx = makeCtx({
|
|
26
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
27
|
+
});
|
|
28
|
+
expect(whm101.check(ctx)).toHaveLength(0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("fails when Chart.yaml is missing", () => {
|
|
32
|
+
const ctx = makeCtx({});
|
|
33
|
+
const diags = whm101.check(ctx);
|
|
34
|
+
expect(diags.length).toBeGreaterThan(0);
|
|
35
|
+
expect(diags[0].message).toContain("missing");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("fails when apiVersion is not v2", () => {
|
|
39
|
+
const ctx = makeCtx({
|
|
40
|
+
"Chart.yaml": "apiVersion: v1\nname: test\nversion: 0.1.0\n",
|
|
41
|
+
});
|
|
42
|
+
const diags = whm101.check(ctx);
|
|
43
|
+
expect(diags.some((d) => d.message.includes("v2"))).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("fails when name is missing", () => {
|
|
47
|
+
const ctx = makeCtx({
|
|
48
|
+
"Chart.yaml": "apiVersion: v2\nversion: 0.1.0\n",
|
|
49
|
+
});
|
|
50
|
+
const diags = whm101.check(ctx);
|
|
51
|
+
expect(diags.some((d) => d.message.includes("name"))).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("fails when version is missing", () => {
|
|
55
|
+
const ctx = makeCtx({
|
|
56
|
+
"Chart.yaml": "apiVersion: v2\nname: test\n",
|
|
57
|
+
});
|
|
58
|
+
const diags = whm101.check(ctx);
|
|
59
|
+
expect(diags.some((d) => d.message.includes("version"))).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("all diagnostics have checkId WHM101", () => {
|
|
63
|
+
const ctx = makeCtx({});
|
|
64
|
+
const diags = whm101.check(ctx);
|
|
65
|
+
for (const d of diags) {
|
|
66
|
+
expect(d.checkId).toBe("WHM101");
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
3
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
4
|
+
import { whm102 } from "./whm102";
|
|
5
|
+
|
|
6
|
+
function makeCtx(files: Record<string, string>): PostSynthContext {
|
|
7
|
+
const result: SerializerResult = { primary: files["Chart.yaml"] ?? "", files };
|
|
8
|
+
const outputs = new Map<string, string | SerializerResult>();
|
|
9
|
+
outputs.set("helm", result);
|
|
10
|
+
return {
|
|
11
|
+
outputs,
|
|
12
|
+
entities: new Map(),
|
|
13
|
+
buildResult: {
|
|
14
|
+
outputs,
|
|
15
|
+
entities: new Map(),
|
|
16
|
+
warnings: [],
|
|
17
|
+
errors: [],
|
|
18
|
+
sourceFileCount: 1,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("WHM102: values.schema.json", () => {
|
|
24
|
+
test("passes when schema exists", () => {
|
|
25
|
+
const ctx = makeCtx({
|
|
26
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
27
|
+
"values.yaml": "replicaCount: 1\n",
|
|
28
|
+
"values.schema.json": "{}",
|
|
29
|
+
});
|
|
30
|
+
expect(whm102.check(ctx)).toHaveLength(0);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("warns when values are non-empty but schema is missing", () => {
|
|
34
|
+
const ctx = makeCtx({
|
|
35
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
36
|
+
"values.yaml": "replicaCount: 1\n",
|
|
37
|
+
});
|
|
38
|
+
const diags = whm102.check(ctx);
|
|
39
|
+
expect(diags).toHaveLength(1);
|
|
40
|
+
expect(diags[0].checkId).toBe("WHM102");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("passes when values.yaml is empty object", () => {
|
|
44
|
+
const ctx = makeCtx({
|
|
45
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
46
|
+
"values.yaml": "{}",
|
|
47
|
+
});
|
|
48
|
+
expect(whm102.check(ctx)).toHaveLength(0);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("passes when values.yaml is absent", () => {
|
|
52
|
+
const ctx = makeCtx({
|
|
53
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
54
|
+
});
|
|
55
|
+
expect(whm102.check(ctx)).toHaveLength(0);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
3
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
4
|
+
import { whm103 } from "./whm103";
|
|
5
|
+
|
|
6
|
+
function makeCtx(files: Record<string, string>): PostSynthContext {
|
|
7
|
+
const result: SerializerResult = { primary: files["Chart.yaml"] ?? "", files };
|
|
8
|
+
const outputs = new Map<string, string | SerializerResult>();
|
|
9
|
+
outputs.set("helm", result);
|
|
10
|
+
return {
|
|
11
|
+
outputs,
|
|
12
|
+
entities: new Map(),
|
|
13
|
+
buildResult: {
|
|
14
|
+
outputs,
|
|
15
|
+
entities: new Map(),
|
|
16
|
+
warnings: [],
|
|
17
|
+
errors: [],
|
|
18
|
+
sourceFileCount: 1,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("WHM103: template syntax", () => {
|
|
24
|
+
test("passes with balanced braces", () => {
|
|
25
|
+
const ctx = makeCtx({
|
|
26
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
27
|
+
"templates/deploy.yaml": "name: {{ .Values.name }}\n",
|
|
28
|
+
});
|
|
29
|
+
expect(whm103.check(ctx)).toHaveLength(0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("fails with unbalanced braces (missing closing)", () => {
|
|
33
|
+
const ctx = makeCtx({
|
|
34
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
35
|
+
"templates/deploy.yaml": "name: {{ .Values.name }\n",
|
|
36
|
+
});
|
|
37
|
+
const diags = whm103.check(ctx);
|
|
38
|
+
expect(diags).toHaveLength(1);
|
|
39
|
+
expect(diags[0].message).toContain("Unbalanced");
|
|
40
|
+
expect(diags[0].checkId).toBe("WHM103");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("passes with multiple balanced expressions", () => {
|
|
44
|
+
const ctx = makeCtx({
|
|
45
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
46
|
+
"templates/deploy.yaml": "name: {{ .Values.name }}\nimage: {{ .Values.image }}\n",
|
|
47
|
+
});
|
|
48
|
+
expect(whm103.check(ctx)).toHaveLength(0);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("ignores non-template files", () => {
|
|
52
|
+
const ctx = makeCtx({
|
|
53
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
54
|
+
"values.yaml": "name: {{ broken\n",
|
|
55
|
+
});
|
|
56
|
+
expect(whm103.check(ctx)).toHaveLength(0);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
3
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
4
|
+
import { whm104 } from "./whm104";
|
|
5
|
+
|
|
6
|
+
function makeCtx(files: Record<string, string>): PostSynthContext {
|
|
7
|
+
const result: SerializerResult = { primary: files["Chart.yaml"] ?? "", files };
|
|
8
|
+
const outputs = new Map<string, string | SerializerResult>();
|
|
9
|
+
outputs.set("helm", result);
|
|
10
|
+
return {
|
|
11
|
+
outputs,
|
|
12
|
+
entities: new Map(),
|
|
13
|
+
buildResult: {
|
|
14
|
+
outputs,
|
|
15
|
+
entities: new Map(),
|
|
16
|
+
warnings: [],
|
|
17
|
+
errors: [],
|
|
18
|
+
sourceFileCount: 1,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("WHM104: NOTES.txt", () => {
|
|
24
|
+
test("info when NOTES.txt is missing for application chart", () => {
|
|
25
|
+
const ctx = makeCtx({
|
|
26
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\ntype: application\n",
|
|
27
|
+
});
|
|
28
|
+
const diags = whm104.check(ctx);
|
|
29
|
+
expect(diags).toHaveLength(1);
|
|
30
|
+
expect(diags[0].severity).toBe("info");
|
|
31
|
+
expect(diags[0].checkId).toBe("WHM104");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("passes for library charts without NOTES.txt", () => {
|
|
35
|
+
const ctx = makeCtx({
|
|
36
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\ntype: library\n",
|
|
37
|
+
});
|
|
38
|
+
expect(whm104.check(ctx)).toHaveLength(0);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("passes when NOTES.txt exists", () => {
|
|
42
|
+
const ctx = makeCtx({
|
|
43
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\ntype: application\n",
|
|
44
|
+
"templates/NOTES.txt": "Hello!",
|
|
45
|
+
});
|
|
46
|
+
expect(whm104.check(ctx)).toHaveLength(0);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("info when type is absent (defaults to application)", () => {
|
|
50
|
+
const ctx = makeCtx({
|
|
51
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
52
|
+
});
|
|
53
|
+
const diags = whm104.check(ctx);
|
|
54
|
+
expect(diags).toHaveLength(1);
|
|
55
|
+
expect(diags[0].severity).toBe("info");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
3
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
4
|
+
import { whm105 } from "./whm105";
|
|
5
|
+
|
|
6
|
+
function makeCtx(files: Record<string, string>): PostSynthContext {
|
|
7
|
+
const result: SerializerResult = { primary: files["Chart.yaml"] ?? "", files };
|
|
8
|
+
const outputs = new Map<string, string | SerializerResult>();
|
|
9
|
+
outputs.set("helm", result);
|
|
10
|
+
return {
|
|
11
|
+
outputs,
|
|
12
|
+
entities: new Map(),
|
|
13
|
+
buildResult: {
|
|
14
|
+
outputs,
|
|
15
|
+
entities: new Map(),
|
|
16
|
+
warnings: [],
|
|
17
|
+
errors: [],
|
|
18
|
+
sourceFileCount: 1,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("WHM105: _helpers.tpl", () => {
|
|
24
|
+
test("warns when _helpers.tpl is missing", () => {
|
|
25
|
+
const ctx = makeCtx({
|
|
26
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
27
|
+
});
|
|
28
|
+
const diags = whm105.check(ctx);
|
|
29
|
+
expect(diags).toHaveLength(1);
|
|
30
|
+
expect(diags[0].checkId).toBe("WHM105");
|
|
31
|
+
expect(diags[0].severity).toBe("warning");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("passes when _helpers.tpl exists", () => {
|
|
35
|
+
const ctx = makeCtx({
|
|
36
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
37
|
+
"templates/_helpers.tpl": "{{/* helpers */}}",
|
|
38
|
+
});
|
|
39
|
+
expect(whm105.check(ctx)).toHaveLength(0);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
3
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
4
|
+
import { whm201 } from "./whm201";
|
|
5
|
+
|
|
6
|
+
function makeCtx(files: Record<string, string>): PostSynthContext {
|
|
7
|
+
const result: SerializerResult = { primary: files["Chart.yaml"] ?? "", files };
|
|
8
|
+
const outputs = new Map<string, string | SerializerResult>();
|
|
9
|
+
outputs.set("helm", result);
|
|
10
|
+
return {
|
|
11
|
+
outputs,
|
|
12
|
+
entities: new Map(),
|
|
13
|
+
buildResult: {
|
|
14
|
+
outputs,
|
|
15
|
+
entities: new Map(),
|
|
16
|
+
warnings: [],
|
|
17
|
+
errors: [],
|
|
18
|
+
sourceFileCount: 1,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("WHM201: standard labels", () => {
|
|
24
|
+
test("info when template lacks labels", () => {
|
|
25
|
+
const ctx = makeCtx({
|
|
26
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
27
|
+
"templates/deploy.yaml": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: test\n",
|
|
28
|
+
});
|
|
29
|
+
const diags = whm201.check(ctx);
|
|
30
|
+
expect(diags).toHaveLength(1);
|
|
31
|
+
expect(diags[0].checkId).toBe("WHM201");
|
|
32
|
+
expect(diags[0].severity).toBe("info");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("passes when template includes labels helper", () => {
|
|
36
|
+
const ctx = makeCtx({
|
|
37
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
38
|
+
"templates/deploy.yaml": 'apiVersion: apps/v1\nkind: Deployment\nmetadata:\n labels: {{ include "test.labels" . }}\n',
|
|
39
|
+
});
|
|
40
|
+
expect(whm201.check(ctx)).toHaveLength(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("passes when template has helm.sh/chart annotation", () => {
|
|
44
|
+
const ctx = makeCtx({
|
|
45
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
46
|
+
"templates/deploy.yaml": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n labels:\n helm.sh/chart: test\n",
|
|
47
|
+
});
|
|
48
|
+
expect(whm201.check(ctx)).toHaveLength(0);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("skips _helpers.tpl and NOTES.txt", () => {
|
|
52
|
+
const ctx = makeCtx({
|
|
53
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
54
|
+
"templates/_helpers.tpl": "kind: something\n",
|
|
55
|
+
"templates/NOTES.txt": "kind: something\n",
|
|
56
|
+
});
|
|
57
|
+
expect(whm201.check(ctx)).toHaveLength(0);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
3
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
4
|
+
import { whm202 } from "./whm202";
|
|
5
|
+
|
|
6
|
+
function makeCtx(files: Record<string, string>): PostSynthContext {
|
|
7
|
+
const result: SerializerResult = { primary: files["Chart.yaml"] ?? "", files };
|
|
8
|
+
const outputs = new Map<string, string | SerializerResult>();
|
|
9
|
+
outputs.set("helm", result);
|
|
10
|
+
return {
|
|
11
|
+
outputs,
|
|
12
|
+
entities: new Map(),
|
|
13
|
+
buildResult: {
|
|
14
|
+
outputs,
|
|
15
|
+
entities: new Map(),
|
|
16
|
+
warnings: [],
|
|
17
|
+
errors: [],
|
|
18
|
+
sourceFileCount: 1,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("WHM202: hook weights", () => {
|
|
24
|
+
test("warns when multiple hooks exist and some lack weights", () => {
|
|
25
|
+
const ctx = makeCtx({
|
|
26
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
27
|
+
"templates/pre-install.yaml": "helm.sh/hook: pre-install\nhelm.sh/hook-weight: \"-5\"\n",
|
|
28
|
+
"templates/post-install.yaml": "helm.sh/hook: post-install\n",
|
|
29
|
+
});
|
|
30
|
+
const diags = whm202.check(ctx);
|
|
31
|
+
expect(diags).toHaveLength(1);
|
|
32
|
+
expect(diags[0].checkId).toBe("WHM202");
|
|
33
|
+
expect(diags[0].severity).toBe("warning");
|
|
34
|
+
expect(diags[0].message).toContain("hook-weight");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("passes when all hooks have weights", () => {
|
|
38
|
+
const ctx = makeCtx({
|
|
39
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
40
|
+
"templates/pre-install.yaml": "helm.sh/hook: pre-install\nhelm.sh/hook-weight: \"-5\"\n",
|
|
41
|
+
"templates/post-install.yaml": "helm.sh/hook: post-install\nhelm.sh/hook-weight: \"5\"\n",
|
|
42
|
+
});
|
|
43
|
+
expect(whm202.check(ctx)).toHaveLength(0);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("passes with a single hook without weight", () => {
|
|
47
|
+
const ctx = makeCtx({
|
|
48
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
49
|
+
"templates/pre-install.yaml": "helm.sh/hook: pre-install\n",
|
|
50
|
+
});
|
|
51
|
+
expect(whm202.check(ctx)).toHaveLength(0);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("ignores test hooks", () => {
|
|
55
|
+
const ctx = makeCtx({
|
|
56
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
57
|
+
"templates/test.yaml": "helm.sh/hook: test\n",
|
|
58
|
+
"templates/pre-install.yaml": "helm.sh/hook: pre-install\n",
|
|
59
|
+
});
|
|
60
|
+
expect(whm202.check(ctx)).toHaveLength(0);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
3
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
4
|
+
import { whm203 } from "./whm203";
|
|
5
|
+
|
|
6
|
+
function makeCtx(files: Record<string, string>): PostSynthContext {
|
|
7
|
+
const result: SerializerResult = { primary: files["Chart.yaml"] ?? "", files };
|
|
8
|
+
const outputs = new Map<string, string | SerializerResult>();
|
|
9
|
+
outputs.set("helm", result);
|
|
10
|
+
return {
|
|
11
|
+
outputs,
|
|
12
|
+
entities: new Map(),
|
|
13
|
+
buildResult: {
|
|
14
|
+
outputs,
|
|
15
|
+
entities: new Map(),
|
|
16
|
+
warnings: [],
|
|
17
|
+
errors: [],
|
|
18
|
+
sourceFileCount: 1,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("WHM203: values documentation", () => {
|
|
24
|
+
test("info when values exist but no schema", () => {
|
|
25
|
+
const ctx = makeCtx({
|
|
26
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
27
|
+
"values.yaml": "replicaCount: 1\n",
|
|
28
|
+
});
|
|
29
|
+
const diags = whm203.check(ctx);
|
|
30
|
+
expect(diags).toHaveLength(1);
|
|
31
|
+
expect(diags[0].checkId).toBe("WHM203");
|
|
32
|
+
expect(diags[0].severity).toBe("info");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("passes when schema exists", () => {
|
|
36
|
+
const ctx = makeCtx({
|
|
37
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
38
|
+
"values.yaml": "replicaCount: 1\n",
|
|
39
|
+
"values.schema.json": '{"type":"object"}',
|
|
40
|
+
});
|
|
41
|
+
expect(whm203.check(ctx)).toHaveLength(0);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("passes when values.yaml is empty object", () => {
|
|
45
|
+
const ctx = makeCtx({
|
|
46
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
47
|
+
"values.yaml": "{}",
|
|
48
|
+
});
|
|
49
|
+
expect(whm203.check(ctx)).toHaveLength(0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("passes when no values.yaml exists", () => {
|
|
53
|
+
const ctx = makeCtx({
|
|
54
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
55
|
+
});
|
|
56
|
+
expect(whm203.check(ctx)).toHaveLength(0);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
3
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
4
|
+
import { whm204 } from "./whm204";
|
|
5
|
+
|
|
6
|
+
function makeCtx(files: Record<string, string>): PostSynthContext {
|
|
7
|
+
const result: SerializerResult = { primary: files["Chart.yaml"] ?? "", files };
|
|
8
|
+
const outputs = new Map<string, string | SerializerResult>();
|
|
9
|
+
outputs.set("helm", result);
|
|
10
|
+
return {
|
|
11
|
+
outputs,
|
|
12
|
+
entities: new Map(),
|
|
13
|
+
buildResult: {
|
|
14
|
+
outputs,
|
|
15
|
+
entities: new Map(),
|
|
16
|
+
warnings: [],
|
|
17
|
+
errors: [],
|
|
18
|
+
sourceFileCount: 1,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("WHM204: dependency semver ranges", () => {
|
|
24
|
+
test("info when dependency uses pinned version", () => {
|
|
25
|
+
const ctx = makeCtx({
|
|
26
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\ndependencies:\n - name: redis\n version: 1.2.3\n repository: https://charts.bitnami.com\n",
|
|
27
|
+
});
|
|
28
|
+
const diags = whm204.check(ctx);
|
|
29
|
+
expect(diags).toHaveLength(1);
|
|
30
|
+
expect(diags[0].checkId).toBe("WHM204");
|
|
31
|
+
expect(diags[0].severity).toBe("info");
|
|
32
|
+
expect(diags[0].message).toContain("redis");
|
|
33
|
+
expect(diags[0].message).toContain("1.2.3");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("passes when dependency uses tilde range", () => {
|
|
37
|
+
const ctx = makeCtx({
|
|
38
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\ndependencies:\n - name: redis\n version: ~1.2.3\n repository: https://charts.bitnami.com\n",
|
|
39
|
+
});
|
|
40
|
+
expect(whm204.check(ctx)).toHaveLength(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("passes when dependency uses caret range", () => {
|
|
44
|
+
const ctx = makeCtx({
|
|
45
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\ndependencies:\n - name: redis\n version: ^1.2.3\n repository: https://charts.bitnami.com\n",
|
|
46
|
+
});
|
|
47
|
+
expect(whm204.check(ctx)).toHaveLength(0);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("passes when no dependencies section", () => {
|
|
51
|
+
const ctx = makeCtx({
|
|
52
|
+
"Chart.yaml": "apiVersion: v2\nname: test\nversion: 0.1.0\n",
|
|
53
|
+
});
|
|
54
|
+
expect(whm204.check(ctx)).toHaveLength(0);
|
|
55
|
+
});
|
|
56
|
+
});
|