From 6776987dd65bf97da0a8db9883ad016592dadb0a Mon Sep 17 00:00:00 2001 From: benank <8016617+benank@users.noreply.github.com> Date: Thu, 17 Mar 2022 11:41:16 -0700 Subject: [PATCH] Fix Zypper Package Manager Return Code 8 (#122) Fixes Zypper package manager return code 8. This error occurs when a package fails to install due to a conflict with another package. --- .../package_managers/ZypperPackageManager.py | 21 +++++++++ src/core/tests/Test_ZypperPackageManager.py | 44 +++++++++++++++++++ .../tests/library/LegacyEnvLayerExtensions.py | 27 ++++++++++++ 3 files changed, 92 insertions(+) diff --git a/src/core/src/package_managers/ZypperPackageManager.py b/src/core/src/package_managers/ZypperPackageManager.py index 3b738e2f..c9a286f1 100644 --- a/src/core/src/package_managers/ZypperPackageManager.py +++ b/src/core/src/package_managers/ZypperPackageManager.py @@ -58,6 +58,7 @@ def __init__(self, env_layer, execution_config, composite_logger, telemetry_writ self.zypper_exitcode_zypp_lib_exit_err = 4 self.zypper_exitcode_no_repos = 6 self.zypper_exitcode_zypp_locked = 7 + self.zypper_exitcode_zypp_exit_err_commit = 8 self.zypper_exitcode_reboot_required = 102 self.zypper_exitcode_zypper_updated = 103 self.zypper_exitcode_repos_skipped = 106 @@ -128,6 +129,15 @@ def invoke_package_manager(self, command): self.__refresh_repo_services() continue + if code == self.zypper_exitcode_zypp_exit_err_commit: + # Run command again with --replacefiles to fix file conflicts + self.composite_logger.log_warning("Warning: package conflict detected on command: {0}".format(str(command))) + modified_command = self.modify_upgrade_or_patch_command_to_replacefiles(command) + if modified_command is not None: + command = modified_command + self.composite_logger.log_debug("Retrying with modified command to replace files: {0}".format(str(command))) + continue + self.log_errors_on_invoke(command, out, code) error_msg = 'Unexpected return code (' + str(code) + ') from package manager on command: ' + command self.status_handler.add_error_to_status(error_msg, Constants.PatchOperationErrorCodes.PACKAGE_MANAGER_FAILURE) @@ -156,6 +166,17 @@ def invoke_package_manager(self, command): self.force_reboot = True return out + def modify_upgrade_or_patch_command_to_replacefiles(self, command): + """ Modifies a command to invoke_package_manager for update or patch to include a --replacefiles flag. + If it is a dry run or already has the flag, it returns None. Otherwise, returns the new command. """ + if "--dry-run" in command or "--replacefiles" in command: + return None + + if self.single_package_upgrade_cmd in command: + return command.replace(self.single_package_upgrade_cmd, self.single_package_upgrade_cmd + '--replacefiles ') + elif self.zypper_install_security_patches in command: + return command.replace(self.zypper_install_security_patches, self.zypper_install_security_patches + ' --replacefiles') + def log_errors_on_invoke(self, command, out, code): """Logs verbose error messages if there is an error on invoke_package_manager""" self.composite_logger.log('[ERROR] Package manager was invoked using: ' + command) diff --git a/src/core/tests/Test_ZypperPackageManager.py b/src/core/tests/Test_ZypperPackageManager.py index b7749f80..ee1054e3 100644 --- a/src/core/tests/Test_ZypperPackageManager.py +++ b/src/core/tests/Test_ZypperPackageManager.py @@ -561,6 +561,50 @@ def mock_run_command_output(cmd, no_output=False, chk_err=False): package_manager.env_layer.run_command_output = backup_mocked_method + def test_package_manager_exit_err_commit(self): + package_manager = self.container.get('package_manager') + self.runtime.status_handler.set_current_operation(Constants.INSTALLATION) + + # Test command modifications with --replacefiles + cmd_to_run = 'sudo zypper --non-interactive update samba-libs=4.15.4+git.327.37e0a40d45f-3.57.1' + replacefiles_cmd_to_run = 'sudo zypper --non-interactive update --replacefiles samba-libs=4.15.4+git.327.37e0a40d45f-3.57.1' + self.assertEqual(replacefiles_cmd_to_run, package_manager.modify_upgrade_or_patch_command_to_replacefiles(cmd_to_run)) + cmd_to_run += " --dry-run" + self.assertEqual(None, package_manager.modify_upgrade_or_patch_command_to_replacefiles(cmd_to_run)) + self.assertEqual(None, package_manager.modify_upgrade_or_patch_command_to_replacefiles(replacefiles_cmd_to_run)) + cmd_to_run = 'sudo zypper --non-interactive patch --category security' + replacefiles_cmd_to_run = 'sudo zypper --non-interactive patch --category security --replacefiles' + self.assertEqual(replacefiles_cmd_to_run, package_manager.modify_upgrade_or_patch_command_to_replacefiles(cmd_to_run)) + + # Wrap count in a mutable container to modify in mocked method to keep track of calls + counter = [0] + replacefiles_counter = [0] + backup_mocked_method = package_manager.env_layer.run_command_output + + def mock_run_command_output(cmd, no_output=False, chk_err=False): + # Only check for refresh services cmd + if cmd == 'sudo zypper --non-interactive update --replacefiles samba-libs=4.15.4+git.327.37e0a40d45f-3.57.1': + # After refreshing, allow it to succeed + replacefiles_counter[0] += 1 + self.runtime.set_legacy_test_type('HappyPath') + elif cmd == 'sudo zypper --non-interactive update samba-libs=4.15.4+git.327.37e0a40d45f-3.57.1': + counter[0] += 1 + return backup_mocked_method(cmd, no_output, chk_err) + + package_manager.env_layer.run_command_output = mock_run_command_output + + # AnotherSadPath uses return code 8 + self.runtime.set_legacy_test_type('AnotherSadPath') + + cmd_to_run = 'sudo zypper --non-interactive update samba-libs=4.15.4+git.327.37e0a40d45f-3.57.1' + package_manager.invoke_package_manager(cmd_to_run) + self.assertEqual(counter[0], 1) + self.assertEqual(replacefiles_counter[0], 1) + self.assertFalse(self.is_string_in_status_file('Unexpected return code (8) from package manager on command')) + + package_manager.env_layer.run_command_output = backup_mocked_method + + if __name__ == '__main__': unittest.main() diff --git a/src/core/tests/library/LegacyEnvLayerExtensions.py b/src/core/tests/library/LegacyEnvLayerExtensions.py index eb28fe3d..ff222d3f 100644 --- a/src/core/tests/library/LegacyEnvLayerExtensions.py +++ b/src/core/tests/library/LegacyEnvLayerExtensions.py @@ -240,6 +240,30 @@ def run_command_output(self, cmd, no_output=False, chk_err=True): output = "Retrieving repository 'SLE-Module-Basesystem15-SP3-Pool' metadata ................................................................[done]\n" + \ "Building repository 'SLE-Module-Basesystem15-SP3-Pool' cache .....................................................................[done]\n" + \ "All repositories have been refreshed." + elif cmd.find('sudo zypper --non-interactive update --replacefiles'): + code = 0 + output = "Refreshing service 'Advanced_Systems_Management_Module_x86_64'.\n" + \ + "Loading repository data...\n" + \ + "Reading installed packages...\n" + \ + "Resolving package dependencies...\n" + \ + "The following 5 items are locked and will not be changed by any action:\n" + \ + " Installed:\n" + \ + " auoms azsec-clamav azsec-monitor azure-security qualys-command-line-agent\n" + \ + "The following 3 packages are going to be upgraded:\n" + \ + " samba-client-libs samba-libs samba-libs-python3\n" + \ + "3 packages to upgrade.\n" + \ + "Overall download size: 6.0 MiB. Already cached: 0 B. No additional space will be used or freed after the operation.\n" + \ + "Continue? [y/n/...? shows all options] (y): y\n" + \ + "Retrieving package samba-client-libs-4.15.4+git.331.61fc89677dd-3.60.1.x86_64 (1/3), 5.2 MiB ( 21.0 MiB unpacked)\n" + \ + "Retrieving: samba-client-libs-4.15.4+git.331.61fc89677dd-3.60.1.x86_64.rpm .................................................[done]\n" + \ + "Retrieving package samba-libs-python3-4.15.4+git.331.61fc89677dd-3.60.1.x86_64 (2/3), 367.5 KiB (290.3 KiB unpacked)\n" + \ + "Retrieving: samba-libs-python3-4.15.4+git.331.61fc89677dd-3.60.1.x86_64.rpm ................................................[done]\n" + \ + "Retrieving package samba-libs-4.15.4+git.331.61fc89677dd-3.60.1.x86_64 (3/3), 441.7 KiB (544.9 KiB unpacked)\n" + \ + "Retrieving: samba-libs-4.15.4+git.331.61fc89677dd-3.60.1.x86_64.rpm ........................................................[done]\n" + \ + "Checking for file conflicts: ...............................................................................................[done]\n" + \ + "(1/3) Installing: samba-client-libs-4.15.4+git.331.61fc89677dd-3.60.1.x86_64 ...............................................[done]\n" + \ + "(2/3) Installing: samba-libs-python3-4.15.4+git.331.61fc89677dd-3.60.1.x86_64 ..............................................[done]\n" + \ + "(3/3) Installing: samba-libs-4.15.4+git.331.61fc89677dd-3.60.1.x86_64 ......................................................[done]" elif self.legacy_package_manager_name is Constants.YUM: if cmd.find("--security check-update") > -1: code = 100 @@ -579,6 +603,9 @@ def run_command_output(self, cmd, no_output=False, chk_err=True): if cmd.find('sudo zypper refresh') > -1: code = 6 output = 'Warning: There are no enabled repositories defined. | Use \'zypper addrepo\' or \'zypper modifyrepo\' commands to add or enable repositories.' + elif cmd.find('sudo zypper --non-interactive update samba-libs=4.15.4+git.327.37e0a40d45f-3.57.1') > -1: + code = 8 + output = '' elif self.legacy_test_type == 'ExceptionPath': code = -1 output = ''