3535"""
3636import errno
3737import os
38+ import warnings
39+ import logging
40+
3841import yaml
3942
4043from rift import DeclError
@@ -56,6 +59,9 @@ def _construct_mapping(loader, node):
5659 loader .flatten_mapping (node )
5760 return OrderedDict (loader .construct_pairs (node ))
5861
62+ class RiftDeprecatedConfWarning (FutureWarning ):
63+ """Warning emitted when deprecated configuration parameter is loaded."""
64+
5965OrderedLoader .add_constructor (
6066 yaml .resolver .BaseResolver .DEFAULT_MAPPING_TAG ,
6167 _construct_mapping )
@@ -175,55 +181,114 @@ class Config():
175181 }
176182 }
177183 },
178- 'vm_image' : {
184+ 'vm' : {
185+ 'check' : 'dict' ,
179186 'required' : True ,
180- # XXX?: default value?
187+ 'syntax' : {
188+ 'image' : {
189+ 'required' : True ,
190+ # XXX?: default value?
191+ },
192+ 'image_copy' : {
193+ 'check' : 'digit' ,
194+ 'default' : 0 ,
195+ },
196+ 'port_range' : {
197+ 'check' : 'dict' ,
198+ 'syntax' : {
199+ 'min' : {
200+ 'check' : 'digit' ,
201+ 'default' : _DEFAULT_VM_PORT_RANGE_MIN ,
202+ },
203+ 'max' : {
204+ 'check' : 'digit' ,
205+ 'default' : _DEFAULT_VM_PORT_RANGE_MAX ,
206+ }
207+ }
208+ },
209+ 'cpu' : {},
210+ 'cpus' : {
211+ 'check' : 'digit' ,
212+ 'default' : _DEFAULT_VM_CPUS ,
213+ },
214+ 'memory' : {
215+ 'check' : 'digit' ,
216+ 'default' : _DEFAULT_VM_MEMORY ,
217+ },
218+ 'address' : {
219+ 'default' : _DEFAULT_VM_ADDRESS ,
220+ },
221+ 'images_cache' : {},
222+ 'additional_rpms' : {
223+ 'check' : 'list' ,
224+ 'default' : _DEFAULT_VM_ADDITIONAL_RPMS ,
225+ },
226+ 'cloud_init_tpl' : {
227+ 'default' : _DEFAULT_VM_CLOUD_INIT_TPL ,
228+ },
229+ 'build_post_script' : {
230+ 'default' : _DEFAULT_VM_BUILD_POST_SCRIPT ,
231+ },
232+ }
233+ },
234+ 'vm_image' : {
235+ 'deprecated' : 'vm.image'
181236 },
182237 'vm_image_copy' : {
183- 'check' : 'digit' ,
184- 'default' : 0 ,
238+ 'deprecated' : 'vm.image_copy'
185239 },
186240 'vm_port_range' : {
187- 'check' : 'dict' ,
188- 'syntax' : {
189- 'min' : {
190- 'check' : 'digit' ,
191- 'default' : _DEFAULT_VM_PORT_RANGE_MIN ,
192- },
193- 'max' : {
194- 'check' : 'digit' ,
195- 'default' : _DEFAULT_VM_PORT_RANGE_MAX ,
196- }
197- }
241+ 'deprecated' : 'vm.port_range'
242+ },
243+ 'vm_cpu' : {
244+ 'deprecated' : 'vm.cpu'
198245 },
199- 'vm_cpu' : {},
200246 'vm_cpus' : {
201- 'check' : 'digit' ,
202- 'default' : _DEFAULT_VM_CPUS ,
247+ 'deprecated' : 'vm.cpus'
203248 },
204249 'vm_memory' : {
205- 'check' : 'digit' ,
206- 'default' : _DEFAULT_VM_MEMORY ,
250+ 'deprecated' : 'vm.memory'
207251 },
208252 'vm_address' : {
209- 'default' : _DEFAULT_VM_ADDRESS ,
253+ 'deprecated' : 'vm.address'
254+ },
255+ 'vm_images_cache' : {
256+ 'deprecated' : 'vm.images_cache'
210257 },
211- 'vm_images_cache' : {},
212258 'vm_additional_rpms' : {
213- 'check' : 'list' ,
214- 'default' : _DEFAULT_VM_ADDITIONAL_RPMS ,
259+ 'deprecated' : 'vm.additional_rpms'
215260 },
216261 'vm_cloud_init_tpl' : {
217- 'default ' : _DEFAULT_VM_CLOUD_INIT_TPL ,
262+ 'deprecated ' : 'vm.cloud_init_tpl'
218263 },
219264 'vm_build_post_script' : {
220- 'default' : _DEFAULT_VM_BUILD_POST_SCRIPT ,
265+ 'deprecated' : 'vm.build_post_script'
266+ },
267+ 'gerrit' : {
268+ 'check' : 'dict' ,
269+ 'syntax' : {
270+ 'realm' : {},
271+ 'server' : {},
272+ 'url' : {},
273+ 'username' : {},
274+ 'password' : {},
275+ }
276+ },
277+ 'gerrit_realm' : {
278+ 'deprecated' : 'gerrit.realm'
279+ },
280+ 'gerrit_server' : {
281+ 'deprecated' : 'gerrit.server'
282+ },
283+ 'gerrit_url' : {
284+ 'deprecated' : 'gerrit.url'
285+ },
286+ 'gerrit_username' : {
287+ 'deprecated' : 'gerrit.username'
288+ },
289+ 'gerrit_password' : {
290+ 'deprecated' : 'gerrit.password'
221291 },
222- 'gerrit_realm' : {},
223- 'gerrit_server' : {},
224- 'gerrit_url' : {},
225- 'gerrit_username' : {},
226- 'gerrit_password' : {},
227292 'rpm_macros' : {
228293 'check' : 'dict' ,
229294 },
@@ -444,6 +509,12 @@ def set(self, key, value, arch=None):
444509 if key not in self .SYNTAX :
445510 raise DeclError (f"Unknown '{ key } ' key" )
446511
512+ # Check not deprecated
513+ replacement = self .SYNTAX [key ].get ('deprecated' )
514+ if replacement :
515+ raise DeclError (f"Parameter { key } is deprecated, use "
516+ f"{ ' > ' .join (replacement .split ('.' ))} instead" )
517+
447518 options = self ._arch_options (arch )
448519 value = self ._key_value (
449520 self .SYNTAX [key ],
@@ -522,8 +593,8 @@ def _dict_value(self, syntax, key, value):
522593 raise DeclError (f"Unknown { key } keys: { ', ' .join (unknown_keys )} " )
523594
524595 # Iterate over the keys defined in syntax. If the subvalue or default
525- # value is defined, set it or raise error if required .
526- for subkey , subkey_format in syntax .items ():
596+ # value is defined, set it.
597+ for subkey in syntax .keys ():
527598 subkey_value = value .get (subkey ,
528599 Config ._syntax_default (syntax , subkey ))
529600 if subkey_value is not None :
@@ -533,12 +604,7 @@ def _dict_value(self, syntax, key, value):
533604 subkey_value ,
534605 syntax [subkey ].get ('check' , 'string' )
535606 )
536- elif subkey_format .get ('required' , False ):
537- raise DeclError (
538- f"Key { subkey } is required in dict parameter { key } "
539- )
540607
541- # Set the key in options dict eventually.
542608 return result
543609
544610 def _record_value (self , syntax , value ):
@@ -556,6 +622,73 @@ def _record_value(self, syntax, value):
556622 )
557623 return result
558624
625+ @staticmethod
626+ def _get_replacement_dict_key (data , replacement ):
627+ """
628+ Return a 2-tuple with the dict that contains the replacement parameter
629+ and the key of this parameter in this dict.
630+ """
631+ sub = data
632+ replacement_items = replacement .split ('.' )
633+ # Browse in data dict depth until last replacement item.
634+ for index , item in enumerate (replacement_items , start = 1 ):
635+ if index < len (replacement_items ):
636+ if item not in sub :
637+ sub [item ] = {}
638+ sub = sub [item ]
639+ else :
640+ return sub , item
641+ return None
642+
643+ def _move_deprecated_param (self , data , param , value ):
644+ """
645+ If the given parameter is deprecated, move its value to its replacement
646+ parameter.
647+ """
648+ # Leave if parameter not found in syntax, the error is managed in set()
649+ # method eventually.
650+ if param not in self .SYNTAX :
651+ return
652+ # Check presence of deprecated attribute and leave if not defined.
653+ replacement = self .SYNTAX [param ].get ("deprecated" )
654+ if replacement is None :
655+ return
656+ # Warn user with FutureWarning.
657+ warnings .warn (f"Configuration parameter { param } is deprecated, use "
658+ f"{ ' > ' .join (replacement .split ('.' ))} instead" ,
659+ RiftDeprecatedConfWarning )
660+ # Get position of replacement parameter.
661+ sub , item = Config ._get_replacement_dict_key (data , replacement )
662+ # If both new and deprecated parameter are defined, emit warning log to
663+ # explain deprecated parameter is ignored. Else, move deprecated
664+ # parameter to its new place.
665+ if item in sub :
666+ logging .warning ("Both deprecated parameter %s and new parameter "
667+ "%s are declared in configuration, deprecated "
668+ "parameter %s is ignored" ,
669+ param , replacement , param )
670+ else :
671+ sub [item ] = value
672+ del data [param ]
673+
674+ def _move_deprecated (self , data ):
675+ """
676+ Iterate over data dict to check for deprecated parameters and move them
677+ to their replacements.
678+ """
679+ # Load generic options (ie. not architecture specific)
680+ for param , value in data .copy ().items ():
681+ # Skip architecture specific options
682+ if param in self .get ('arch' ):
683+ continue
684+ self ._move_deprecated_param (data , param , value )
685+
686+ # Load architecture specific options
687+ for arch in self .get ('arch' ):
688+ if arch in data and isinstance (data [arch ], dict ):
689+ for param , value in data .copy ()[arch ].items ():
690+ self ._move_deprecated_param (data [arch ], param , value )
691+
559692 def update (self , data ):
560693 """
561694 Update config content with data dict, checking data content respect
@@ -566,6 +699,9 @@ def update(self, data):
566699 self .set ('arch' , data ['arch' ])
567700 del data ['arch' ]
568701
702+ # Look for deprecated parameters, and update dict with new parameters.
703+ self ._move_deprecated (data )
704+
569705 # Load generic options (ie. not architecture specific)
570706 for key , value in data .items ():
571707 # Skip architecture specific options
@@ -585,22 +721,45 @@ def update(self, data):
585721 self .set (key , value , arch = arch )
586722
587723 def _check (self ):
588- """Checks for mandatory options."""
589- for key , value in self .SYNTAX .items ():
724+ """Checks for required options in main syntax recursively."""
725+ self ._check_syntax (self .SYNTAX , self .options )
726+
727+ def _check_syntax (self , syntax , options , param = '__main__' ):
728+ """Checks for mandatory options regarding the provided syntax recursively."""
729+ for key in syntax :
590730 if (
591- value .get ('required' , False ) and
592- 'default' not in value
731+ syntax [ key ] .get ('required' , False ) and
732+ 'default' not in syntax [ key ]
593733 ):
594734 # Check key is in options or defined in all supported arch
595735 # specific options.
596736 if (
597- key not in self . options and
737+ key not in options and
598738 not all (
599- arch in self . options and key in self . options [arch ]
739+ arch in options and key in options [arch ]
600740 for arch in self .get ('arch' )
601741 )
602742 ):
603- raise DeclError (f"'{ key } ' is not defined" )
743+ if param == '__main__' :
744+ raise DeclError (f"'{ key } ' is not defined" )
745+ raise DeclError (
746+ f"Key { key } is required in dict parameter { param } "
747+ )
748+ # If the parameter is a dict with a syntax, check the value.
749+ if (
750+ syntax [key ].get ('check' ) == 'dict' and
751+ syntax [key ].get ('syntax' ) is not None and key in options
752+ ):
753+ self ._check_syntax (syntax [key ]['syntax' ], options [key ], key )
754+ # If the parameter is a record with dict values and a syntax, check
755+ # all values.
756+ if (
757+ syntax [key ].get ('check' ) == 'record' and
758+ syntax [key ].get ('content' ) == 'dict' and
759+ syntax [key ].get ('syntax' ) is not None and key in options
760+ ):
761+ for value in options [key ].values ():
762+ self ._check_syntax (syntax [key ]['syntax' ], value , key )
604763
605764
606765class Staff ():
0 commit comments