Skip to content

Commit

Permalink
ansible_freeipa_module_utils: Add EntryFactory class
Browse files Browse the repository at this point in the history
This patch adds the class EntryFactory to the ansible-freeipa module
utils.

This class allows the handling of modules with multiple object entries
as list of objects. When the multi-object parameter is not used, it
creates a list of a single object, allowing for the same code idiom to
be user.

The entries created can be used both as objects, by acessing the values
as properties, or as dictionaires, by accessing the elements as
key-value pairs. The entry objects are read-only, and the values cannot
be modified.
  • Loading branch information
rjeffman committed Nov 4, 2024
1 parent 7c7d988 commit d6b7d38
Showing 1 changed file with 157 additions and 0 deletions.
157 changes: 157 additions & 0 deletions plugins/module_utils/ansible_freeipa_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)):
Expand Down Expand Up @@ -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()

0 comments on commit d6b7d38

Please sign in to comment.