forked from rpms/leapp-repository
334 lines
14 KiB
Diff
334 lines
14 KiB
Diff
From dcf53c28ea9c3fdd03277abcdeb1d124660f7f8e Mon Sep 17 00:00:00 2001
|
|
From: karolinku <kkula@redhat.com>
|
|
Date: Tue, 19 Aug 2025 09:48:11 +0200
|
|
Subject: [PATCH 01/55] Add upgrade inhibitor for custom DNF pluginpath
|
|
configuration
|
|
|
|
Implements detection and inhibition of the upgrade when DNF
|
|
pluginpath is configured in /etc/dnf/dnf.conf:
|
|
- Add DnfPluginPathDetected model to communicate detection results
|
|
- Add ScanDnfPluginPath actor (FactsPhase) to scan DNF configuration
|
|
- Add CheckDnfPluginPath actor (ChecksPhase) to create inhibitor report
|
|
- Add related unit tests
|
|
|
|
Localisation of dnf plugins is not constant between system releases
|
|
which can cause issues with the upgrade, so the user should remove
|
|
this option or comment it out.
|
|
|
|
Jira: RHEL-69601
|
|
---
|
|
.../common/actors/checkdnfpluginpath/actor.py | 22 ++++++++
|
|
.../libraries/checkdnfpluginpath.py | 35 ++++++++++++
|
|
.../tests/test_checkdnfpluginpath.py | 34 ++++++++++++
|
|
.../common/actors/scandnfpluginpath/actor.py | 21 ++++++++
|
|
.../libraries/scandnfpluginpath.py | 30 +++++++++++
|
|
.../files/dnf_config_incorrect_pluginpath | 7 +++
|
|
.../tests/files/dnf_config_no_pluginpath | 6 +++
|
|
.../tests/files/dnf_config_with_pluginpath | 7 +++
|
|
.../tests/test_scandnfpluginpath.py | 53 +++++++++++++++++++
|
|
.../common/models/dnfpluginpathdetected.py | 14 +++++
|
|
10 files changed, 229 insertions(+)
|
|
create mode 100644 repos/system_upgrade/common/actors/checkdnfpluginpath/actor.py
|
|
create mode 100644 repos/system_upgrade/common/actors/checkdnfpluginpath/libraries/checkdnfpluginpath.py
|
|
create mode 100644 repos/system_upgrade/common/actors/checkdnfpluginpath/tests/test_checkdnfpluginpath.py
|
|
create mode 100644 repos/system_upgrade/common/actors/scandnfpluginpath/actor.py
|
|
create mode 100644 repos/system_upgrade/common/actors/scandnfpluginpath/libraries/scandnfpluginpath.py
|
|
create mode 100644 repos/system_upgrade/common/actors/scandnfpluginpath/tests/files/dnf_config_incorrect_pluginpath
|
|
create mode 100644 repos/system_upgrade/common/actors/scandnfpluginpath/tests/files/dnf_config_no_pluginpath
|
|
create mode 100644 repos/system_upgrade/common/actors/scandnfpluginpath/tests/files/dnf_config_with_pluginpath
|
|
create mode 100644 repos/system_upgrade/common/actors/scandnfpluginpath/tests/test_scandnfpluginpath.py
|
|
create mode 100644 repos/system_upgrade/common/models/dnfpluginpathdetected.py
|
|
|
|
diff --git a/repos/system_upgrade/common/actors/checkdnfpluginpath/actor.py b/repos/system_upgrade/common/actors/checkdnfpluginpath/actor.py
|
|
new file mode 100644
|
|
index 00000000..34055886
|
|
--- /dev/null
|
|
+++ b/repos/system_upgrade/common/actors/checkdnfpluginpath/actor.py
|
|
@@ -0,0 +1,22 @@
|
|
+from leapp.actors import Actor
|
|
+from leapp.libraries.actor.checkdnfpluginpath import perform_check
|
|
+from leapp.models import DnfPluginPathDetected
|
|
+from leapp.reporting import Report
|
|
+from leapp.tags import ChecksPhaseTag, IPUWorkflowTag
|
|
+
|
|
+
|
|
+class CheckDnfPluginPath(Actor):
|
|
+ """
|
|
+ Inhibits the upgrade if a custom DNF plugin path is configured.
|
|
+
|
|
+ This actor checks whether the pluginpath option is configured in /etc/dnf/dnf.conf and produces a report if it is.
|
|
+ If the option is detected with any value, the upgrade is inhibited.
|
|
+ """
|
|
+
|
|
+ name = 'check_dnf_pluginpath'
|
|
+ consumes = (DnfPluginPathDetected,)
|
|
+ produces = (Report,)
|
|
+ tags = (ChecksPhaseTag, IPUWorkflowTag)
|
|
+
|
|
+ def process(self):
|
|
+ perform_check()
|
|
diff --git a/repos/system_upgrade/common/actors/checkdnfpluginpath/libraries/checkdnfpluginpath.py b/repos/system_upgrade/common/actors/checkdnfpluginpath/libraries/checkdnfpluginpath.py
|
|
new file mode 100644
|
|
index 00000000..ce705361
|
|
--- /dev/null
|
|
+++ b/repos/system_upgrade/common/actors/checkdnfpluginpath/libraries/checkdnfpluginpath.py
|
|
@@ -0,0 +1,35 @@
|
|
+from leapp import reporting
|
|
+from leapp.libraries.stdlib import api
|
|
+from leapp.models import DnfPluginPathDetected
|
|
+
|
|
+DNF_CONFIG_PATH = '/etc/dnf/dnf.conf'
|
|
+
|
|
+
|
|
+def check_dnf_pluginpath(dnf_pluginpath_detected):
|
|
+ """Create an inhibitor when pluginpath is detected in DNF configuration."""
|
|
+ if not dnf_pluginpath_detected.is_pluginpath_detected:
|
|
+ return
|
|
+ reporting.create_report([
|
|
+ reporting.Title('Detected specified pluginpath in DNF configuration.'),
|
|
+ reporting.Summary(
|
|
+ 'The "pluginpath" option is set in the {} file. The path to DNF plugins differs between '
|
|
+ 'system major releases due to different versions of Python. '
|
|
+ 'This breaks the in-place upgrades if defined explicitly as DNF plugins '
|
|
+ 'are stored on a different path on the new system.'
|
|
+ .format(DNF_CONFIG_PATH)
|
|
+ ),
|
|
+ reporting.Remediation(
|
|
+ hint='Remove or comment out the pluginpath option in the DNF '
|
|
+ 'configuration file to be able to upgrade the system',
|
|
+ commands=[['sed', '-i', '\'s/^pluginpath[[:space:]]*=/#pluginpath=/\'', DNF_CONFIG_PATH]],
|
|
+ ),
|
|
+ reporting.Severity(reporting.Severity.HIGH),
|
|
+ reporting.Groups([reporting.Groups.INHIBITOR]),
|
|
+ reporting.RelatedResource('file', DNF_CONFIG_PATH),
|
|
+ ])
|
|
+
|
|
+
|
|
+def perform_check():
|
|
+ dnf_pluginpath_detected = next(api.consume(DnfPluginPathDetected), None)
|
|
+ if dnf_pluginpath_detected:
|
|
+ check_dnf_pluginpath(dnf_pluginpath_detected)
|
|
diff --git a/repos/system_upgrade/common/actors/checkdnfpluginpath/tests/test_checkdnfpluginpath.py b/repos/system_upgrade/common/actors/checkdnfpluginpath/tests/test_checkdnfpluginpath.py
|
|
new file mode 100644
|
|
index 00000000..7dd8bbf2
|
|
--- /dev/null
|
|
+++ b/repos/system_upgrade/common/actors/checkdnfpluginpath/tests/test_checkdnfpluginpath.py
|
|
@@ -0,0 +1,34 @@
|
|
+import pytest
|
|
+
|
|
+from leapp import reporting
|
|
+from leapp.libraries.actor.checkdnfpluginpath import check_dnf_pluginpath, perform_check
|
|
+from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked
|
|
+from leapp.libraries.stdlib import api
|
|
+from leapp.models import DnfPluginPathDetected
|
|
+from leapp.utils.report import is_inhibitor
|
|
+
|
|
+
|
|
+@pytest.mark.parametrize('is_detected', [False, True])
|
|
+def test_check_dnf_pluginpath(monkeypatch, is_detected):
|
|
+ actor_reports = create_report_mocked()
|
|
+ msg = DnfPluginPathDetected(is_pluginpath_detected=is_detected)
|
|
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[msg]))
|
|
+ monkeypatch.setattr(reporting, 'create_report', actor_reports)
|
|
+
|
|
+ perform_check()
|
|
+
|
|
+ assert bool(actor_reports.called) == is_detected
|
|
+
|
|
+ if is_detected:
|
|
+ assert is_inhibitor(actor_reports.report_fields)
|
|
+
|
|
+
|
|
+def test_perform_check_no_message_available(monkeypatch):
|
|
+ """Test perform_check when no DnfPluginPathDetected message is available."""
|
|
+ actor_reports = create_report_mocked()
|
|
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked())
|
|
+ monkeypatch.setattr(reporting, 'create_report', actor_reports)
|
|
+
|
|
+ perform_check()
|
|
+
|
|
+ assert not actor_reports.called
|
|
diff --git a/repos/system_upgrade/common/actors/scandnfpluginpath/actor.py b/repos/system_upgrade/common/actors/scandnfpluginpath/actor.py
|
|
new file mode 100644
|
|
index 00000000..e43a691e
|
|
--- /dev/null
|
|
+++ b/repos/system_upgrade/common/actors/scandnfpluginpath/actor.py
|
|
@@ -0,0 +1,21 @@
|
|
+from leapp.actors import Actor
|
|
+from leapp.libraries.actor.scandnfpluginpath import scan_dnf_pluginpath
|
|
+from leapp.models import DnfPluginPathDetected
|
|
+from leapp.tags import FactsPhaseTag, IPUWorkflowTag
|
|
+
|
|
+
|
|
+class ScanDnfPluginPath(Actor):
|
|
+ """
|
|
+ Scans DNF configuration for custom pluginpath option.
|
|
+
|
|
+ This actor collects information about whether the pluginpath option is configured in DNF configuration
|
|
+ and produces a DnfPluginPathDetected message, containing the information.
|
|
+ """
|
|
+
|
|
+ name = 'scan_dnf_pluginpath'
|
|
+ consumes = ()
|
|
+ produces = (DnfPluginPathDetected,)
|
|
+ tags = (FactsPhaseTag, IPUWorkflowTag)
|
|
+
|
|
+ def process(self):
|
|
+ scan_dnf_pluginpath()
|
|
diff --git a/repos/system_upgrade/common/actors/scandnfpluginpath/libraries/scandnfpluginpath.py b/repos/system_upgrade/common/actors/scandnfpluginpath/libraries/scandnfpluginpath.py
|
|
new file mode 100644
|
|
index 00000000..818f7700
|
|
--- /dev/null
|
|
+++ b/repos/system_upgrade/common/actors/scandnfpluginpath/libraries/scandnfpluginpath.py
|
|
@@ -0,0 +1,30 @@
|
|
+import os
|
|
+
|
|
+from six.moves import configparser
|
|
+
|
|
+from leapp.libraries.stdlib import api
|
|
+from leapp.models import DnfPluginPathDetected
|
|
+
|
|
+DNF_CONFIG_PATH = '/etc/dnf/dnf.conf'
|
|
+
|
|
+
|
|
+def _is_pluginpath_set(config_path):
|
|
+ """Check if pluginpath option is set in DNF configuration file."""
|
|
+ if not os.path.isfile(config_path):
|
|
+ api.current_logger().warning('The %s file is missing.', config_path)
|
|
+ return False
|
|
+
|
|
+ parser = configparser.ConfigParser()
|
|
+
|
|
+ try:
|
|
+ parser.read(config_path)
|
|
+ return parser.has_option('main', 'pluginpath')
|
|
+ except (configparser.Error, IOError) as e:
|
|
+ api.current_logger().warning('The DNF config file %s couldn\'t be parsed: %s', config_path, e)
|
|
+ return False
|
|
+
|
|
+
|
|
+def scan_dnf_pluginpath():
|
|
+ """Scan DNF configuration and produce DnfPluginPathDetected message."""
|
|
+ is_detected = _is_pluginpath_set(DNF_CONFIG_PATH)
|
|
+ api.produce(DnfPluginPathDetected(is_pluginpath_detected=is_detected))
|
|
diff --git a/repos/system_upgrade/common/actors/scandnfpluginpath/tests/files/dnf_config_incorrect_pluginpath b/repos/system_upgrade/common/actors/scandnfpluginpath/tests/files/dnf_config_incorrect_pluginpath
|
|
new file mode 100644
|
|
index 00000000..aa29db09
|
|
--- /dev/null
|
|
+++ b/repos/system_upgrade/common/actors/scandnfpluginpath/tests/files/dnf_config_incorrect_pluginpath
|
|
@@ -0,0 +1,7 @@
|
|
+[main]
|
|
+gpgcheck=1
|
|
+installonly_limit=3
|
|
+clean_requirements_on_remove=True
|
|
+best=True
|
|
+skip_if_unavailable=False
|
|
+pluginpathincorrect=/usr/lib/python3.6/site-packages/dnf-plugins
|
|
diff --git a/repos/system_upgrade/common/actors/scandnfpluginpath/tests/files/dnf_config_no_pluginpath b/repos/system_upgrade/common/actors/scandnfpluginpath/tests/files/dnf_config_no_pluginpath
|
|
new file mode 100644
|
|
index 00000000..3d08d075
|
|
--- /dev/null
|
|
+++ b/repos/system_upgrade/common/actors/scandnfpluginpath/tests/files/dnf_config_no_pluginpath
|
|
@@ -0,0 +1,6 @@
|
|
+[main]
|
|
+gpgcheck=1
|
|
+installonly_limit=3
|
|
+clean_requirements_on_remove=True
|
|
+best=True
|
|
+skip_if_unavailable=False
|
|
diff --git a/repos/system_upgrade/common/actors/scandnfpluginpath/tests/files/dnf_config_with_pluginpath b/repos/system_upgrade/common/actors/scandnfpluginpath/tests/files/dnf_config_with_pluginpath
|
|
new file mode 100644
|
|
index 00000000..09a81e64
|
|
--- /dev/null
|
|
+++ b/repos/system_upgrade/common/actors/scandnfpluginpath/tests/files/dnf_config_with_pluginpath
|
|
@@ -0,0 +1,7 @@
|
|
+[main]
|
|
+gpgcheck=1
|
|
+installonly_limit=3
|
|
+clean_requirements_on_remove=True
|
|
+best=True
|
|
+skip_if_unavailable=False
|
|
+pluginpath=/usr/lib/python3.6/site-packages/dnf-plugins
|
|
diff --git a/repos/system_upgrade/common/actors/scandnfpluginpath/tests/test_scandnfpluginpath.py b/repos/system_upgrade/common/actors/scandnfpluginpath/tests/test_scandnfpluginpath.py
|
|
new file mode 100644
|
|
index 00000000..fefb9d3f
|
|
--- /dev/null
|
|
+++ b/repos/system_upgrade/common/actors/scandnfpluginpath/tests/test_scandnfpluginpath.py
|
|
@@ -0,0 +1,53 @@
|
|
+import os
|
|
+
|
|
+import pytest
|
|
+
|
|
+from leapp.libraries.actor import scandnfpluginpath
|
|
+from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked, produce_mocked
|
|
+from leapp.libraries.stdlib import api
|
|
+from leapp.models import DnfPluginPathDetected
|
|
+
|
|
+
|
|
+@pytest.mark.parametrize('is_detected', [False, True])
|
|
+def test_scan_detects_pluginpath(monkeypatch, is_detected):
|
|
+ mocked_producer = produce_mocked()
|
|
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked())
|
|
+ monkeypatch.setattr(api, 'produce', mocked_producer)
|
|
+
|
|
+ monkeypatch.setattr(scandnfpluginpath, '_is_pluginpath_set',
|
|
+ lambda path: is_detected)
|
|
+
|
|
+ scandnfpluginpath.scan_dnf_pluginpath()
|
|
+
|
|
+ assert mocked_producer.called == 1
|
|
+ assert mocked_producer.model_instances[0].is_pluginpath_detected is is_detected
|
|
+
|
|
+
|
|
+@pytest.mark.parametrize(('config_file', 'result'), [
|
|
+ ('files/dnf_config_no_pluginpath', False),
|
|
+ ('files/dnf_config_with_pluginpath', True),
|
|
+ ('files/dnf_config_incorrect_pluginpath', False),
|
|
+ ('files/not_existing_file.conf', False)
|
|
+])
|
|
+def test_is_pluginpath_set(config_file, result):
|
|
+ CUR_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
+
|
|
+ assert scandnfpluginpath._is_pluginpath_set(os.path.join(CUR_DIR, config_file)) == result
|
|
+
|
|
+
|
|
+def test_scan_no_config_file(monkeypatch):
|
|
+ mocked_producer = produce_mocked()
|
|
+ logger = logger_mocked()
|
|
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked())
|
|
+ monkeypatch.setattr(api, 'produce', mocked_producer)
|
|
+ monkeypatch.setattr(api, 'current_logger', lambda: logger)
|
|
+
|
|
+ filename = 'files/not_existing_file.conf'
|
|
+ monkeypatch.setattr(scandnfpluginpath, 'DNF_CONFIG_PATH', filename)
|
|
+ scandnfpluginpath.scan_dnf_pluginpath()
|
|
+
|
|
+ assert mocked_producer.called == 1
|
|
+ assert mocked_producer.model_instances[0].is_pluginpath_detected is False
|
|
+
|
|
+ assert 'The %s file is missing.' in logger.warnmsg
|
|
+ assert filename in logger.warnmsg
|
|
diff --git a/repos/system_upgrade/common/models/dnfpluginpathdetected.py b/repos/system_upgrade/common/models/dnfpluginpathdetected.py
|
|
new file mode 100644
|
|
index 00000000..c5474857
|
|
--- /dev/null
|
|
+++ b/repos/system_upgrade/common/models/dnfpluginpathdetected.py
|
|
@@ -0,0 +1,14 @@
|
|
+from leapp.models import fields, Model
|
|
+from leapp.topics import SystemInfoTopic
|
|
+
|
|
+
|
|
+class DnfPluginPathDetected(Model):
|
|
+ """
|
|
+ This model contains information about whether DNF pluginpath option is configured in /etc/dnf/dnf.conf.
|
|
+ """
|
|
+ topic = SystemInfoTopic
|
|
+
|
|
+ is_pluginpath_detected = fields.Boolean()
|
|
+ """
|
|
+ True if pluginpath option is found in /etc/dnf/dnf.conf, False otherwise.
|
|
+ """
|
|
--
|
|
2.51.1
|
|
|