@intentius/chant-lexicon-helm 0.0.16
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/README.md +22 -0
- package/dist/integrity.json +36 -0
- package/dist/manifest.json +37 -0
- package/dist/meta.json +208 -0
- package/dist/rules/chart-metadata.ts +64 -0
- package/dist/rules/helm-helpers.ts +64 -0
- package/dist/rules/no-hardcoded-image.ts +62 -0
- package/dist/rules/values-no-secrets.ts +82 -0
- package/dist/rules/whm101.ts +46 -0
- package/dist/rules/whm102.ts +33 -0
- package/dist/rules/whm103.ts +59 -0
- package/dist/rules/whm104.ts +35 -0
- package/dist/rules/whm105.ts +30 -0
- package/dist/rules/whm201.ts +36 -0
- package/dist/rules/whm202.ts +50 -0
- package/dist/rules/whm203.ts +39 -0
- package/dist/rules/whm204.ts +60 -0
- package/dist/rules/whm301.ts +41 -0
- package/dist/rules/whm302.ts +40 -0
- package/dist/rules/whm401.ts +57 -0
- package/dist/rules/whm402.ts +45 -0
- package/dist/rules/whm403.ts +45 -0
- package/dist/rules/whm404.ts +36 -0
- package/dist/rules/whm405.ts +53 -0
- package/dist/rules/whm406.ts +34 -0
- package/dist/rules/whm407.ts +83 -0
- package/dist/rules/whm501.ts +103 -0
- package/dist/rules/whm502.ts +94 -0
- package/dist/skills/chant-helm-chart-patterns.md +229 -0
- package/dist/skills/chant-helm-chart-security-patterns.md +192 -0
- package/dist/skills/chant-helm-create-chart.md +211 -0
- package/dist/types/index.d.ts +132 -0
- package/package.json +34 -0
- package/src/codegen/docs-cli.ts +4 -0
- package/src/codegen/docs.ts +483 -0
- package/src/codegen/generate-cli.ts +28 -0
- package/src/codegen/generate.ts +249 -0
- package/src/codegen/naming.ts +38 -0
- package/src/codegen/package.ts +64 -0
- package/src/composites/composites.test.ts +1050 -0
- package/src/composites/helm-batch-job.ts +209 -0
- package/src/composites/helm-crd-lifecycle.ts +184 -0
- package/src/composites/helm-cron-job.ts +177 -0
- package/src/composites/helm-daemon-set.ts +169 -0
- package/src/composites/helm-external-secret.ts +93 -0
- package/src/composites/helm-library.ts +51 -0
- package/src/composites/helm-microservice.ts +331 -0
- package/src/composites/helm-monitored-service.ts +252 -0
- package/src/composites/helm-namespace-env.ts +154 -0
- package/src/composites/helm-secure-ingress.ts +114 -0
- package/src/composites/helm-stateful-service.ts +213 -0
- package/src/composites/helm-web-app.ts +264 -0
- package/src/composites/helm-worker.ts +207 -0
- package/src/composites/index.ts +38 -0
- package/src/coverage.test.ts +21 -0
- package/src/coverage.ts +50 -0
- package/src/generated/index.d.ts +132 -0
- package/src/generated/index.ts +13 -0
- package/src/generated/lexicon-helm.json +208 -0
- package/src/helpers.test.ts +51 -0
- package/src/helpers.ts +100 -0
- package/src/import/generator.ts +285 -0
- package/src/import/import.test.ts +224 -0
- package/src/import/parser.ts +160 -0
- package/src/import/template-stripper.ts +123 -0
- package/src/index.ts +108 -0
- package/src/intrinsics.test.ts +380 -0
- package/src/intrinsics.ts +484 -0
- package/src/lint/post-synth/helm-helpers.ts +64 -0
- package/src/lint/post-synth/post-synth.test.ts +504 -0
- package/src/lint/post-synth/whm101.ts +46 -0
- package/src/lint/post-synth/whm102.ts +33 -0
- package/src/lint/post-synth/whm103.ts +59 -0
- package/src/lint/post-synth/whm104.ts +35 -0
- package/src/lint/post-synth/whm105.ts +30 -0
- package/src/lint/post-synth/whm201.ts +36 -0
- package/src/lint/post-synth/whm202.ts +50 -0
- package/src/lint/post-synth/whm203.ts +39 -0
- package/src/lint/post-synth/whm204.ts +60 -0
- package/src/lint/post-synth/whm301.ts +41 -0
- package/src/lint/post-synth/whm302.ts +40 -0
- package/src/lint/post-synth/whm401.ts +57 -0
- package/src/lint/post-synth/whm402.ts +45 -0
- package/src/lint/post-synth/whm403.ts +45 -0
- package/src/lint/post-synth/whm404.ts +36 -0
- package/src/lint/post-synth/whm405.ts +53 -0
- package/src/lint/post-synth/whm406.ts +34 -0
- package/src/lint/post-synth/whm407.ts +83 -0
- package/src/lint/post-synth/whm501.ts +103 -0
- package/src/lint/post-synth/whm502.ts +94 -0
- package/src/lint/rules/chart-metadata.ts +64 -0
- package/src/lint/rules/lint-rules.test.ts +97 -0
- package/src/lint/rules/no-hardcoded-image.ts +62 -0
- package/src/lint/rules/values-no-secrets.ts +82 -0
- package/src/lsp/completions.test.ts +72 -0
- package/src/lsp/completions.ts +20 -0
- package/src/lsp/hover.test.ts +46 -0
- package/src/lsp/hover.ts +46 -0
- package/src/package-cli.ts +28 -0
- package/src/plugin.test.ts +71 -0
- package/src/plugin.ts +206 -0
- package/src/resources.ts +77 -0
- package/src/serializer.test.ts +930 -0
- package/src/serializer.ts +835 -0
- package/src/skills/chart-patterns.md +229 -0
- package/src/skills/chart-security-patterns.md +192 -0
- package/src/skills/create-chart.md +211 -0
- package/src/validate-cli.ts +21 -0
- package/src/validate.test.ts +37 -0
- package/src/validate.ts +36 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHM105: _helpers.tpl exists.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
6
|
+
import { getChartFiles } from "./helm-helpers";
|
|
7
|
+
|
|
8
|
+
export const whm105: PostSynthCheck = {
|
|
9
|
+
id: "WHM105",
|
|
10
|
+
description: "_helpers.tpl must exist in templates/",
|
|
11
|
+
|
|
12
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
13
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
14
|
+
|
|
15
|
+
for (const [, output] of ctx.outputs) {
|
|
16
|
+
const files = getChartFiles(output);
|
|
17
|
+
|
|
18
|
+
if (!files["templates/_helpers.tpl"]) {
|
|
19
|
+
diagnostics.push({
|
|
20
|
+
checkId: "WHM105",
|
|
21
|
+
severity: "warning",
|
|
22
|
+
message: "templates/_helpers.tpl is missing",
|
|
23
|
+
lexicon: "helm",
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return diagnostics;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHM201: Resources should have standard Helm labels.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
6
|
+
import { getChartFiles } from "./helm-helpers";
|
|
7
|
+
|
|
8
|
+
export const whm201: PostSynthCheck = {
|
|
9
|
+
id: "WHM201",
|
|
10
|
+
description: "K8s resources should include standard Helm labels",
|
|
11
|
+
|
|
12
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
13
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
14
|
+
|
|
15
|
+
for (const [, output] of ctx.outputs) {
|
|
16
|
+
const files = getChartFiles(output);
|
|
17
|
+
|
|
18
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
19
|
+
if (!filename.startsWith("templates/") || filename.endsWith("_helpers.tpl") || filename.endsWith("NOTES.txt")) continue;
|
|
20
|
+
|
|
21
|
+
// Check if the template references the standard labels helper
|
|
22
|
+
if (content.includes("kind:") && !content.includes(".labels") && !content.includes("helm.sh/chart")) {
|
|
23
|
+
diagnostics.push({
|
|
24
|
+
checkId: "WHM201",
|
|
25
|
+
severity: "info",
|
|
26
|
+
message: `${filename} does not include standard Helm labels — consider using include "<chart>.labels"`,
|
|
27
|
+
entity: filename,
|
|
28
|
+
lexicon: "helm",
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return diagnostics;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHM202: Hook weights should be defined for multi-hook charts.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
6
|
+
import { getChartFiles } from "./helm-helpers";
|
|
7
|
+
|
|
8
|
+
export const whm202: PostSynthCheck = {
|
|
9
|
+
id: "WHM202",
|
|
10
|
+
description: "Hook weights should be defined when multiple hooks exist",
|
|
11
|
+
|
|
12
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
13
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
14
|
+
|
|
15
|
+
for (const [, output] of ctx.outputs) {
|
|
16
|
+
const files = getChartFiles(output);
|
|
17
|
+
|
|
18
|
+
const hooksWithWeight: string[] = [];
|
|
19
|
+
const hooksWithoutWeight: string[] = [];
|
|
20
|
+
|
|
21
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
22
|
+
if (!filename.startsWith("templates/")) continue;
|
|
23
|
+
|
|
24
|
+
if (content.includes("helm.sh/hook:") && !content.includes("helm.sh/hook: test")) {
|
|
25
|
+
if (content.includes("helm.sh/hook-weight:")) {
|
|
26
|
+
hooksWithWeight.push(filename);
|
|
27
|
+
} else {
|
|
28
|
+
hooksWithoutWeight.push(filename);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Only warn if there are multiple hooks and some lack weights
|
|
34
|
+
const totalHooks = hooksWithWeight.length + hooksWithoutWeight.length;
|
|
35
|
+
if (totalHooks > 1 && hooksWithoutWeight.length > 0) {
|
|
36
|
+
for (const filename of hooksWithoutWeight) {
|
|
37
|
+
diagnostics.push({
|
|
38
|
+
checkId: "WHM202",
|
|
39
|
+
severity: "warning",
|
|
40
|
+
message: `${filename} has a hook but no hook-weight — define weights to control execution order in multi-hook charts`,
|
|
41
|
+
entity: filename,
|
|
42
|
+
lexicon: "helm",
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return diagnostics;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHM203: Values entries should be documented.
|
|
3
|
+
*
|
|
4
|
+
* Checks that values.yaml has YAML comments or that values.schema.json
|
|
5
|
+
* provides descriptions. Since Chant generates values.yaml from code,
|
|
6
|
+
* this check looks for values.schema.json as the documentation source.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
10
|
+
import { getChartFiles } from "./helm-helpers";
|
|
11
|
+
|
|
12
|
+
export const whm203: PostSynthCheck = {
|
|
13
|
+
id: "WHM203",
|
|
14
|
+
description: "Values entries should be documented via schema or comments",
|
|
15
|
+
|
|
16
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
17
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
18
|
+
|
|
19
|
+
for (const [, output] of ctx.outputs) {
|
|
20
|
+
const files = getChartFiles(output);
|
|
21
|
+
const valuesYaml = files["values.yaml"];
|
|
22
|
+
const schemaJson = files["values.schema.json"];
|
|
23
|
+
|
|
24
|
+
if (!valuesYaml || valuesYaml.trim() === "{}") continue;
|
|
25
|
+
|
|
26
|
+
// If no schema, values are undocumented
|
|
27
|
+
if (!schemaJson) {
|
|
28
|
+
diagnostics.push({
|
|
29
|
+
checkId: "WHM203",
|
|
30
|
+
severity: "info",
|
|
31
|
+
message: "Values are not documented — add a Values type to generate values.schema.json",
|
|
32
|
+
lexicon: "helm",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return diagnostics;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHM204: Dependencies should use semver ranges.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
6
|
+
import { getChartFiles } from "./helm-helpers";
|
|
7
|
+
|
|
8
|
+
const SEMVER_RANGE_PATTERN = /^[~^>=<*]|\.x/;
|
|
9
|
+
|
|
10
|
+
export const whm204: PostSynthCheck = {
|
|
11
|
+
id: "WHM204",
|
|
12
|
+
description: "Chart dependencies should use semver ranges, not pinned versions",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
16
|
+
|
|
17
|
+
for (const [, output] of ctx.outputs) {
|
|
18
|
+
const files = getChartFiles(output);
|
|
19
|
+
const chartYaml = files["Chart.yaml"];
|
|
20
|
+
if (!chartYaml) continue;
|
|
21
|
+
|
|
22
|
+
// Simple extraction of dependency versions from Chart.yaml
|
|
23
|
+
const lines = chartYaml.split("\n");
|
|
24
|
+
let inDependencies = false;
|
|
25
|
+
let currentDep = "";
|
|
26
|
+
|
|
27
|
+
for (const line of lines) {
|
|
28
|
+
if (line.startsWith("dependencies:")) {
|
|
29
|
+
inDependencies = true;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (inDependencies) {
|
|
33
|
+
if (/^\S/.test(line) && !line.startsWith(" ")) {
|
|
34
|
+
inDependencies = false;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const nameMatch = line.match(/name:\s*(.+)/);
|
|
39
|
+
if (nameMatch) currentDep = nameMatch[1].trim();
|
|
40
|
+
|
|
41
|
+
const versionMatch = line.match(/version:\s*'?([^']+)'?/);
|
|
42
|
+
if (versionMatch && currentDep) {
|
|
43
|
+
const version = versionMatch[1].trim();
|
|
44
|
+
if (!SEMVER_RANGE_PATTERN.test(version)) {
|
|
45
|
+
diagnostics.push({
|
|
46
|
+
checkId: "WHM204",
|
|
47
|
+
severity: "info",
|
|
48
|
+
message: `Dependency "${currentDep}" uses pinned version "${version}" — consider a semver range (e.g. "~${version}" or "^${version}")`,
|
|
49
|
+
entity: currentDep,
|
|
50
|
+
lexicon: "helm",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return diagnostics;
|
|
59
|
+
},
|
|
60
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHM301: At least one test should be defined for application charts.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
6
|
+
import { getChartFiles, parseChartYaml } from "./helm-helpers";
|
|
7
|
+
|
|
8
|
+
export const whm301: PostSynthCheck = {
|
|
9
|
+
id: "WHM301",
|
|
10
|
+
description: "Application charts should include at least one Helm test",
|
|
11
|
+
|
|
12
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
13
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
14
|
+
|
|
15
|
+
for (const [, output] of ctx.outputs) {
|
|
16
|
+
const files = getChartFiles(output);
|
|
17
|
+
const chartYaml = files["Chart.yaml"];
|
|
18
|
+
if (!chartYaml) continue;
|
|
19
|
+
|
|
20
|
+
const parsed = parseChartYaml(chartYaml);
|
|
21
|
+
if (parsed.type === "library") continue;
|
|
22
|
+
|
|
23
|
+
// Check if any template has helm.sh/hook: test
|
|
24
|
+
const hasTest = Object.entries(files).some(
|
|
25
|
+
([filename, content]) =>
|
|
26
|
+
filename.startsWith("templates/") && content.includes("helm.sh/hook: test"),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (!hasTest) {
|
|
30
|
+
diagnostics.push({
|
|
31
|
+
checkId: "WHM301",
|
|
32
|
+
severity: "info",
|
|
33
|
+
message: "No Helm test defined — consider adding a HelmTest for chart validation",
|
|
34
|
+
lexicon: "helm",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return diagnostics;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHM302: Resource limits should be set (via values or defaults).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
6
|
+
import { getChartFiles } from "./helm-helpers";
|
|
7
|
+
|
|
8
|
+
export const whm302: PostSynthCheck = {
|
|
9
|
+
id: "WHM302",
|
|
10
|
+
description: "Container resources (limits/requests) should be set via values or defaults",
|
|
11
|
+
|
|
12
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
13
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
14
|
+
|
|
15
|
+
for (const [, output] of ctx.outputs) {
|
|
16
|
+
const files = getChartFiles(output);
|
|
17
|
+
|
|
18
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
19
|
+
if (!filename.startsWith("templates/") || filename.endsWith("_helpers.tpl") || filename.endsWith("NOTES.txt")) continue;
|
|
20
|
+
if (filename.includes("tests/")) continue;
|
|
21
|
+
|
|
22
|
+
// Check for workload types with containers
|
|
23
|
+
const hasContainers = content.includes("containers:");
|
|
24
|
+
const hasResources = content.includes("resources:") || content.includes(".Values.resources");
|
|
25
|
+
|
|
26
|
+
if (hasContainers && !hasResources) {
|
|
27
|
+
diagnostics.push({
|
|
28
|
+
checkId: "WHM302",
|
|
29
|
+
severity: "info",
|
|
30
|
+
message: `${filename} has containers but no resource limits — consider using toYaml(values.resources) or setting defaults`,
|
|
31
|
+
entity: filename,
|
|
32
|
+
lexicon: "helm",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return diagnostics;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHM401: Image uses `:latest` tag or no tag.
|
|
3
|
+
*
|
|
4
|
+
* Scans template `image:` lines for literal values (not `.Values` refs)
|
|
5
|
+
* ending in `:latest` or missing `:`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
9
|
+
import { getChartFiles } from "./helm-helpers";
|
|
10
|
+
|
|
11
|
+
export const whm401: PostSynthCheck = {
|
|
12
|
+
id: "WHM401",
|
|
13
|
+
description: "Container images should not use :latest tag or omit tag entirely",
|
|
14
|
+
|
|
15
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
16
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
17
|
+
|
|
18
|
+
for (const [, output] of ctx.outputs) {
|
|
19
|
+
const files = getChartFiles(output);
|
|
20
|
+
|
|
21
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
22
|
+
if (!filename.startsWith("templates/") || filename.endsWith("_helpers.tpl") || filename.endsWith("NOTES.txt")) continue;
|
|
23
|
+
if (filename.includes("tests/")) continue;
|
|
24
|
+
|
|
25
|
+
for (const line of content.split("\n")) {
|
|
26
|
+
const trimmed = line.trim();
|
|
27
|
+
if (!trimmed.startsWith("image:")) continue;
|
|
28
|
+
const value = trimmed.slice("image:".length).trim();
|
|
29
|
+
|
|
30
|
+
// Skip template references
|
|
31
|
+
if (value.includes(".Values") || value.includes("{{")) continue;
|
|
32
|
+
|
|
33
|
+
// Check for :latest or no tag
|
|
34
|
+
if (value.endsWith(":latest") || value.endsWith(":latest'") || value.endsWith(':latest"')) {
|
|
35
|
+
diagnostics.push({
|
|
36
|
+
checkId: "WHM401",
|
|
37
|
+
severity: "warning",
|
|
38
|
+
message: `${filename}: image uses :latest tag — pin to a specific version for reproducible deployments`,
|
|
39
|
+
entity: filename,
|
|
40
|
+
lexicon: "helm",
|
|
41
|
+
});
|
|
42
|
+
} else if (value && !value.includes(":") && !value.includes("{{")) {
|
|
43
|
+
diagnostics.push({
|
|
44
|
+
checkId: "WHM401",
|
|
45
|
+
severity: "warning",
|
|
46
|
+
message: `${filename}: image has no tag — defaults to :latest, pin to a specific version`,
|
|
47
|
+
entity: filename,
|
|
48
|
+
lexicon: "helm",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return diagnostics;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHM402: No `runAsNonRoot` security context.
|
|
3
|
+
*
|
|
4
|
+
* For files with `containers:`, checks for `runAsNonRoot: true` or
|
|
5
|
+
* a `.Values.securityContext` reference.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
9
|
+
import { getChartFiles } from "./helm-helpers";
|
|
10
|
+
|
|
11
|
+
export const whm402: PostSynthCheck = {
|
|
12
|
+
id: "WHM402",
|
|
13
|
+
description: "Containers should set runAsNonRoot in security context",
|
|
14
|
+
|
|
15
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
16
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
17
|
+
|
|
18
|
+
for (const [, output] of ctx.outputs) {
|
|
19
|
+
const files = getChartFiles(output);
|
|
20
|
+
|
|
21
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
22
|
+
if (!filename.startsWith("templates/") || filename.endsWith("_helpers.tpl") || filename.endsWith("NOTES.txt")) continue;
|
|
23
|
+
if (filename.includes("tests/")) continue;
|
|
24
|
+
|
|
25
|
+
const hasContainers = content.includes("containers:");
|
|
26
|
+
if (!hasContainers) continue;
|
|
27
|
+
|
|
28
|
+
const hasRunAsNonRoot = content.includes("runAsNonRoot: true") || content.includes("runAsNonRoot: true");
|
|
29
|
+
const hasSecurityContextRef = content.includes(".Values.securityContext") || content.includes(".Values.podSecurityContext");
|
|
30
|
+
|
|
31
|
+
if (!hasRunAsNonRoot && !hasSecurityContextRef) {
|
|
32
|
+
diagnostics.push({
|
|
33
|
+
checkId: "WHM402",
|
|
34
|
+
severity: "warning",
|
|
35
|
+
message: `${filename}: containers lack runAsNonRoot — set securityContext.runAsNonRoot: true or use .Values.securityContext`,
|
|
36
|
+
entity: filename,
|
|
37
|
+
lexicon: "helm",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return diagnostics;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHM403: No `readOnlyRootFilesystem` security context.
|
|
3
|
+
*
|
|
4
|
+
* For files with `containers:`, checks for `readOnlyRootFilesystem: true`
|
|
5
|
+
* or a `.Values.securityContext` reference.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
9
|
+
import { getChartFiles } from "./helm-helpers";
|
|
10
|
+
|
|
11
|
+
export const whm403: PostSynthCheck = {
|
|
12
|
+
id: "WHM403",
|
|
13
|
+
description: "Containers should set readOnlyRootFilesystem in security context",
|
|
14
|
+
|
|
15
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
16
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
17
|
+
|
|
18
|
+
for (const [, output] of ctx.outputs) {
|
|
19
|
+
const files = getChartFiles(output);
|
|
20
|
+
|
|
21
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
22
|
+
if (!filename.startsWith("templates/") || filename.endsWith("_helpers.tpl") || filename.endsWith("NOTES.txt")) continue;
|
|
23
|
+
if (filename.includes("tests/")) continue;
|
|
24
|
+
|
|
25
|
+
const hasContainers = content.includes("containers:");
|
|
26
|
+
if (!hasContainers) continue;
|
|
27
|
+
|
|
28
|
+
const hasReadOnly = content.includes("readOnlyRootFilesystem: true");
|
|
29
|
+
const hasSecurityContextRef = content.includes(".Values.securityContext") || content.includes(".Values.podSecurityContext");
|
|
30
|
+
|
|
31
|
+
if (!hasReadOnly && !hasSecurityContextRef) {
|
|
32
|
+
diagnostics.push({
|
|
33
|
+
checkId: "WHM403",
|
|
34
|
+
severity: "info",
|
|
35
|
+
message: `${filename}: containers lack readOnlyRootFilesystem — consider setting securityContext.readOnlyRootFilesystem: true`,
|
|
36
|
+
entity: filename,
|
|
37
|
+
lexicon: "helm",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return diagnostics;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHM404: `privileged: true` found — security risk.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
6
|
+
import { getChartFiles } from "./helm-helpers";
|
|
7
|
+
|
|
8
|
+
export const whm404: PostSynthCheck = {
|
|
9
|
+
id: "WHM404",
|
|
10
|
+
description: "Containers must not run in privileged mode",
|
|
11
|
+
|
|
12
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
13
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
14
|
+
|
|
15
|
+
for (const [, output] of ctx.outputs) {
|
|
16
|
+
const files = getChartFiles(output);
|
|
17
|
+
|
|
18
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
19
|
+
if (!filename.startsWith("templates/") || filename.endsWith("_helpers.tpl") || filename.endsWith("NOTES.txt")) continue;
|
|
20
|
+
if (filename.includes("tests/")) continue;
|
|
21
|
+
|
|
22
|
+
if (content.includes("privileged: true")) {
|
|
23
|
+
diagnostics.push({
|
|
24
|
+
checkId: "WHM404",
|
|
25
|
+
severity: "error",
|
|
26
|
+
message: `${filename}: privileged: true detected — containers should not run in privileged mode`,
|
|
27
|
+
entity: filename,
|
|
28
|
+
lexicon: "helm",
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return diagnostics;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHM405: Resource spec missing `cpu`/`memory` in `limits`/`requests`.
|
|
3
|
+
*
|
|
4
|
+
* When `resources:` is present (not via `.Values`), validates that
|
|
5
|
+
* sub-keys `limits` and `requests` with `cpu`/`memory` exist.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
9
|
+
import { getChartFiles } from "./helm-helpers";
|
|
10
|
+
|
|
11
|
+
export const whm405: PostSynthCheck = {
|
|
12
|
+
id: "WHM405",
|
|
13
|
+
description: "Resource specs should include cpu and memory in limits/requests",
|
|
14
|
+
|
|
15
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
16
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
17
|
+
|
|
18
|
+
for (const [, output] of ctx.outputs) {
|
|
19
|
+
const files = getChartFiles(output);
|
|
20
|
+
|
|
21
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
22
|
+
if (!filename.startsWith("templates/") || filename.endsWith("_helpers.tpl") || filename.endsWith("NOTES.txt")) continue;
|
|
23
|
+
if (filename.includes("tests/")) continue;
|
|
24
|
+
|
|
25
|
+
// Skip if resources are via values
|
|
26
|
+
if (!content.includes("resources:") || content.includes(".Values.resources")) continue;
|
|
27
|
+
|
|
28
|
+
const hasLimits = content.includes("limits:");
|
|
29
|
+
const hasRequests = content.includes("requests:");
|
|
30
|
+
|
|
31
|
+
if (!hasLimits && !hasRequests) continue;
|
|
32
|
+
|
|
33
|
+
const hasCpu = content.includes("cpu:");
|
|
34
|
+
const hasMemory = content.includes("memory:");
|
|
35
|
+
|
|
36
|
+
if (!hasCpu || !hasMemory) {
|
|
37
|
+
const missing = [];
|
|
38
|
+
if (!hasCpu) missing.push("cpu");
|
|
39
|
+
if (!hasMemory) missing.push("memory");
|
|
40
|
+
diagnostics.push({
|
|
41
|
+
checkId: "WHM405",
|
|
42
|
+
severity: "warning",
|
|
43
|
+
message: `${filename}: resource spec missing ${missing.join(" and ")} — set both cpu and memory in limits/requests`,
|
|
44
|
+
entity: filename,
|
|
45
|
+
lexicon: "helm",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return diagnostics;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHM406: Chart uses `crds/` directory — warn that Helm never upgrades/deletes CRDs.
|
|
3
|
+
*
|
|
4
|
+
* Suggests using `HelmCRDLifecycle` composite for managed CRD lifecycle.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { getChartFiles } from "./helm-helpers";
|
|
9
|
+
|
|
10
|
+
export const whm406: PostSynthCheck = {
|
|
11
|
+
id: "WHM406",
|
|
12
|
+
description: "CRDs in crds/ directory are never upgraded or deleted by Helm",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
16
|
+
|
|
17
|
+
for (const [, output] of ctx.outputs) {
|
|
18
|
+
const files = getChartFiles(output);
|
|
19
|
+
|
|
20
|
+
const hasCrdDir = Object.keys(files).some((f) => f.startsWith("crds/"));
|
|
21
|
+
if (hasCrdDir) {
|
|
22
|
+
diagnostics.push({
|
|
23
|
+
checkId: "WHM406",
|
|
24
|
+
severity: "info",
|
|
25
|
+
message: "Chart uses crds/ directory — Helm installs CRDs but never upgrades or deletes them. Consider using HelmCRDLifecycle composite for managed CRD lifecycle.",
|
|
26
|
+
entity: "crds/",
|
|
27
|
+
lexicon: "helm",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return diagnostics;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHM407: `kind: Secret` with inline `data:` values and no ExternalSecret/SealedSecret.
|
|
3
|
+
*
|
|
4
|
+
* Warns when secrets contain inline data values (not `.Values` refs) and the chart
|
|
5
|
+
* does not use ExternalSecret or SealedSecret resources.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
9
|
+
import { getChartFiles } from "./helm-helpers";
|
|
10
|
+
|
|
11
|
+
export const whm407: PostSynthCheck = {
|
|
12
|
+
id: "WHM407",
|
|
13
|
+
description: "Secrets with inline data should use ExternalSecret or SealedSecret",
|
|
14
|
+
|
|
15
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
16
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
17
|
+
|
|
18
|
+
for (const [, output] of ctx.outputs) {
|
|
19
|
+
const files = getChartFiles(output);
|
|
20
|
+
|
|
21
|
+
// Check if chart uses ExternalSecret or SealedSecret anywhere
|
|
22
|
+
const allContent = Object.values(files).join("\n");
|
|
23
|
+
const hasExternalSecret = allContent.includes("ExternalSecret") || allContent.includes("SealedSecret");
|
|
24
|
+
|
|
25
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
26
|
+
if (!filename.startsWith("templates/") || filename.endsWith("_helpers.tpl") || filename.endsWith("NOTES.txt")) continue;
|
|
27
|
+
if (filename.includes("tests/")) continue;
|
|
28
|
+
|
|
29
|
+
if (!content.includes("kind: Secret")) continue;
|
|
30
|
+
|
|
31
|
+
// Scan for inline data values in data: or stringData: sections
|
|
32
|
+
const lines = content.split("\n");
|
|
33
|
+
let inData = false;
|
|
34
|
+
let dataIndent = -1;
|
|
35
|
+
let hasLiteralValues = false;
|
|
36
|
+
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
const trimmed = line.trim();
|
|
39
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
40
|
+
|
|
41
|
+
const indent = line.length - line.trimStart().length;
|
|
42
|
+
|
|
43
|
+
if (trimmed === "data:" || trimmed === "stringData:") {
|
|
44
|
+
inData = true;
|
|
45
|
+
dataIndent = indent;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (inData) {
|
|
50
|
+
// If we've returned to the same or lower indent level, exit data section
|
|
51
|
+
if (indent <= dataIndent && trimmed) {
|
|
52
|
+
inData = false;
|
|
53
|
+
dataIndent = -1;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
// Check if value is a literal (not a template expression)
|
|
57
|
+
if (!trimmed.includes("{{") && !trimmed.includes(".Values")) {
|
|
58
|
+
const colonIdx = trimmed.indexOf(":");
|
|
59
|
+
if (colonIdx > 0 && trimmed.length > colonIdx + 1 && trimmed[colonIdx + 1] === " ") {
|
|
60
|
+
const val = trimmed.slice(colonIdx + 1).trim();
|
|
61
|
+
if (val) {
|
|
62
|
+
hasLiteralValues = true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (hasLiteralValues && !hasExternalSecret) {
|
|
70
|
+
diagnostics.push({
|
|
71
|
+
checkId: "WHM407",
|
|
72
|
+
severity: "warning",
|
|
73
|
+
message: `${filename}: Secret with inline data values — consider using ExternalSecret or SealedSecret for secret management`,
|
|
74
|
+
entity: filename,
|
|
75
|
+
lexicon: "helm",
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return diagnostics;
|
|
82
|
+
},
|
|
83
|
+
};
|