@intentius/chant-lexicon-helm 0.124.31 → 0.124.32

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.
Files changed (111) hide show
  1. package/dist/archive-render.js +76 -0
  2. package/dist/codegen/docs-cli.js +3 -0
  3. package/dist/codegen/docs.js +113 -0
  4. package/dist/codegen/generate-cli.js +19 -0
  5. package/dist/codegen/generate.js +210 -0
  6. package/dist/codegen/naming.js +29 -0
  7. package/dist/codegen/package.js +44 -0
  8. package/dist/codegen/values-type.js +87 -0
  9. package/dist/commands.js +413 -0
  10. package/dist/components/builders.js +9 -0
  11. package/dist/components/capability-plugin.js +17 -0
  12. package/dist/components/helm-upgrade.js +109 -0
  13. package/dist/components/index.js +11 -0
  14. package/dist/composites/catalog.js +1119 -0
  15. package/dist/composites/helm-batch-job.js +148 -0
  16. package/dist/composites/helm-crd-lifecycle.js +145 -0
  17. package/dist/composites/helm-cron-job.js +130 -0
  18. package/dist/composites/helm-daemon-set.js +125 -0
  19. package/dist/composites/helm-external-secret.js +63 -0
  20. package/dist/composites/helm-library.js +24 -0
  21. package/dist/composites/helm-microservice.js +260 -0
  22. package/dist/composites/helm-monitored-service.js +188 -0
  23. package/dist/composites/helm-namespace-env.js +116 -0
  24. package/dist/composites/helm-secure-ingress.js +86 -0
  25. package/dist/composites/helm-stateful-service.js +160 -0
  26. package/dist/composites/helm-web-app.js +201 -0
  27. package/dist/composites/helm-worker.js +164 -0
  28. package/dist/composites/index.js +13 -0
  29. package/dist/config.js +161 -0
  30. package/dist/coverage.js +37 -0
  31. package/dist/deep-observe-hooks.js +19 -0
  32. package/dist/deep-observe.js +55 -0
  33. package/dist/describe-resources.js +119 -0
  34. package/dist/describe-stack-status.js +66 -0
  35. package/dist/detect.js +11 -0
  36. package/dist/generated/index.js +11 -0
  37. package/dist/helpers.js +84 -0
  38. package/dist/import/generator.js +292 -0
  39. package/dist/import/parser.js +141 -0
  40. package/dist/import/template-stripper.js +86 -0
  41. package/dist/index.js +55 -0
  42. package/dist/integrity.json +2 -2
  43. package/dist/intrinsics.js +482 -0
  44. package/dist/lint/audit-catalog.js +33 -0
  45. package/dist/lint/audit-lineage.js +53 -0
  46. package/dist/lint/post-synth/helm-helpers.js +60 -0
  47. package/dist/lint/post-synth/index.js +48 -0
  48. package/dist/lint/post-synth/whm005-no-empty-wrapper.js +47 -0
  49. package/dist/lint/post-synth/whm101.js +38 -0
  50. package/dist/lint/post-synth/whm102.js +26 -0
  51. package/dist/lint/post-synth/whm103.js +51 -0
  52. package/dist/lint/post-synth/whm104.js +29 -0
  53. package/dist/lint/post-synth/whm105.js +23 -0
  54. package/dist/lint/post-synth/whm201.js +29 -0
  55. package/dist/lint/post-synth/whm202.js +42 -0
  56. package/dist/lint/post-synth/whm203.js +32 -0
  57. package/dist/lint/post-synth/whm204.js +51 -0
  58. package/dist/lint/post-synth/whm301.js +31 -0
  59. package/dist/lint/post-synth/whm302.js +33 -0
  60. package/dist/lint/post-synth/whm401.js +52 -0
  61. package/dist/lint/post-synth/whm402.js +38 -0
  62. package/dist/lint/post-synth/whm403.js +38 -0
  63. package/dist/lint/post-synth/whm404.js +30 -0
  64. package/dist/lint/post-synth/whm405.js +47 -0
  65. package/dist/lint/post-synth/whm406.js +27 -0
  66. package/dist/lint/post-synth/whm407.js +72 -0
  67. package/dist/lint/post-synth/whm501.js +89 -0
  68. package/dist/lint/post-synth/whm502.js +85 -0
  69. package/dist/lint/post-synth/whm503.js +82 -0
  70. package/dist/lint/post-synth/whm504.js +47 -0
  71. package/dist/lint/rules/chart-metadata.js +53 -0
  72. package/dist/lint/rules/no-hardcoded-image.js +50 -0
  73. package/dist/lint/rules/values-no-helm-tpl.js +78 -0
  74. package/dist/lint/rules/values-no-secrets.js +65 -0
  75. package/dist/list-artifacts.js +134 -0
  76. package/dist/lsp/completions.d.ts.map +1 -1
  77. package/dist/lsp/completions.js +18 -0
  78. package/dist/lsp/hover.d.ts.map +1 -1
  79. package/dist/lsp/hover.js +40 -0
  80. package/dist/manifest.json +1 -1
  81. package/dist/op/activities/cluster-probe.js +116 -0
  82. package/dist/op/activities/helm.js +449 -0
  83. package/dist/op/activities/index.js +10 -0
  84. package/dist/op/builders.js +45 -0
  85. package/dist/package-cli.js +21 -0
  86. package/dist/pinnability/actions.js +207 -0
  87. package/dist/pinnability/classify.js +416 -0
  88. package/dist/pinnability/conditions.js +0 -0
  89. package/dist/pinnability/index.js +16 -0
  90. package/dist/pinnability/localize.js +518 -0
  91. package/dist/pinnability/render-stream.js +53 -0
  92. package/dist/pinnability/values.js +151 -0
  93. package/dist/plugin.js +458 -0
  94. package/dist/project-codegen.js +215 -0
  95. package/dist/release-observe.js +287 -0
  96. package/dist/render-diff.js +279 -0
  97. package/dist/render-digest.js +0 -0
  98. package/dist/render-records.js +29 -0
  99. package/dist/render-store.js +401 -0
  100. package/dist/render-wrapper.js +164 -0
  101. package/dist/render.js +394 -0
  102. package/dist/resources.js +109 -0
  103. package/dist/serializer.js +903 -0
  104. package/dist/validate-cli.js +17 -0
  105. package/dist/validate.js +30 -0
  106. package/dist/values-probe.js +504 -0
  107. package/dist/wrapper-chart.js +95 -0
  108. package/package.json +11 -11
  109. package/src/codegen/package.ts +2 -2
  110. package/src/lsp/completions.ts +2 -1
  111. package/src/lsp/hover.ts +2 -1
@@ -0,0 +1,47 @@
1
+ /**
2
+ * WHM005: Chart Has Sub-chart Dependencies But Generates No Templates
3
+ *
4
+ * A chart with HelmDependency entries but no templates/*.yaml files generates
5
+ * an empty templates/ directory. Deploying it requires `helm dependency build`
6
+ * as a non-obvious prerequisite.
7
+ *
8
+ * If you only need value overrides for an upstream chart, deploy it directly:
9
+ * helm upgrade upstream-chart -f values-override.yaml
10
+ * and use ValuesOverride to generate the override file.
11
+ */
12
+ import { getChartFiles } from "./helm-helpers.js";
13
+ export const whm005 = {
14
+ id: "WHM005",
15
+ description: "Chart with sub-chart dependencies but no templates should deploy upstream chart directly",
16
+ check(ctx) {
17
+ const diagnostics = [];
18
+ for (const [, output] of ctx.outputs) {
19
+ const files = getChartFiles(output);
20
+ const chartYaml = files["Chart.yaml"];
21
+ if (!chartYaml)
22
+ continue;
23
+ // Check for non-empty dependencies block
24
+ const hasDependencies = /^dependencies:/m.test(chartYaml);
25
+ if (!hasDependencies)
26
+ continue;
27
+ // Check for template files (excluding _helpers.tpl and NOTES.txt)
28
+ const hasTemplates = Object.keys(files).some((path) => {
29
+ if (!path.startsWith("templates/"))
30
+ return false;
31
+ const filename = path.slice("templates/".length);
32
+ if (filename === "_helpers.tpl" || filename === "NOTES.txt")
33
+ return false;
34
+ return filename.endsWith(".yaml") || filename.endsWith(".yml");
35
+ });
36
+ if (!hasTemplates) {
37
+ diagnostics.push({
38
+ checkId: "WHM005",
39
+ severity: "warning",
40
+ message: "Chart has sub-chart dependencies but generates no templates. Deploying this chart requires 'helm dependency build' first. If you only need value overrides for an upstream chart, deploy it directly with 'helm upgrade upstream-chart -f values-override.yaml' and use ValuesOverride to generate the override file.",
41
+ lexicon: "helm",
42
+ });
43
+ }
44
+ }
45
+ return diagnostics;
46
+ },
47
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * WHM101: Chart.yaml has required fields and valid apiVersion (v2).
3
+ */
4
+ import { getChartFiles, parseChartYaml } from "./helm-helpers.js";
5
+ export const whm101 = {
6
+ id: "WHM101",
7
+ description: "Chart.yaml must have required fields (apiVersion v2, name, version)",
8
+ check(ctx) {
9
+ const diagnostics = [];
10
+ for (const [, output] of ctx.outputs) {
11
+ const files = getChartFiles(output);
12
+ const chartYaml = files["Chart.yaml"];
13
+ if (!chartYaml) {
14
+ diagnostics.push({
15
+ checkId: "WHM101",
16
+ severity: "error",
17
+ message: "Chart.yaml is missing from serializer output",
18
+ lexicon: "helm",
19
+ });
20
+ continue;
21
+ }
22
+ const parsed = parseChartYaml(chartYaml);
23
+ if (!parsed.apiVersion) {
24
+ diagnostics.push({ checkId: "WHM101", severity: "error", message: "Chart.yaml missing apiVersion", lexicon: "helm" });
25
+ }
26
+ else if (parsed.apiVersion !== "v2") {
27
+ diagnostics.push({ checkId: "WHM101", severity: "error", message: `Chart.yaml apiVersion should be "v2", got "${parsed.apiVersion}"`, lexicon: "helm" });
28
+ }
29
+ if (!parsed.name) {
30
+ diagnostics.push({ checkId: "WHM101", severity: "error", message: "Chart.yaml missing name", lexicon: "helm" });
31
+ }
32
+ if (!parsed.version) {
33
+ diagnostics.push({ checkId: "WHM101", severity: "error", message: "Chart.yaml missing version", lexicon: "helm" });
34
+ }
35
+ }
36
+ return diagnostics;
37
+ },
38
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * WHM102: values.schema.json present when Values type is used.
3
+ */
4
+ import { getChartFiles } from "./helm-helpers.js";
5
+ export const whm102 = {
6
+ id: "WHM102",
7
+ description: "values.schema.json should be present when Values are non-empty",
8
+ check(ctx) {
9
+ const diagnostics = [];
10
+ for (const [, output] of ctx.outputs) {
11
+ const files = getChartFiles(output);
12
+ const valuesYaml = files["values.yaml"];
13
+ const valuesSchema = files["values.schema.json"];
14
+ // If values.yaml has content (not just "{}"), schema should exist
15
+ if (valuesYaml && valuesYaml.trim() !== "{}" && !valuesSchema) {
16
+ diagnostics.push({
17
+ checkId: "WHM102",
18
+ severity: "warning",
19
+ message: "values.schema.json is missing — consider adding typed Values to generate a schema",
20
+ lexicon: "helm",
21
+ });
22
+ }
23
+ }
24
+ return diagnostics;
25
+ },
26
+ };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * WHM103: Go template syntax valid (balanced braces, valid function names).
3
+ */
4
+ import { getChartFiles, hasBalancedBraces } from "./helm-helpers.js";
5
+ const VALID_FUNCTIONS = new Set([
6
+ // Built-in Go template functions
7
+ "if", "else", "end", "range", "with", "define", "template", "block",
8
+ // Helm/Sprig functions
9
+ "include", "required", "default", "toYaml", "toJson", "fromYaml", "fromJson",
10
+ "quote", "squote", "upper", "lower", "title", "trim", "trimSuffix", "trimPrefix",
11
+ "printf", "print", "println", "tpl", "lookup",
12
+ "nindent", "indent", "replace", "contains", "hasPrefix", "hasSuffix",
13
+ "repeat", "substr", "trunc", "abbrev", "randAlphaNum", "randAlpha",
14
+ "b64enc", "b64dec", "sha256sum", "now", "date",
15
+ "list", "dict", "get", "set", "unset", "hasKey", "pluck", "keys", "values",
16
+ "append", "prepend", "first", "last", "uniq", "without", "has",
17
+ "empty", "coalesce", "ternary", "cat", "join", "split", "splitList",
18
+ "toStrings", "toString", "int", "int64", "float64", "atoi",
19
+ "add", "sub", "mul", "div", "mod", "max", "min", "ceil", "floor", "round",
20
+ "and", "or", "not", "eq", "ne", "lt", "le", "gt", "ge",
21
+ "typeOf", "kindOf", "typeIs", "kindIs", "deepEqual",
22
+ "semver", "semverCompare",
23
+ "regexMatch", "regexFind", "regexReplaceAll",
24
+ "sha1sum", "derivePassword", "genPrivateKey", "buildCustomCert", "genCA", "genSignedCert",
25
+ "env", "expandenv",
26
+ "fail",
27
+ ]);
28
+ export const whm103 = {
29
+ id: "WHM103",
30
+ description: "Go template syntax must be valid (balanced braces)",
31
+ check(ctx) {
32
+ const diagnostics = [];
33
+ for (const [, output] of ctx.outputs) {
34
+ const files = getChartFiles(output);
35
+ for (const [filename, content] of Object.entries(files)) {
36
+ if (!filename.startsWith("templates/"))
37
+ continue;
38
+ if (!hasBalancedBraces(content)) {
39
+ diagnostics.push({
40
+ checkId: "WHM103",
41
+ severity: "error",
42
+ message: `Unbalanced template braces in ${filename}`,
43
+ entity: filename,
44
+ lexicon: "helm",
45
+ });
46
+ }
47
+ }
48
+ }
49
+ return diagnostics;
50
+ },
51
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * WHM104: NOTES.txt exists for application charts.
3
+ */
4
+ import { getChartFiles, parseChartYaml } from "./helm-helpers.js";
5
+ export const whm104 = {
6
+ id: "WHM104",
7
+ description: "NOTES.txt should exist for application charts",
8
+ check(ctx) {
9
+ const diagnostics = [];
10
+ for (const [, output] of ctx.outputs) {
11
+ const files = getChartFiles(output);
12
+ const chartYaml = files["Chart.yaml"];
13
+ if (!chartYaml)
14
+ continue;
15
+ const parsed = parseChartYaml(chartYaml);
16
+ if (parsed.type === "library")
17
+ continue; // Libraries don't need NOTES.txt
18
+ if (!files["templates/NOTES.txt"]) {
19
+ diagnostics.push({
20
+ checkId: "WHM104",
21
+ severity: "info",
22
+ message: "NOTES.txt is missing — consider adding installation notes for users",
23
+ lexicon: "helm",
24
+ });
25
+ }
26
+ }
27
+ return diagnostics;
28
+ },
29
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * WHM105: _helpers.tpl exists.
3
+ */
4
+ import { getChartFiles } from "./helm-helpers.js";
5
+ export const whm105 = {
6
+ id: "WHM105",
7
+ description: "_helpers.tpl must exist in templates/",
8
+ check(ctx) {
9
+ const diagnostics = [];
10
+ for (const [, output] of ctx.outputs) {
11
+ const files = getChartFiles(output);
12
+ if (!files["templates/_helpers.tpl"]) {
13
+ diagnostics.push({
14
+ checkId: "WHM105",
15
+ severity: "warning",
16
+ message: "templates/_helpers.tpl is missing",
17
+ lexicon: "helm",
18
+ });
19
+ }
20
+ }
21
+ return diagnostics;
22
+ },
23
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * WHM201: Resources should have standard Helm labels.
3
+ */
4
+ import { getChartFiles } from "./helm-helpers.js";
5
+ export const whm201 = {
6
+ id: "WHM201",
7
+ description: "K8s resources should include standard Helm labels",
8
+ check(ctx) {
9
+ const diagnostics = [];
10
+ for (const [, output] of ctx.outputs) {
11
+ const files = getChartFiles(output);
12
+ for (const [filename, content] of Object.entries(files)) {
13
+ if (!filename.startsWith("templates/") || filename.endsWith("_helpers.tpl") || filename.endsWith("NOTES.txt"))
14
+ continue;
15
+ // Check if the template references the standard labels helper
16
+ if (content.includes("kind:") && !content.includes(".labels") && !content.includes("helm.sh/chart")) {
17
+ diagnostics.push({
18
+ checkId: "WHM201",
19
+ severity: "info",
20
+ message: `${filename} does not include standard Helm labels — consider using include "<chart>.labels"`,
21
+ entity: filename,
22
+ lexicon: "helm",
23
+ });
24
+ }
25
+ }
26
+ }
27
+ return diagnostics;
28
+ },
29
+ };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * WHM202: Hook weights should be defined for multi-hook charts.
3
+ */
4
+ import { getChartFiles } from "./helm-helpers.js";
5
+ export const whm202 = {
6
+ id: "WHM202",
7
+ description: "Hook weights should be defined when multiple hooks exist",
8
+ check(ctx) {
9
+ const diagnostics = [];
10
+ for (const [, output] of ctx.outputs) {
11
+ const files = getChartFiles(output);
12
+ const hooksWithWeight = [];
13
+ const hooksWithoutWeight = [];
14
+ for (const [filename, content] of Object.entries(files)) {
15
+ if (!filename.startsWith("templates/"))
16
+ continue;
17
+ if (content.includes("helm.sh/hook:") && !content.includes("helm.sh/hook: test")) {
18
+ if (content.includes("helm.sh/hook-weight:")) {
19
+ hooksWithWeight.push(filename);
20
+ }
21
+ else {
22
+ hooksWithoutWeight.push(filename);
23
+ }
24
+ }
25
+ }
26
+ // Only warn if there are multiple hooks and some lack weights
27
+ const totalHooks = hooksWithWeight.length + hooksWithoutWeight.length;
28
+ if (totalHooks > 1 && hooksWithoutWeight.length > 0) {
29
+ for (const filename of hooksWithoutWeight) {
30
+ diagnostics.push({
31
+ checkId: "WHM202",
32
+ severity: "warning",
33
+ message: `${filename} has a hook but no hook-weight — define weights to control execution order in multi-hook charts`,
34
+ entity: filename,
35
+ lexicon: "helm",
36
+ });
37
+ }
38
+ }
39
+ }
40
+ return diagnostics;
41
+ },
42
+ };
@@ -0,0 +1,32 @@
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
+ import { getChartFiles } from "./helm-helpers.js";
9
+ export const whm203 = {
10
+ id: "WHM203",
11
+ description: "Values entries should be documented via schema or comments",
12
+ check(ctx) {
13
+ const diagnostics = [];
14
+ for (const [, output] of ctx.outputs) {
15
+ const files = getChartFiles(output);
16
+ const valuesYaml = files["values.yaml"];
17
+ const schemaJson = files["values.schema.json"];
18
+ if (!valuesYaml || valuesYaml.trim() === "{}")
19
+ continue;
20
+ // If no schema, values are undocumented
21
+ if (!schemaJson) {
22
+ diagnostics.push({
23
+ checkId: "WHM203",
24
+ severity: "info",
25
+ message: "Values are not documented — add a Values type to generate values.schema.json",
26
+ lexicon: "helm",
27
+ });
28
+ }
29
+ }
30
+ return diagnostics;
31
+ },
32
+ };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * WHM204: Dependencies should use semver ranges.
3
+ */
4
+ import { getChartFiles } from "./helm-helpers.js";
5
+ const SEMVER_RANGE_PATTERN = /^[~^>=<*]|\.x/;
6
+ export const whm204 = {
7
+ id: "WHM204",
8
+ description: "Chart dependencies should use semver ranges, not pinned versions",
9
+ check(ctx) {
10
+ const diagnostics = [];
11
+ for (const [, output] of ctx.outputs) {
12
+ const files = getChartFiles(output);
13
+ const chartYaml = files["Chart.yaml"];
14
+ if (!chartYaml)
15
+ continue;
16
+ // Simple extraction of dependency versions from Chart.yaml
17
+ const lines = chartYaml.split("\n");
18
+ let inDependencies = false;
19
+ let currentDep = "";
20
+ for (const line of lines) {
21
+ if (line.startsWith("dependencies:")) {
22
+ inDependencies = true;
23
+ continue;
24
+ }
25
+ if (inDependencies) {
26
+ if (/^\S/.test(line) && !line.startsWith(" ")) {
27
+ inDependencies = false;
28
+ continue;
29
+ }
30
+ const nameMatch = line.match(/name:\s*(.+)/);
31
+ if (nameMatch)
32
+ currentDep = nameMatch[1].trim();
33
+ const versionMatch = line.match(/version:\s*'?([^']+)'?/);
34
+ if (versionMatch && currentDep) {
35
+ const version = versionMatch[1].trim();
36
+ if (!SEMVER_RANGE_PATTERN.test(version)) {
37
+ diagnostics.push({
38
+ checkId: "WHM204",
39
+ severity: "info",
40
+ message: `Dependency "${currentDep}" uses pinned version "${version}" — consider a semver range (e.g. "~${version}" or "^${version}")`,
41
+ entity: currentDep,
42
+ lexicon: "helm",
43
+ });
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ return diagnostics;
50
+ },
51
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * WHM301: At least one test should be defined for application charts.
3
+ */
4
+ import { getChartFiles, parseChartYaml } from "./helm-helpers.js";
5
+ export const whm301 = {
6
+ id: "WHM301",
7
+ description: "Application charts should include at least one Helm test",
8
+ check(ctx) {
9
+ const diagnostics = [];
10
+ for (const [, output] of ctx.outputs) {
11
+ const files = getChartFiles(output);
12
+ const chartYaml = files["Chart.yaml"];
13
+ if (!chartYaml)
14
+ continue;
15
+ const parsed = parseChartYaml(chartYaml);
16
+ if (parsed.type === "library")
17
+ continue;
18
+ // Check if any template has helm.sh/hook: test
19
+ const hasTest = Object.entries(files).some(([filename, content]) => filename.startsWith("templates/") && content.includes("helm.sh/hook: test"));
20
+ if (!hasTest) {
21
+ diagnostics.push({
22
+ checkId: "WHM301",
23
+ severity: "info",
24
+ message: "No Helm test defined — consider adding a HelmTest for chart validation",
25
+ lexicon: "helm",
26
+ });
27
+ }
28
+ }
29
+ return diagnostics;
30
+ },
31
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * WHM302: Resource limits should be set (via values or defaults).
3
+ */
4
+ import { getChartFiles } from "./helm-helpers.js";
5
+ export const whm302 = {
6
+ id: "WHM302",
7
+ description: "Container resources (limits/requests) should be set via values or defaults",
8
+ check(ctx) {
9
+ const diagnostics = [];
10
+ for (const [, output] of ctx.outputs) {
11
+ const files = getChartFiles(output);
12
+ for (const [filename, content] of Object.entries(files)) {
13
+ if (!filename.startsWith("templates/") || filename.endsWith("_helpers.tpl") || filename.endsWith("NOTES.txt"))
14
+ continue;
15
+ if (filename.includes("tests/"))
16
+ continue;
17
+ // Check for workload types with containers
18
+ const hasContainers = content.includes("containers:");
19
+ const hasResources = content.includes("resources:") || content.includes(".Values.resources");
20
+ if (hasContainers && !hasResources) {
21
+ diagnostics.push({
22
+ checkId: "WHM302",
23
+ severity: "info",
24
+ message: `${filename} has containers but no resource limits — consider using toYaml(values.resources) or setting defaults`,
25
+ entity: filename,
26
+ lexicon: "helm",
27
+ });
28
+ }
29
+ }
30
+ }
31
+ return diagnostics;
32
+ },
33
+ };
@@ -0,0 +1,52 @@
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
+ import { getChartFiles } from "./helm-helpers.js";
8
+ export const whm401 = {
9
+ id: "WHM401",
10
+ description: "Container images should not use :latest tag or omit tag entirely",
11
+ check(ctx) {
12
+ const diagnostics = [];
13
+ for (const [, output] of ctx.outputs) {
14
+ const files = getChartFiles(output);
15
+ for (const [filename, content] of Object.entries(files)) {
16
+ if (!filename.startsWith("templates/") || filename.endsWith("_helpers.tpl") || filename.endsWith("NOTES.txt"))
17
+ continue;
18
+ if (filename.includes("tests/"))
19
+ continue;
20
+ for (const line of content.split("\n")) {
21
+ const trimmed = line.trim();
22
+ if (!trimmed.startsWith("image:"))
23
+ continue;
24
+ const value = trimmed.slice("image:".length).trim();
25
+ // Skip template references
26
+ if (value.includes(".Values") || value.includes("{{"))
27
+ continue;
28
+ // Check for :latest or no tag
29
+ if (value.endsWith(":latest") || value.endsWith(":latest'") || value.endsWith(':latest"')) {
30
+ diagnostics.push({
31
+ checkId: "WHM401",
32
+ severity: "warning",
33
+ message: `${filename}: image uses :latest tag — pin to a specific version for reproducible deployments`,
34
+ entity: filename,
35
+ lexicon: "helm",
36
+ });
37
+ }
38
+ else if (value && !value.includes(":") && !value.includes("{{")) {
39
+ diagnostics.push({
40
+ checkId: "WHM401",
41
+ severity: "warning",
42
+ message: `${filename}: image has no tag — defaults to :latest, pin to a specific version`,
43
+ entity: filename,
44
+ lexicon: "helm",
45
+ });
46
+ }
47
+ }
48
+ }
49
+ }
50
+ return diagnostics;
51
+ },
52
+ };
@@ -0,0 +1,38 @@
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
+ import { getChartFiles } from "./helm-helpers.js";
8
+ export const whm402 = {
9
+ id: "WHM402",
10
+ description: "Containers should set runAsNonRoot in security context",
11
+ check(ctx) {
12
+ const diagnostics = [];
13
+ for (const [, output] of ctx.outputs) {
14
+ const files = getChartFiles(output);
15
+ for (const [filename, content] of Object.entries(files)) {
16
+ if (!filename.startsWith("templates/") || filename.endsWith("_helpers.tpl") || filename.endsWith("NOTES.txt"))
17
+ continue;
18
+ if (filename.includes("tests/"))
19
+ continue;
20
+ const hasContainers = content.includes("containers:");
21
+ if (!hasContainers)
22
+ continue;
23
+ const hasRunAsNonRoot = content.includes("runAsNonRoot: true") || content.includes("runAsNonRoot: true");
24
+ const hasSecurityContextRef = content.includes(".Values.securityContext") || content.includes(".Values.podSecurityContext");
25
+ if (!hasRunAsNonRoot && !hasSecurityContextRef) {
26
+ diagnostics.push({
27
+ checkId: "WHM402",
28
+ severity: "warning",
29
+ message: `${filename}: containers lack runAsNonRoot — set securityContext.runAsNonRoot: true or use .Values.securityContext`,
30
+ entity: filename,
31
+ lexicon: "helm",
32
+ });
33
+ }
34
+ }
35
+ }
36
+ return diagnostics;
37
+ },
38
+ };
@@ -0,0 +1,38 @@
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
+ import { getChartFiles } from "./helm-helpers.js";
8
+ export const whm403 = {
9
+ id: "WHM403",
10
+ description: "Containers should set readOnlyRootFilesystem in security context",
11
+ check(ctx) {
12
+ const diagnostics = [];
13
+ for (const [, output] of ctx.outputs) {
14
+ const files = getChartFiles(output);
15
+ for (const [filename, content] of Object.entries(files)) {
16
+ if (!filename.startsWith("templates/") || filename.endsWith("_helpers.tpl") || filename.endsWith("NOTES.txt"))
17
+ continue;
18
+ if (filename.includes("tests/"))
19
+ continue;
20
+ const hasContainers = content.includes("containers:");
21
+ if (!hasContainers)
22
+ continue;
23
+ const hasReadOnly = content.includes("readOnlyRootFilesystem: true");
24
+ const hasSecurityContextRef = content.includes(".Values.securityContext") || content.includes(".Values.podSecurityContext");
25
+ if (!hasReadOnly && !hasSecurityContextRef) {
26
+ diagnostics.push({
27
+ checkId: "WHM403",
28
+ severity: "info",
29
+ message: `${filename}: containers lack readOnlyRootFilesystem — consider setting securityContext.readOnlyRootFilesystem: true`,
30
+ entity: filename,
31
+ lexicon: "helm",
32
+ });
33
+ }
34
+ }
35
+ }
36
+ return diagnostics;
37
+ },
38
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * WHM404: `privileged: true` found — security risk.
3
+ */
4
+ import { getChartFiles } from "./helm-helpers.js";
5
+ export const whm404 = {
6
+ id: "WHM404",
7
+ description: "Containers must not run in privileged mode",
8
+ check(ctx) {
9
+ const diagnostics = [];
10
+ for (const [, output] of ctx.outputs) {
11
+ const files = getChartFiles(output);
12
+ for (const [filename, content] of Object.entries(files)) {
13
+ if (!filename.startsWith("templates/") || filename.endsWith("_helpers.tpl") || filename.endsWith("NOTES.txt"))
14
+ continue;
15
+ if (filename.includes("tests/"))
16
+ continue;
17
+ if (content.includes("privileged: true")) {
18
+ diagnostics.push({
19
+ checkId: "WHM404",
20
+ severity: "error",
21
+ message: `${filename}: privileged: true detected — containers should not run in privileged mode`,
22
+ entity: filename,
23
+ lexicon: "helm",
24
+ });
25
+ }
26
+ }
27
+ }
28
+ return diagnostics;
29
+ },
30
+ };
@@ -0,0 +1,47 @@
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
+ import { getChartFiles } from "./helm-helpers.js";
8
+ export const whm405 = {
9
+ id: "WHM405",
10
+ description: "Resource specs should include cpu and memory in limits/requests",
11
+ check(ctx) {
12
+ const diagnostics = [];
13
+ for (const [, output] of ctx.outputs) {
14
+ const files = getChartFiles(output);
15
+ for (const [filename, content] of Object.entries(files)) {
16
+ if (!filename.startsWith("templates/") || filename.endsWith("_helpers.tpl") || filename.endsWith("NOTES.txt"))
17
+ continue;
18
+ if (filename.includes("tests/"))
19
+ continue;
20
+ // Skip if resources are via values
21
+ if (!content.includes("resources:") || content.includes(".Values.resources"))
22
+ continue;
23
+ const hasLimits = content.includes("limits:");
24
+ const hasRequests = content.includes("requests:");
25
+ if (!hasLimits && !hasRequests)
26
+ continue;
27
+ const hasCpu = content.includes("cpu:");
28
+ const hasMemory = content.includes("memory:");
29
+ if (!hasCpu || !hasMemory) {
30
+ const missing = [];
31
+ if (!hasCpu)
32
+ missing.push("cpu");
33
+ if (!hasMemory)
34
+ missing.push("memory");
35
+ diagnostics.push({
36
+ checkId: "WHM405",
37
+ severity: "warning",
38
+ message: `${filename}: resource spec missing ${missing.join(" and ")} — set both cpu and memory in limits/requests`,
39
+ entity: filename,
40
+ lexicon: "helm",
41
+ });
42
+ }
43
+ }
44
+ }
45
+ return diagnostics;
46
+ },
47
+ };