diff --git a/src/core/src/package_managers/Dnf5PackageManager.py b/src/core/src/package_managers/Dnf5PackageManager.py index 80be8ca2..fb9b0896 100644 --- a/src/core/src/package_managers/Dnf5PackageManager.py +++ b/src/core/src/package_managers/Dnf5PackageManager.py @@ -59,7 +59,8 @@ def __init__(self, env_layer, execution_config, composite_logger, telemetry_writ # auto OS updates self.current_auto_os_update_service = None - self.os_patch_configuration_settings_file_path = '' + self.os_patch_default_configuration_settings_file_path = '' + self.os_patch_override_configuration_settings_file_path = '' self.auto_update_service_enabled = False self.auto_update_config_pattern_match_text = "" self.download_updates_identifier_text = "" @@ -354,13 +355,16 @@ def __init_constants_for_dnf5_automatic(self): self.dnf5_automatic_enable_on_reboot_check_cmd = 'systemctl is-enabled dnf5-automatic.timer' self.dnf5_automatic_disable_on_reboot_cmd = 'systemctl disable --now dnf5-automatic.timer' self.dnf5_automatic_enable_on_reboot_cmd = 'systemctl enable --now dnf5-automatic.timer' - self.dnf5_automatic_configuration_file_path = '/etc/dnf/automatic.conf' + self.dnf5_automatic_default_configuration_file_path = '/usr/share/dnf5/dnf5-plugins/automatic.conf' + self.dnf5_automatic_override_configuration_file_path = '/etc/dnf/automatic.conf' self.dnf5_automatic_config_pattern_match_text = ' = (no|yes)' self.dnf5_automatic_download_updates_identifier_text = "download_updates" self.dnf5_automatic_apply_updates_identifier_text = "apply_updates" self.dnf5_automatic_enable_on_reboot_identifier_text = "enable_on_reboot" self.dnf5_automatic_installation_state_identifier_text = "installation_state" self.dnf5_auto_os_update_service = "dnf5-automatic" + self.dnf5_default_auto_os_config_backup_key = "default-dnf5-automatic" + self.dnf5_override_auto_os_config_backup_key = "override-dnf5-automatic" def get_current_auto_os_patch_state(self): """ Gets the current auto OS update patch state on the machine """ @@ -395,7 +399,8 @@ def __get_current_auto_os_patch_state_for_dnf5_automatic(self): return Constants.AutomaticOSPatchStates.UNKNOWN def __init_auto_update_for_dnf5_automatic(self): - self.os_patch_configuration_settings_file_path = self.dnf5_automatic_configuration_file_path + self.os_patch_default_configuration_settings_file_path = self.dnf5_automatic_default_configuration_file_path + self.os_patch_override_configuration_settings_file_path = self.dnf5_automatic_override_configuration_file_path self.auto_update_config_pattern_match_text = self.dnf5_automatic_config_pattern_match_text self.download_updates_identifier_text = self.dnf5_automatic_download_updates_identifier_text self.apply_updates_identifier_text = self.dnf5_automatic_apply_updates_identifier_text @@ -405,6 +410,8 @@ def __init_auto_update_for_dnf5_automatic(self): self.enable_on_reboot_cmd = self.dnf5_automatic_enable_on_reboot_cmd self.install_check_cmd = self.dnf5_automatic_install_check_cmd self.current_auto_os_update_service = self.dnf5_auto_os_update_service + self.os_patch_default_configuration_backup_key = self.dnf5_default_auto_os_config_backup_key + self.os_patch_override_configuration_backup_key = self.dnf5_override_auto_os_config_backup_key def __get_current_auto_os_updates_setting_on_machine(self): """Gets all auto-OS update settings for dnf5-automatic (DNF5) via config + timer state.""" @@ -420,7 +427,7 @@ def __get_current_auto_os_updates_setting_on_machine(self): code, service_output = self.env_layer.run_command_output(self.dnf5_automatic_configuration_service, False, False) exec_start_line = "" - #Only print ExecStart details + # Only print ExecStart details for line in service_output.splitlines(): if line.strip().startswith("ExecStart"): exec_start_line = line.strip() @@ -431,17 +438,10 @@ def __get_current_auto_os_updates_setting_on_machine(self): enable_on_reboot_value = self.is_service_set_to_enable_on_reboot(self.enable_on_reboot_check_cmd) self.composite_logger.log_verbose("[DNF5] Checking if auto updates are currently enabled...") - image_default_patch_configuration = self.env_layer.file_system.read_with_retry(self.os_patch_configuration_settings_file_path, raise_if_not_found=False) - if image_default_patch_configuration is not None: - settings = image_default_patch_configuration.strip().split('\n') - for setting in settings: - match = re.search(self.download_updates_identifier_text + self.auto_update_config_pattern_match_text, str(setting)) - if match is not None: - download_updates_value = match.group(1) - - match = re.search(self.apply_updates_identifier_text + self.auto_update_config_pattern_match_text, str(setting)) - if match is not None: - apply_updates_value = match.group(1) + default_download_updates_value, default_apply_updates_value, override_download_updates_value, override_apply_updates_value = self.__get_default_and_override_config_values() + + download_updates_value = (override_download_updates_value if override_download_updates_value != "" else default_download_updates_value) + apply_updates_value = (override_apply_updates_value if override_apply_updates_value != "" else default_apply_updates_value) if download_updates_value == "": self.composite_logger.log_verbose("[DNF5] Machine did not have any value set for [Setting={0}]".format(str(self.download_updates_identifier_text))) @@ -495,7 +495,6 @@ def __disable_auto_os_update_for_dnf5_automatic(self): """ Disables auto OS updates, using dnf5-automatic service, and logs the default settings the machine comes with """ self.composite_logger.log_verbose("[DNF5] Disabling auto OS updates using dnf5-automatic") self.__init_auto_update_for_dnf5_automatic() - self.backup_image_default_patch_configuration_if_not_exists() if not self.is_auto_update_service_installed(self.dnf5_automatic_install_check_cmd): @@ -503,11 +502,22 @@ def __disable_auto_os_update_for_dnf5_automatic(self): return self.composite_logger.log_verbose("[DNF5] Preemptively disabling auto OS updates using dnf5-automatic") + # Check if override.conf file exists, if not copy/create from default config location + self.__ensure_override_configuration_exists() self.update_os_patch_configuration_sub_setting(self.download_updates_identifier_text, "no", self.dnf5_automatic_config_pattern_match_text) self.update_os_patch_configuration_sub_setting(self.apply_updates_identifier_text, "no", self.dnf5_automatic_config_pattern_match_text) self.disable_auto_update_on_reboot(self.dnf5_automatic_disable_on_reboot_cmd) self.composite_logger.log_debug("[DNF5] Successfully disabled auto OS updates using dnf5-automatic") + def __ensure_override_configuration_exists(self): + override_config_file = self.env_layer.file_system.read_with_retry(self.os_patch_override_configuration_settings_file_path, raise_if_not_found=False) + if override_config_file is not None: + return + + self.composite_logger.log_debug("[DNF5] Override configuration file does not exist. Creating it from default configuration.") + default_config = self.env_layer.file_system.read_with_retry(self.os_patch_default_configuration_settings_file_path) + self.env_layer.file_system.write_with_retry(self.os_patch_override_configuration_settings_file_path, default_config, mode='w+') + def disable_auto_update_on_reboot(self, command): """ Disables auto update on reboot by executing systemctl command """ self.composite_logger.log_verbose("[DNF5] Disabling auto update on reboot. [Command={0}] ".format(command)) @@ -526,68 +536,124 @@ def backup_image_default_patch_configuration_if_not_exists(self): Log the default system settings a VM comes with, any subsequent updates will not be recorded""" """ JSON format for backup file: { - "dnf5-automatic": { - "apply_updates": "yes/no/empty string", - "download_updates": "yes/no/empty string", - "enable_on_reboot": true/false, - "installation_state": true/false + "default-dnf5-automatic": { + "apply_updates": "yes/no/empty string", + "download_updates": "yes/no/empty string", + "enable_on_reboot": true/false, + "installation_state": true/false + }, + "override-dnf5-automatic": { + "apply_updates": "yes/no/empty string", + "download_updates": "yes/no/empty string", + "enable_on_reboot": true/false, + "installation_state": true/false } } """ try: self.composite_logger.log_verbose("[DNF5] Ensuring there is a backup of the default patch state for [AutoOSUpdateService={0}]".format(str(self.current_auto_os_update_service))) - image_default_patch_configuration_backup = self.__get_image_default_patch_configuration_backup() # verify if existing backup is valid if not, write to backup is_backup_valid = self.is_image_default_patch_configuration_backup_valid(image_default_patch_configuration_backup) if is_backup_valid: - self.composite_logger.log_debug("[DNF5] Since extension has a valid backup, no need to log the current settings again. ""[Default Auto OS update settings={0}] [File path={1}]".format(str(image_default_patch_configuration_backup),self.image_default_patch_configuration_backup_path)) + self.composite_logger.log_debug("[DNF5] Since extension has a valid backup, no need to log the current settings again. [Default Auto OS update settings={0}] [File path={1}]".format(str(image_default_patch_configuration_backup), self.image_default_patch_configuration_backup_path)) else: self.composite_logger.log_debug("[DNF5] Since the backup is invalid, will add a new backup with the current auto OS update settings") self.composite_logger.log_verbose("[DNF5] Fetching current auto OS update settings for [AutoOSUpdateService={0}]".format(str(self.current_auto_os_update_service))) - is_service_installed, enable_on_reboot_value, download_updates_value, apply_updates_value = self.__get_current_auto_os_updates_setting_on_machine() + is_service_installed, enable_on_reboot_value, _, _ = self.__get_current_auto_os_updates_setting_on_machine() + default_download_updates_value, default_apply_updates_value, override_download_updates_value, override_apply_updates_value = self.__get_default_and_override_config_values() backup_image_default_patch_configuration_json_to_add = { - self.current_auto_os_update_service: { - self.download_updates_identifier_text: download_updates_value, - self.apply_updates_identifier_text: apply_updates_value, + self.os_patch_default_configuration_backup_key: { + self.download_updates_identifier_text: default_download_updates_value, + self.apply_updates_identifier_text: default_apply_updates_value, + self.enable_on_reboot_identifier_text: enable_on_reboot_value, + self.installation_state_identifier_text: is_service_installed + }, + self.os_patch_override_configuration_backup_key: { + self.download_updates_identifier_text: override_download_updates_value, + self.apply_updates_identifier_text: override_apply_updates_value, self.enable_on_reboot_identifier_text: enable_on_reboot_value, self.installation_state_identifier_text: is_service_installed } } image_default_patch_configuration_backup.update(backup_image_default_patch_configuration_json_to_add) - self.composite_logger.log_debug("[DNF5] Logging default system configuration settings for auto OS updates. [Settings={0}] [Log file path={1}]" - .format(str(image_default_patch_configuration_backup),self.image_default_patch_configuration_backup_path)) - self.env_layer.file_system.write_with_retry(self.image_default_patch_configuration_backup_path,'{0}'.format(json.dumps(image_default_patch_configuration_backup)),mode='w+') + .format(str(image_default_patch_configuration_backup), self.image_default_patch_configuration_backup_path)) + self.env_layer.file_system.write_with_retry(self.image_default_patch_configuration_backup_path, '{0}'.format(json.dumps(image_default_patch_configuration_backup)), mode='w+') except Exception as error: self.composite_logger.log_error("[DNF5] Exception during fetching and logging default auto update settings on the machine. [Exception={0}]".format(repr(error))) self.status_handler.add_error_to_status("[DNF5] Exception during fetching and logging default auto update settings on the machine. [Exception={0}]".format(repr(error)), Constants.PatchOperationErrorCodes.DEFAULT_ERROR) raise + def __get_default_and_override_config_values(self): + self.composite_logger.log_verbose("[DNF5] Reading default configuration file. [Path={0}]".format(self.os_patch_default_configuration_settings_file_path)) + default_download_updates_value, default_apply_updates_value = self.__get_config_values(self.os_patch_default_configuration_settings_file_path) + self.composite_logger.log_verbose("[DNF5] Default configuration values found.[download_updates={0}][apply_updates={1}]".format(default_download_updates_value, default_apply_updates_value)) + + self.composite_logger.log_verbose("[DNF5] Reading override configuration file. [Path={0}]".format(self.os_patch_override_configuration_settings_file_path)) + override_download_updates_value, override_apply_updates_value = self.__get_config_values(self.os_patch_override_configuration_settings_file_path) + self.composite_logger.log_verbose("[DNF5] Override configuration values found.[download_updates={0}][apply_updates={1}]".format(override_download_updates_value, override_apply_updates_value)) + + return default_download_updates_value, default_apply_updates_value, override_download_updates_value, override_apply_updates_value + + def __get_config_values(self, config_file_path): + download_updates_value = "" + apply_updates_value = "" + + self.composite_logger.log_verbose("[DNF5] Reading config file. [Path={0}]".format(config_file_path)) + config = self.env_layer.file_system.read_with_retry(config_file_path, raise_if_not_found=False) + if config is None: + self.composite_logger.log_verbose("[DNF5] Config file not found. [Path={0}]".format(config_file_path)) + return download_updates_value, apply_updates_value + + settings = config.strip().split('\n') + + for setting in settings: + match = re.search(self.download_updates_identifier_text + self.auto_update_config_pattern_match_text, str(setting)) + if match is not None: + download_updates_value = match.group(1) + self.composite_logger.log_verbose("[DNF5] Found download_updates setting.[Value={0}]".format(download_updates_value)) + + match = re.search(self.apply_updates_identifier_text + self.auto_update_config_pattern_match_text, str(setting)) + if match is not None: + apply_updates_value = match.group(1) + self.composite_logger.log_verbose("[DNF5] Found apply_updates setting.[Value={0}]".format(apply_updates_value)) + + self.composite_logger.log_verbose("[DNF5] Finished parsing configuration values.[Path={0}][DownloadUpdates={1}][ApplyUpdates={2}]".format(config_file_path, download_updates_value, apply_updates_value)) + return download_updates_value, apply_updates_value + def is_image_default_patch_configuration_backup_valid(self, image_default_patch_configuration_backup): """ Verifies if default auto update configurations, for a service under consideration, are saved in backup """ return self.is_backup_valid_for_dnf5_automatic(image_default_patch_configuration_backup) def is_backup_valid_for_dnf5_automatic(self, image_default_patch_configuration_backup): - if self.dnf5_auto_os_update_service in image_default_patch_configuration_backup \ - and self.dnf5_automatic_download_updates_identifier_text in image_default_patch_configuration_backup[self.dnf5_auto_os_update_service] \ - and self.dnf5_automatic_apply_updates_identifier_text in image_default_patch_configuration_backup[self.dnf5_auto_os_update_service] \ - and self.dnf5_automatic_enable_on_reboot_identifier_text in image_default_patch_configuration_backup[self.dnf5_auto_os_update_service] \ - and self.dnf5_automatic_installation_state_identifier_text in image_default_patch_configuration_backup[self.dnf5_auto_os_update_service]: - self.composite_logger.log_debug("[DNF5] Extension has a valid backup for default dnf5-automatic configuration settings") + default_backup_valid = self.__is_backup_valid(image_default_patch_configuration_backup, self.os_patch_default_configuration_backup_key) + override_backup_valid = self.__is_backup_valid(image_default_patch_configuration_backup, self.os_patch_override_configuration_backup_key) + + if default_backup_valid and override_backup_valid: + self.composite_logger.log_debug("[DNF5] Extension has a valid backup for default and override dnf5-automatic configuration settings") return True - else: - self.composite_logger.log_debug("[DNF5] Extension does not have a valid backup for default dnf5-automatic configuration settings") - return False - def update_os_patch_configuration_sub_setting(self, patch_configuration_sub_setting, value="no",config_pattern_match_text=""): + self.composite_logger.log_debug("[DNF5] Extension does not have a valid backup for default and override dnf5-automatic configuration settings") + return False + + def __is_backup_valid(self, image_default_patch_configuration_backup, backup_key): + return (backup_key in image_default_patch_configuration_backup + and self.dnf5_automatic_download_updates_identifier_text in image_default_patch_configuration_backup[backup_key] + and self.dnf5_automatic_apply_updates_identifier_text in image_default_patch_configuration_backup[backup_key] + and self.dnf5_automatic_enable_on_reboot_identifier_text in image_default_patch_configuration_backup[backup_key] + and self.dnf5_automatic_installation_state_identifier_text in image_default_patch_configuration_backup[backup_key]) + + def update_os_patch_configuration_sub_setting(self, patch_configuration_sub_setting, value="no", config_pattern_match_text="", config_file_path=None): try: # note: adding space between the patch_configuration_sub_setting and value since, we will have to do that if we have to add a patch_configuration_sub_setting that did not exist before - self.composite_logger.log_debug("[DNF5] Updating system configuration settings for auto OS updates. [Patch Configuration Sub Setting={0}] [Value={1}]".format( - str(patch_configuration_sub_setting), value)) - os_patch_configuration_settings = self.env_layer.file_system.read_with_retry(self.os_patch_configuration_settings_file_path) + if config_file_path is None: + config_file_path = self.os_patch_override_configuration_settings_file_path + self.composite_logger.log_debug("[DNF5] Updating system configuration settings for auto OS updates. [Patch Configuration Sub Setting={0}] [Value={1}]" + .format(str(patch_configuration_sub_setting), value)) + os_patch_configuration_settings = self.env_layer.file_system.read_with_retry(config_file_path) patch_configuration_sub_setting_to_update = patch_configuration_sub_setting + ' = ' + value patch_configuration_sub_setting_found_in_file = False updated_patch_configuration_sub_setting = "" @@ -604,10 +670,9 @@ def update_os_patch_configuration_sub_setting(self, patch_configuration_sub_sett if not patch_configuration_sub_setting_found_in_file: updated_patch_configuration_sub_setting += patch_configuration_sub_setting_to_update + "\n" - self.env_layer.file_system.write_with_retry(self.os_patch_configuration_settings_file_path,'{0}'.format(updated_patch_configuration_sub_setting.lstrip()),mode='w+') + self.env_layer.file_system.write_with_retry(config_file_path,'{0}'.format(updated_patch_configuration_sub_setting.lstrip()), mode='w+') except Exception as error: - error_msg = "[DNF5] Error occurred while updating system configuration settings for auto OS updates. [Patch Configuration={0}] [Error={1}]".format( - str(patch_configuration_sub_setting), repr(error)) + error_msg = "[DNF5] Error occurred while updating system configuration settings for auto OS updates. [Patch Configuration={0}] [Error={1}]".format(str(patch_configuration_sub_setting), repr(error)) self.composite_logger.log_error(error_msg) self.status_handler.add_error_to_status(error_msg, Constants.PatchOperationErrorCodes.DEFAULT_ERROR) raise @@ -630,25 +695,84 @@ def revert_auto_os_update_to_system_default_for_dnf5_automatic(self): self.composite_logger.log_debug("[DNF5] Machine default auto OS update service is not installed on the VM and hence no config to revert. [Service={0}]".format(str(self.current_auto_os_update_service))) return - self.composite_logger.log_verbose("[DNF5] Logging current configuration settings for auto OS updates [Service={0}][Is_Service_Installed={1}][Machine_default_update_enable_on_reboot={2}]".format( - str(self.current_auto_os_update_service), str(is_service_installed), str(enable_on_reboot_value))) + default_download_updates_value, default_apply_updates_value, override_download_updates_value, override_apply_updates_value = self.__get_default_and_override_config_values() + self.composite_logger.log_verbose("[DNF5] Logging current configuration settings for auto OS updates [Service={0}][Is_Service_Installed={1}][Machine_default_update_enable_on_reboot={2}][Effective_download_updates={3}]" + "[Effective_apply_updates={4}][Default_download_updates={5}][Default_apply_updates={6}][Override_download_updates={7}][Override_apply_updates={8}]" + .format(str(self.current_auto_os_update_service), str(is_service_installed), str(enable_on_reboot_value), str(download_updates_value), str(apply_updates_value), + str(default_download_updates_value), str(default_apply_updates_value), str(override_download_updates_value), str(override_apply_updates_value))) image_default_patch_configuration_backup = self.__get_image_default_patch_configuration_backup() self.composite_logger.log_verbose("[DNF5] Logging system default configuration settings for auto OS updates. [Settings={0}]".format(str(image_default_patch_configuration_backup))) is_backup_valid = self.is_image_default_patch_configuration_backup_valid(image_default_patch_configuration_backup) if is_backup_valid: - download_updates_value_from_backup = image_default_patch_configuration_backup[self.current_auto_os_update_service][self.download_updates_identifier_text] - apply_updates_value_from_backup = image_default_patch_configuration_backup[self.current_auto_os_update_service][self.apply_updates_identifier_text] - enable_on_reboot_value_from_backup = image_default_patch_configuration_backup[self.current_auto_os_update_service][self.enable_on_reboot_identifier_text] - - self.update_os_patch_configuration_sub_setting(self.download_updates_identifier_text, download_updates_value_from_backup, self.auto_update_config_pattern_match_text) - self.update_os_patch_configuration_sub_setting(self.apply_updates_identifier_text, apply_updates_value_from_backup, self.auto_update_config_pattern_match_text) - if str(enable_on_reboot_value_from_backup).lower() == 'true': - self.enable_auto_update_on_reboot() + default_backup = image_default_patch_configuration_backup[self.os_patch_default_configuration_backup_key] + override_backup = image_default_patch_configuration_backup[self.os_patch_override_configuration_backup_key] + + self.__restore_default_configuration_from_backup(default_backup) + self.__restore_override_configuration_from_backup(override_backup) + self.__restore_enable_on_reboot_state_from_backup(default_backup) else: self.composite_logger.log_debug("[DNF5] Since the backup is invalid or does not exist for current service, we won't be able to revert auto OS patch settings to their system default value. [Service={0}]".format(str(self.current_auto_os_update_service))) + def __remove_override_configuration_if_exists(self): + """Removes dnf5-automatic override configuration file if it exists. Missing override file is valid by design, so this method must not throw + when the file is absent.""" + override_config_file = self.env_layer.file_system.read_with_retry(self.os_patch_override_configuration_settings_file_path, raise_if_not_found=False) + + if override_config_file is None: + self.composite_logger.log_debug("[DNF5] Override configuration file does not exist. Nothing to remove. [Path={0}]".format(self.os_patch_override_configuration_settings_file_path)) + return + + self.composite_logger.log_debug("[DNF5] Removing override configuration file to restore machine default.[Path={0}]".format(self.os_patch_override_configuration_settings_file_path)) + command = "rm -f {0}".format(self.os_patch_override_configuration_settings_file_path) + code, out = self.env_layer.run_command_output(command, False, False) + + if code != 0: + error_msg = "[DNF5] Error removing override configuration file. [Command={0}][Code={1}][Output={2}]".format(command, str(code), out) + self.composite_logger.log_error(error_msg) + self.status_handler.add_error_to_status(error_msg, Constants.PatchOperationErrorCodes.OPERATION_FAILED) + raise Exception(error_msg, "[{0}]".format(Constants.ERROR_ADDED_TO_STATUS)) + + self.composite_logger.log_debug("[DNF5] Removed override configuration file. [Command={0}][Code={1}][Output={2}]".format(command, str(code), out)) + + def __restore_default_configuration_from_backup(self, default_backup): + """Restore default dnf5-automatic configuration to its backed up state.""" + default_download_updates = default_backup[self.download_updates_identifier_text] + default_apply_updates = default_backup[self.apply_updates_identifier_text] + + self.composite_logger.log_debug("[DNF5] Restoring default dnf5-automatic configuration values from backup.[Path={0}][download_updates={1}][apply_updates={2}]" + .format(self.os_patch_default_configuration_settings_file_path, str(default_download_updates), str(default_apply_updates))) + + self.update_os_patch_configuration_sub_setting(self.download_updates_identifier_text, default_download_updates, self.auto_update_config_pattern_match_text, self.os_patch_default_configuration_settings_file_path) + self.update_os_patch_configuration_sub_setting(self.apply_updates_identifier_text, default_apply_updates, self.auto_update_config_pattern_match_text, self.os_patch_default_configuration_settings_file_path) + + def __restore_override_configuration_from_backup(self, override_backup): + """Restore override dnf5-automatic configuration to its backed up state.""" + override_download_updates = override_backup[self.download_updates_identifier_text] + override_apply_updates = override_backup[self.apply_updates_identifier_text] + + # Empty values indicate override file did not exist before onboarding. + if override_download_updates == "" and override_apply_updates == "": + self.composite_logger.log_debug("[DNF5] Override dnf5-automatic configuration did not exist before onboarding. Removing override configuration file if it exists.") + self.__remove_override_configuration_if_exists() + return + + self.composite_logger.log_debug("[DNF5] Restoring override dnf5-automatic configuration values from backup. [Path={0}][download_updates={1}][apply_updates={2}]".format( + self.os_patch_override_configuration_settings_file_path, str(override_download_updates), str(override_apply_updates))) + + self.__ensure_override_configuration_exists() + + self.update_os_patch_configuration_sub_setting(self.download_updates_identifier_text, override_download_updates, self.auto_update_config_pattern_match_text, self.os_patch_override_configuration_settings_file_path) + self.update_os_patch_configuration_sub_setting(self.apply_updates_identifier_text, override_apply_updates, self.auto_update_config_pattern_match_text, self.os_patch_override_configuration_settings_file_path) + + def __restore_enable_on_reboot_state_from_backup(self, default_backup): + enable_on_reboot_value = default_backup[self.enable_on_reboot_identifier_text] + + if str(enable_on_reboot_value).lower() == 'true': + self.composite_logger.log_debug("[DNF5] Restoring dnf5-automatic timer to enabled state from backup.") + self.enable_auto_update_on_reboot() + def enable_auto_update_on_reboot(self): """ Enables machine default auto update on reboot """ # type () -> None @@ -662,7 +786,7 @@ def enable_auto_update_on_reboot(self): self.status_handler.add_error_to_status(error_msg, Constants.PatchOperationErrorCodes.OPERATION_FAILED) raise Exception(error_msg, "[{0}]".format(Constants.ERROR_ADDED_TO_STATUS)) else: - self.composite_logger.log_debug("[DNF5] Enabled auto update on reboot. [Command={0}][Code={1}][Output={2}]".format(command, str(code),out)) + self.composite_logger.log_debug("[DNF5] Enabled auto update on reboot. [Command={0}][Code={1}][Output={2}]".format(command, str(code), out)) def __get_image_default_patch_configuration_backup(self): """ Get image_default_patch_configuration_backup file""" diff --git a/src/core/tests/Test_Dnf5PackageManager.py b/src/core/tests/Test_Dnf5PackageManager.py index 2a66eb9c..a03843fd 100644 --- a/src/core/tests/Test_Dnf5PackageManager.py +++ b/src/core/tests/Test_Dnf5PackageManager.py @@ -42,7 +42,7 @@ def mock_write_with_retry_raise_exception(self, file_path_or_handle, data, mode= def mock_run_command_output_check_update(self, cmd, no_output=False, chk_err=True): if "check-update" in cmd: return 0, "" - return None + return 0, "" def mock_run_command_output_no_reboot(self, cmd, no_output=False, chk_err=True): if "needs-restarting" in cmd: @@ -50,9 +50,12 @@ def mock_run_command_output_no_reboot(self, cmd, no_output=False, chk_err=True): "Reboot should not be necessary.\n") return 0, "" + def mock_run_command_output_remove_override_failure(self, cmd, no_output=False, chk_err=True): + return 1, "remove failed" + # region Utility Functions def __setup_config_and_invoke_revert_auto_os_to_system_default(self, package_manager, create_current_auto_os_config=True, create_backup_for_system_default_config=True, current_auto_os_update_config_value='', apply_updates_value="", - download_updates_value="", enable_on_reboot_value=False, installation_state_value=False, set_installation_state=True): + download_updates_value="", override_apply_updates_value="", override_download_updates_value="",enable_on_reboot_value=False, installation_state_value=False, set_installation_state=True): """ Sets up current auto OS update config, backup for system default config (if requested) and invoke revert to system default """ # setup current auto OS update config if create_current_auto_os_config: @@ -60,28 +63,42 @@ def __setup_config_and_invoke_revert_auto_os_to_system_default(self, package_man # setup backup for system default auto OS update config if create_backup_for_system_default_config: - self.__setup_backup_for_system_default_OS_update_config(package_manager, apply_updates_value=apply_updates_value, download_updates_value=download_updates_value, enable_on_reboot_value=enable_on_reboot_value, - installation_state_value=installation_state_value, set_installation_state=set_installation_state) + self.__setup_backup_for_system_default_OS_update_config(package_manager, apply_updates_value=apply_updates_value, download_updates_value=download_updates_value, + override_apply_updates_value = override_apply_updates_value, override_download_updates_value = override_download_updates_value, + enable_on_reboot_value=enable_on_reboot_value, installation_state_value=installation_state_value, set_installation_state=set_installation_state) package_manager.revert_auto_os_update_to_system_default() - def __setup_current_auto_os_update_config(self, package_manager, config_value='', - config_file_name="automatic.conf"): + def __setup_current_auto_os_update_config(self, package_manager, config_value=''): # setup current auto OS update config - package_manager.dnf5_automatic_configuration_file_path = os.path.join(self.runtime.execution_config.config_folder, config_file_name) - self.runtime.write_to_file(package_manager.dnf5_automatic_configuration_file_path, config_value) + default_config_path = os.path.join(self.runtime.execution_config.config_folder, "default_automatic.conf") + override_config_path = os.path.join(self.runtime.execution_config.config_folder, "automatic.conf") + + package_manager.dnf5_automatic_default_configuration_file_path = default_config_path + package_manager.dnf5_automatic_override_configuration_file_path = override_config_path - def __setup_backup_for_system_default_OS_update_config(self, package_manager, apply_updates_value="", download_updates_value="", enable_on_reboot_value=False, installation_state_value=False, set_installation_state=True): + self.runtime.write_to_file(default_config_path, config_value) + self.runtime.write_to_file(override_config_path, config_value) + + def __setup_backup_for_system_default_OS_update_config(self, package_manager, apply_updates_value="", download_updates_value="", override_apply_updates_value="", + override_download_updates_value="", enable_on_reboot_value=False, installation_state_value=False, set_installation_state=True): # setup backup for system default auto OS update config package_manager.image_default_patch_configuration_backup_path = os.path.join(self.runtime.execution_config.config_folder, Constants.IMAGE_DEFAULT_PATCH_CONFIGURATION_BACKUP_PATH) backup_image_default_patch_configuration_json = { - "dnf5-automatic": { - "apply_updates": apply_updates_value, - "download_updates": download_updates_value, - "enable_on_reboot": enable_on_reboot_value + package_manager.dnf5_default_auto_os_config_backup_key: { + package_manager.dnf5_automatic_apply_updates_identifier_text: apply_updates_value, + package_manager.dnf5_automatic_download_updates_identifier_text: download_updates_value, + package_manager.dnf5_automatic_enable_on_reboot_identifier_text: enable_on_reboot_value + }, + package_manager.dnf5_override_auto_os_config_backup_key: { + package_manager.dnf5_automatic_apply_updates_identifier_text: override_apply_updates_value, + package_manager.dnf5_automatic_download_updates_identifier_text: override_download_updates_value, + package_manager.dnf5_automatic_enable_on_reboot_identifier_text: enable_on_reboot_value } } + if set_installation_state: - backup_image_default_patch_configuration_json["dnf5-automatic"]["installation_state"] = installation_state_value + backup_image_default_patch_configuration_json[package_manager.dnf5_default_auto_os_config_backup_key][package_manager.dnf5_automatic_installation_state_identifier_text] = installation_state_value + backup_image_default_patch_configuration_json[package_manager.dnf5_override_auto_os_config_backup_key][package_manager.dnf5_automatic_installation_state_identifier_text] = installation_state_value self.runtime.write_to_file(package_manager.image_default_patch_configuration_backup_path, '{0}'.format(json.dumps(backup_image_default_patch_configuration_json))) @staticmethod @@ -97,12 +114,14 @@ def __assert_std_io(self, captured_output, expected_output=''): self.assertIn(expected_output, output) def __assert_reverted_automatic_patch_configuration_settings(self, package_manager, config_exists=True, config_value_expected=''): + reverted_dnf5_automatic_patch_configuration_settings = self.runtime.env_layer.file_system.read_with_retry( + package_manager.dnf5_automatic_default_configuration_file_path, raise_if_not_found=False) + if config_exists: - reverted_dnf5_automatic_patch_configuration_settings = self.runtime.env_layer.file_system.read_with_retry( - package_manager.dnf5_automatic_configuration_file_path) self.assertIsNotNone(reverted_dnf5_automatic_patch_configuration_settings) + self.assertEqual(config_value_expected, reverted_dnf5_automatic_patch_configuration_settings) else: - self.assertFalse(os.path.exists(package_manager.dnf5_automatic_configuration_file_path)) + self.assertIsNone(reverted_dnf5_automatic_patch_configuration_settings) # endregion def test_refresh_repo(self): @@ -124,31 +143,58 @@ def test_disable_auto_os_updates_with_uninstalled_services(self): self.assertIsNotNone(image_default_patch_configuration_backup) # validating backup for dnf-automatic - self.assertIn(package_manager.dnf5_auto_os_update_service, image_default_patch_configuration_backup) - self.assertEqual(image_default_patch_configuration_backup[package_manager.dnf5_auto_os_update_service][package_manager.dnf5_automatic_download_updates_identifier_text], "") - self.assertEqual(image_default_patch_configuration_backup[package_manager.dnf5_auto_os_update_service][package_manager.dnf5_automatic_apply_updates_identifier_text], "") - self.assertEqual(image_default_patch_configuration_backup[package_manager.dnf5_auto_os_update_service][package_manager.dnf5_automatic_enable_on_reboot_identifier_text], False) - self.assertEqual(image_default_patch_configuration_backup[package_manager.dnf5_auto_os_update_service][package_manager.dnf5_automatic_installation_state_identifier_text], False) + self.assertIn(package_manager.dnf5_default_auto_os_config_backup_key, image_default_patch_configuration_backup) + self.assertIn(package_manager.dnf5_override_auto_os_config_backup_key, image_default_patch_configuration_backup) + + default_backup = image_default_patch_configuration_backup[package_manager.dnf5_default_auto_os_config_backup_key] + override_backup = image_default_patch_configuration_backup[package_manager.dnf5_override_auto_os_config_backup_key] + + self.assertEqual(default_backup[package_manager.dnf5_automatic_download_updates_identifier_text], "") + self.assertEqual(default_backup[package_manager.dnf5_automatic_apply_updates_identifier_text], "") + self.assertEqual(default_backup[package_manager.dnf5_automatic_enable_on_reboot_identifier_text], False) + self.assertEqual(default_backup[package_manager.dnf5_automatic_installation_state_identifier_text], False) + + self.assertEqual(override_backup[package_manager.dnf5_automatic_download_updates_identifier_text], "") + self.assertEqual(override_backup[package_manager.dnf5_automatic_apply_updates_identifier_text], "") + self.assertEqual(override_backup[package_manager.dnf5_automatic_enable_on_reboot_identifier_text], False) + self.assertEqual(override_backup[package_manager.dnf5_automatic_installation_state_identifier_text], False) def test_disable_auto_os_updates_with_installed_services(self): self.runtime.set_legacy_test_type('HappyPath') package_manager = self.container.get('package_manager') - package_manager.dnf5_automatic_configuration_file_path = os.path.join(self.runtime.execution_config.config_folder, "automatic.conf") - dnf5_automatic_os_patch_configuration_settings = 'apply_updates = yes\ndownload_updates = yes\n' - self.runtime.write_to_file(package_manager.dnf5_automatic_configuration_file_path, dnf5_automatic_os_patch_configuration_settings) + default_config_path = os.path.join(self.runtime.execution_config.config_folder, "default_automatic.conf") + override_config_path = os.path.join(self.runtime.execution_config.config_folder, "automatic.conf") + + dnf5_automatic_os_patch_configuration_settings = "apply_updates = yes\ndownload_updates = yes\n" + + package_manager.dnf5_automatic_default_configuration_file_path = default_config_path + package_manager.dnf5_automatic_override_configuration_file_path = override_config_path + + self.runtime.write_to_file(default_config_path, dnf5_automatic_os_patch_configuration_settings) + self.runtime.write_to_file(override_config_path, dnf5_automatic_os_patch_configuration_settings) package_manager.disable_auto_os_update() self.assertTrue(package_manager.image_default_patch_configuration_backup_exists()) + image_default_patch_configuration_backup = json.loads(self.runtime.env_layer.file_system.read_with_retry(package_manager.image_default_patch_configuration_backup_path)) - self.assertIsNot(image_default_patch_configuration_backup, None) - # validating backup for dnf-automatic - self.assertIn(package_manager.dnf5_auto_os_update_service, image_default_patch_configuration_backup) - self.assertEqual(image_default_patch_configuration_backup[package_manager.dnf5_auto_os_update_service][package_manager.dnf5_automatic_download_updates_identifier_text], "yes") - self.assertEqual(image_default_patch_configuration_backup[package_manager.dnf5_auto_os_update_service][package_manager.dnf5_automatic_apply_updates_identifier_text], "yes") - self.assertEqual(image_default_patch_configuration_backup[package_manager.dnf5_auto_os_update_service][package_manager.dnf5_automatic_enable_on_reboot_identifier_text], False) - self.assertEqual(image_default_patch_configuration_backup[package_manager.dnf5_auto_os_update_service][package_manager.dnf5_automatic_installation_state_identifier_text], True) + self.assertIsNotNone(image_default_patch_configuration_backup) + self.assertIn(package_manager.dnf5_default_auto_os_config_backup_key, image_default_patch_configuration_backup) + self.assertIn(package_manager.dnf5_override_auto_os_config_backup_key, image_default_patch_configuration_backup) + + default_backup = image_default_patch_configuration_backup[package_manager.dnf5_default_auto_os_config_backup_key] + override_backup = image_default_patch_configuration_backup[package_manager.dnf5_override_auto_os_config_backup_key] + + self.assertEqual(default_backup[package_manager.dnf5_automatic_download_updates_identifier_text], "yes") + self.assertEqual(default_backup[package_manager.dnf5_automatic_apply_updates_identifier_text], "yes") + self.assertEqual(default_backup[package_manager.dnf5_automatic_enable_on_reboot_identifier_text], False) + self.assertEqual(default_backup[package_manager.dnf5_automatic_installation_state_identifier_text], True) + + self.assertEqual(override_backup[package_manager.dnf5_automatic_download_updates_identifier_text], "yes") + self.assertEqual(override_backup[package_manager.dnf5_automatic_apply_updates_identifier_text], "yes") + self.assertEqual(override_backup[package_manager.dnf5_automatic_enable_on_reboot_identifier_text], False) + self.assertEqual(override_backup[package_manager.dnf5_automatic_installation_state_identifier_text], True) def test_disable_auto_os_update_failure(self): package_manager = self.container.get('package_manager') @@ -178,35 +224,45 @@ def test_get_current_auto_os_patch_state_with_uninstalled_services(self): self.assertEqual(current_auto_os_patch_state, Constants.AutomaticOSPatchStates.DISABLED) def test_get_current_auto_os_patch_state_with_installed_services_and_state_enabled(self): - # with enable on reboot set to false self.runtime.set_legacy_test_type('HappyPath') package_manager = self.container.get('package_manager') package_manager.get_current_auto_os_patch_state = self.runtime.backup_get_current_auto_os_patch_state - package_manager.dnf5_automatic_configuration_file_path = os.path.join(self.runtime.execution_config.config_folder, "automatic.conf") - dnf5_automatic_os_patch_configuration_settings = 'apply_updates = yes\ndownload_updates = yes\n' - self.runtime.write_to_file(package_manager.dnf5_automatic_configuration_file_path, dnf5_automatic_os_patch_configuration_settings) + default_config_path = os.path.join(self.runtime.execution_config.config_folder, "default_automatic.conf") + override_config_path = os.path.join(self.runtime.execution_config.config_folder, "automatic.conf") + + package_manager.dnf5_automatic_default_configuration_file_path = default_config_path + package_manager.dnf5_automatic_override_configuration_file_path = override_config_path + + self.runtime.write_to_file(default_config_path, "apply_updates = yes\ndownload_updates = yes\n") + self.runtime.write_to_file(override_config_path, "apply_updates = yes\ndownload_updates = yes\n") is_enabled = package_manager.is_service_set_to_enable_on_reboot(package_manager.enable_on_reboot_check_cmd) self.assertFalse(is_enabled) + current_auto_os_patch_state = package_manager.get_current_auto_os_patch_state() self.assertFalse(package_manager.image_default_patch_configuration_backup_exists()) self.assertEqual(current_auto_os_patch_state, Constants.AutomaticOSPatchStates.ENABLED) - # with enable on reboot set to true self.runtime.set_legacy_test_type('AnotherSadPath') package_manager = self.container.get('package_manager') package_manager.get_current_auto_os_patch_state = self.runtime.backup_get_current_auto_os_patch_state - package_manager.dnf5_automatic_configuration_file_path = os.path.join(self.runtime.execution_config.config_folder, "automatic.conf") - dnf5_automatic_os_patch_configuration_settings = 'apply_updates = no\ndownload_updates = yes\n' - self.runtime.write_to_file(package_manager.dnf5_automatic_configuration_file_path, - dnf5_automatic_os_patch_configuration_settings) + default_config_path = os.path.join(self.runtime.execution_config.config_folder, "default_automatic.conf") + override_config_path = os.path.join(self.runtime.execution_config.config_folder, "automatic.conf") + + package_manager.dnf5_automatic_default_configuration_file_path = default_config_path + package_manager.dnf5_automatic_override_configuration_file_path = override_config_path + + self.runtime.write_to_file(default_config_path, "apply_updates = no\ndownload_updates = yes\n") + self.runtime.write_to_file(override_config_path, "apply_updates = no\ndownload_updates = yes\n") is_enabled = package_manager.is_service_set_to_enable_on_reboot(package_manager.enable_on_reboot_check_cmd) self.assertTrue(is_enabled) + current_auto_os_patch_state = package_manager.get_current_auto_os_patch_state() + self.assertFalse(package_manager.image_default_patch_configuration_backup_exists()) self.assertEqual(current_auto_os_patch_state, Constants.AutomaticOSPatchStates.ENABLED) @@ -233,8 +289,6 @@ def test_revert_auto_os_update_to_system_default_with_service_not_installed(self } } self.runtime.write_to_file(package_manager.image_default_patch_configuration_backup_path, json.dumps(backup_config)) - - # Should complete without error even when service is not installed package_manager.revert_auto_os_update_to_system_default() def test_revert_auto_os_update_to_system_default(self): @@ -311,7 +365,7 @@ def test_revert_auto_os_update_to_system_default(self): } }, "assertions": { - "config_value_expected": 'download_updates =\napply_updates = \n', + "config_value_expected": 'test_value = yes\ndownload_updates =\napply_updates = \n', "config_exists": True } } @@ -368,10 +422,39 @@ def test_revert_auto_os_update_to_system_default(self): } } + revert_success_with_override_backup_values_testcase = { + "legacy_type": "HappyPath", + "stdio": { + "capture_output": False, + "expected_output": None + }, + "config": { + "current_auto_update_config": { + "create_current_auto_os_config": True, + "current_auto_os_update_config_value": "apply_updates = no\ndownload_updates = no\n" + }, + "backup_system_default_config": { + "create_backup_for_system_default_config": True, + "apply_updates_value": "yes", + "download_updates_value": "yes", + "override_apply_updates_value": "yes", + "override_download_updates_value": "yes", + "enable_on_reboot_value": False, + "installation_state_value": True, + "set_installation_state": True + } + }, + "assertions": { + "config_exists": True, + "config_value_expected": "apply_updates = yes\ndownload_updates = yes\n" + } + } + all_testcases = [revert_success_testcase, revert_success_with_dnf_not_installed_testcase, revert_success_with_dnf_installed_but_no_config_value_testcase, revert_success_backup_config_does_not_exist_testcase, - revert_success_default_backup_config_invalid_testcase] + revert_success_default_backup_config_invalid_testcase, + revert_success_with_override_backup_values_testcase] for testcase in all_testcases: self.tearDown() @@ -391,6 +474,8 @@ def test_revert_auto_os_update_to_system_default(self): create_backup_for_system_default_config=bool(testcase["config"]["backup_system_default_config"]["create_backup_for_system_default_config"]), apply_updates_value=testcase["config"]["backup_system_default_config"]["apply_updates_value"], download_updates_value=testcase["config"]["backup_system_default_config"]["download_updates_value"], + override_apply_updates_value=testcase["config"]["backup_system_default_config"].get("override_apply_updates_value", ""), + override_download_updates_value=testcase["config"]["backup_system_default_config"].get("override_download_updates_value", ""), enable_on_reboot_value=bool(testcase["config"]["backup_system_default_config"]["enable_on_reboot_value"]), installation_state_value=bool(testcase["config"]["backup_system_default_config"]["installation_state_value"]), set_installation_state=bool(testcase["config"]["backup_system_default_config"]["set_installation_state"])) @@ -542,7 +627,7 @@ def test_package_manager(self): self.assertIsNotNone(package_manager) # Test: get_dependent_list - dependent_list = package_manager.get_dependent_list(["hyperv-daemons.x86_64"]) + dependent_list = package_manager.get_dependent_list(["openssl"]) self.assertIsNotNone(dependent_list) self.runtime.set_legacy_test_type('AnotherSadPath') @@ -590,45 +675,41 @@ def test_inclusion_type_other(self): def test_update_image_default_patch_mode(self): package_manager = self.container.get('package_manager') - package_manager.os_patch_configuration_settings_file_path = package_manager.dnf5_automatic_configuration_file_path = os.path.join( - self.runtime.execution_config.config_folder, "automatic.conf") + override_config_path = os.path.join(self.runtime.execution_config.config_folder, "automatic.conf") + + package_manager.dnf5_automatic_override_configuration_file_path = override_config_path + package_manager.os_patch_override_configuration_settings_file_path = override_config_path + + package_manager.download_updates_identifier_text = package_manager.dnf5_automatic_download_updates_identifier_text + package_manager.apply_updates_identifier_text = package_manager.dnf5_automatic_apply_updates_identifier_text + package_manager.auto_update_config_pattern_match_text = package_manager.dnf5_automatic_config_pattern_match_text - # disable apply_updates when enabled by default dnf5_automatic_os_patch_configuration_settings = 'apply_updates = yes\ndownload_updates = yes\n' - self.runtime.write_to_file(package_manager.dnf5_automatic_configuration_file_path, - dnf5_automatic_os_patch_configuration_settings) - - package_manager.update_os_patch_configuration_sub_setting( - package_manager.dnf5_automatic_apply_updates_identifier_text, "no", - package_manager.dnf5_automatic_config_pattern_match_text) - dnf5_automatic_os_patch_configuration_settings_file_path_read = self.runtime.env_layer.file_system.read_with_retry( - package_manager.os_patch_configuration_settings_file_path) + self.runtime.write_to_file(override_config_path, dnf5_automatic_os_patch_configuration_settings) + + package_manager.update_os_patch_configuration_sub_setting(package_manager.dnf5_automatic_apply_updates_identifier_text, "no", package_manager.dnf5_automatic_config_pattern_match_text) + dnf5_automatic_os_patch_configuration_settings_file_path_read = self.runtime.env_layer.file_system.read_with_retry(override_config_path) + self.assertIsNotNone(dnf5_automatic_os_patch_configuration_settings_file_path_read) self.assertIn('apply_updates = no', dnf5_automatic_os_patch_configuration_settings_file_path_read) - self.assertIn('download_updates = yes' , dnf5_automatic_os_patch_configuration_settings_file_path_read) + self.assertIn('download_updates = yes', dnf5_automatic_os_patch_configuration_settings_file_path_read) - # disable download_updates when enabled by default dnf5_automatic_os_patch_configuration_settings = 'apply_updates = yes\ndownload_updates = yes\n' - self.runtime.write_to_file(package_manager.os_patch_configuration_settings_file_path, - dnf5_automatic_os_patch_configuration_settings) - package_manager.update_os_patch_configuration_sub_setting( - package_manager.dnf5_automatic_download_updates_identifier_text, "no", - package_manager.dnf5_automatic_config_pattern_match_text) - dnf5_automatic_os_patch_configuration_settings_file_path_read = self.runtime.env_layer.file_system.read_with_retry( - package_manager.os_patch_configuration_settings_file_path) + self.runtime.write_to_file(override_config_path, dnf5_automatic_os_patch_configuration_settings) + + package_manager.update_os_patch_configuration_sub_setting(package_manager.dnf5_automatic_download_updates_identifier_text, "no", package_manager.dnf5_automatic_config_pattern_match_text) + dnf5_automatic_os_patch_configuration_settings_file_path_read = self.runtime.env_layer.file_system.read_with_retry(override_config_path) + self.assertIsNotNone(dnf5_automatic_os_patch_configuration_settings_file_path_read) self.assertIn('apply_updates = yes', dnf5_automatic_os_patch_configuration_settings_file_path_read) self.assertIn('download_updates = no', dnf5_automatic_os_patch_configuration_settings_file_path_read) - # disable apply_updates when default patch mode settings file is empty dnf5_automatic_os_patch_configuration_settings = '' - self.runtime.write_to_file(package_manager.os_patch_configuration_settings_file_path, - dnf5_automatic_os_patch_configuration_settings) - package_manager.update_os_patch_configuration_sub_setting( - package_manager.dnf5_automatic_apply_updates_identifier_text, "no", - package_manager.dnf5_automatic_config_pattern_match_text) - dnf5_automatic_os_patch_configuration_settings_file_path_read = self.runtime.env_layer.file_system.read_with_retry( - package_manager.os_patch_configuration_settings_file_path) + self.runtime.write_to_file(override_config_path, dnf5_automatic_os_patch_configuration_settings) + + package_manager.update_os_patch_configuration_sub_setting(package_manager.dnf5_automatic_apply_updates_identifier_text, "no", package_manager.dnf5_automatic_config_pattern_match_text) + dnf5_automatic_os_patch_configuration_settings_file_path_read = self.runtime.env_layer.file_system.read_with_retry(override_config_path) + self.assertIsNotNone(dnf5_automatic_os_patch_configuration_settings_file_path_read) self.assertNotIn('download_updates', dnf5_automatic_os_patch_configuration_settings_file_path_read) self.assertIn('apply_updates = no', dnf5_automatic_os_patch_configuration_settings_file_path_read) @@ -664,13 +745,19 @@ def test_is_reboot_pending(self): # Restart not required (needs-restarting returns code=0) self.runtime.set_legacy_test_type('SadPath') - self.runtime.env_layer.run_output_command = self.mock_run_command_output_no_reboot + self.runtime.env_layer.run_command_output = self.mock_run_command_output_no_reboot self.assertFalse(package_manager.is_reboot_pending()) # Exception Path self.runtime.set_legacy_test_type('HappyPath') package_manager = self.container.get('package_manager') self.assertIsNotNone(package_manager) + package_manager.os_patch_override_configuration_settings_file_path = os.path.join(self.runtime.execution_config.config_folder, "override.conf") + self.runtime.write_to_file(package_manager.os_patch_override_configuration_settings_file_path, '[commands]\napply_updates = yes\ndownload_updates = yes\n') + + self.runtime.env_layer.file_system.write_with_retry = self.mock_write_with_retry_raise_exception + self.assertRaises(Exception, package_manager.update_os_patch_configuration_sub_setting, package_manager.download_updates_identifier_text, "no", + package_manager.auto_update_config_pattern_match_text) self.runtime.env_layer.file_system.write_with_retry = self.mock_write_with_retry_raise_exception self.assertRaises(Exception, package_manager.is_reboot_pending()) @@ -702,6 +789,10 @@ def test_update_os_patch_configuration_sub_setting_exception_handling(self): self.runtime.set_legacy_test_type('HappyPath') package_manager = self.container.get('package_manager') # Mock file_system.write_with_retry to raise exception + package_manager.os_patch_override_configuration_settings_file_path = os.path.join(self.runtime.execution_config.config_folder, "override.conf") + self.runtime.write_to_file(package_manager.os_patch_override_configuration_settings_file_path, '[commands]\napply_updates = yes\ndownload_updates = yes\n') + self.runtime.env_layer.file_system.write_with_retry = self.mock_write_with_retry_raise_exception + self.assertRaises(Exception, package_manager.update_os_patch_configuration_sub_setting) self.runtime.env_layer.file_system.write_with_retry = self.mock_write_with_retry_raise_exception self.assertRaises(Exception, package_manager.update_os_patch_configuration_sub_setting, ) @@ -710,6 +801,11 @@ def test_backup_image_default_patch_configuration_if_not_exists_exception_handli self.runtime.set_legacy_test_type('HappyPath') package_manager = self.container.get('package_manager') # Mock file_system.write_with_retry to raise exception + package_manager.os_patch_override_configuration_settings_file_path = os.path.join(self.runtime.execution_config.config_folder, "override.conf") + self.runtime.write_to_file(package_manager.os_patch_override_configuration_settings_file_path, '[commands]\napply_updates = yes\ndownload_updates = yes\n') + + self.runtime.env_layer.file_system.write_with_retry = self.mock_write_with_retry_raise_exception + self.assertRaises(Exception, package_manager.update_os_patch_configuration_sub_setting) self.runtime.env_layer.file_system.write_with_retry = self.mock_write_with_retry_raise_exception self.assertRaises(Exception, package_manager.backup_image_default_patch_configuration_if_not_exists, ) @@ -730,6 +826,15 @@ def test_no_op_methods(self): package_manager.set_security_esm_package_status("op", []) package_manager.separate_out_esm_packages([], []) + def test_remove_override_configuration_failure(self): + self.runtime.set_legacy_test_type('HappyPath') + package_manager = self.container.get('package_manager') + override_config_path = os.path.join(self.runtime.execution_config.config_folder, "automatic.conf") + package_manager.os_patch_override_configuration_settings_file_path = override_config_path + self.runtime.write_to_file(override_config_path, "apply_updates = yes\ndownload_updates = yes\n") + self.runtime.env_layer.run_command_output = self.mock_run_command_output_remove_override_failure + self.assertRaises(Exception, package_manager._Dnf5PackageManager__remove_override_configuration_if_exists) + if __name__ == '__main__': unittest.main() diff --git a/src/core/tests/library/LegacyEnvLayerExtensions.py b/src/core/tests/library/LegacyEnvLayerExtensions.py index 0913fb34..d52de5da 100644 --- a/src/core/tests/library/LegacyEnvLayerExtensions.py +++ b/src/core/tests/library/LegacyEnvLayerExtensions.py @@ -1098,25 +1098,27 @@ def run_command_output(self, cmd, no_output=False, chk_err=True): 'Repositories loaded.\n' 'Package "rubygem-json-2.13.2-2.azl4~20260501.x86_64" is already installed.\n\n' 'Nothing to do.\n') - elif "hyperv-daemons" in cmd and "--assumeno" in cmd: - code = 1 - output = ( - "Updating and loading repositories:\n" - "Repositories loaded.\n" - "Package Arch Version Repository Size\n" - "Installing:\n" - " hyperv-daemons x86_64 6.10-3.azl4~20260501 azurelinux-base 20.08k\n\n" - "Installing dependencies:\n" - " hyperv-daemons-license noarch 6.10-3.azl4~20260501 azurelinux-base 18.3 KiB\n\n" - "Transaction Summary:\n" - " Installing: 2 packages\n\n" - "Total download size: 135.09k\n" - "Operation aborted by the user.\n") elif "dnf5 list --installed rubygem-json" in cmd: code = 0 output = ( "Installed packages\n" "rubygem-json.x86_64 2.13.2-2.azl4~20260501 azurelinux-base\n") + elif cmd.find("sudo dnf5 upgrade --assumeno openssl") > -1: + code = 0 + output = "Updating and loading repositories:\n" + \ + "Repositories loaded.\n" + \ + "Package Arch Version Repository Size\n" + \ + "Upgrading:\n" + \ + " openssl x86_64 1:3.5.4-7.azl4 azurelinux-base 1.8 MiB\n" + \ + " replacing openssl x86_64 1:3.5.4-4.azl4 64806a6b30824b51aafe6d2d87587286 1.8 MiB\n" + \ + " openssl-libs x86_64 1:3.5.4-7.azl4 azurelinux-base 6.5 MiB\n" + \ + " replacing openssl-libs x86_64 1:3.5.4-4.azl4 64806a6b30824b51aafe6d2d87587286 6.5 MiB\n" + \ + "Transaction Summary:\n" + \ + " Upgrading: 2 packages\n" + \ + " Replacing: 2 packages\n\n" + \ + "Total size of inbound packages is 3 MiB. Need to download 3 MiB.\n" + \ + "After this operation, 32 B extra will be used (install 8 MiB, remove 8 MiB).\n" + \ + "Operation aborted by the user." elif self.legacy_test_type == 'FailInstallPath': if cmd.find("cat /proc/cpuinfo | grep name") > -1: code = 0