diff --git a/src/core/src/bootstrap/CoreUtility.py b/src/core/src/core_logic/VersionComparator.py similarity index 55% rename from src/core/src/bootstrap/CoreUtility.py rename to src/core/src/core_logic/VersionComparator.py index e8c88a3d..de5e7d2d 100644 --- a/src/core/src/bootstrap/CoreUtility.py +++ b/src/core/src/core_logic/VersionComparator.py @@ -16,46 +16,37 @@ import re -class CoreUtility(object): +class VersionComparator(object): - @staticmethod - def compare_version(version_a, version_b): + def compare_version(self, version_a, version_b): + # type (str, str) -> int """ Compare two versions with handling numeric and string parts, return -1 (less), +1 (greater), 0 (equal) """ - def split_version_components(version): - """ Split a version into numeric and non-numeric into components list: 27.13.4~18.04.1 -> [27][14][4]""" - #return [int(x) if x.isdigit() else x for x in re.split(r'(\d+)', version) if x] - return [int(x) if x.isdigit() else x for x in re.split(r'(\d+)', version) if x] - - def parse_version(version_components): - """ Parse the split version list into list [27][14][4] -> [[27], [14], [4]]""" - return [split_version_components(x) for x in version_components.split(".")] - - parse_version_a = parse_version(version_a) - parse_version_b = parse_version(version_b) + parse_version_a = self.__parse_version(version_a) + parse_version_b = self.__parse_version(version_b) for v1, v2 in zip(parse_version_a, parse_version_b): for sub_v1, sub_v2 in zip(v1, v2): if sub_v1 < sub_v2: - return -1 # less + return -1 # less elif sub_v1 > sub_v2: return 1 # greater # If equal 27.13.4 vs 27.13.4, return 0 return (len(parse_version_a) > len(parse_version_b)) - (len(parse_version_a) - len(parse_version_b)) - @staticmethod - def extract_version(path): + def extract_version(self, path): + # type (str) -> str """ Extract the version part from a given path. Input: /var/lib/waagent/Microsoft.CPlat.Core.LinuxPatchExtension-1.2.5/config - Return: 1.2.5 + Return: "1.2.5" """ match = re.search(r'([\d]+\.[\d]+\.[\d]+)', path) - return match.group(1) if match else "" + return match.group(1) if match else str() - @staticmethod - def sort_versions(paths): + def sort_versions_desc_order(self, paths): + # type (list[str]) -> list[str] """ Sort paths based on version numbers extracted from paths. Input: @@ -67,12 +58,25 @@ def sort_versions(paths): "Microsoft.CPlat.Core.LinuxPatchExtension-1.21.100", "Microsoft.CPlat.Core.LinuxPatchExtension-1.6.100"] """ + return sorted(paths, key=self.__version_key, reverse=True) - def version_key(path): - version_numbers = CoreUtility.extract_version(path) - return tuple(map(int, version_numbers.split('.'))) if version_numbers else (0, 0, 0) + def __version_key(self, path): + # type (str) -> (int) + """ Extract version number from input and return int tuple. + Input: "Microsoft.CPlat.Core.LinuxPatchExtension-1.6.100" + Return: (1.6.100) + """ + version_numbers = self.extract_version(path) + return tuple(map(int, version_numbers.split('.'))) if version_numbers else (0, 0, 0) - return sorted(paths, key=version_key, reverse=True) + def __split_version_components(self, version): + # type (str) -> [any] + """ Split a version into numeric and non-numeric into components list: 27.13.4~18.04.1 -> [27][14][4]""" + return [int(x) if x.isdigit() else x for x in re.split(r'(\d+)', version) if x] + def __parse_version(self, version_components): + # type (str) -> [[any]] + """ Parse the split version list into list [27][14][4] -> [[27], [14], [4]]""" + return [self.__split_version_components(x) for x in version_components.split(".")] diff --git a/src/core/src/package_managers/UbuntuProClient.py b/src/core/src/package_managers/UbuntuProClient.py index 97c4cb45..8f2e8ac1 100644 --- a/src/core/src/package_managers/UbuntuProClient.py +++ b/src/core/src/package_managers/UbuntuProClient.py @@ -17,7 +17,7 @@ """This is the Ubuntu Pro Client implementation""" import json -from core.src.bootstrap.CoreUtility import CoreUtility +from core.src.core_logic.VersionComparator import VersionComparator from core.src.bootstrap.Constants import Constants @@ -29,6 +29,7 @@ def __init__(self, env_layer, composite_logger): self.ubuntu_pro_client_security_status_cmd = 'pro security-status --format=json' self.security_esm_criteria_strings = ["esm-infra", "esm-apps"] self.is_ubuntu_pro_client_attached = False + self.version_comparator = VersionComparator() def install_or_update_pro(self): """install/update pro(ubuntu-advantage-tools) to the latest version""" @@ -56,10 +57,12 @@ def is_pro_working(self): ubuntu_pro_client_version = version_result.installed_version # extract version from pro_client_verison 27.13.4~18.04.1 -> 27.13.4 - extracted_ubuntu_pro_client_version = CoreUtility.extract_version(ubuntu_pro_client_version) + extracted_ubuntu_pro_client_version = self.version_comparator.extract_version(ubuntu_pro_client_version) + + self.composite_logger.log_debug("Ubuntu Pro Client current version: [ClientVersion={0}]".format(str(extracted_ubuntu_pro_client_version))) # use custom comparator output 0 (equal), -1 (less), +1 (greater) - is_minimum_ubuntu_pro_version_installed = CoreUtility.compare_version(extracted_ubuntu_pro_client_version, Constants.UbuntuProClientSettings.MINIMUM_CLIENT_VERSION) >= 0 + is_minimum_ubuntu_pro_version_installed = self.version_comparator.compare_version(extracted_ubuntu_pro_client_version, Constants.UbuntuProClientSettings.MINIMUM_CLIENT_VERSION) >= 0 if ubuntu_pro_client_version is not None and is_minimum_ubuntu_pro_version_installed: is_ubuntu_pro_client_working = True diff --git a/src/core/tests/Test_CoreUtility.py b/src/core/tests/Test_CoreUtility.py deleted file mode 100644 index aa56f21b..00000000 --- a/src/core/tests/Test_CoreUtility.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright 2020 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.7+ - -import unittest - -from core.src.bootstrap.CoreUtility import CoreUtility - - -class TestContainer(unittest.TestCase): - def test_core_utility_comparator(self): - # Test extract version logic - self.assertEqual(CoreUtility.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25"), "1.2.25") - self.assertEqual(CoreUtility.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc"), "1.2.25") - self.assertEqual(CoreUtility.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25+abc.123"), "1.2.25") - self.assertEqual(CoreUtility.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc+def.123"),"1.2.25") - self.assertEqual(CoreUtility.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.21.1001"), "1.21.1001") - self.assertEqual(CoreUtility.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.6.100"), "1.6.100") - self.assertEqual(CoreUtility.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.6.99"), "1.6.99") - self.assertEqual(CoreUtility.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.6."), "") - self.assertEqual(CoreUtility.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-a.b.c"), "") - - expected_extracted_version = "27.13.4" - test_extracted_v1 = CoreUtility.extract_version("27.13.4~18.04.1") - test_extracted_v2 = CoreUtility.extract_version("27.13.4+18.04.1") - test_extracted_v3 = CoreUtility.extract_version("27.13.4-18.04.1") - - self.assertEqual(test_extracted_v1, expected_extracted_version) - self.assertEqual(test_extracted_v2, expected_extracted_version) - self.assertEqual(test_extracted_v3, expected_extracted_version) - - # Test compare versions logic - self.assertEqual(CoreUtility.compare_version(test_extracted_v1, "27.13.4"), 0) # equal - self.assertEqual(CoreUtility.compare_version(test_extracted_v2, "27.13.3"), 1) # greater - self.assertEqual(CoreUtility.compare_version(test_extracted_v3, "27.13.5"), -1) # less - - # Test sort versions logic - unsorted_path_versions = [ - "Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc+def.123", - "Microsoft.CPlat.Core.LinuxPatchExtension-1.21.1001", - "Microsoft.CPlat.Core.LinuxPatchExtension-1.6.100", - "Microsoft.CPlat.Core.LinuxPatchExtension-1.6.99", - "Microsoft.CPlat.Core.LinuxPatchExtension-1.21.100", - "Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc", - ] - - expected_sorted_path_versions = [ - "Microsoft.CPlat.Core.LinuxPatchExtension-1.21.1001", - "Microsoft.CPlat.Core.LinuxPatchExtension-1.21.100", - "Microsoft.CPlat.Core.LinuxPatchExtension-1.6.100", - "Microsoft.CPlat.Core.LinuxPatchExtension-1.6.99", - "Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc+def.123", - "Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc" - ] - - # valid versions - self.assertEqual(CoreUtility.sort_versions(unsorted_path_versions), expected_sorted_path_versions) - -if __name__ == '__main__': - unittest.main() - diff --git a/src/core/tests/Test_VersionComparator.py b/src/core/tests/Test_VersionComparator.py new file mode 100644 index 00000000..5c774b2c --- /dev/null +++ b/src/core/tests/Test_VersionComparator.py @@ -0,0 +1,77 @@ +# Copyright 2020 Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Requires Python 2.7+ + +import unittest + +from core.src.core_logic.VersionComparator import VersionComparator + + +class TestVersionComparator(unittest.TestCase): + + def setUp(self): + self.version_comparator = VersionComparator() + + def test_linux_version_comparator(self): + # Test extract version logic + self.assertEqual(self.version_comparator.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25"), "1.2.25") + self.assertEqual(self.version_comparator.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc"), "1.2.25") + self.assertEqual(self.version_comparator.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25+abc.123"), "1.2.25") + self.assertEqual(self.version_comparator.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc+def.123"), "1.2.25") + self.assertEqual(self.version_comparator.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.21.1001"), "1.21.1001") + self.assertEqual(self.version_comparator.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.6.100"), "1.6.100") + self.assertEqual(self.version_comparator.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.6.99"), "1.6.99") + self.assertEqual(self.version_comparator.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.6."), "") + self.assertEqual(self.version_comparator.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-a.b.c"), "") + + expected_extracted_version = "27.13.4" + test_extracted_v1 = self.version_comparator.extract_version("27.13.4~18.04.1") + test_extracted_v2 = self.version_comparator.extract_version("27.13.4+18.04.1") + test_extracted_v3 = self.version_comparator.extract_version("27.13.4-18.04.1") + + self.assertEqual(test_extracted_v1, expected_extracted_version) + self.assertEqual(test_extracted_v2, expected_extracted_version) + self.assertEqual(test_extracted_v3, expected_extracted_version) + + # Test compare versions logic + self.assertEqual(self.version_comparator.compare_version(test_extracted_v1, "27.13.4"), 0) # equal + self.assertEqual(self.version_comparator.compare_version(test_extracted_v2, "27.13.3"), 1) # greater + self.assertEqual(self.version_comparator.compare_version(test_extracted_v3, "27.13.5"), -1) # less + + # Test sort versions logic + unsorted_path_versions = [ + "Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc+def.123", + "Microsoft.CPlat.Core.LinuxPatchExtension-1.21.1001", + "Microsoft.CPlat.Core.LinuxPatchExtension-1.6.100", + "Microsoft.CPlat.Core.LinuxPatchExtension-1.6.99", + "Microsoft.CPlat.Core.LinuxPatchExtension-1.21.100", + "Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc", + ] + + expected_sorted_path_versions = [ + "Microsoft.CPlat.Core.LinuxPatchExtension-1.21.1001", + "Microsoft.CPlat.Core.LinuxPatchExtension-1.21.100", + "Microsoft.CPlat.Core.LinuxPatchExtension-1.6.100", + "Microsoft.CPlat.Core.LinuxPatchExtension-1.6.99", + "Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc+def.123", + "Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc" + ] + + # valid versions + self.assertEqual(self.version_comparator.sort_versions_desc_order(unsorted_path_versions), expected_sorted_path_versions) + +if __name__ == '__main__': + unittest.main() + diff --git a/src/extension/tests/Test_Utility.py b/src/extension/tests/Test_Utility.py index 4a318289..c5c1b627 100644 --- a/src/extension/tests/Test_Utility.py +++ b/src/extension/tests/Test_Utility.py @@ -106,6 +106,6 @@ def test_extract_sorted_versions(self): ] # valid versions - self.assertEqual(self.utility.sort_versions(unsorted_path_versions), expected_sorted_path_versions) + self.assertEqual(self.utility.sort_versions_desc_order(unsorted_path_versions), expected_sorted_path_versions)