Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 21 additions & 0 deletions src/core/src/package_managers/ZypperPackageManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
44 changes: 44 additions & 0 deletions src/core/tests/Test_ZypperPackageManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
27 changes: 27 additions & 0 deletions src/core/tests/library/LegacyEnvLayerExtensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = ''
Expand Down