From 5bd7bdf5e9c81ec306e567a147dc270adfd27da2 Mon Sep 17 00:00:00 2001 From: Matej Matuska Date: Tue, 14 Nov 2023 09:51:41 +0100 Subject: [PATCH 36/38] Copy dnf.conf to target userspace and allow a custom one This change allows working around the fact that source and target `dnf.conf` files might be incompatible. For example some of the proxy configuration between RHEL7 and RHEL8. Target system compatible configuration can be specified in /etc/leapp/files/dnf.conf. If this file is present it is copied into the target userspace and also applied to the target system. If it doesn't exist, the `/etc/dnf/dnf.conf` from the source system will be copied instead. Errors that could be caused by incompatible/incorrect proxy configuration now contain a hint with a remediation with the steps above mentioned. * pstodulk@redhat.com: Updated text in the error msg. Jira: OAMG-6544 --- .../common/actors/applycustomdnfconf/actor.py | 19 ++++++++++++++ .../libraries/applycustomdnfconf.py | 15 +++++++++++ .../tests/test_applycustomdnfconf.py | 23 ++++++++++++++++ .../copydnfconfintotargetuserspace/actor.py | 24 +++++++++++++++++ .../copydnfconfintotargetuserspace.py | 19 ++++++++++++++ .../tests/test_dnfconfuserspacecopy.py | 26 +++++++++++++++++++ .../libraries/userspacegen.py | 18 ++++++++++--- .../common/libraries/dnfplugin.py | 24 ++++++++++++++++- 8 files changed, 163 insertions(+), 5 deletions(-) create mode 100644 repos/system_upgrade/common/actors/applycustomdnfconf/actor.py create mode 100644 repos/system_upgrade/common/actors/applycustomdnfconf/libraries/applycustomdnfconf.py create mode 100644 repos/system_upgrade/common/actors/applycustomdnfconf/tests/test_applycustomdnfconf.py create mode 100644 repos/system_upgrade/common/actors/copydnfconfintotargetuserspace/actor.py create mode 100644 repos/system_upgrade/common/actors/copydnfconfintotargetuserspace/libraries/copydnfconfintotargetuserspace.py create mode 100644 repos/system_upgrade/common/actors/copydnfconfintotargetuserspace/tests/test_dnfconfuserspacecopy.py diff --git a/repos/system_upgrade/common/actors/applycustomdnfconf/actor.py b/repos/system_upgrade/common/actors/applycustomdnfconf/actor.py new file mode 100644 index 00000000..d7c7fe87 --- /dev/null +++ b/repos/system_upgrade/common/actors/applycustomdnfconf/actor.py @@ -0,0 +1,19 @@ +from leapp.actors import Actor +from leapp.libraries.actor import applycustomdnfconf +from leapp.tags import ApplicationsPhaseTag, IPUWorkflowTag + + +class ApplyCustomDNFConf(Actor): + """ + Move /etc/leapp/files/dnf.conf to /etc/dnf/dnf.conf if it exists + + An actor in FactsPhase copies this file to the target userspace if present. + In such case we also want to use the file on the target system. + """ + name = "apply_custom_dnf_conf" + consumes = () + produces = () + tags = (ApplicationsPhaseTag, IPUWorkflowTag) + + def process(self): + applycustomdnfconf.process() diff --git a/repos/system_upgrade/common/actors/applycustomdnfconf/libraries/applycustomdnfconf.py b/repos/system_upgrade/common/actors/applycustomdnfconf/libraries/applycustomdnfconf.py new file mode 100644 index 00000000..2eabd678 --- /dev/null +++ b/repos/system_upgrade/common/actors/applycustomdnfconf/libraries/applycustomdnfconf.py @@ -0,0 +1,15 @@ +import os + +from leapp.libraries.stdlib import api, CalledProcessError, run + +CUSTOM_DNF_CONF_PATH = "/etc/leapp/files/dnf.conf" + + +def process(): + if os.path.exists(CUSTOM_DNF_CONF_PATH): + try: + run(["mv", CUSTOM_DNF_CONF_PATH, "/etc/dnf/dnf.conf"]) + except (CalledProcessError, OSError) as e: + api.current_logger().debug( + "Failed to move /etc/leapp/files/dnf.conf to /etc/dnf/dnf.conf: {}".format(e) + ) diff --git a/repos/system_upgrade/common/actors/applycustomdnfconf/tests/test_applycustomdnfconf.py b/repos/system_upgrade/common/actors/applycustomdnfconf/tests/test_applycustomdnfconf.py new file mode 100644 index 00000000..6dbc4291 --- /dev/null +++ b/repos/system_upgrade/common/actors/applycustomdnfconf/tests/test_applycustomdnfconf.py @@ -0,0 +1,23 @@ +import os + +import pytest + +from leapp.libraries.actor import applycustomdnfconf + + +@pytest.mark.parametrize( + "exists,should_move", + [(False, False), (True, True)], +) +def test_copy_correct_dnf_conf(monkeypatch, exists, should_move): + monkeypatch.setattr(os.path, "exists", lambda _: exists) + + run_called = [False] + + def mocked_run(_): + run_called[0] = True + + monkeypatch.setattr(applycustomdnfconf, 'run', mocked_run) + + applycustomdnfconf.process() + assert run_called[0] == should_move diff --git a/repos/system_upgrade/common/actors/copydnfconfintotargetuserspace/actor.py b/repos/system_upgrade/common/actors/copydnfconfintotargetuserspace/actor.py new file mode 100644 index 00000000..46ce1934 --- /dev/null +++ b/repos/system_upgrade/common/actors/copydnfconfintotargetuserspace/actor.py @@ -0,0 +1,24 @@ +from leapp.actors import Actor +from leapp.libraries.actor import copydnfconfintotargetuserspace +from leapp.models import TargetUserSpacePreupgradeTasks +from leapp.tags import FactsPhaseTag, IPUWorkflowTag + + +class CopyDNFConfIntoTargetUserspace(Actor): + """ + Copy dnf.conf into target userspace + + Copies /etc/leapp/files/dnf.conf to target userspace. If it isn't available + /etc/dnf/dnf.conf is copied instead. This allows specifying a different + config for the target userspace, which might be required if the source + system configuration file isn't compatible with the target one. One such + example is incompatible proxy configuration between RHEL7 and RHEL8 DNF + versions. + """ + name = "copy_dnf_conf_into_target_userspace" + consumes = () + produces = (TargetUserSpacePreupgradeTasks,) + tags = (FactsPhaseTag, IPUWorkflowTag) + + def process(self): + copydnfconfintotargetuserspace.process() diff --git a/repos/system_upgrade/common/actors/copydnfconfintotargetuserspace/libraries/copydnfconfintotargetuserspace.py b/repos/system_upgrade/common/actors/copydnfconfintotargetuserspace/libraries/copydnfconfintotargetuserspace.py new file mode 100644 index 00000000..4e74acdb --- /dev/null +++ b/repos/system_upgrade/common/actors/copydnfconfintotargetuserspace/libraries/copydnfconfintotargetuserspace.py @@ -0,0 +1,19 @@ +import os + +from leapp.libraries.stdlib import api +from leapp.models import CopyFile, TargetUserSpacePreupgradeTasks + + +def process(): + src = "/etc/dnf/dnf.conf" + if os.path.exists("/etc/leapp/files/dnf.conf"): + src = "/etc/leapp/files/dnf.conf" + + api.current_logger().debug( + "Copying dnf.conf at {} to the target userspace".format(src) + ) + api.produce( + TargetUserSpacePreupgradeTasks( + copy_files=[CopyFile(src=src, dst="/etc/dnf/dnf.conf")] + ) + ) diff --git a/repos/system_upgrade/common/actors/copydnfconfintotargetuserspace/tests/test_dnfconfuserspacecopy.py b/repos/system_upgrade/common/actors/copydnfconfintotargetuserspace/tests/test_dnfconfuserspacecopy.py new file mode 100644 index 00000000..6c99925e --- /dev/null +++ b/repos/system_upgrade/common/actors/copydnfconfintotargetuserspace/tests/test_dnfconfuserspacecopy.py @@ -0,0 +1,26 @@ +import os + +import pytest + +from leapp.libraries.actor import copydnfconfintotargetuserspace +from leapp.libraries.common.testutils import logger_mocked, produce_mocked + + +@pytest.mark.parametrize( + "userspace_conf_exists,expected", + [(False, "/etc/dnf/dnf.conf"), (True, "/etc/leapp/files/dnf.conf")], +) +def test_copy_correct_dnf_conf(monkeypatch, userspace_conf_exists, expected): + monkeypatch.setattr(os.path, "exists", lambda _: userspace_conf_exists) + + mocked_produce = produce_mocked() + monkeypatch.setattr(copydnfconfintotargetuserspace.api, 'produce', mocked_produce) + monkeypatch.setattr(copydnfconfintotargetuserspace.api, 'current_logger', logger_mocked()) + + copydnfconfintotargetuserspace.process() + + assert mocked_produce.called == 1 + assert len(mocked_produce.model_instances) == 1 + assert len(mocked_produce.model_instances[0].copy_files) == 1 + assert mocked_produce.model_instances[0].copy_files[0].src == expected + assert mocked_produce.model_instances[0].copy_files[0].dst == "/etc/dnf/dnf.conf" diff --git a/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py b/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py index 050ad7fe..e015a741 100644 --- a/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py +++ b/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py @@ -269,15 +269,25 @@ def prepare_target_userspace(context, userspace_dir, enabled_repos, packages): # failed since leapp does not support updates behind proxy yet. for manager_info in api.consume(PkgManagerInfo): if manager_info.configured_proxies: - details['details'] = ("DNF failed to install userspace packages, likely due to the proxy " - "configuration detected in the YUM/DNF configuration file.") + details['details'] = ( + "DNF failed to install userspace packages, likely due to the proxy " + "configuration detected in the YUM/DNF configuration file. " + "Make sure the proxy is properly configured in /etc/dnf/dnf.conf. " + "It's also possible the proxy settings in the DNF configuration file are " + "incompatible with the target system. A compatible configuration can be " + "placed in /etc/leapp/files/dnf.conf which, if present, will be used during " + "the upgrade instead of /etc/dnf/dnf.conf. " + "In such case the configuration will also be applied to the target system." + ) # Similarly if a proxy was set specifically for one of the repositories. for repo_facts in api.consume(RepositoriesFacts): for repo_file in repo_facts.repositories: if any(repo_data.proxy and repo_data.enabled for repo_data in repo_file.data): - details['details'] = ("DNF failed to install userspace packages, likely due to the proxy " - "configuration detected in a repository configuration file.") + details['details'] = ( + "DNF failed to install userspace packages, likely due to the proxy " + "configuration detected in a repository configuration file." + ) raise StopActorExecutionError(message=message, details=details) diff --git a/repos/system_upgrade/common/libraries/dnfplugin.py b/repos/system_upgrade/common/libraries/dnfplugin.py index 26810e94..d3ec5901 100644 --- a/repos/system_upgrade/common/libraries/dnfplugin.py +++ b/repos/system_upgrade/common/libraries/dnfplugin.py @@ -178,8 +178,30 @@ def _handle_transaction_err_msg(stage, xfs_info, err, is_container=False): return # not needed actually as the above function raises error, but for visibility NO_SPACE_STR = 'more space needed on the' message = 'DNF execution failed with non zero exit code.' - details = {'STDOUT': err.stdout, 'STDERR': err.stderr} if NO_SPACE_STR not in err.stderr: + # if there was a problem reaching repos and proxy is configured in DNF/YUM configs, the + # proxy is likely the problem. + # NOTE(mmatuska): We can't consistently detect there was a problem reaching some repos, + # because it isn't clear what are all the possible DNF error messages we can encounter, + # such as: "Failed to synchronize cache for repo ..." or "Errors during downloading + # metadata for # repository" or "No more mirrors to try - All mirrors were already tried + # without success" + # NOTE(mmatuska): We could check PkgManagerInfo to detect if proxy is indeed configured, + # however it would be pretty ugly to pass it all the way down here + proxy_hint = ( + "If there was a problem reaching remote content (see stderr output) and proxy is " + "configured in the YUM/DNF configuration file, the proxy configuration is likely " + "causing this error. " + "Make sure the proxy is properly configured in /etc/dnf/dnf.conf. " + "It's also possible the proxy settings in the DNF configuration file are " + "incompatible with the target system. A compatible configuration can be " + "placed in /etc/leapp/files/dnf.conf which, if present, it will be used during " + "some parts of the upgrade instead of original /etc/dnf/dnf.conf. " + "In such case the configuration will also be applied to the target system. " + "Note that /etc/dnf/dnf.conf needs to be still configured correctly " + "for your current system to pass the early phases of the upgrade process." + ) + details = {'STDOUT': err.stdout, 'STDERR': err.stderr, 'hint': proxy_hint} raise StopActorExecutionError(message=message, details=details) # Disk Requirements: -- 2.41.0