Skip to content

Commit 143c89b

Browse files
committed
Add config merging and dumping function
Signed-off-by: Denys Fedoryshchenko <[email protected]>
1 parent d7754f9 commit 143c89b

File tree

1 file changed

+130
-109
lines changed

1 file changed

+130
-109
lines changed

tests/validate_yaml.py

+130-109
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,32 @@
11
#!/usr/bin/env python3
2-
'''
2+
"""
33
Validate all yaml files in the config/ directory
4-
'''
4+
"""
55

66
import os
77
import yaml
88
import sys
9+
import argparse
10+
11+
12+
class NoAliasDumper(yaml.SafeDumper):
13+
def ignore_aliases(self, data):
14+
return True
15+
916

1017
def recursive_merge(d1, d2, detect_same_keys=False):
11-
'''
18+
"""
1219
Recursively merge two dictionaries, which might have lists
1320
Not sure if i done this right, but it works
14-
'''
21+
"""
1522
for k, v in d2.items():
1623
if detect_same_keys and k in d1:
1724
if d1[k] != v:
1825
raise ValueError(f"Key {k} has different values in both dictionaries")
19-
# We have entries duplication in the yaml files, we need to deal with it later
20-
# so previous verification is very important
21-
# else:
22-
# print(f"Warning: Key {k} has same values in both dictionaries")
26+
# We have entries duplication in the yaml files, we need to deal with it later
27+
# so previous verification is very important
28+
# else:
29+
# print(f"Warning: Key {k} has same values in both dictionaries")
2330
if k in d1:
2431
if isinstance(v, dict):
2532
d1[k] = recursive_merge(d1[k], v, detect_same_keys=True)
@@ -31,152 +38,136 @@ def recursive_merge(d1, d2, detect_same_keys=False):
3138
d1[k] = v
3239
return d1
3340

41+
3442
def validate_jobs(jobs):
35-
'''
43+
"""
3644
Validate jobs, they must have a kcidb_test_suite mapping
37-
'''
45+
"""
3846
for name, definition in jobs.items():
39-
if not definition.get('kind'):
40-
raise yaml.YAMLError(
41-
f"Kind not found for job: {name}'"
42-
)
43-
if definition.get('kind') in ("test", "job"):
44-
if not definition.get('kcidb_test_suite'):
47+
if not definition.get("kind"):
48+
raise yaml.YAMLError(f"Kind not found for job: {name}'")
49+
if definition.get("kind") in ("test", "job"):
50+
if not definition.get("kcidb_test_suite"):
4551
raise yaml.YAMLError(
4652
f"KCIDB test suite mapping not found for job: {name}'"
4753
)
48-
if definition.get('kind') == "job":
49-
if not definition.get('template'):
50-
raise yaml.YAMLError(
51-
f"Template not found for job: {name}'"
52-
)
53-
template = definition.get('template')
54-
if template == 'generic.jinja2':
54+
if definition.get("kind") == "job":
55+
if not definition.get("template"):
56+
raise yaml.YAMLError(f"Template not found for job: {name}'")
57+
template = definition.get("template")
58+
if template == "generic.jinja2":
5559
# test_method in params
56-
if not definition.get('params'):
57-
raise yaml.YAMLError(
58-
f"Params not found for job: {name}'"
59-
)
60-
params = definition.get('params')
61-
if not params.get('test_method'):
62-
raise yaml.YAMLError(
63-
f"Test method not found for job: {name}'"
64-
)
65-
if params.get('fragments'):
66-
raise yaml.YAMLError(
67-
f"Fragments not allowed in jobs: {name}'"
68-
)
60+
if not definition.get("params"):
61+
raise yaml.YAMLError(f"Params not found for job: {name}'")
62+
params = definition.get("params")
63+
if not params.get("test_method"):
64+
raise yaml.YAMLError(f"Test method not found for job: {name}'")
65+
if params.get("fragments"):
66+
raise yaml.YAMLError(f"Fragments not allowed in jobs: {name}'")
6967

7068

7169
def validate_scheduler_jobs(data):
72-
'''
70+
"""
7371
Each entry in scheduler have a job, that should be defined in jobs
74-
'''
75-
schedules = data.get('scheduler')
76-
jobs = data.get('jobs')
72+
"""
73+
schedules = data.get("scheduler")
74+
jobs = data.get("jobs")
7775
for entry in schedules:
78-
if entry.get('job') not in jobs.keys():
79-
raise yaml.YAMLError(
80-
f"Job {entry.get('job')} not found in jobs"
81-
)
76+
if entry.get("job") not in jobs.keys():
77+
raise yaml.YAMLError(f"Job {entry.get('job')} not found in jobs")
8278
# if we have parameter: platforms, we need to make sure it exists in config
83-
if entry.get('platforms'):
84-
for platform in entry.get('platforms'):
85-
if platform not in data.get('platforms'):
86-
raise yaml.YAMLError(
87-
f"Platform {platform} not found in platforms"
88-
)
79+
if entry.get("platforms"):
80+
for platform in entry.get("platforms"):
81+
if platform not in data.get("platforms"):
82+
raise yaml.YAMLError(f"Platform {platform} not found in platforms")
83+
8984

9085
def validate_unused_jobs(data):
91-
'''
86+
"""
9287
Check if all jobs are used in scheduler
93-
'''
94-
schedules = data.get('scheduler')
95-
jobs = data.get('jobs')
96-
sch_jobs = [entry.get('job') for entry in schedules]
88+
"""
89+
schedules = data.get("scheduler")
90+
jobs = data.get("jobs")
91+
sch_jobs = [entry.get("job") for entry in schedules]
9792
for job in jobs.keys():
9893
if job not in sch_jobs:
9994
print(f"Warning: Job {job} is not used in scheduler")
10095

96+
10197
def validate_build_configs(data):
102-
'''
98+
"""
10399
Each entry in build_configs must have a tree and branch attribute
104100
When referenced, a given tree must exist in the trees: section
105-
'''
106-
build_configs = data.get('build_configs')
107-
trees = data.get('trees')
101+
"""
102+
build_configs = data.get("build_configs")
103+
trees = data.get("trees")
108104
for entry in build_configs:
109-
tree = build_configs[entry].get('tree')
105+
tree = build_configs[entry].get("tree")
110106
if not tree:
111-
raise yaml.YAMLError(
112-
f"Tree not found for build config: {entry}'"
113-
)
114-
if not build_configs[entry].get('branch'):
115-
raise yaml.YAMLError(
116-
f"Branch not found for build config: {entry}'"
117-
)
107+
raise yaml.YAMLError(f"Tree not found for build config: {entry}'")
108+
if not build_configs[entry].get("branch"):
109+
raise yaml.YAMLError(f"Branch not found for build config: {entry}'")
118110
if not tree in trees.keys():
119-
raise yaml.YAMLError(
120-
f"Tree {tree} not found in trees"
121-
)
111+
raise yaml.YAMLError(f"Tree {tree} not found in trees")
112+
122113

123114
def validate_platforms(data):
124-
'''
115+
"""
125116
Each entry in platforms must have arch, boot_method, mach
126117
If have compatible, it is must be a list
127-
'''
128-
platforms = data.get('platforms')
118+
"""
119+
platforms = data.get("platforms")
129120
for entry in platforms:
130-
if entry == 'docker' or entry == 'kubernetes' or entry == 'shell':
121+
if entry == "docker" or entry == "kubernetes" or entry == "shell":
131122
continue
132-
if not platforms[entry].get('arch'):
133-
raise yaml.YAMLError(
134-
f"Arch not found for platform: {entry}'"
135-
)
136-
if not platforms[entry].get('boot_method'):
137-
raise yaml.YAMLError(
138-
f"Boot method not found for platform: {entry}'"
139-
)
140-
if not platforms[entry].get('mach'):
141-
raise yaml.YAMLError(
142-
f"Mach not found for platform: {entry}'"
143-
)
144-
if platforms[entry].get('compatible'):
145-
if not isinstance(platforms[entry].get('compatible'), list):
123+
if not platforms[entry].get("arch"):
124+
raise yaml.YAMLError(f"Arch not found for platform: {entry}'")
125+
if not platforms[entry].get("boot_method"):
126+
raise yaml.YAMLError(f"Boot method not found for platform: {entry}'")
127+
if not platforms[entry].get("mach"):
128+
raise yaml.YAMLError(f"Mach not found for platform: {entry}'")
129+
if platforms[entry].get("compatible"):
130+
if not isinstance(platforms[entry].get("compatible"), list):
146131
raise yaml.YAMLError(
147132
f"Compatible must be a list for platform: {entry}'"
148133
)
149134

135+
150136
def validate_unused_trees(data):
151-
'''
137+
"""
152138
Check if all trees are used in build_configs
153-
'''
154-
build_configs = data.get('build_configs')
155-
trees = data.get('trees')
156-
build_trees = [build_configs[entry].get('tree') for entry in build_configs]
139+
"""
140+
build_configs = data.get("build_configs")
141+
trees = data.get("trees")
142+
build_trees = [build_configs[entry].get("tree") for entry in build_configs]
157143
for tree in trees.keys():
158144
if tree not in build_trees:
159145
print(f"Warning: Tree {tree} is not used in build_configs")
160146

161-
def validate_yaml(dir='config'):
162-
'''
163-
Validate all yaml files in the config/ directory
164-
'''
147+
148+
def merge_files(dir="config"):
149+
"""
150+
Merge all yaml files in the config/ directory
151+
"""
165152
merged_data = {}
166153
for file in os.listdir(dir):
167-
if file.endswith('.yaml'):
168-
print(f"Validating {file}")
154+
if file.endswith(".yaml"):
155+
print(f"Merging {file}")
169156
fpath = os.path.join(dir, file)
170-
with open(fpath, 'r') as stream:
157+
with open(fpath, "r") as stream:
171158
try:
172159
data = yaml.safe_load(stream)
173160
merged_data = recursive_merge(merged_data, data)
174-
jobs = data.get('jobs')
175-
if jobs:
176-
validate_jobs(jobs)
177161
except yaml.YAMLError as exc:
178-
print(f'Error in {file}: {exc}')
162+
print(f"Error in {file}: {exc}")
179163
sys.exit(1)
164+
return merged_data
165+
166+
167+
def validate_yaml(merged_data):
168+
"""
169+
Validate all yaml files in the config/ directory
170+
"""
180171
print("Validating scheduler entries to jobs")
181172
validate_scheduler_jobs(merged_data)
182173
validate_unused_jobs(merged_data)
@@ -185,8 +176,38 @@ def validate_yaml(dir='config'):
185176
validate_platforms(merged_data)
186177
print("All yaml files are valid")
187178

188-
if __name__ == '__main__':
189-
if len(sys.argv) > 1:
190-
validate_yaml(sys.argv[1])
191-
else:
192-
validate_yaml()
179+
180+
def dumper(o_filename, merged_data):
181+
raw = yaml.dump(merged_data, Dumper=NoAliasDumper, indent=2)
182+
with open(o_filename, "w") as f:
183+
f.write(raw)
184+
print(f"Dumped merged data to {o_filename}")
185+
186+
187+
def help():
188+
print("Usage: python validate_yaml.py -d <directory> -o <output_file>")
189+
print("Options:")
190+
print("-d, --dir Directory to validate yaml files (default: config)")
191+
print("-o, --output Output file to dump merged yaml file without anchors")
192+
print("-h, --help Show this help message and exit")
193+
sys.exit(1)
194+
195+
196+
if __name__ == "__main__":
197+
parser = argparse.ArgumentParser(description="Validate and dump yaml files")
198+
parser.add_argument(
199+
"-d",
200+
"--dir",
201+
type=str,
202+
default="config",
203+
help="Directory to validate yaml files",
204+
)
205+
parser.add_argument(
206+
"-o", "--output", type=str, help="Output file to dump yaml files"
207+
)
208+
args = parser.parse_args()
209+
merged_data = merge_files(args.dir)
210+
if args.output:
211+
dumper(args.output, merged_data)
212+
213+
validate_yaml(merged_data)

0 commit comments

Comments
 (0)