mirror of
https://github.com/ansible-collections/community.routeros.git
synced 2025-06-21 09:35:45 +02:00
api_modify/api_info: add restrict
option (#305)
* Allow to restrict api_info output. * Allow to restrict what api_modify modifies. * Add changelog. * Fix docs. * Move shared code/docs to module utils and doc fragments. * Refactor and allow to match by regex. * Simplify rules, allow to invert rule matcher. * Add more tests.
This commit is contained in:
parent
49cd8a2b2f
commit
0a9b749508
7 changed files with 776 additions and 5 deletions
|
@ -32,6 +32,7 @@ requirements:
|
|||
- Needs L(ordereddict,https://pypi.org/project/ordereddict) for Python 2.6
|
||||
extends_documentation_fragment:
|
||||
- community.routeros.api
|
||||
- community.routeros.api.restrict
|
||||
- community.routeros.attributes
|
||||
- community.routeros.attributes.actiongroup_api
|
||||
attributes:
|
||||
|
@ -333,6 +334,15 @@ options:
|
|||
- error
|
||||
default: create_only
|
||||
version_added: 2.10.0
|
||||
restrict:
|
||||
description:
|
||||
- Restrict operation to entries matching the following criteria.
|
||||
- This can be useful together with O(handle_absent_entries=remove) to operate on a subset of
|
||||
the values.
|
||||
- For example, for O(path=ip firewall filter), you can set O(restrict[].field=chain) and
|
||||
O(restrict[].values=input) to restrict operation to the input chain, and ignore the
|
||||
forward and output chains.
|
||||
version_added: 2.18.0
|
||||
seealso:
|
||||
- module: community.routeros.api
|
||||
- module: community.routeros.api_facts
|
||||
|
@ -378,6 +388,23 @@ EXAMPLES = '''
|
|||
out-interface:
|
||||
to-addresses: ~
|
||||
'!to-ports':
|
||||
|
||||
- name: Block all incoming connections
|
||||
community.routeros.api_modify:
|
||||
hostname: "{{ hostname }}"
|
||||
password: "{{ password }}"
|
||||
username: "{{ username }}"
|
||||
path: ip firewall filter
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
restrict:
|
||||
# Do not touch any chain except the input chain
|
||||
- field: chain
|
||||
values:
|
||||
- input
|
||||
data:
|
||||
- action: drop
|
||||
chain: input
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
|
@ -434,6 +461,12 @@ from ansible_collections.community.routeros.plugins.module_utils._api_data impor
|
|||
split_path,
|
||||
)
|
||||
|
||||
from ansible_collections.community.routeros.plugins.module_utils._api_helper import (
|
||||
restrict_argument_spec,
|
||||
restrict_entry_accepted,
|
||||
validate_and_prepare_restrict,
|
||||
)
|
||||
|
||||
HAS_ORDEREDDICT = True
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
|
@ -699,18 +732,29 @@ def prepare_for_add(entry, path_info):
|
|||
return new_entry
|
||||
|
||||
|
||||
def sync_list(module, api, path, path_info):
|
||||
def remove_rejected(data, path_info, restrict_data):
|
||||
return [
|
||||
entry for entry in data
|
||||
if restrict_entry_accepted(entry, path_info, restrict_data)
|
||||
]
|
||||
|
||||
|
||||
def sync_list(module, api, path, path_info, restrict_data):
|
||||
handle_absent_entries = module.params['handle_absent_entries']
|
||||
handle_entries_content = module.params['handle_entries_content']
|
||||
if handle_absent_entries == 'remove':
|
||||
if handle_entries_content == 'ignore':
|
||||
module.fail_json('For this path, handle_absent_entries=remove cannot be combined with handle_entries_content=ignore')
|
||||
module.fail_json(
|
||||
msg='For this path, handle_absent_entries=remove cannot be combined with handle_entries_content=ignore'
|
||||
)
|
||||
|
||||
stratify_keys = path_info.stratify_keys or ()
|
||||
|
||||
data = module.params['data']
|
||||
stratified_data = defaultdict(list)
|
||||
for index, entry in enumerate(data):
|
||||
if not restrict_entry_accepted(entry, path_info, restrict_data):
|
||||
module.fail_json(msg='The element at index #{index} does not match `restrict`'.format(index=index + 1))
|
||||
for stratify_key in stratify_keys:
|
||||
if stratify_key not in entry:
|
||||
module.fail_json(
|
||||
|
@ -731,6 +775,7 @@ def sync_list(module, api, path, path_info):
|
|||
|
||||
old_data = get_api_data(api_path, path_info)
|
||||
old_data = remove_dynamic(old_data)
|
||||
old_data = remove_rejected(old_data, path_info, restrict_data)
|
||||
stratified_old_data = defaultdict(list)
|
||||
for index, entry in enumerate(old_data):
|
||||
sks = tuple(entry[stratify_key] for stratify_key in stratify_keys)
|
||||
|
@ -843,6 +888,7 @@ def sync_list(module, api, path, path_info):
|
|||
# For sake of completeness, retrieve the full new data:
|
||||
if modify_list or create_list or reorder_list:
|
||||
new_data = remove_dynamic(get_api_data(api_path, path_info))
|
||||
new_data = remove_rejected(new_data, path_info, restrict_data)
|
||||
|
||||
# Remove 'irrelevant' data
|
||||
for entry in old_data:
|
||||
|
@ -869,7 +915,7 @@ def sync_list(module, api, path, path_info):
|
|||
)
|
||||
|
||||
|
||||
def sync_with_primary_keys(module, api, path, path_info):
|
||||
def sync_with_primary_keys(module, api, path, path_info, restrict_data):
|
||||
primary_keys = path_info.primary_keys
|
||||
|
||||
if path_info.fixed_entries:
|
||||
|
@ -881,6 +927,8 @@ def sync_with_primary_keys(module, api, path, path_info):
|
|||
data = module.params['data']
|
||||
new_data_by_key = OrderedDict()
|
||||
for index, entry in enumerate(data):
|
||||
if not restrict_entry_accepted(entry, path_info, restrict_data):
|
||||
module.fail_json(msg='The element at index #{index} does not match `restrict`'.format(index=index + 1))
|
||||
for primary_key in primary_keys:
|
||||
if primary_key not in entry:
|
||||
module.fail_json(
|
||||
|
@ -912,6 +960,7 @@ def sync_with_primary_keys(module, api, path, path_info):
|
|||
|
||||
old_data = get_api_data(api_path, path_info)
|
||||
old_data = remove_dynamic(old_data)
|
||||
old_data = remove_rejected(old_data, path_info, restrict_data)
|
||||
old_data_by_key = OrderedDict()
|
||||
id_by_key = {}
|
||||
for entry in old_data:
|
||||
|
@ -1038,6 +1087,7 @@ def sync_with_primary_keys(module, api, path, path_info):
|
|||
# For sake of completeness, retrieve the full new data:
|
||||
if modify_list or create_list or reorder_list:
|
||||
new_data = remove_dynamic(get_api_data(api_path, path_info))
|
||||
new_data = remove_rejected(new_data, path_info, restrict_data)
|
||||
|
||||
# Remove 'irrelevant' data
|
||||
for entry in old_data:
|
||||
|
@ -1064,7 +1114,9 @@ def sync_with_primary_keys(module, api, path, path_info):
|
|||
)
|
||||
|
||||
|
||||
def sync_single_value(module, api, path, path_info):
|
||||
def sync_single_value(module, api, path, path_info, restrict_data):
|
||||
if module.params['restrict'] is not None:
|
||||
module.fail_json(msg='The restrict option cannot be used with this path, since there is precisely one entry.')
|
||||
data = module.params['data']
|
||||
if len(data) != 1:
|
||||
module.fail_json(msg='Data must be a list with exactly one element.')
|
||||
|
@ -1162,6 +1214,7 @@ def main():
|
|||
handle_write_only=dict(type='str', default='create_only', choices=['create_only', 'always_update', 'error']),
|
||||
)
|
||||
module_args.update(api_argument_spec())
|
||||
module_args.update(restrict_argument_spec())
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=module_args,
|
||||
|
@ -1193,7 +1246,9 @@ def main():
|
|||
if path_info is None or backend is None:
|
||||
module.fail_json(msg='Path /{path} is not yet supported'.format(path='/'.join(path)))
|
||||
|
||||
backend(module, api, path, path_info)
|
||||
restrict_data = validate_and_prepare_restrict(module, path_info)
|
||||
|
||||
backend(module, api, path, path_info, restrict_data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue