Feature filter replace_keys (#8446)

* Add filter replace_keys.

* Update examples and integration tests.

* Fix examples and copyright.

* Update documentation, examples and integration tests.

* Implement #8445. Add filter replace_keys

* Fix documentation formatting.

* Fix documentation.

* Fix type(target). Formatting improved.

* Instead of a dictionary, _keys_filter_target_dict returns a list

* No target testing in _keys_filter_params
* Interface changed _keys_filter_params(data, matching_parameter)
* If there are items with equal C(before) the B(first) one will be used.

* Update remove_keys. Interface changed _keys_filter_params(data, matching_parameter)

* The target can't be empty also in _keys_filter_target_dict

* Update plugins/filter/replace_keys.py

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update plugins/filter/replace_keys.py

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update plugins/filter/replace_keys.py

Co-authored-by: Felix Fontein <felix@fontein.de>

* Test attributes before and after are strings in the iteration of target.

* Update plugins/filter/replace_keys.py

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
Vladimir Botka 2024-06-14 21:54:58 +02:00 committed by GitHub
commit 1d61541951
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 397 additions and 35 deletions

View file

@ -13,11 +13,10 @@ from ansible.module_utils.six import string_types
from ansible.module_utils.common._collections_compat import Mapping, Sequence
def _keys_filter_params(data, target, matching_parameter):
def _keys_filter_params(data, matching_parameter):
"""test parameters:
* data must be a list of dictionaries. All keys must be strings.
* target must be a non-empty sequence.
* matching_parameter is member of a list.
* data must be a list of dictionaries. All keys must be strings.
* matching_parameter is member of a list.
"""
mp = matching_parameter
@ -37,30 +36,32 @@ def _keys_filter_params(data, target, matching_parameter):
msg = "Top level keys must be strings. keys: %s"
raise AnsibleFilterError(msg % elem.keys())
if not isinstance(target, Sequence):
msg = ("The target must be a string or a list. target is %s.")
raise AnsibleFilterError(msg % target)
if len(target) == 0:
msg = ("The target can't be empty.")
raise AnsibleFilterError(msg)
if mp not in ml:
msg = ("The matching_parameter must be one of %s. matching_parameter is %s")
msg = "The matching_parameter must be one of %s. matching_parameter=%s"
raise AnsibleFilterError(msg % (ml, mp))
return
def _keys_filter_target_str(target, matching_parameter):
"""test:
* If target is list all items are strings
* If matching_parameter=regex target is a string or list with single string
convert and return:
* tuple of unique target items, or
* tuple with single item, or
* compiled regex if matching_parameter=regex
"""
Test:
* target is a non-empty string or list.
* If target is list all items are strings.
* target is a string or list with single string if matching_parameter=regex.
Convert target and return:
* tuple of unique target items, or
* tuple with single item, or
* compiled regex if matching_parameter=regex.
"""
if not isinstance(target, Sequence):
msg = "The target must be a string or a list. target is %s."
raise AnsibleFilterError(msg % type(target))
if len(target) == 0:
msg = "The target can't be empty."
raise AnsibleFilterError(msg)
if isinstance(target, list):
for elem in target:
@ -73,15 +74,14 @@ def _keys_filter_target_str(target, matching_parameter):
r = target
else:
if len(target) > 1:
msg = ("Single item is required in the target list if matching_parameter is regex.")
msg = "Single item is required in the target list if matching_parameter=regex."
raise AnsibleFilterError(msg)
else:
r = target[0]
try:
tt = re.compile(r)
except re.error:
msg = ("The target must be a valid regex if matching_parameter is regex."
" target is %s")
msg = "The target must be a valid regex if matching_parameter=regex. target is %s"
raise AnsibleFilterError(msg % r)
elif isinstance(target, string_types):
tt = (target, )
@ -92,17 +92,50 @@ def _keys_filter_target_str(target, matching_parameter):
def _keys_filter_target_dict(target, matching_parameter):
"""test:
* target is a list of dictionaries
* ...
"""
Test:
* target is a list of dictionaries with attributes 'after' and 'before'.
* Attributes 'before' must be valid regex if matching_parameter=regex.
* Otherwise, the attributes 'before' must be strings.
Convert target and return:
* iterator that aggregates attributes 'before' and 'after', or
* iterator that aggregates compiled regex of attributes 'before' and 'after' if matching_parameter=regex.
"""
# TODO: Complete and use this in filter replace_keys
if not isinstance(target, list):
msg = "The target must be a list. target is %s."
raise AnsibleFilterError(msg % (target, type(target)))
if isinstance(target, list):
for elem in target:
if not isinstance(elem, Mapping):
msg = "The target items must be dictionary. %s is %s"
raise AnsibleFilterError(msg % (elem, type(elem)))
if len(target) == 0:
msg = "The target can't be empty."
raise AnsibleFilterError(msg)
return
for elem in target:
if not isinstance(elem, Mapping):
msg = "The target items must be dictionaries. %s is %s"
raise AnsibleFilterError(msg % (elem, type(elem)))
if not all(k in elem for k in ('before', 'after')):
msg = "All dictionaries in target must include attributes: after, before."
raise AnsibleFilterError(msg)
if not isinstance(elem['before'], string_types):
msg = "The attributes before must be strings. %s is %s"
raise AnsibleFilterError(msg % (elem['before'], type(elem['before'])))
if not isinstance(elem['after'], string_types):
msg = "The attributes after must be strings. %s is %s"
raise AnsibleFilterError(msg % (elem['after'], type(elem['after'])))
before = [d['before'] for d in target]
after = [d['after'] for d in target]
if matching_parameter == 'regex':
try:
tr = map(re.compile, before)
tz = list(zip(tr, after))
except re.error:
msg = ("The attributes before must be valid regex if matching_parameter=regex."
" Not all items are valid regex in: %s")
raise AnsibleFilterError(msg % before)
else:
tz = list(zip(before, after))
return tz