Allow to ignore dynamic and builtin entries. (#373)

This commit is contained in:
Felix Fontein 2025-05-31 16:50:52 +02:00 committed by GitHub
parent bfd6b0bb13
commit ab1026504c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 118 additions and 4 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- "api_find_and_modify - allow to control whether ``dynamic`` and/or ``builtin`` entries are ignored with the new ``ignore_dynamic`` and ``ignore_builtin`` options (https://github.com/ansible-collections/community.routeros/issues/372, https://github.com/ansible-collections/community.routeros/pull/373)."

View file

@ -75,6 +75,22 @@ options:
- Whether to allow that no match is found.
- If not specified, this value is induced from whether O(require_matches_min) is 0 or larger.
type: bool
ignore_dynamic:
description:
- Whether to ignore dynamic entries.
- By default, they are considered. If set to V(true), they are not considered.
- It is generally recommended to set this to V(true) unless when you really need to modify dynamic entries.
type: bool
default: false
version_added: 3.7.0
ignore_builtin:
description:
- Whether to ignore builtin entries.
- By default, they are considered. If set to V(true), they are not considered.
- It is generally recommended to set this to V(true) unless when you really need to modify builtin entries.
type: bool
default: false
version_added: 3.7.0
seealso:
- module: community.routeros.api
- module: community.routeros.api_facts
@ -94,6 +110,10 @@ EXAMPLES = r"""
name: bridge
values:
name: my-bridge
# Always ignore dynamic and builtin entries
# (not relevant for this path, but generally recommended)
ignore_dynamic: true
ignore_builtin: true
- name: Change IP address to 192.168.1.1 for interface bridge - assuming there is only one
community.routeros.api_find_and_modify:
@ -109,6 +129,10 @@ EXAMPLES = r"""
# exactly one is configured.
require_matches_min: 1
require_matches_max: 1
# Always ignore dynamic and builtin entries
# (not relevant for this path, but generally recommended)
ignore_dynamic: true
ignore_builtin: true
"""
RETURN = r"""
@ -185,6 +209,17 @@ def compose_api_path(api, path):
return api_path
def filter_entries(entries, ignore_dynamic=False, ignore_builtin=False):
result = []
for entry in entries:
if ignore_dynamic and entry.get('dynamic', False):
continue
if ignore_builtin and entry.get('builtin', False):
continue
result.append(entry)
return result
DISABLED_MEANS_EMPTY_STRING = ('comment', )
@ -196,6 +231,8 @@ def main():
require_matches_min=dict(type='int', default=0),
require_matches_max=dict(type='int'),
allow_no_matches=dict(type='bool'),
ignore_dynamic=dict(type='bool', default=False),
ignore_builtin=dict(type='bool', default=False),
)
module_args.update(api_argument_spec())
@ -223,6 +260,9 @@ def main():
if key in values:
module.fail_json(msg='`values` must not contain both "{key}" and "!{key}"!'.format(key=key))
ignore_dynamic = module.params['ignore_dynamic']
ignore_builtin = module.params['ignore_builtin']
check_has_library(module)
api = create_api(module)
@ -230,7 +270,7 @@ def main():
api_path = compose_api_path(api, path)
old_data = list(api_path)
old_data = filter_entries(list(api_path), ignore_dynamic=ignore_dynamic, ignore_builtin=ignore_builtin)
new_data = [entry.copy() for entry in old_data]
# Find matching entries
@ -299,7 +339,7 @@ def main():
error=to_native(e),
)
)
new_data = list(api_path)
new_data = filter_entries(list(api_path), ignore_dynamic=ignore_dynamic, ignore_builtin=ignore_builtin)
# Produce return value
more = {}

View file

@ -94,6 +94,52 @@ START_IP_FIREWALL_FILTER = [
START_IP_FIREWALL_FILTER_OLD_DATA = massage_expected_result_data(START_IP_FIREWALL_FILTER, ('ip', 'firewall', 'filter'), keep_all=True)
START_IP_SERVICE = [
# I removed all entryes not for 'api' and 'api-ssl'
{
"certificate": None,
"tls-version": None,
".id": "*7",
"address": "",
"disabled": True,
"dynamic": False,
"invalid": True,
"name": "api",
"port": 8728,
"proto": "tcp",
"vrf": "main"
},
{
".id": "*9",
"address": "192.168.1.0/24",
"certificate": "mycert",
"dynamic": False,
"invalid": False,
"name": "api-ssl",
"port": 8729,
"proto": "tcp",
"tls-version": "only-1.2",
"vrf": "main"
},
{
"address": None,
"certificate": None,
"max-sessions": None,
"tls-version": None,
".id": "*13",
"connection": True,
"dynamic": True,
"invalid": False,
"local": "192.168.1.1",
"name": "api-ssl",
"port": 8729,
"proto": "tcp",
"remote": "192.168.1.2:12346"
}
]
START_IP_SERVICE_OLD_DATA = massage_expected_result_data(START_IP_SERVICE, ('ip', 'service'), keep_all=True)
class TestRouterosApiFindAndModifyModule(ModuleTestCase):
@ -685,3 +731,29 @@ class TestRouterosApiFindAndModifyModule(ModuleTestCase):
])
self.assertEqual(result['match_count'], 3)
self.assertEqual(result['modify_count'], 2)
@patch('ansible_collections.community.routeros.plugins.modules.api_find_and_modify.compose_api_path',
new=create_fake_path(('ip', 'service'), START_IP_SERVICE))
def test_change_ignore_dynamic(self):
with self.assertRaises(AnsibleExitJson) as exc:
args = self.config_module_args.copy()
args.update({
'path': 'ip service',
'find': {
'name': 'api-ssl',
},
'values': {
'address': '192.168.1.0/24',
},
'ignore_dynamic': True,
'_ansible_diff': True,
})
with set_module_args(args):
self.module.main()
result = exc.exception.args[0]
self.assertEqual(result['changed'], False)
self.assertEqual(result['old_data'], [entry for entry in START_IP_SERVICE_OLD_DATA if entry["dynamic"] is False])
self.assertEqual(result['new_data'], [entry for entry in START_IP_SERVICE_OLD_DATA if entry["dynamic"] is False])
self.assertEqual(result['match_count'], 1)
self.assertEqual(result['modify_count'], 0)