Skip to content

Commit 52ad078

Browse files
committed
feat(template): Check that Helm defined templates carry the chart name
Adds a script called by prek which walks our charts and checks whether all defines/calls use a namespaced name stackabletech/issues#882
1 parent bcf54b7 commit 52ad078

2 files changed

Lines changed: 95 additions & 0 deletions

File tree

‎template/.pre-commit-config.yaml.j2‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ repos:
6666

6767
- repo: local
6868
hooks:
69+
- id: check-namespaced-defines
70+
name: check-namespaced-defines
71+
language: system
72+
entry: python3 scripts/check_namespaced_defines.py
73+
files: ^deploy/helm/[^/]+/templates/
74+
6975
- id: regenerate-charts
7076
name: regenerate-charts
7177
language: system
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
"""
2+
Fails when a Helm defined template, or a call to one, is not prefixed with the
3+
name of the chart it lives in.
4+
5+
To run tests for this script:
6+
python3 -m unittest check_namespaced_defines.py
7+
"""
8+
9+
import re
10+
import unittest
11+
from pathlib import Path
12+
13+
NAME_PATTERN = r'\b(?:define|include|template)\s+"([^"]+)"'
14+
CHART_PATTERN = r"(?:^|/)deploy/helm/([^/]+)/templates/"
15+
16+
17+
def chart_of(path):
18+
"""The chart whose templates directory holds path, or None if it is outside one."""
19+
found = re.search(CHART_PATTERN, Path(path).as_posix())
20+
return found.group(1) if found else None
21+
22+
23+
def unprefixed(text, chart):
24+
"""The defined template names in text, and the calls to them, lacking the chart prefix."""
25+
names = re.findall(NAME_PATTERN, text)
26+
return sorted({name for name in names if not name.startswith(f"{chart}.")})
27+
28+
29+
class TestCoreMethods(unittest.TestCase):
30+
def test_chart_of(self):
31+
self.assertEqual(
32+
chart_of("deploy/helm/trino-operator/templates/x.yaml"), "trino-operator"
33+
)
34+
# An absolute path has to give the same answer, or a manual run invents a chart name.
35+
self.assertEqual(
36+
chart_of("/tmp/wt/deploy/helm/secret-operator/templates/a/b.yaml"),
37+
"secret-operator",
38+
)
39+
self.assertIsNone(chart_of("deploy/helm/trino-operator/values.yaml"))
40+
self.assertIsNone(chart_of("README.md"))
41+
42+
def test_prefixed_is_accepted(self):
43+
text = '{{- define "trino-operator.labels" -}}\n{{ include "trino-operator.chart" . }}\n'
44+
self.assertEqual(unprefixed(text, "trino-operator"), [])
45+
46+
def test_unprefixed_define_is_reported(self):
47+
self.assertEqual(
48+
unprefixed('{{- define "helper.thing" -}}', "trino-operator"),
49+
["helper.thing"],
50+
)
51+
52+
def test_unprefixed_call_is_reported(self):
53+
# A renamed definition whose call sites did not move with it stops the chart rendering.
54+
text = (
55+
'{{ include "operator.fullname" . }}\n{{ template "operator.labels" . }}\n'
56+
)
57+
self.assertEqual(
58+
unprefixed(text, "trino-operator"), ["operator.fullname", "operator.labels"]
59+
)
60+
61+
def test_keyword_must_stand_alone(self):
62+
# Without a word boundary any identifier ending in the keyword matches.
63+
self.assertEqual(
64+
unprefixed('{{ .Values.xinclude "bar.baz" }}', "foo-operator"), []
65+
)
66+
67+
68+
if __name__ == "__main__":
69+
import sys
70+
71+
failed = False
72+
for path in sys.argv[1:]:
73+
chart = chart_of(path)
74+
if chart is None:
75+
print(
76+
f"{sys.argv[0]}: {path} is not inside deploy/helm/<chart>/templates/",
77+
file=sys.stderr,
78+
)
79+
sys.exit(2)
80+
names = unprefixed(Path(path).read_text(), chart)
81+
if names:
82+
failed = True
83+
print(
84+
f"{path}: defined templates and the calls to them must be prefixed with '{chart}.'"
85+
)
86+
for name in names:
87+
print(f" {name}")
88+
89+
sys.exit(1 if failed else 0)

0 commit comments

Comments
 (0)