Skip to content

Commit 22143dc

Browse files
authored
Merge pull request #1491 from cernymi/cernymi/netbox_versions
add _version_sanitize and improve _version_check_greater in netbox_utils
2 parents 48d2d15 + 1e2bdc3 commit 22143dc

File tree

4 files changed

+85
-30
lines changed

4 files changed

+85
-30
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
minor_changes:
3+
- improve version_check_greater to be more universal
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
minor_changes:
3+
- sanitize netbox versions received from api

plugins/module_utils/netbox_utils.py

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -771,9 +771,11 @@ def __init__(self, module, endpoint, nb_client=None):
771771
else:
772772
self.nb = nb_client
773773
try:
774-
self.version = self.nb.version
774+
self.version = self._version_sanitize(self.nb.version)
775775
try:
776-
self.full_version = self.nb.status().get("netbox-version")
776+
self.full_version = self._version_sanitize(
777+
self.nb.status().get("netbox-version")
778+
)
777779
except Exception:
778780
# For NetBox versions without /api/status endpoint
779781
self.full_version = f"{self.version}.0"
@@ -790,32 +792,40 @@ def __init__(self, module, endpoint, nb_client=None):
790792
data = self._find_ids(choices_data, query_params)
791793
self.data = self._convert_identical_keys(data)
792794

793-
def _version_check_greater(self, greater, lesser, greater_or_equal=False):
795+
@staticmethod
796+
def _version_sanitize(raw_value: str) -> str:
797+
"""Return sanitized Netbox version.
798+
Sanitize 4.2.9-Docker-3.2.1 and return only 4.2.9.
799+
"""
800+
if not isinstance(raw_value, str):
801+
raise ValueError(f"Invalid value {raw_value!r}: expected a string")
802+
803+
version_match = re.match(r"^(\d[\d.]*)", raw_value)
804+
if version_match:
805+
return version_match.group(1).rstrip(".")
806+
807+
raise ValueError(
808+
f"Invalid version {raw_value!r}: must start with a digit (e.g. '1', '2.5', '4.2.9')"
809+
)
810+
811+
def _version_check_greater(
812+
self, greater: str, lesser: str, greater_or_equal=False
813+
) -> bool:
794814
"""Determine if first argument is greater than second argument.
795815
796816
Args:
797817
greater (str): decimal string
798-
lesser (str): decimal string
818+
lesser (str): decimal string
799819
"""
800-
g_major, g_minor = greater.split(".")
801-
l_major, l_minor = lesser.split(".")
802-
803-
# convert to ints
804-
g_major = int(g_major)
805-
g_minor = int(g_minor)
806-
l_major = int(l_major)
807-
l_minor = int(l_minor)
808-
809-
# If major version is higher then return true right off the bat
810-
if g_major > l_major:
811-
return True
812-
elif greater_or_equal and g_major == l_major and g_minor >= l_minor:
813-
return True
814-
# If major versions are equal, and minor version is higher, return True
815-
elif g_major == l_major and g_minor > l_minor:
816-
return True
817-
818-
return False
820+
t_greater = tuple(int(x) for x in self._version_sanitize(greater).split("."))
821+
t_lesser = tuple(int(x) for x in self._version_sanitize(lesser).split("."))
822+
823+
# Pad shorter tuple with zeros
824+
max_len = max(len(t_greater), len(t_lesser))
825+
t_greater += (0,) * (max_len - len(t_greater))
826+
t_lesser += (0,) * (max_len - len(t_lesser))
827+
828+
return t_greater > t_lesser if not greater_or_equal else t_greater >= t_lesser
819829

820830
def _connect_netbox_api(self, url, token, ssl_verify, cert, headers=None):
821831
try:
@@ -830,9 +840,11 @@ def _connect_netbox_api(self, url, token, ssl_verify, cert, headers=None):
830840
nb = pynetbox.api(url, token=token)
831841
nb.http_session = session
832842
try:
833-
self.version = nb.version
843+
self.version = self._version_sanitize(nb.version)
834844
try:
835-
self.full_version = nb.status().get("netbox-version")
845+
self.full_version = self._version_sanitize(
846+
nb.status().get("netbox-version")
847+
)
836848
except Exception:
837849
# For NetBox versions without /api/status endpoint
838850
self.full_version = f"{self.version}.0"

tests/unit/module_utils/test_netbox_base_class.py

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
__metaclass__ = type
99

10+
import re
1011
import os
1112
from functools import partial
1213
from unittest.mock import MagicMock
@@ -374,24 +375,29 @@ def test_update_netbox_object_with_changes_check_mode_true(
374375
assert diff == on_update_diff
375376

376377

377-
@pytest.mark.parametrize("version", ["2.9", "2.8", "2.7"])
378+
@pytest.mark.parametrize("version", ["2.13", "2.12", "2.11", "2.10.8", "2.10"])
378379
def test_version_check_greater_true(mock_netbox_module, nb_obj_mock, version):
379380
mock_netbox_module.nb_object = nb_obj_mock
380-
assert mock_netbox_module._version_check_greater("2.10", version)
381+
assert mock_netbox_module._version_check_greater(version, "2.9")
382+
assert mock_netbox_module._version_check_greater(version, "2.9.11")
381383

382384

383-
@pytest.mark.parametrize("version", ["2.13", "2.12", "2.11", "2.10"])
385+
@pytest.mark.parametrize("version", ["2.9", "2.8", "2.7.12", "2.7"])
384386
def test_version_check_greater_false(mock_netbox_module, nb_obj_mock, version):
385387
mock_netbox_module.nb_object = nb_obj_mock
386-
assert not mock_netbox_module._version_check_greater("2.10", version)
388+
assert not mock_netbox_module._version_check_greater(version, "2.10")
389+
assert not mock_netbox_module._version_check_greater(version, "2.10.8")
387390

388391

389-
@pytest.mark.parametrize("version", ["2.9", "2.8", "2.7"])
392+
@pytest.mark.parametrize("version", ["2.9", "2.8", "2.7.5", "2.7"])
390393
def test_version_check_greater_equal_to_true(mock_netbox_module, nb_obj_mock, version):
391394
mock_netbox_module.nb_object = nb_obj_mock
392395
assert mock_netbox_module._version_check_greater(
393396
version, "2.7", greater_or_equal=True
394397
)
398+
assert mock_netbox_module._version_check_greater(
399+
version, "2.6.12", greater_or_equal=True
400+
)
395401

396402

397403
@pytest.mark.parametrize("version", ["2.6", "2.5", "2.4"])
@@ -400,3 +406,34 @@ def test_version_check_greater_equal_to_false(mock_netbox_module, nb_obj_mock, v
400406
assert not mock_netbox_module._version_check_greater(
401407
version, "2.7", greater_or_equal=True
402408
)
409+
assert not mock_netbox_module._version_check_greater(
410+
version, "2.7.7", greater_or_equal=True
411+
)
412+
413+
414+
@pytest.mark.parametrize(
415+
"raw_value,expected",
416+
[
417+
("2.6", "2.6"),
418+
("2.6.", "2.6"),
419+
("4.2-dev", "4.2"),
420+
("4", "4"),
421+
("4-dev", "4"),
422+
("4.-dev", "4"),
423+
("4.2.9-Docker-3.2.1", "4.2.9"),
424+
("3.1.0-extra-info", "3.1.0"),
425+
("10.20.30foobar", "10.20.30"),
426+
],
427+
)
428+
def test_version_sanitize_to_true(mock_netbox_module, nb_obj_mock, raw_value, expected):
429+
mock_netbox_module.nb_object = nb_obj_mock
430+
sanitized = mock_netbox_module._version_sanitize(raw_value)
431+
assert sanitized == expected
432+
assert re.match(r"^\d+(\.\d+)*$", sanitized)
433+
434+
435+
@pytest.mark.parametrize("version", [None, [], {}, "", "aa-dev", "-4", ".4", "dev-4"])
436+
def test_version_sanitize_value_error(mock_netbox_module, nb_obj_mock, version):
437+
mock_netbox_module.nb_object = nb_obj_mock
438+
with pytest.raises(ValueError):
439+
mock_netbox_module._version_sanitize(version)

0 commit comments

Comments
 (0)