Skip to content

Commit

Permalink
Support DNF5's config-manager
Browse files Browse the repository at this point in the history
Fedora rawhide is installing DNF5 on new systems. Payload that sets up
multilib support on the system runs on the installed system, thus using
DNF5. DNF5 changed config-manager plugin syntax. This commit makes sure
that it works with both DNF4 as well as with DNF5.
  • Loading branch information
marusak committed Aug 15, 2024
1 parent 96e4846 commit 6f39cb0
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 20 deletions.
2 changes: 2 additions & 0 deletions pyanaconda/modules/payloads/payload/dnf/dnf.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ def install_with_tasks(self):
ResolvePackagesTask(
dnf_manager=self.dnf_manager,
selection=self.packages_selection,
configuration=self.packages_configuration,
),
PrepareDownloadLocationTask(
dnf_manager=self.dnf_manager,
Expand Down Expand Up @@ -472,6 +473,7 @@ def post_install_with_tasks(self):
UpdateDNFConfigurationTask(
sysroot=conf.target.system_root,
configuration=self.packages_configuration,
dnf_manager=self.dnf_manager,
),
ResetDNFManagerTask(
dnf_manager=self.dnf_manager
Expand Down
36 changes: 28 additions & 8 deletions pyanaconda/modules/payloads/payload/dnf/installation.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@
from pyanaconda.anaconda_loggers import get_module_logger
from pyanaconda.core import util
from pyanaconda.core.configuration.anaconda import conf
from pyanaconda.core.constants import RPM_LANGUAGES_NONE, RPM_LANGUAGES_ALL, MULTILIB_POLICY_BEST
from pyanaconda.core.constants import RPM_LANGUAGES_NONE, RPM_LANGUAGES_ALL, \
MULTILIB_POLICY_BEST
from pyanaconda.core.i18n import _
from pyanaconda.core.path import join_paths, make_directories
from pyanaconda.modules.common.errors.installation import PayloadInstallationError, \
NonCriticalInstallationError
from pyanaconda.modules.common.structures.packages import PackagesConfigurationData
from pyanaconda.modules.common.task import Task
from pyanaconda.modules.payloads.payload.dnf.requirements import collect_remote_requirements, \
collect_language_requirements, collect_platform_requirements, \
collect_language_requirements, collect_platform_requirements, collect_dnf_requirements, \
collect_driver_disk_requirements, apply_requirements
from pyanaconda.modules.payloads.payload.dnf.utils import pick_download_location, \
get_kernel_version_list
Expand Down Expand Up @@ -100,6 +101,16 @@ def _install_macros(self, macros):
class ResolvePackagesTask(CheckPackagesSelectionTask):
"""Installation task to resolve the software selection."""

def __init__(self, dnf_manager, selection, configuration):
"""Create a new task.
:param dnf_manager: a DNF manager
"""
super().__init__()
self._dnf_manager = dnf_manager
self._selection = selection
self._configuration = configuration

@property
def name(self):
"""The name of the task."""
Expand Down Expand Up @@ -132,6 +143,7 @@ def _requirements(self):
return collect_remote_requirements() \
+ collect_language_requirements(self._dnf_manager) \
+ collect_platform_requirements(self._dnf_manager) \
+ collect_dnf_requirements(self._dnf_manager, self._configuration) \
+ collect_driver_disk_requirements()

def _collect_required_specs(self):
Expand Down Expand Up @@ -377,7 +389,7 @@ def run(self):
class UpdateDNFConfigurationTask(Task):
"""The installation task to update the dnf.conf file."""

def __init__(self, sysroot, configuration: PackagesConfigurationData):
def __init__(self, sysroot, configuration: PackagesConfigurationData, dnf_manager):
"""Create a new task.
:param sysroot: a path to the system root
Expand All @@ -386,6 +398,7 @@ def __init__(self, sysroot, configuration: PackagesConfigurationData):
super().__init__()
self._sysroot = sysroot
self._data = configuration
self._dnf_manager = dnf_manager

@property
def name(self):
Expand All @@ -405,11 +418,18 @@ def _set_option(self, option, value):
log.debug("Setting '%s' to '%s'.", option, value)

cmd = "dnf"
args = [
"config-manager",
"--save",
"--setopt={}={}".format(option, value),
]
if self._dnf_manager.is_package_available("dnf5"):
args = [
"config-manager",
"setopt",
"{}={}".format(option, value)
]
else:
args = [
"config-manager",
"--save",
"--setopt={}={}".format(option, value)
]

try:
rc = util.execWithRedirect(cmd, args, root=self._sysroot)
Expand Down
26 changes: 25 additions & 1 deletion pyanaconda/modules/payloads/payload/dnf/requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@

from pyanaconda.anaconda_loggers import get_module_logger
from pyanaconda.core.configuration.anaconda import conf
from pyanaconda.core.constants import REQUIREMENT_TYPE_PACKAGE, REQUIREMENT_TYPE_GROUP
from pyanaconda.core.constants import REQUIREMENT_TYPE_PACKAGE, REQUIREMENT_TYPE_GROUP, \
MULTILIB_POLICY_BEST
from pyanaconda.core.hw import detect_virtualized_platform
from pyanaconda.localization import find_best_locale_match, is_valid_langcode
from pyanaconda.modules.common.constants.services import LOCALIZATION, BOSS
Expand Down Expand Up @@ -80,6 +81,29 @@ def collect_language_requirements(dnf_manager):
return requirements


def collect_dnf_requirements(dnf_manager, packages_configuration):
"""Collect the requirements for the current dnf.
:param dnf_manager: a DNF manager
:param package_configuration: packages selection
:return: a list of requirements
"""
requirements = []

# Detect if dnf plugin is required
if dnf_manager.is_package_available("dnf5"):
plugins_name = "dnf5-modules"
else:
plugins_name = "dnf-plugins-core"

if packages_configuration.multilib_policy != MULTILIB_POLICY_BEST:
requirements.append(
Requirement.for_package(plugins_name, reason="Needed to enable multilib support.")
)

return requirements


def collect_platform_requirements(dnf_manager):
"""Collect the requirements for the current platform.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,8 +374,9 @@ def test_resolve(self, kernel_getter, req_getter1, req_getter2, req_getter3, req

dnf_manager = Mock()
dnf_manager.default_environment = None
data = PackagesConfigurationData()

task = ResolvePackagesTask(dnf_manager, selection)
task = ResolvePackagesTask(dnf_manager, selection, data)
task.run()

dnf_manager.clear_selection.assert_called_once_with()
Expand Down Expand Up @@ -408,7 +409,8 @@ def test_fail(self, kernel_getter, req_getter1, req_getter2, req_getter3, req_ge
dnf_manager.apply_specs.side_effect = MissingSpecsError("e2")

with pytest.raises(NonCriticalInstallationError) as cm:
task = ResolvePackagesTask(dnf_manager, selection)
data = PackagesConfigurationData()
task = ResolvePackagesTask(dnf_manager, selection, data)
task.run()

expected = "e1\n\ne2"
Expand All @@ -418,7 +420,8 @@ def test_fail(self, kernel_getter, req_getter1, req_getter2, req_getter3, req_ge
dnf_manager.resolve_selection.side_effect = InvalidSelectionError("e4")

with pytest.raises(PayloadInstallationError) as cm:
task = ResolvePackagesTask(dnf_manager, selection)
data = PackagesConfigurationData()
task = ResolvePackagesTask(dnf_manager, selection, data)
task.run()

expected = "e3\n\ne4"
Expand All @@ -433,8 +436,9 @@ def test_no_update(self, execute):
"""Don't update the DNF configuration."""
with tempfile.TemporaryDirectory() as sysroot:
data = PackagesConfigurationData()
dnf_manager = DNFManager()

task = UpdateDNFConfigurationTask(sysroot, data)
task = UpdateDNFConfigurationTask(sysroot, data, dnf_manager)
task.run()

execute.assert_not_called()
Expand All @@ -447,8 +451,9 @@ def test_failed_update(self, execute):
with tempfile.TemporaryDirectory() as sysroot:
data = PackagesConfigurationData()
data.multilib_policy = MULTILIB_POLICY_ALL
dnf_manager = DNFManager()

task = UpdateDNFConfigurationTask(sysroot, data)
task = UpdateDNFConfigurationTask(sysroot, data, dnf_manager)

with self.assertLogs(level="WARNING") as cm:
task.run()
Expand All @@ -464,8 +469,9 @@ def test_error_update(self, execute):
with tempfile.TemporaryDirectory() as sysroot:
data = PackagesConfigurationData()
data.multilib_policy = MULTILIB_POLICY_ALL
dnf_manager = DNFManager()

task = UpdateDNFConfigurationTask(sysroot, data)
task = UpdateDNFConfigurationTask(sysroot, data, dnf_manager)

with self.assertLogs(level="WARNING") as cm:
task.run()
Expand All @@ -474,15 +480,17 @@ def test_error_update(self, execute):
assert any(map(lambda x: msg in x, cm.output))

@patch("pyanaconda.core.util.execWithRedirect")
def test_multilib_policy(self, execute):
"""Update the multilib policy."""
def test_multilib_policy_dnf4(self, execute):
"""Update the multilib policy on pre-dnf5 systems."""
execute.return_value = 0

with tempfile.TemporaryDirectory() as sysroot:
data = PackagesConfigurationData()
data.multilib_policy = MULTILIB_POLICY_ALL
dnf_manager = Mock(spec=DNFManager)
dnf_manager.is_package_available.return_value = False

task = UpdateDNFConfigurationTask(sysroot, data)
task = UpdateDNFConfigurationTask(sysroot, data, dnf_manager)
task.run()

execute.assert_called_once_with(
Expand All @@ -495,6 +503,30 @@ def test_multilib_policy(self, execute):
root=sysroot
)

@patch("pyanaconda.core.util.execWithRedirect")
def test_multilib_policy_dnf5(self, execute):
"""Update the multilib policy on dnf5 systems."""
execute.return_value = 0

with tempfile.TemporaryDirectory() as sysroot:
data = PackagesConfigurationData()
data.multilib_policy = MULTILIB_POLICY_ALL
dnf_manager = Mock(spec=DNFManager)
dnf_manager.is_package_available.return_value = True

task = UpdateDNFConfigurationTask(sysroot, data, dnf_manager)
task.run()

execute.assert_called_once_with(
"dnf",
[
"config-manager",
"setopt",
"multilib_policy=all",
],
root=sysroot
)


class WriteRepositoriesTaskTestCase(unittest.TestCase):
"""Test the WriteRepositoriesTask task."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@
import unittest
from unittest.mock import Mock, patch

from pyanaconda.core.constants import REQUIREMENT_TYPE_PACKAGE, REQUIREMENT_TYPE_GROUP
from pyanaconda.core.constants import REQUIREMENT_TYPE_PACKAGE, REQUIREMENT_TYPE_GROUP, \
MULTILIB_POLICY_ALL, MULTILIB_POLICY_BEST
from pyanaconda.modules.common.constants.services import LOCALIZATION, BOSS
from pyanaconda.modules.common.structures.requirement import Requirement
from pyanaconda.modules.payloads.payload.dnf.dnf_manager import DNFManager
from pyanaconda.modules.payloads.payload.dnf.requirements import collect_language_requirements, \
collect_platform_requirements, collect_driver_disk_requirements, collect_remote_requirements, \
apply_requirements
apply_requirements, collect_dnf_requirements
from tests.unit_tests.pyanaconda_tests import patch_dbus_get_proxy_with_cache
from pyanaconda.modules.common.structures.packages import PackagesConfigurationData


class DNFRequirementsTestCase(unittest.TestCase):
Expand Down Expand Up @@ -118,6 +120,40 @@ def test_collect_platform_requirements(self, execute):

self._compare_requirements(requirements, [r1])

@patch('pyanaconda.core.hw.execWithCapture')
def test_collect_dnf_requirements(self, execute):
"""Test the function collect_dnf_requirements."""

data = PackagesConfigurationData()
data.multilib_policy = MULTILIB_POLICY_BEST
dnf_manager = Mock(spec=DNFManager)
dnf_manager.is_package_available.return_value = True

# No need for dnf config
execute.return_value = None
requirements = collect_dnf_requirements(dnf_manager, data)
assert requirements == []

data.multilib_policy = MULTILIB_POLICY_ALL

# Require dnf-5 version of plugins
dnf_manager.is_package_available.return_value = True
requirements = collect_dnf_requirements(dnf_manager, data)
r = self._create_requirement(
name="dnf5-modules",
reason="Needed to enable multilib support."
)
self._compare_requirements(requirements, [r])

# Require dnf-4 version of plugins
dnf_manager.is_package_available.return_value = False
requirements = collect_dnf_requirements(dnf_manager, data)
r = self._create_requirement(
name="dnf-plugins-core",
reason="Needed to enable multilib support."
)
self._compare_requirements(requirements, [r])

def test_collect_driver_disk_requirements(self):
"""Test the function collect_driver_disk_requirements."""
requirements = collect_driver_disk_requirements("/non/existent/file")
Expand Down

0 comments on commit 6f39cb0

Please sign in to comment.