1
1
#!/usr/bin/env python3
2
- '''
2
+ """
3
3
Validate all yaml files in the config/ directory
4
- '''
4
+ """
5
5
6
6
import os
7
7
import yaml
8
8
import sys
9
+ import argparse
10
+
11
+
12
+ class NoAliasDumper (yaml .SafeDumper ):
13
+ def ignore_aliases (self , data ):
14
+ return True
15
+
9
16
10
17
def recursive_merge (d1 , d2 , detect_same_keys = False ):
11
- '''
18
+ """
12
19
Recursively merge two dictionaries, which might have lists
13
20
Not sure if i done this right, but it works
14
- '''
21
+ """
15
22
for k , v in d2 .items ():
16
23
if detect_same_keys and k in d1 :
17
24
if d1 [k ] != v :
18
25
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")
23
30
if k in d1 :
24
31
if isinstance (v , dict ):
25
32
d1 [k ] = recursive_merge (d1 [k ], v , detect_same_keys = True )
@@ -31,152 +38,136 @@ def recursive_merge(d1, d2, detect_same_keys=False):
31
38
d1 [k ] = v
32
39
return d1
33
40
41
+
34
42
def validate_jobs (jobs ):
35
- '''
43
+ """
36
44
Validate jobs, they must have a kcidb_test_suite mapping
37
- '''
45
+ """
38
46
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" ):
45
51
raise yaml .YAMLError (
46
52
f"KCIDB test suite mapping not found for job: { name } '"
47
53
)
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" :
55
59
# 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 } '" )
69
67
70
68
71
69
def validate_scheduler_jobs (data ):
72
- '''
70
+ """
73
71
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" )
77
75
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" )
82
78
# 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
+
89
84
90
85
def validate_unused_jobs (data ):
91
- '''
86
+ """
92
87
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 ]
97
92
for job in jobs .keys ():
98
93
if job not in sch_jobs :
99
94
print (f"Warning: Job { job } is not used in scheduler" )
100
95
96
+
101
97
def validate_build_configs (data ):
102
- '''
98
+ """
103
99
Each entry in build_configs must have a tree and branch attribute
104
100
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" )
108
104
for entry in build_configs :
109
- tree = build_configs [entry ].get (' tree' )
105
+ tree = build_configs [entry ].get (" tree" )
110
106
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 } '" )
118
110
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
+
122
113
123
114
def validate_platforms (data ):
124
- '''
115
+ """
125
116
Each entry in platforms must have arch, boot_method, mach
126
117
If have compatible, it is must be a list
127
- '''
128
- platforms = data .get (' platforms' )
118
+ """
119
+ platforms = data .get (" platforms" )
129
120
for entry in platforms :
130
- if entry == ' docker' or entry == ' kubernetes' or entry == ' shell' :
121
+ if entry == " docker" or entry == " kubernetes" or entry == " shell" :
131
122
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 ):
146
131
raise yaml .YAMLError (
147
132
f"Compatible must be a list for platform: { entry } '"
148
133
)
149
134
135
+
150
136
def validate_unused_trees (data ):
151
- '''
137
+ """
152
138
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 ]
157
143
for tree in trees .keys ():
158
144
if tree not in build_trees :
159
145
print (f"Warning: Tree { tree } is not used in build_configs" )
160
146
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
+ """
165
152
merged_data = {}
166
153
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 } " )
169
156
fpath = os .path .join (dir , file )
170
- with open (fpath , 'r' ) as stream :
157
+ with open (fpath , "r" ) as stream :
171
158
try :
172
159
data = yaml .safe_load (stream )
173
160
merged_data = recursive_merge (merged_data , data )
174
- jobs = data .get ('jobs' )
175
- if jobs :
176
- validate_jobs (jobs )
177
161
except yaml .YAMLError as exc :
178
- print (f' Error in { file } : { exc } ' )
162
+ print (f" Error in { file } : { exc } " )
179
163
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
+ """
180
171
print ("Validating scheduler entries to jobs" )
181
172
validate_scheduler_jobs (merged_data )
182
173
validate_unused_jobs (merged_data )
@@ -185,8 +176,38 @@ def validate_yaml(dir='config'):
185
176
validate_platforms (merged_data )
186
177
print ("All yaml files are valid" )
187
178
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