From 93c8ceb89b7587a495e393822d4ecbbf4fe43093 Mon Sep 17 00:00:00 2001 From: johnfeng Date: Mon, 25 Nov 2024 00:00:17 -0800 Subject: [PATCH] add custom comparator to corelogic to compare ubuntu version --- src/core/src/bootstrap/CoreUtility.py | 78 +++++++++++++++++++ .../src/package_managers/UbuntuProClient.py | 11 ++- src/extension/src/ActionHandler.py | 6 +- src/extension/src/Utility.py | 19 +++-- 4 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 src/core/src/bootstrap/CoreUtility.py diff --git a/src/core/src/bootstrap/CoreUtility.py b/src/core/src/bootstrap/CoreUtility.py new file mode 100644 index 00000000..a19ba832 --- /dev/null +++ b/src/core/src/bootstrap/CoreUtility.py @@ -0,0 +1,78 @@ +# 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 re + + +class CoreUtility(object): + + @staticmethod + def compare_version(version_a, version_b): + """ 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) + + 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 + 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): + """ + Extract the version part from a given path. + Input: /var/lib/waagent/Microsoft.CPlat.Core.LinuxPatchExtension-1.2.5/config + Return: 1.2.5 + """ + match = re.search(r'([\d]+\.[\d]+\.[\d])', path) + return match.group(1) if match else "" + + @staticmethod + def sort_versions(paths): + """ + Sort paths based on version numbers extracted from paths. + Input: + ["Microsoft.CPlat.Core.LinuxPatchExtension-1.21.1001", + "Microsoft.CPlat.Core.LinuxPatchExtension-1.6.100", + "Microsoft.CPlat.Core.LinuxPatchExtension-1.21.100"] + Return: + ["Microsoft.CPlat.Core.LinuxPatchExtension-1.21.1001", + "Microsoft.CPlat.Core.LinuxPatchExtension-1.21.100", + "Microsoft.CPlat.Core.LinuxPatchExtension-1.6.100"] + """ + + def version_key(path): + version_numbers = CoreUtility.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) + + + diff --git a/src/core/src/package_managers/UbuntuProClient.py b/src/core/src/package_managers/UbuntuProClient.py index 0fe82346..97c4cb45 100644 --- a/src/core/src/package_managers/UbuntuProClient.py +++ b/src/core/src/package_managers/UbuntuProClient.py @@ -16,6 +16,8 @@ """This is the Ubuntu Pro Client implementation""" import json + +from core.src.bootstrap.CoreUtility import CoreUtility from core.src.bootstrap.Constants import Constants @@ -50,10 +52,15 @@ def is_pro_working(self): is_minimum_ubuntu_pro_version_installed = False try: from uaclient.api.u.pro.version.v1 import version - from distutils.version import LooseVersion # Importing this module here as there is conflict between "distutils.version" and "uaclient.api.u.pro.version.v1.version when 'LooseVersion' is called." version_result = version() ubuntu_pro_client_version = version_result.installed_version - is_minimum_ubuntu_pro_version_installed = LooseVersion(ubuntu_pro_client_version) >= LooseVersion(Constants.UbuntuProClientSettings.MINIMUM_CLIENT_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) + + # 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 + if ubuntu_pro_client_version is not None and is_minimum_ubuntu_pro_version_installed: is_ubuntu_pro_client_working = True self.is_ubuntu_pro_client_attached = self.log_ubuntu_pro_client_attached() diff --git a/src/extension/src/ActionHandler.py b/src/extension/src/ActionHandler.py index 9e1f99a3..08c1fb6b 100644 --- a/src/extension/src/ActionHandler.py +++ b/src/extension/src/ActionHandler.py @@ -19,7 +19,6 @@ import os import shutil import time -#from distutils.version import LooseVersion from extension.src.Constants import Constants from extension.src.EnableCommandHandler import EnableCommandHandler @@ -226,11 +225,10 @@ def update(self): # identify the version preceding current self.logger.log("Fetching the extension version preceding current from all available versions...") - # sort path based on version numbers + # use custom sort logic to sort path based on version numbers sorted_versions = Utility.sort_versions(paths_to_all_versions) - - #paths_to_all_versions.sort(reverse=True, key=LooseVersion) preceding_version_path = sorted_versions[1] + if preceding_version_path is None or preceding_version_path == "" or not os.path.exists(preceding_version_path): error_msg = "Could not find path where preceding extension version artifacts are stored. Hence, cannot copy the required artifacts to the latest version. "\ "[Preceding extension version path={0}]".format(str(preceding_version_path)) diff --git a/src/extension/src/Utility.py b/src/extension/src/Utility.py index d34cf4d9..f359acd8 100644 --- a/src/extension/src/Utility.py +++ b/src/extension/src/Utility.py @@ -73,18 +73,27 @@ def get_str_from_datetime(date): def extract_version(path): """ Extract the version part from a given path. - Example: /var/lib/waagent/Microsoft.CPlat.Core.LinuxPatchExtension-1.2.5/config -> 1.2.5 + Input: /var/lib/waagent/Microsoft.CPlat.Core.LinuxPatchExtension-1.2.5/config + Return: 1.2.5 """ - match = re.search(r'-([\d]+\.[\d]+\.[\d]+)', path) + match = re.search(r'([\d]+\.[\d]+\.[\d])', path) return match.group(1) if match else "" @staticmethod def sort_versions(paths): """ - Sort paths based on version numbers extracted from folder names. + Sort paths based on version numbers extracted from paths. + Input: + ["Microsoft.CPlat.Core.LinuxPatchExtension-1.21.1001", + "Microsoft.CPlat.Core.LinuxPatchExtension-1.6.100", + "Microsoft.CPlat.Core.LinuxPatchExtension-1.21.100"] + Return: + ["Microsoft.CPlat.Core.LinuxPatchExtension-1.21.1001", + "Microsoft.CPlat.Core.LinuxPatchExtension-1.21.100", + "Microsoft.CPlat.Core.LinuxPatchExtension-1.6.100"] """ def version_key(path): - version_str = Utility.extract_version(path) - return tuple(map(int, version_str.split('.'))) if version_str else (0,0,0) + version_numbers = Utility.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)