@@ -91,38 +91,56 @@ def __init__(self, name, mand, values, desc = ""):
9191 else :
9292 self .desc = name
9393
94- def is_ok (self , props , warn = "" ):
94+ def check (self , props , warn ):
9595 "Check validity of properties"
9696 if self .name in props :
97+ # Property exists but is not in allowed value list => ERROR
9798 if self .values and not props [self .name ] in self .values :
9899 if warn :
99100 print (f'ERROR: Image "{ warn } ": value "{ props [self .name ]} " for property '
100101 f'"{ self .name } " not allowed' , file = sys .stderr )
101- return False
102- if not props [self .name ] and not self .values :
103- err = "ERROR"
104- ret = False
102+ return (False , True )
103+ # Non-empty: OK
104+ if props [self .name ]:
105+ return (True , True )
106+ # Empty property without an allowed value list => ERROR
107+ if not self .values :
108+ ok = True
105109 else :
106- err = "WARNING"
107- ret = True
108- if not props [self .name ] and (verbose or not self .values ) and warn :
109- print (f'{ err } : Image "{ warn } ": empty value for property "{ self .name } " not recommended' ,
110+ ok = False
111+ # Property empty => WARN (or ERROR if no allowed value list)
112+ if (verbose or not self .values ) and warn :
113+ errstr = "WARNING" if ok else "ERROR"
114+ print (f'{ errstr } : Image "{ warn } ": empty value for property "{ self .name } " not recommended' ,
110115 file = sys .stderr )
111- return ret
116+ return (ok , not ok )
117+ # Property does not exist but is mandatory => ERROR
112118 if self .ismand :
113119 if warn :
114120 print (f'ERROR: Image "{ warn } ": Mandatory property "{ self .name } " is missing' ,
115121 file = sys .stderr )
116- return False
122+ return (False , True )
123+ # Property does not exist => WARN/INFO
117124 if warn and verbose :
118125 print (f'INFO: Image "{ warn } ": Optional property "{ self .name } " is missing' ) # , file=sys.stderr)
119- return True
126+ return (True , False )
127+
128+ def is_ok (self , props , warn = "" ):
129+ "Return True if no hard error is found"
130+ ok , nowarn = self .check (props , warn )
131+ return ok
132+
133+ def is_nowarn (self , props , warn = "" ):
134+ "Return True if nothing to warn about is found"
135+ ok , nowarn = self .check (props , warn )
136+ return nowarn
120137
121138
122139# From the image metadata specification
123140# https://github.com/SovereignCloudStack/Docs/blob/main/Design-Docs/Image-Properties-Spec.md
124141os_props = (Property ("os_distro" , True , ()),
125- Property ("os_version" , True , ()))
142+ Property ("os_version" , True , ()),
143+ Property ("os_purpose" , False , ("generic" , "minimal" , "k8snode" , "gpu" , "network" , "custom" )))
126144arch_props = (Property ("architecture" , True , ("x86_64" , "aarch64" , "risc-v" ), "CPU architecture" ),
127145 Property ("hypervisor_type" , True , ("qemu" , "kvm" , "xen" , "hyper-v" , "esxi" , None )))
128146hw_props = (Property ("hw_rng_model" , False , ("virtio" , None ), "Random Number Generator" ),
@@ -204,16 +222,24 @@ def validate_imageMD(img, outd_list):
204222 # Now the hard work: Look at properties ....
205223 errors = 0
206224 warnings = 0
207- # (1) recommended os_* and hw_*
225+ # (1) mandatory and recommended os_* and hw_*
208226 # (4) image_build_date, image_original_user, image_source (opt image_description)
209227 # (5) maintained_until, provided_until, uuid_validity, replace_frequency
210228 for prop in (* os_props , * arch_props , * hw_props ):
211229 if not prop .is_ok (img , imgnm ):
212230 errors += 1
231+ elif not prop .is_nowarn (img , "" ):
232+ warnings += 1
233+ # We do not count missing optional fields in these
213234 for prop in (* build_props , * maint_props ):
214235 if not prop .is_ok (img .properties , imgnm ):
215236 errors += 1
237+ # Construct a name from os_distro and os_version
216238 constr_name = f"{ img .os_distro } { img .os_version } "
239+ # TODO: Could add warnings for
240+ # - os_distro not being all-lowercase (they all should be acc. to
241+ # https://docs.openstack.org/glance/2025.1/admin/useful-image-properties.html
242+ # - os_version not matching regexp r'[0-9\.]*' (should be a numeric version no)
217243 # (3) os_hash
218244 if img .hash_algo not in ('sha256' , 'sha512' ):
219245 print (f'WARNING: Image "{ imgnm } ": no valid hash algorithm { img .hash_algo } ' , file = sys .stderr )
@@ -278,9 +304,9 @@ def validate_imageMD(img, outd_list):
278304 outd_list .append (imgnm )
279305 elif outd :
280306 outd_list .append (imgnm )
281- # (2) sanity min_ram (>=64 ), min_disk (>= size)
282- if img .min_ram < 64 :
283- print (f'WARNING: Image "{ imgnm } ": min_ram == { img .min_ram } MiB < 64 MiB' , file = sys .stderr )
307+ # (2) sanity min_ram (>=32 ), min_disk (>= size)
308+ if img .min_ram < 32 :
309+ print (f'WARNING: Image "{ imgnm } ": min_ram == { img .min_ram } MiB < 32 MiB' , file = sys .stderr )
284310 warnings += 1
285311 if not img .min_ram :
286312 print (f'ERROR: Image "{ imgnm } ": min_ram == 0' , file = sys .stderr )
@@ -304,6 +330,8 @@ def validate_imageMD(img, outd_list):
304330
305331 if not errors and verbose :
306332 print (f'Image "{ imgnm } ": All good ({ warnings } warnings)' )
333+ elif verbose :
334+ print (f'Image "{ imgnm } ": { errors } errors and { warnings } warnings' )
307335 return errors
308336
309337
@@ -313,7 +341,7 @@ def report_stdimage_coverage(imgs):
313341 for inm in mand_images :
314342 if inm not in imgs :
315343 err += 1
316- print (f'WARNING : Mandatory image "{ inm } " is missing' , file = sys .stderr )
344+ print (f'ERROR : Mandatory image "{ inm } " is missing' , file = sys .stderr )
317345 for inm in (* rec1_images , * rec2_images ):
318346 if inm not in imgs :
319347 print (f'INFO: Recommended image "{ inm } " is missing' , file = sys .stderr )
0 commit comments