Skip to content

Commit dcd5a46

Browse files
authored
fea(ci): add example charts sync validation (#206)
* feat: add chart-sync-check action * chore: trigger CI, make a test * rollback test * feat: add validate_production_files * fix regex, print diff
1 parent 880f695 commit dcd5a46

File tree

2 files changed

+170
-0
lines changed

2 files changed

+170
-0
lines changed

.github/scripts/validate_charts.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#!/usr/bin/env python3
2+
3+
import glob
4+
import os
5+
import re
6+
import sys
7+
from difflib import unified_diff
8+
from pathlib import Path
9+
10+
import yaml
11+
12+
13+
def load_yaml_file(file_path):
14+
if not os.path.exists(file_path):
15+
print(f"Error: File not found: {file_path}")
16+
return None, None
17+
18+
try:
19+
with open(file_path, "r", encoding="utf-8") as f:
20+
raw_content = f.read()
21+
yaml_content = yaml.safe_load(raw_content)
22+
return yaml_content, raw_content
23+
except Exception as e:
24+
print(f"Error loading {file_path}: {e}")
25+
return None, None
26+
27+
28+
def compare_yaml_files(file1, file2):
29+
result1 = load_yaml_file(file1)
30+
result2 = load_yaml_file(file2)
31+
32+
if result1[0] is None or result2[0] is None:
33+
return False
34+
35+
content1, raw1 = result1
36+
content2, raw2 = result2
37+
38+
if content1 != content2:
39+
print("❌ Files are not in sync!")
40+
print(f"Source file: {file1}")
41+
print(f"Target file: {file2}")
42+
print("-" * 80)
43+
44+
# Generate and print git-style diff
45+
diff = unified_diff(
46+
raw1.splitlines(keepends=True),
47+
raw2.splitlines(keepends=True),
48+
fromfile="Source",
49+
tofile="Target",
50+
lineterm="",
51+
)
52+
print("".join(list(diff)))
53+
return False
54+
return True
55+
56+
57+
def validate_production_files():
58+
success = True
59+
60+
# Check values files sync
61+
chart_values = glob.glob("charts/**/values/production*.yaml", recursive=True)
62+
for chart_value_file in chart_values:
63+
service_name = Path(chart_value_file).parts[-3]
64+
production_file = Path(chart_value_file).parts[-1] # e.g. "production-0.yaml"
65+
66+
# Construct the corresponding example file path
67+
example_file = f"examples/values/{service_name}-{production_file}"
68+
69+
if os.path.exists(example_file):
70+
if not compare_yaml_files(chart_value_file, example_file):
71+
success = False
72+
else:
73+
print(f"❌ Missing example values file: {example_file}")
74+
success = False
75+
76+
return success
77+
78+
79+
def validate_example_makefile():
80+
makefile_path = "examples/Makefile.example"
81+
if not os.path.exists(makefile_path):
82+
return True
83+
84+
with open(makefile_path, "r") as f:
85+
# Replace line continuations (\newline) with spaces
86+
makefile_content = re.sub(r"\\\n\s*", " ", f.read())
87+
88+
# Extract version patterns from helm upgrade commands
89+
version_patterns = re.findall(
90+
r"helm upgrade -i ([a-zA-Z0-9-]+)\s+oci://[^\s]+\s+.*?--version=(\d+\.\d+\.\d+)",
91+
makefile_content,
92+
)
93+
94+
success = True
95+
for service, version in version_patterns:
96+
# Handle special cases like l2-sequencer-0 -> l2-sequencer
97+
base_service = re.sub(r"-\d+$", "", service)
98+
99+
# Find corresponding Chart.yaml
100+
chart_file = f"charts/{base_service}/Chart.yaml"
101+
if not os.path.exists(chart_file):
102+
print(f"❌ Chart file not found for service: {base_service}")
103+
success = False
104+
continue
105+
106+
chart_data = load_yaml_file(chart_file)
107+
if not chart_data[0]:
108+
continue
109+
110+
chart_version = chart_data[0].get("version")
111+
if version != chart_version:
112+
print(f"❌ Version mismatch in Makefile.example for {base_service}")
113+
print(f" Makefile version: {version}")
114+
print(f" Chart version: {chart_version}")
115+
success = False
116+
117+
return success
118+
119+
120+
def main():
121+
success = True
122+
123+
# Check production files sync
124+
if not validate_production_files():
125+
success = False
126+
127+
# Check example Makefile versions
128+
if not validate_example_makefile():
129+
success = False
130+
131+
if not success:
132+
sys.exit(1)
133+
134+
print("✅ All checks passed!")
135+
136+
137+
if __name__ == "__main__":
138+
main()
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Chart Sync Validation
2+
3+
on:
4+
push:
5+
paths:
6+
- "charts/**/values/production.yaml"
7+
- "examples/values/*-production.yaml"
8+
- "charts/**/Chart.yaml"
9+
- "examples/Makefile.example"
10+
pull_request:
11+
paths:
12+
- "charts/**/values/production.yaml"
13+
- "examples/values/*-production.yaml"
14+
- "charts/**/Chart.yaml"
15+
- "examples/Makefile.example"
16+
17+
jobs:
18+
validate-chart-sync:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- name: Set up Python
24+
uses: actions/setup-python@v4
25+
with:
26+
python-version: "3.x"
27+
28+
- name: Install dependencies
29+
run: pip install pyyaml
30+
31+
- name: Validate Chart Files
32+
run: python .github/scripts/validate_charts.py

0 commit comments

Comments
 (0)