Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4ac96b2
add logic to skip security plugin for rhel8+ images
feng-j678 Sep 23, 2024
b904a1c
add log and ut for disable security plug for rhel8+
feng-j678 Sep 23, 2024
2dbcd79
Merge branch 'master' of https://github.com/Azure/LinuxPatchExtension
feng-j678 Sep 24, 2024
7baaa93
resolve master conflicts
feng-j678 Oct 7, 2024
53cbc0c
add raw string to interpret string literal
feng-j678 Oct 7, 2024
30f54a1
add raw string avoid escape sequence error
feng-j678 Oct 8, 2024
060b200
add helper comparator class
feng-j678 Nov 19, 2024
39d0ff4
Merge branch 'master' of https://github.com/Azure/LinuxPatchExtension…
feng-j678 Nov 19, 2024
865334d
modify sorted_vrsion to compare 3 digits
feng-j678 Nov 23, 2024
3269944
handle the disutil deprecation in test_actionhandler.py
feng-j678 Nov 25, 2024
93c8ceb
add custom comparator to corelogic to compare ubuntu version
feng-j678 Nov 25, 2024
643dd8a
add ut for coreutilty, and add regex to fix extract_version
feng-j678 Nov 25, 2024
97b2fe1
remove this strange file tatu
feng-j678 Nov 25, 2024
5ff57c6
refactor CoreUtility to VersionComparator
feng-j678 Dec 10, 2024
4aa238b
move version compare logic into VersionComparatorHandler file
feng-j678 Dec 10, 2024
875e505
revert changs in utilty.py
feng-j678 Dec 10, 2024
8c1b42e
remove import re
feng-j678 Dec 10, 2024
aade45c
update copyright year
feng-j678 Dec 10, 2024
e6bfecb
Merge branch 'master' of https://github.com/Azure/LinuxPatchExtension…
feng-j678 Dec 12, 2024
3dcc016
correc the typo in compare_version
feng-j678 Dec 12, 2024
c73dd2a
refactor extract_version and compare_version
feng-j678 Dec 12, 2024
2500380
remove unnecessary test in action handler
feng-j678 Dec 18, 2024
bb0906b
Merge branch 'master' of https://github.com/Azure/LinuxPatchExtension…
feng-j678 Dec 18, 2024
2d6affd
Merge branch 'fix/distutil_deprecation' of https://github.com/Azure/L…
feng-j678 Dec 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions src/core/src/core_logic/VersionComparator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Copyright 2024 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 VersionComparator(object):

def compare_version_nums(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) """

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
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))

def extract_version_nums(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"
"""
match = re.search(r'([\d]+\.[\d]+\.[\d]+)', path)
return match.group(1) if match else str()

def sort_versions_desc_order(self, paths):
# type (list[str]) -> list[str]
"""
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"]
"""
return sorted(paths, key=self.__version_key, reverse=True)

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_nums(path)
return tuple(map(int, version_numbers.split('.'))) if version_numbers else (0, 0, 0)

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(".")]


14 changes: 12 additions & 2 deletions src/core/src/package_managers/UbuntuProClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

"""This is the Ubuntu Pro Client implementation"""
import json

from core.src.core_logic.VersionComparator import VersionComparator
from core.src.bootstrap.Constants import Constants


Expand All @@ -27,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"""
Expand All @@ -50,10 +53,17 @@ 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 = self.version_comparator.extract_version_nums(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 = self.version_comparator.compare_version_nums(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()
Expand Down
77 changes: 77 additions & 0 deletions src/core/tests/Test_VersionComparator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Copyright 2024 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_nums("Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25"), "1.2.25")
self.assertEqual(self.version_comparator.extract_version_nums("Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc"), "1.2.25")
self.assertEqual(self.version_comparator.extract_version_nums("Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25+abc.123"), "1.2.25")
self.assertEqual(self.version_comparator.extract_version_nums("Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc+def.123"), "1.2.25")
self.assertEqual(self.version_comparator.extract_version_nums("Microsoft.CPlat.Core.LinuxPatchExtension-1.21.1001"), "1.21.1001")
self.assertEqual(self.version_comparator.extract_version_nums("Microsoft.CPlat.Core.LinuxPatchExtension-1.6.100"), "1.6.100")
self.assertEqual(self.version_comparator.extract_version_nums("Microsoft.CPlat.Core.LinuxPatchExtension-1.6.99"), "1.6.99")
self.assertEqual(self.version_comparator.extract_version_nums("Microsoft.CPlat.Core.LinuxPatchExtension-1.6."), "")
self.assertEqual(self.version_comparator.extract_version_nums("Microsoft.CPlat.Core.LinuxPatchExtension-a.b.c"), "")

expected_extracted_version = "27.13.4"
test_extracted_v1 = self.version_comparator.extract_version_nums("27.13.4~18.04.1")
test_extracted_v2 = self.version_comparator.extract_version_nums("27.13.4+18.04.1")
test_extracted_v3 = self.version_comparator.extract_version_nums("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_nums(test_extracted_v1, "27.13.4"), 0) # equal
self.assertEqual(self.version_comparator.compare_version_nums(test_extracted_v2, "27.13.3"), 1) # greater
self.assertEqual(self.version_comparator.compare_version_nums(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()

Check warning on line 76 in src/core/tests/Test_VersionComparator.py

View check run for this annotation

Codecov / codecov/patch

src/core/tests/Test_VersionComparator.py#L76

Added line #L76 was not covered by tests

13 changes: 10 additions & 3 deletions src/extension/src/ActionHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@
import os
import shutil
import time
from distutils.version import LooseVersion

from extension.src.Constants import Constants
from extension.src.EnableCommandHandler import EnableCommandHandler
from extension.src.InstallCommandHandler import InstallCommandHandler
from extension.src.Utility import Utility
from extension.src.VersionComparatorHandler import VersionComparatorHandler
from extension.src.local_loggers.StdOutFileMirror import StdOutFileMirror


Expand All @@ -48,6 +49,7 @@ def __init__(self, logger, env_layer, telemetry_writer, utility, runtime_context
self.file_logger = None
self.operation_id_substitute_for_all_actions_in_telemetry = str((datetime.datetime.utcnow()).strftime(Constants.UTC_DATETIME_FORMAT))
self.seq_no = self.ext_config_settings_handler.get_seq_no_from_env_var()
self.version_comparator_handler = VersionComparatorHandler()

def determine_operation(self, command):
switcher = {
Expand Down Expand Up @@ -224,8 +226,13 @@ def update(self):

# identify the version preceding current
self.logger.log("Fetching the extension version preceding current from all available versions...")
paths_to_all_versions.sort(reverse=True, key=LooseVersion)
preceding_version_path = paths_to_all_versions[1]

# use custom sort logic to sort path based on version numbers
sorted_versions = self.version_comparator_handler.sort_versions_desc_order(paths_to_all_versions)
self.logger.log_debug("List of extension versions in descending order: [SortedVersion={0}]".format(sorted_versions))

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))
Expand Down
6 changes: 3 additions & 3 deletions src/extension/src/EnvLayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,12 @@ def is_tty_required_in_sudoers(self):
if self.require_tty_setting not in str(setting):
continue

if re.match('.*!' + self.require_tty_setting, setting):
setting_substr_without_requiretty = re.search('(.*)!' + self.require_tty_setting, setting).group(1).strip()
if re.match(r'.*!' + self.require_tty_setting, setting):
setting_substr_without_requiretty = re.search(r'(.*)!' + self.require_tty_setting, setting).group(1).strip()
if self.is_tty_defaults_set(setting_substr_without_requiretty):
tty_set_to_required = False
else:
setting_substr_without_requiretty = re.search('(.*)' + self.require_tty_setting, setting).group(1).strip()
setting_substr_without_requiretty = re.search(r'(.*)' + self.require_tty_setting, setting).group(1).strip()
if self.is_tty_defaults_set(setting_substr_without_requiretty):
tty_set_to_required = True

Expand Down
54 changes: 54 additions & 0 deletions src/extension/src/VersionComparatorHandler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Copyright 2024 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 VersionComparatorHandler(object):

def extract_version_nums(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"
"""
match = re.search(r'([\d]+\.[\d]+\.[\d]+)', path)
return match.group(1) if match else ""

def sort_versions_desc_order(self, paths):
# type (list[str]) -> list[str]
"""
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"]
"""
return sorted(paths, key=self.__version_key,reverse=True)

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_nums(path)
return tuple(map(int, version_numbers.split('.'))) if version_numbers else (0, 0, 0)
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def __get_seq_no_from_config_settings(self):
for subdir, dirs, files in os.walk(self.config_folder):
for file in files:
try:
if re.match('^\d+' + self.file_ext + '$', file):
if re.match(r'^\d+' + self.file_ext + '$', file):
cur_seq_no = int(os.path.basename(file).split('.')[0])
file_modified_time = os.path.getmtime(os.path.join(self.config_folder, file))
self.logger.log("Sequence number being considered and the corresponding file modified time. [Sequence No={0}] [Modified={1}]".format(str(cur_seq_no), str(file_modified_time)))
Expand Down
59 changes: 59 additions & 0 deletions src/extension/tests/Test_VersionComparatorHandler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Copyright 2024 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 extension.src.VersionComparatorHandler import VersionComparatorHandler


class TestVersionComparatorHandler(unittest.TestCase):

def setUp(self):
self.version_comparator_handler = VersionComparatorHandler()

def test_linux_version_comparator_handler(self):
# Test extract version logic
self.assertEqual(self.version_comparator_handler.extract_version_nums("Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25"), "1.2.25")
self.assertEqual(self.version_comparator_handler.extract_version_nums("Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc"), "1.2.25")
self.assertEqual(self.version_comparator_handler.extract_version_nums("Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25+abc.123"), "1.2.25")
self.assertEqual(self.version_comparator_handler.extract_version_nums("Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc+def.123"), "1.2.25")
self.assertEqual(self.version_comparator_handler.extract_version_nums("Microsoft.CPlat.Core.LinuxPatchExtension-1.21.1001"), "1.21.1001")
self.assertEqual(self.version_comparator_handler.extract_version_nums("Microsoft.CPlat.Core.LinuxPatchExtension-1.6.100"), "1.6.100")
self.assertEqual(self.version_comparator_handler.extract_version_nums("Microsoft.CPlat.Core.LinuxPatchExtension-1.6.99"), "1.6.99")
self.assertEqual(self.version_comparator_handler.extract_version_nums("Microsoft.CPlat.Core.LinuxPatchExtension-1.6."), "")
self.assertEqual(self.version_comparator_handler.extract_version_nums("Microsoft.CPlat.Core.LinuxPatchExtension-a.b.c"), "")

# 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_handler.sort_versions_desc_order(unsorted_path_versions), expected_sorted_path_versions)

3 changes: 1 addition & 2 deletions src/tools/Package-All.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@
# imports in VERY_FIRST_IMPORTS, order should be kept
VERY_FIRST_IMPORTS = [
'from __future__ import print_function\n',
'from abc import ABCMeta, abstractmethod\n',
'from distutils.version import LooseVersion\n']
'from abc import ABCMeta, abstractmethod\n']
GLOBAL_IMPORTS = set()


Expand Down
Loading