mirror of
https://github.com/ansible-collections/community.routeros.git
synced 2025-07-13 19:54:35 +02:00
Allow to differ on API paths based on RouterOS version (2/2) (#212)
* Allow to add versioned field for paths. * The field added in1aa41ad375
is RouterOS 7.7+. * The fields added in2e1159b4c4
are RouterOS 7.5+.
This commit is contained in:
parent
4b0995135c
commit
dcc1cf441d
4 changed files with 79 additions and 17 deletions
|
@ -37,7 +37,7 @@ class APIData(object):
|
||||||
self.unversioned = unversioned
|
self.unversioned = unversioned
|
||||||
self.versioned = versioned
|
self.versioned = versioned
|
||||||
if self.unversioned is not None:
|
if self.unversioned is not None:
|
||||||
self.needs_version = False
|
self.needs_version = self.unversioned.needs_version
|
||||||
self.fully_understood = self.unversioned.fully_understood
|
self.fully_understood = self.unversioned.fully_understood
|
||||||
else:
|
else:
|
||||||
self.needs_version = self.versioned is not None
|
self.needs_version = self.versioned is not None
|
||||||
|
@ -47,30 +47,35 @@ class APIData(object):
|
||||||
if unversioned.fully_understood:
|
if unversioned.fully_understood:
|
||||||
self.fully_understood = True
|
self.fully_understood = True
|
||||||
break
|
break
|
||||||
|
self._current = None if self.needs_version else self.unversioned
|
||||||
|
|
||||||
def provide_version(self, version):
|
def provide_version(self, version):
|
||||||
if not self.needs_version:
|
if not self.needs_version:
|
||||||
return self.unversioned.fully_understood
|
return self.unversioned.fully_understood
|
||||||
api_version = LooseVersion(version)
|
api_version = LooseVersion(version)
|
||||||
for other_version, comparator, unversioned in self.versioned:
|
if self.unversioned is not None:
|
||||||
|
self._current = self.unversioned.specialize_for_version(api_version)
|
||||||
|
return self._current.fully_understood
|
||||||
|
for other_version, comparator, data in self.versioned:
|
||||||
if other_version == '*' and comparator == '*':
|
if other_version == '*' and comparator == '*':
|
||||||
self.unversioned = unversioned
|
self._current = data.specialize_for_version(api_version)
|
||||||
return self.unversioned.fully_supported
|
return self._current.fully_supported
|
||||||
other_api_version = LooseVersion(other_version)
|
other_api_version = LooseVersion(other_version)
|
||||||
if _compare(api_version, other_api_version, comparator):
|
if _compare(api_version, other_api_version, comparator):
|
||||||
self.unversioned = unversioned
|
self._current = data.specialize_for_version(api_version)
|
||||||
return self.unversioned.fully_supported
|
return self._current.fully_supported
|
||||||
self.unversioned = None
|
self._current = None
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
if self.unversioned is None:
|
if self._current is None:
|
||||||
raise ValueError('either provide_version() was not called or it returned False')
|
raise ValueError('either provide_version() was not called or it returned False')
|
||||||
return self.unversioned
|
return self._current
|
||||||
|
|
||||||
|
|
||||||
class VersionedAPIData(object):
|
class VersionedAPIData(object):
|
||||||
def __init__(self, primary_keys=None,
|
def __init__(self,
|
||||||
|
primary_keys=None,
|
||||||
stratify_keys=None,
|
stratify_keys=None,
|
||||||
required_one_of=None,
|
required_one_of=None,
|
||||||
mutually_exclusive=None,
|
mutually_exclusive=None,
|
||||||
|
@ -79,7 +84,8 @@ class VersionedAPIData(object):
|
||||||
unknown_mechanism=False,
|
unknown_mechanism=False,
|
||||||
fully_understood=False,
|
fully_understood=False,
|
||||||
fixed_entries=False,
|
fixed_entries=False,
|
||||||
fields=None):
|
fields=None,
|
||||||
|
versioned_fields=None):
|
||||||
if sum([primary_keys is not None, stratify_keys is not None, has_identifier, single_value, unknown_mechanism]) > 1:
|
if sum([primary_keys is not None, stratify_keys is not None, has_identifier, single_value, unknown_mechanism]) > 1:
|
||||||
raise ValueError('primary_keys, stratify_keys, has_identifier, single_value, and unknown_mechanism are mutually exclusive')
|
raise ValueError('primary_keys, stratify_keys, has_identifier, single_value, and unknown_mechanism are mutually exclusive')
|
||||||
if unknown_mechanism and fully_understood:
|
if unknown_mechanism and fully_understood:
|
||||||
|
@ -98,6 +104,17 @@ class VersionedAPIData(object):
|
||||||
if fields is None:
|
if fields is None:
|
||||||
raise ValueError('fields must be provided')
|
raise ValueError('fields must be provided')
|
||||||
self.fields = fields
|
self.fields = fields
|
||||||
|
if versioned_fields is not None:
|
||||||
|
if not isinstance(versioned_fields, list):
|
||||||
|
raise ValueError('unversioned_fields must be a list')
|
||||||
|
for conditions, name, field in versioned_fields:
|
||||||
|
if not isinstance(conditions, (tuple, list)):
|
||||||
|
raise ValueError('conditions must be a list or tuple')
|
||||||
|
if not isinstance(field, KeyInfo):
|
||||||
|
raise ValueError('field must be a KeyInfo object')
|
||||||
|
if name in fields:
|
||||||
|
raise ValueError('"{name}" appears both in fields and versioned_fields'.format(name=name))
|
||||||
|
self.versioned_fields = versioned_fields or []
|
||||||
if primary_keys:
|
if primary_keys:
|
||||||
for pk in primary_keys:
|
for pk in primary_keys:
|
||||||
if pk not in fields:
|
if pk not in fields:
|
||||||
|
@ -120,6 +137,35 @@ class VersionedAPIData(object):
|
||||||
for ek in exclusive_list:
|
for ek in exclusive_list:
|
||||||
if ek not in fields:
|
if ek not in fields:
|
||||||
raise ValueError('Mutually exclusive key {ek} must be in fields!'.format(ek=ek))
|
raise ValueError('Mutually exclusive key {ek} must be in fields!'.format(ek=ek))
|
||||||
|
self.needs_version = len(self.versioned_fields) > 0
|
||||||
|
|
||||||
|
def specialize_for_version(self, api_version):
|
||||||
|
fields = self.fields.copy()
|
||||||
|
for conditions, name, field in self.versioned_fields:
|
||||||
|
matching = True
|
||||||
|
for other_version, comparator in conditions:
|
||||||
|
other_api_version = LooseVersion(other_version)
|
||||||
|
if not _compare(api_version, other_api_version, comparator):
|
||||||
|
matching = False
|
||||||
|
break
|
||||||
|
if matching:
|
||||||
|
if name in fields:
|
||||||
|
raise ValueError(
|
||||||
|
'Internal error: field "{field}" already exists for {version}'.format(field=name, version=api_version)
|
||||||
|
)
|
||||||
|
fields[name] = field
|
||||||
|
return VersionedAPIData(
|
||||||
|
primary_keys=self.primary_keys,
|
||||||
|
stratify_keys=self.stratify_keys,
|
||||||
|
required_one_of=self.required_one_of,
|
||||||
|
mutually_exclusive=self.mutually_exclusive,
|
||||||
|
has_identifier=self.has_identifier,
|
||||||
|
single_value=self.single_value,
|
||||||
|
unknown_mechanism=self.unknown_mechanism,
|
||||||
|
fully_understood=self.fully_understood,
|
||||||
|
fixed_entries=self.fixed_entries,
|
||||||
|
fields=fields,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class KeyInfo(object):
|
class KeyInfo(object):
|
||||||
|
@ -1241,10 +1287,12 @@ PATHS = {
|
||||||
unversioned=VersionedAPIData(
|
unversioned=VersionedAPIData(
|
||||||
single_value=True,
|
single_value=True,
|
||||||
fully_understood=True,
|
fully_understood=True,
|
||||||
|
versioned_fields=[
|
||||||
|
([('7.7', '>=')], 'mode', KeyInfo(default='tx-and-rx')),
|
||||||
|
],
|
||||||
fields={
|
fields={
|
||||||
'discover-interface-list': KeyInfo(),
|
'discover-interface-list': KeyInfo(),
|
||||||
'lldp-med-net-policy-vlan': KeyInfo(default='disabled'),
|
'lldp-med-net-policy-vlan': KeyInfo(default='disabled'),
|
||||||
'mode': KeyInfo(default='tx-and-rx'),
|
|
||||||
'protocol': KeyInfo(default='cdp,lldp,mndp'),
|
'protocol': KeyInfo(default='cdp,lldp,mndp'),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -1797,14 +1845,16 @@ PATHS = {
|
||||||
fully_understood=True,
|
fully_understood=True,
|
||||||
required_one_of=[['name', 'regexp']],
|
required_one_of=[['name', 'regexp']],
|
||||||
mutually_exclusive=[['name', 'regexp']],
|
mutually_exclusive=[['name', 'regexp']],
|
||||||
|
versioned_fields=[
|
||||||
|
([('7.5', '>=')], 'address-list', KeyInfo()),
|
||||||
|
([('7.5', '>=')], 'match-subdomain', KeyInfo(default=False)),
|
||||||
|
],
|
||||||
fields={
|
fields={
|
||||||
'address': KeyInfo(),
|
'address': KeyInfo(),
|
||||||
'address-list': KeyInfo(),
|
|
||||||
'cname': KeyInfo(),
|
'cname': KeyInfo(),
|
||||||
'comment': KeyInfo(can_disable=True, remove_value=''),
|
'comment': KeyInfo(can_disable=True, remove_value=''),
|
||||||
'disabled': KeyInfo(default=False),
|
'disabled': KeyInfo(default=False),
|
||||||
'forward-to': KeyInfo(),
|
'forward-to': KeyInfo(),
|
||||||
'match-subdomain': KeyInfo(default=False),
|
|
||||||
'mx-exchange': KeyInfo(),
|
'mx-exchange': KeyInfo(),
|
||||||
'mx-preference': KeyInfo(),
|
'mx-preference': KeyInfo(),
|
||||||
'name': KeyInfo(),
|
'name': KeyInfo(),
|
||||||
|
|
|
@ -9,7 +9,7 @@ __metaclass__ = type
|
||||||
from ansible_collections.community.routeros.plugins.module_utils._api_data import PATHS
|
from ansible_collections.community.routeros.plugins.module_utils._api_data import PATHS
|
||||||
|
|
||||||
|
|
||||||
FAKE_ROS_VERSION = '7.0.0'
|
FAKE_ROS_VERSION = '7.5.0'
|
||||||
|
|
||||||
|
|
||||||
class FakeLibRouterosError(Exception):
|
class FakeLibRouterosError(Exception):
|
||||||
|
|
|
@ -7,7 +7,9 @@ from __future__ import (absolute_import, division, print_function)
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
from ansible_collections.community.routeros.tests.unit.compat.mock import patch, MagicMock
|
from ansible_collections.community.routeros.tests.unit.compat.mock import patch, MagicMock
|
||||||
from ansible_collections.community.routeros.tests.unit.plugins.modules.fake_api import FakeLibRouterosError, Key, fake_ros_api
|
from ansible_collections.community.routeros.tests.unit.plugins.modules.fake_api import (
|
||||||
|
FAKE_ROS_VERSION, FakeLibRouterosError, Key, fake_ros_api,
|
||||||
|
)
|
||||||
from ansible_collections.community.routeros.tests.unit.plugins.modules.utils import set_module_args, AnsibleExitJson, AnsibleFailJson, ModuleTestCase
|
from ansible_collections.community.routeros.tests.unit.plugins.modules.utils import set_module_args, AnsibleExitJson, AnsibleFailJson, ModuleTestCase
|
||||||
from ansible_collections.community.routeros.plugins.modules import api_info
|
from ansible_collections.community.routeros.plugins.modules import api_info
|
||||||
|
|
||||||
|
@ -22,6 +24,10 @@ class TestRouterosApiInfoModule(ModuleTestCase):
|
||||||
self.module.check_has_library = MagicMock()
|
self.module.check_has_library = MagicMock()
|
||||||
self.patch_create_api = patch('ansible_collections.community.routeros.plugins.modules.api_info.create_api', MagicMock(new=fake_ros_api))
|
self.patch_create_api = patch('ansible_collections.community.routeros.plugins.modules.api_info.create_api', MagicMock(new=fake_ros_api))
|
||||||
self.patch_create_api.start()
|
self.patch_create_api.start()
|
||||||
|
self.patch_get_api_version = patch(
|
||||||
|
'ansible_collections.community.routeros.plugins.modules.api_info.get_api_version',
|
||||||
|
MagicMock(return_value=FAKE_ROS_VERSION))
|
||||||
|
self.patch_get_api_version.start()
|
||||||
self.module.Key = MagicMock(new=Key)
|
self.module.Key = MagicMock(new=Key)
|
||||||
self.config_module_args = {
|
self.config_module_args = {
|
||||||
'username': 'admin',
|
'username': 'admin',
|
||||||
|
@ -30,6 +36,7 @@ class TestRouterosApiInfoModule(ModuleTestCase):
|
||||||
}
|
}
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
self.patch_get_api_version.stop()
|
||||||
self.patch_create_api.stop()
|
self.patch_create_api.stop()
|
||||||
|
|
||||||
def test_module_fail_when_required_args_missing(self):
|
def test_module_fail_when_required_args_missing(self):
|
||||||
|
|
|
@ -8,7 +8,7 @@ __metaclass__ = type
|
||||||
|
|
||||||
from ansible_collections.community.routeros.tests.unit.compat.mock import patch, MagicMock
|
from ansible_collections.community.routeros.tests.unit.compat.mock import patch, MagicMock
|
||||||
from ansible_collections.community.routeros.tests.unit.plugins.modules.fake_api import (
|
from ansible_collections.community.routeros.tests.unit.plugins.modules.fake_api import (
|
||||||
FakeLibRouterosError, fake_ros_api, massage_expected_result_data, create_fake_path,
|
FAKE_ROS_VERSION, FakeLibRouterosError, fake_ros_api, massage_expected_result_data, create_fake_path,
|
||||||
)
|
)
|
||||||
from ansible_collections.community.routeros.tests.unit.plugins.modules.utils import set_module_args, AnsibleExitJson, AnsibleFailJson, ModuleTestCase
|
from ansible_collections.community.routeros.tests.unit.plugins.modules.utils import set_module_args, AnsibleExitJson, AnsibleFailJson, ModuleTestCase
|
||||||
from ansible_collections.community.routeros.plugins.modules import api_modify
|
from ansible_collections.community.routeros.plugins.modules import api_modify
|
||||||
|
@ -302,6 +302,10 @@ class TestRouterosApiModifyModule(ModuleTestCase):
|
||||||
'ansible_collections.community.routeros.plugins.modules.api_modify.create_api',
|
'ansible_collections.community.routeros.plugins.modules.api_modify.create_api',
|
||||||
MagicMock(new=fake_ros_api))
|
MagicMock(new=fake_ros_api))
|
||||||
self.patch_create_api.start()
|
self.patch_create_api.start()
|
||||||
|
self.patch_get_api_version = patch(
|
||||||
|
'ansible_collections.community.routeros.plugins.modules.api_modify.get_api_version',
|
||||||
|
MagicMock(return_value=FAKE_ROS_VERSION))
|
||||||
|
self.patch_get_api_version.start()
|
||||||
self.config_module_args = {
|
self.config_module_args = {
|
||||||
'username': 'admin',
|
'username': 'admin',
|
||||||
'password': 'pаss',
|
'password': 'pаss',
|
||||||
|
@ -309,6 +313,7 @@ class TestRouterosApiModifyModule(ModuleTestCase):
|
||||||
}
|
}
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
self.patch_get_api_version.stop()
|
||||||
self.patch_create_api.stop()
|
self.patch_create_api.stop()
|
||||||
|
|
||||||
def test_module_fail_when_required_args_missing(self):
|
def test_module_fail_when_required_args_missing(self):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue