mirror of
https://github.com/ansible-collections/community.routeros.git
synced 2025-06-30 13:24:36 +02:00
Add regexp field to ip dns static (#142)
* Add regexp field to "ip dns static" * Change test_invalid_required_missing to use "ip dhcp-server" "ip dns static" requires name or regexp so it cannot be used in this test. * Add required_one_of attribute to APIData Used by "ip dns static" which requires either "name" or "regexp. * Add mutually_exclusive attribute to APIData Used by "ip dns static" where only one of "name" or "regexp" can be used. * Add changelog fragment
This commit is contained in:
parent
62da7dd4e3
commit
586edbc211
5 changed files with 98 additions and 4 deletions
6
changelogs/fragments/142-dns-regexp.yml
Normal file
6
changelogs/fragments/142-dns-regexp.yml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
minor_changes:
|
||||||
|
- api_modify, api_info - add field ``regexp`` to ``ip dns static`` (https://github.com/ansible-collections/community.routeros/issues/141).
|
||||||
|
|
||||||
|
bugfixes:
|
||||||
|
- api_modify, api_info - do not crash if router contains ``regexp`` DNS entries in ``ip dns static`` (https://github.com/ansible-collections/community.routeros/issues/141).
|
||||||
|
- api_modify - do not use ``name`` as a unique key in ``ip dns static`` (https://github.com/ansible-collections/community.routeros/issues/141).
|
|
@ -13,6 +13,8 @@ __metaclass__ = type
|
||||||
class APIData(object):
|
class APIData(object):
|
||||||
def __init__(self, primary_keys=None,
|
def __init__(self, primary_keys=None,
|
||||||
stratify_keys=None,
|
stratify_keys=None,
|
||||||
|
required_one_of=None,
|
||||||
|
mutually_exclusive=None,
|
||||||
has_identifier=False,
|
has_identifier=False,
|
||||||
single_value=False,
|
single_value=False,
|
||||||
unknown_mechanism=False,
|
unknown_mechanism=False,
|
||||||
|
@ -25,6 +27,8 @@ class APIData(object):
|
||||||
raise ValueError('unknown_mechanism and fully_understood cannot be combined')
|
raise ValueError('unknown_mechanism and fully_understood cannot be combined')
|
||||||
self.primary_keys = primary_keys
|
self.primary_keys = primary_keys
|
||||||
self.stratify_keys = stratify_keys
|
self.stratify_keys = stratify_keys
|
||||||
|
self.required_one_of = required_one_of or []
|
||||||
|
self.mutually_exclusive = mutually_exclusive or []
|
||||||
self.has_identifier = has_identifier
|
self.has_identifier = has_identifier
|
||||||
self.single_value = single_value
|
self.single_value = single_value
|
||||||
self.unknown_mechanism = unknown_mechanism
|
self.unknown_mechanism = unknown_mechanism
|
||||||
|
@ -43,6 +47,20 @@ class APIData(object):
|
||||||
for sk in stratify_keys:
|
for sk in stratify_keys:
|
||||||
if sk not in fields:
|
if sk not in fields:
|
||||||
raise ValueError('Stratify key {sk} must be in fields!'.format(sk=sk))
|
raise ValueError('Stratify key {sk} must be in fields!'.format(sk=sk))
|
||||||
|
if required_one_of:
|
||||||
|
for index, require_list in enumerate(required_one_of):
|
||||||
|
if not isinstance(require_list, list):
|
||||||
|
raise ValueError('Require one of element at index #{index} must be a list!'.format(index=index + 1))
|
||||||
|
for rk in require_list:
|
||||||
|
if rk not in fields:
|
||||||
|
raise ValueError('Require one of key {rk} must be in fields!'.format(rk=rk))
|
||||||
|
if mutually_exclusive:
|
||||||
|
for index, exclusive_list in enumerate(mutually_exclusive):
|
||||||
|
if not isinstance(exclusive_list, list):
|
||||||
|
raise ValueError('Mutually exclusive element at index #{index} must be a list!'.format(index=index + 1))
|
||||||
|
for ek in exclusive_list:
|
||||||
|
if ek not in fields:
|
||||||
|
raise ValueError('Mutually exclusive key {ek} must be in fields!'.format(ek=ek))
|
||||||
|
|
||||||
|
|
||||||
class KeyInfo(object):
|
class KeyInfo(object):
|
||||||
|
@ -1356,7 +1374,8 @@ PATHS = {
|
||||||
),
|
),
|
||||||
('ip', 'dns', 'static'): APIData(
|
('ip', 'dns', 'static'): APIData(
|
||||||
fully_understood=True,
|
fully_understood=True,
|
||||||
stratify_keys=('name', ),
|
required_one_of=[['name', 'regexp']],
|
||||||
|
mutually_exclusive=[['name', 'regexp']],
|
||||||
fields={
|
fields={
|
||||||
'address': KeyInfo(),
|
'address': KeyInfo(),
|
||||||
'cname': KeyInfo(),
|
'cname': KeyInfo(),
|
||||||
|
@ -1365,8 +1384,9 @@ PATHS = {
|
||||||
'forward-to': KeyInfo(),
|
'forward-to': KeyInfo(),
|
||||||
'mx-exchange': KeyInfo(),
|
'mx-exchange': KeyInfo(),
|
||||||
'mx-preference': KeyInfo(),
|
'mx-preference': KeyInfo(),
|
||||||
'name': KeyInfo(required=True),
|
'name': KeyInfo(),
|
||||||
'ns': KeyInfo(),
|
'ns': KeyInfo(),
|
||||||
|
'regexp': KeyInfo(),
|
||||||
'srv-port': KeyInfo(),
|
'srv-port': KeyInfo(),
|
||||||
'srv-priority': KeyInfo(),
|
'srv-priority': KeyInfo(),
|
||||||
'srv-target': KeyInfo(),
|
'srv-target': KeyInfo(),
|
||||||
|
|
|
@ -465,6 +465,24 @@ def polish_entry(entry, path_info, module, for_text):
|
||||||
for key, field_info in path_info.fields.items():
|
for key, field_info in path_info.fields.items():
|
||||||
if field_info.required and key not in entry:
|
if field_info.required and key not in entry:
|
||||||
module.fail_json(msg='Key "{key}" must be present{for_text}.'.format(key=key, for_text=for_text))
|
module.fail_json(msg='Key "{key}" must be present{for_text}.'.format(key=key, for_text=for_text))
|
||||||
|
for require_list in path_info.required_one_of:
|
||||||
|
found_req_keys = [rk for rk in require_list if rk in entry]
|
||||||
|
if len(require_list) > 0 and not found_req_keys:
|
||||||
|
module.fail_json(
|
||||||
|
msg='Every element in data must contain one of {required_keys}. For example, the element{for_text} does not provide it.'.format(
|
||||||
|
required_keys=', '.join(['"{k}"'.format(k=k) for k in require_list]),
|
||||||
|
for_text=for_text,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for exclusive_list in path_info.mutually_exclusive:
|
||||||
|
found_ex_keys = [ek for ek in exclusive_list if ek in entry]
|
||||||
|
if len(found_ex_keys) > 1:
|
||||||
|
module.fail_json(
|
||||||
|
msg='Keys {exclusive_keys} cannot be used at the same time{for_text}.'.format(
|
||||||
|
exclusive_keys=', '.join(['"{k}"'.format(k=k) for k in found_ex_keys]),
|
||||||
|
for_text=for_text,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def remove_irrelevant_data(entry, path_info):
|
def remove_irrelevant_data(entry, path_info):
|
||||||
|
|
|
@ -54,6 +54,22 @@ def test_api_data_errors():
|
||||||
APIData(stratify_keys=['foo'], fields={})
|
APIData(stratify_keys=['foo'], fields={})
|
||||||
assert exc.value.args[0] == 'Stratify key foo must be in fields!'
|
assert exc.value.args[0] == 'Stratify key foo must be in fields!'
|
||||||
|
|
||||||
|
with pytest.raises(ValueError) as exc:
|
||||||
|
APIData(required_one_of=['foo'], fields={})
|
||||||
|
assert exc.value.args[0] == 'Require one of element at index #1 must be a list!'
|
||||||
|
|
||||||
|
with pytest.raises(ValueError) as exc:
|
||||||
|
APIData(required_one_of=[['foo']], fields={})
|
||||||
|
assert exc.value.args[0] == 'Require one of key foo must be in fields!'
|
||||||
|
|
||||||
|
with pytest.raises(ValueError) as exc:
|
||||||
|
APIData(mutually_exclusive=['foo'], fields={})
|
||||||
|
assert exc.value.args[0] == 'Mutually exclusive element at index #1 must be a list!'
|
||||||
|
|
||||||
|
with pytest.raises(ValueError) as exc:
|
||||||
|
APIData(mutually_exclusive=[['foo']], fields={})
|
||||||
|
assert exc.value.args[0] == 'Mutually exclusive key foo must be in fields!'
|
||||||
|
|
||||||
|
|
||||||
def test_key_info_errors():
|
def test_key_info_errors():
|
||||||
values = [
|
values = [
|
||||||
|
|
|
@ -385,9 +385,9 @@ class TestRouterosApiModifyModule(ModuleTestCase):
|
||||||
with self.assertRaises(AnsibleFailJson) as exc:
|
with self.assertRaises(AnsibleFailJson) as exc:
|
||||||
args = self.config_module_args.copy()
|
args = self.config_module_args.copy()
|
||||||
args.update({
|
args.update({
|
||||||
'path': 'ip dns static',
|
'path': 'ip dhcp-server',
|
||||||
'data': [{
|
'data': [{
|
||||||
'address': '1.2.3.4',
|
'interface': 'eth0',
|
||||||
}],
|
}],
|
||||||
})
|
})
|
||||||
set_module_args(args)
|
set_module_args(args)
|
||||||
|
@ -397,6 +397,40 @@ class TestRouterosApiModifyModule(ModuleTestCase):
|
||||||
self.assertEqual(result['failed'], True)
|
self.assertEqual(result['failed'], True)
|
||||||
self.assertEqual(result['msg'], 'Every element in data must contain "name". For example, the element at index #1 does not provide it.')
|
self.assertEqual(result['msg'], 'Every element in data must contain "name". For example, the element at index #1 does not provide it.')
|
||||||
|
|
||||||
|
def test_invalid_required_one_of_missing(self):
|
||||||
|
with self.assertRaises(AnsibleFailJson) as exc:
|
||||||
|
args = self.config_module_args.copy()
|
||||||
|
args.update({
|
||||||
|
'path': 'ip dns static',
|
||||||
|
'data': [{
|
||||||
|
'address': '192.168.88.1',
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
set_module_args(args)
|
||||||
|
self.module.main()
|
||||||
|
|
||||||
|
result = exc.exception.args[0]
|
||||||
|
self.assertEqual(result['failed'], True)
|
||||||
|
self.assertEqual(result['msg'], 'Every element in data must contain one of "name", "regexp". For example, the element at index 1 does not provide it.')
|
||||||
|
|
||||||
|
def test_invalid_mutually_exclusive_both(self):
|
||||||
|
with self.assertRaises(AnsibleFailJson) as exc:
|
||||||
|
args = self.config_module_args.copy()
|
||||||
|
args.update({
|
||||||
|
'path': 'ip dns static',
|
||||||
|
'data': [{
|
||||||
|
'name': 'foo',
|
||||||
|
'regexp': 'bar',
|
||||||
|
'address': '192.168.88.1',
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
set_module_args(args)
|
||||||
|
self.module.main()
|
||||||
|
|
||||||
|
result = exc.exception.args[0]
|
||||||
|
self.assertEqual(result['failed'], True)
|
||||||
|
self.assertEqual(result['msg'], 'Keys "name", "regexp" cannot be used at the same time at index 1.')
|
||||||
|
|
||||||
@patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path',
|
@patch('ansible_collections.community.routeros.plugins.modules.api_modify.compose_api_path',
|
||||||
new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC, read_only=True))
|
new=create_fake_path(('ip', 'dns', 'static'), START_IP_DNS_STATIC, read_only=True))
|
||||||
def test_sync_list_idempotent(self):
|
def test_sync_list_idempotent(self):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue