diff --git a/plugins/module_utils/ansible_freeipa_module.py b/plugins/module_utils/ansible_freeipa_module.py index 2f90b3e10f..171c98cb51 100644 --- a/plugins/module_utils/ansible_freeipa_module.py +++ b/plugins/module_utils/ansible_freeipa_module.py @@ -526,6 +526,10 @@ def module_params_get(module, name, allow_empty_list_item=False): def module_params_get_lowercase(module, name, allow_empty_list_item=False): value = module_params_get(module, name, allow_empty_list_item) + return convert_param_value_to_lowercase(value) + + +def convert_param_value_to_lowercase(value): if isinstance(value, list): value = [v.lower() for v in value] if isinstance(value, (str, unicode)): @@ -1584,3 +1588,156 @@ def tm_warn(self, warning): ts = time.time() # pylint: disable=super-with-arguments super(IPAAnsibleModule, self).warn("%f %s" % (ts, warning)) + + +class EntryFactory: + """ + Implement an Entry Factory to extract objects from modules. + + When defining an ansible-freeipa module which allows the setting of + multiple objects in a single task, the object parameters can be set + as a set of parameters, or as a list of dictionaries with multiple + objects. + + The EntryFactory abstracts the extraction of the entry values so + that the entries set in a module can be treated as a list of objects + independent of the way the objects have been defined (as single object + defined by its parameters or as a list). + + Parameters + ---------- + ansible_module: The ansible module to be processed. + invalid_params: The list of invalid parameters for the current + state/action combination. + multiname: The name of the list of objects parameters. + params: a dict of the entry parameters with optional list of data + conversion functions. The functions are applied to the parameter + value in the order they appear in the list. + aliases: an optional dict of aliases where the keys are the alias + name and the value is the target parameter. + validate_entry: an optional function to validate the entry values. + This function is called after the parameters for the current + state/action are checked, and can be used to perform further + validation. If the entry is not valid, 'fail_json' should be + called by the validate_entry function. If the function ends, it + is assumed that the entry is valid. + + """ + + def __init__( + self, + ansible_module, + invalid_params, + multiname, + params, + aliases=None, + validate_entry=None + ): + """Initialize the Entry Factory.""" + self.module = ansible_module + self.invalid_params = invalid_params + self.multiname = multiname + self.params = params + self.aliases = aliases + self.validate_entry = validate_entry + self.entries = None + + class Entry: + """Provide an abstraction to handle module entries.""" + + def __init__(self, values): + """Initialize entry to be used as dict or object.""" + self.values = values + for key, value in values.items(): + setattr(self, key, value) + + def __getitem__(self, item): + """Allow entries to be treated as dictionaries.""" + return self.values[item] + + def __repr__(self): + """Provide a string representation of the stored values.""" + return repr(self.values) + + def _get_entries(self): + """Retrieve all entries from the module.""" + name = self.module.params_get("name") + if name is not None: + if isinstance(name, list): + self.entries = [ + EntryFactory.Entry({"name": _name}) for _name in name + ] + else: + self.entries = [ + self._extract_entry( + self.module, + IPAAnsibleModule.params_get + ) + ] + self.entries[0].name = name + else: + self.entries = [ + self._extract_entry(data, dict.get) + for data in self.module.params_get(self.multiname) + ] + + def _extract_entry(self, data, param_get): + """Extract an entry from the given data, using the given method.""" + def get_entry_param(entry_param): + _param, _conversions = entry_param + _value = param_get(data, _param) + for fn in _conversions: + _value = fn(_value) + result = (_param, _value) + return result + + # entry = {} + # for entry_param in self.params.items(): + # param, value = get_entry_param(entry_param) + # entry[param] = value + entry = dict( + get_entry_param(entry_param) + for entry_param in self.params.items() + ) + if self.aliases: + entry.update({ + alias: entry[param] + for alias, param in self.aliases.items() + }) + state = self.module.params_get("state") + action = self.module.params_get("action") + _result = EntryFactory.Entry(entry) + self._check_invalid_params( + _result, state, action, self.invalid_params + ) + # Call entry validation callback, if provided. + if self.validate_entry: + self.validate_entry(self.module, _result, state, action) + + return _result + + def _check_invalid_params(self, entry, state, action, invalid_params): + """Check if all parameters are valid for the given state/action.""" + if action is None: + msg = "Argument '{0}' can not be used with state '{1}'" + else: + msg = "Argument '{0}' can not be used with action " \ + "'{2}' and state '{1}'" + + for param in invalid_params: + if getattr(entry, param, None) is not None: + self.module.fail_json(msg=msg.format(param, state, action)) + + if not entry.name: + self.module.fail_json(msg="Entry 'name' is not set.") + + def __iter__(self): + """Initialize factory iterator.""" + self._get_entries() + return self + + def __next__(self): + """Retrieve next entry.""" + if not self.entries: + raise StopIteration() + return self.entries.pop()