Skip to content

Commit 69345a8

Browse files
committed
ci: add Helm diff step
1 parent dcf7f5b commit 69345a8

File tree

3 files changed

+105
-0
lines changed

3 files changed

+105
-0
lines changed

.woodpecker/helm-diff.yaml

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
when:
2+
branch: ${CI_REPO_DEFAULT_BRANCH}
3+
4+
matrix:
5+
STACK:
6+
- system
7+
- platform
8+
- apps
9+
10+
steps:
11+
# TODO DRY with nix develop and custom entrypoint https://github.com/woodpecker-ci/woodpecker/pull/2985,
12+
# but first we need a Nix cache. See the nix-cache branch for the WIP.
13+
diff:
14+
image: nixery.dev/shell/git/python3/kubernetes-helm/diffutils/dyff # TODO replace with nix develop
15+
commands:
16+
- ./scripts/helm-diff --repository "${CI_REPO_CLONE_URL}" --source "${CI_COMMIT_SOURCE_BRANCH}" --target "${CI_COMMIT_TARGET_BRANCH}" --subpath "${STACK}"
17+
when:
18+
- event: pull_request
19+
path: '${STACK}/**'
20+
depends_on: []

flake.nix

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
diffutils
2727
docker
2828
docker-compose_1 # TODO upgrade to version 2
29+
dyff
2930
git
3031
go
3132
gotestsum

scripts/helm-diff

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#!/usr/bin/env python
2+
3+
from argparse import ArgumentParser
4+
from glob import glob
5+
from os import path
6+
from subprocess import run
7+
from tempfile import mkdtemp, NamedTemporaryFile
8+
9+
10+
def clone_repository(repo, branch, target_path):
11+
run(
12+
['git', 'clone', repo, '--depth', '1', '--branch', branch, target_path],
13+
check=True
14+
)
15+
16+
17+
def render_helm_chart(chart_path, namespace, release_name, rendered_path):
18+
# Even if there is no Helm chart at the specified chart path, do not raise an error.
19+
# This accommodates cases where the entire chart is removed, or a new chart is added.
20+
# In such cases, the rendered file will simply be empty.
21+
if path.isdir(chart_path):
22+
run(
23+
['helm', 'dependency', 'update', chart_path],
24+
check=True
25+
)
26+
27+
run(
28+
['helm', 'template', '--namespace', namespace, release_name, chart_path],
29+
stdout=open(rendered_path, 'w'),
30+
check=True
31+
)
32+
33+
34+
def changed_charts(source_path, target_path, subpath):
35+
changed_charts = []
36+
37+
# Convert to set for deduplication
38+
all_charts = set(
39+
glob(f"*", root_dir=f"{source_path}/{subpath}")
40+
+ glob(f"*", root_dir=f"{target_path}/{subpath}")
41+
)
42+
43+
for chart in all_charts:
44+
source_chart_path = path.join(source_path, subpath, chart)
45+
target_chart_path = path.join(target_path, subpath, chart)
46+
47+
if run(['diff', source_chart_path, target_chart_path], capture_output=True).returncode != 0:
48+
changed_charts.append(chart)
49+
50+
return changed_charts
51+
52+
53+
def main():
54+
parser = ArgumentParser(description='Compare Helm charts in a directory between two Git revisions.')
55+
parser.add_argument('--repository', required=True, help='Repository to clone')
56+
parser.add_argument('--source', required=True, help='Source branch (e.g. pull request branch)')
57+
parser.add_argument('--target', required=True, help='Target branch (e.g. master branch)')
58+
parser.add_argument('--subpath', required=True, help='Subpath containing the charts (e.g. system)')
59+
60+
args = parser.parse_args()
61+
62+
source_path = mkdtemp()
63+
target_path = mkdtemp()
64+
65+
clone_repository(args.repository, args.source, source_path)
66+
clone_repository(args.repository, args.target, target_path)
67+
68+
for chart in changed_charts(source_path, target_path, args.subpath):
69+
with NamedTemporaryFile(suffix='.yaml', mode='w+', delete=False) as f_source, NamedTemporaryFile(suffix='.yaml', mode='w+', delete=False) as f_target:
70+
render_helm_chart(f"{source_path}/{args.subpath}/{chart}", chart, chart, f_source.name)
71+
render_helm_chart(f"{target_path}/{args.subpath}/{chart}", chart, chart, f_target.name)
72+
73+
diff_result = run(
74+
['dyff', 'between', '--omit-header', '--use-go-patch-style', '--color=on', '--truecolor=off', f_target.name, f_source.name],
75+
capture_output=True,
76+
text=True,
77+
check=True
78+
)
79+
80+
print(diff_result.stdout)
81+
82+
83+
if __name__ == "__main__":
84+
main()

0 commit comments

Comments
 (0)