Skip to content

Commit a8f35df

Browse files
authored
Merge pull request #7 from rezib/pr/conf-struct
Restructure vm and gerrit configuration parameters
2 parents f3603cd + eb7c85a commit a8f35df

File tree

10 files changed

+727
-178
lines changed

10 files changed

+727
-178
lines changed

lib/rift/Config.py

Lines changed: 204 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
"""
3636
import errno
3737
import os
38+
import warnings
39+
import logging
40+
3841
import yaml
3942

4043
from 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+
5965
OrderedLoader.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

606765
class Staff():

lib/rift/Controller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,7 @@ def vm_build(vm, args, config):
682682
"Both --deploy and -o,--output options cannot be used together"
683683
)
684684
if args.deploy:
685-
output = config.get('vm_image')
685+
output = config.get('vm').get('image')
686686
else:
687687
output = args.output
688688
message(f"Building new vm image {output}")

lib/rift/Gerrit.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,20 @@ def invalidate(self):
8080
def push(self, config, changeid, revid):
8181
"""Send REST request to Gerrit server from config"""
8282
auth_methods = ('digest', 'basic')
83-
realm = config.get('gerrit_realm')
84-
server = config.get('gerrit_server')
85-
username = config.get('gerrit_username')
86-
password = config.get('gerrit_password')
87-
auth_method = config.get('gerrit_auth_method', 'basic')
83+
84+
gerrit_config = config.get('gerrit')
85+
if gerrit_config is None:
86+
raise RiftError("Gerrit configuration is not defined")
87+
88+
realm = gerrit_config.get('realm')
89+
server = gerrit_config.get('server')
90+
username = gerrit_config.get('username')
91+
password = gerrit_config.get('password')
92+
auth_method = gerrit_config.get('auth_method', 'basic')
8893

8994
if realm is None:
9095
raise RiftError("Gerrit realm is not defined")
91-
if server is None and config.get('gerrit_url') is None:
96+
if server is None and gerrit_config.get('url') is None:
9297
raise RiftError("Gerrit url is not defined")
9398
if username is None:
9499
raise RiftError("Gerrit username is not defined")
@@ -98,7 +103,7 @@ def push(self, config, changeid, revid):
98103
raise RiftError(f"Gerrit auth_method is not correct (supported {auth_methods})")
99104

100105
# Set a default url if only gerrit_server was defined
101-
url = config.get('gerrit_url', f"https://{server}")
106+
url = gerrit_config.get('url', f"https://{server}")
102107

103108
# FIXME: Don't check certificate
104109
ctx = ssl.create_default_context()

0 commit comments

Comments
 (0)