Skip to content

Commit

Permalink
[RHELC-1332] Port PkgManagerConf() to the Action framework (#1321)
Browse files Browse the repository at this point in the history
* [RHELC-1332] Port PkgManagerConf() to the Action framework

* Add pkg manager config and unit test

* Apply suggestions from code review

Co-authored-by: Adam Hošek <[email protected]>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Draft PkgManagerConf changes

* Updated PkgManagerConfig structure to use instance variable

* Apply suggestions from code review

Co-authored-by: Freya Gustavsson <[email protected]>
Co-authored-by: Rodolfo Olivieri <[email protected]>

* Update class name

* Add dependency

* Add mocks for write_altered_pkg_manager_conf

* Remove PosixPath from unit test

* Remove sys import

* Update convert2rhel/redhatrelease.py

Co-authored-by: Rodolfo Olivieri <[email protected]>

* Fix changed-yum int test.

---------

Co-authored-by: Adam Hošek <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Freya Gustavsson <[email protected]>
Co-authored-by: Rodolfo Olivieri <[email protected]>
Co-authored-by: Rodolfo Olivieri <[email protected]>
  • Loading branch information
6 people authored Aug 15, 2024
1 parent 80333d9 commit 7c12aa8
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 57 deletions.
39 changes: 39 additions & 0 deletions convert2rhel/actions/conversion/pkg_manager_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright(C) 2024 Red Hat, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

__metaclass__ = type

import logging

from convert2rhel import actions, redhatrelease


logger = logging.getLogger(__name__)


class ConfigurePkgManager(actions.Action):
id = "CONFIGURE_PKG_MANAGER"
dependencies = ("CONVERT_SYSTEM_PACKAGES",)

def run(self):
"""
Check if the distroverpkg tag inside the package manager config has been modified before the conversion and if so
comment it out and write to the file.
"""
super(ConfigurePkgManager, self).run()

logger.task("Convert: Patch package manager configuration file")
pmc = redhatrelease.PkgManagerConf()
pmc.patch()
3 changes: 1 addition & 2 deletions convert2rhel/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,7 @@ def post_ponr_changes():

def post_ponr_conversion():
"""Perform main steps for system conversion."""
loggerinst.task("Convert: Patch yum configuration file")
redhatrelease.YumConf().patch()

loggerinst.task("Convert: Lock releasever in RHEL repositories")
subscription.lock_releasever_in_rhel_repositories()

Expand Down
49 changes: 30 additions & 19 deletions convert2rhel/redhatrelease.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,45 +49,56 @@ def get_system_release_content():
loggerinst.critical("%s\n%s file is essential for running this tool." % (err, filepath))


class YumConf:
_yum_conf_path = "/etc/yum.conf"
class PkgManagerConf:
"""
Check if the config file of the systems package manager has been modified and if it has then
remove those changes before the conversion completes.
.. note::
The pkg manager config file path only needs to be set to yum.conf as there is a symlink between yum and dnf.
This means that on dnf systems the dnf.conf will be modified even the path is for the yum.conf.
"""

def __init__(self):
self._yum_conf_content = utils.get_file_content(self._yum_conf_path)
_pkg_manager_conf_path = "" # type: str
_pkg_manager_conf_content = "" # type: str

def __init__(self, config_path=None): # type: (str|None) -> None
if not config_path:
self._pkg_manager_conf_path = "/etc/yum.conf" if pkgmanager.TYPE == "yum" else "/etc/dnf/dnf.conf"
else:
self._pkg_manager_conf_path = config_path
self._pkg_manager_conf_content = utils.get_file_content(self._pkg_manager_conf_path)

def patch(self):
"""Comment out the distroverpkg variable in yum.conf so yum can determine
release version ($releasever) based on the installed redhat-release
package.
"""
if YumConf.is_modified():
# When the user touches the yum.conf before executing the conversion, then during the conversion yum as a
if self.is_modified():
# When the user touches the yum/dnf config before executing the conversion, then during the conversion yum/dmf as a
# package is replaced but this config file is left unchanged and it keeps the original distroverpkg setting.
self._comment_out_distroverpkg_tag()
self._write_altered_yum_conf()
loggerinst.info("%s patched." % self._yum_conf_path)
self._write_altered_pkg_manager_conf()
loggerinst.info("%s patched." % self._pkg_manager_conf_path)
else:
loggerinst.info("Skipping patching, yum configuration file not modified")
loggerinst.info("Skipping patching, package manager configuration file has not been modified.")

return

def _comment_out_distroverpkg_tag(self):
if re.search(r"^distroverpkg=", self._yum_conf_content, re.MULTILINE):
self._yum_conf_content = re.sub(r"\n(distroverpkg=).*", r"\n#\1", self._yum_conf_content)
if re.search(r"^distroverpkg=", self._pkg_manager_conf_content, re.MULTILINE):
self._pkg_manager_conf_content = re.sub(r"\n(distroverpkg=).*", r"\n#\1", self._pkg_manager_conf_content)

def _write_altered_yum_conf(self):
with open(self._yum_conf_path, "w") as file_to_write:
file_to_write.write(self._yum_conf_content)
def _write_altered_pkg_manager_conf(self):
with open(self._pkg_manager_conf_path, "w") as file_to_write:
file_to_write.write(self._pkg_manager_conf_content)

@staticmethod
def is_modified():
def is_modified(self):
"""Return true if the YUM/DNF configuration file has been modified by the user."""
conf = "/etc/yum.conf" if pkgmanager.TYPE == "yum" else "/etc/dnf/dnf.conf"

output, _ = utils.run_subprocess(["rpm", "-Vf", conf], print_output=False)
output, _ = utils.run_subprocess(["rpm", "-Vf", self._pkg_manager_conf_path], print_output=False)
# rpm -Vf does not return information about the queried file but about all files owned by the rpm
# that owns the queried file. Character '5' on position 3 means that the file was modified.
return True if re.search(r"^.{2}5.*? %s$" % conf, output, re.MULTILINE) else False
return True if re.search(r"^.{2}5.*? %s$" % self._pkg_manager_conf_path, output, re.MULTILINE) else False


# Code to be executed upon module import
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright(C) 2024 Red Hat, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

__metaclass__ = type
import pytest
import six

from convert2rhel import redhatrelease
from convert2rhel.actions.conversion import pkg_manager_config


six.add_move(six.MovedModule("mock", "mock", "unittest.mock"))
from six.moves import mock


@pytest.fixture
def pkg_manager_config_instance():
return pkg_manager_config.ConfigurePkgManager()


def test_pkg_manager_config(pkg_manager_config_instance, monkeypatch):
redhat_release_mock = mock.Mock()
monkeypatch.setattr(redhatrelease.PkgManagerConf, "patch", redhat_release_mock)
pkg_manager_config_instance.run()

assert redhat_release_mock.call_count == 1
3 changes: 0 additions & 3 deletions convert2rhel/unit_tests/main_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,15 +208,12 @@ def test_show_eula_nonexisting_file(self, caplog, monkeypatch, tmpdir):

def test_post_ponr_conversion(monkeypatch):
post_ponr_set_efi_configuration_mock = mock.Mock()
yum_conf_patch_mock = mock.Mock()
lock_releasever_in_rhel_repositories_mock = mock.Mock()

monkeypatch.setattr(redhatrelease.YumConf, "patch", yum_conf_patch_mock)
monkeypatch.setattr(subscription, "lock_releasever_in_rhel_repositories", lock_releasever_in_rhel_repositories_mock)

main.post_ponr_conversion()

assert yum_conf_patch_mock.call_count == 1
assert lock_releasever_in_rhel_repositories_mock.call_count == 1


Expand Down
67 changes: 36 additions & 31 deletions convert2rhel/unit_tests/redhatrelease_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@

from convert2rhel import unit_tests # Imports unit_tests/__init__.py
from convert2rhel import pkgmanager, redhatrelease, systeminfo, utils
from convert2rhel.redhatrelease import YumConf, get_system_release_filepath
from convert2rhel.redhatrelease import PkgManagerConf, get_system_release_filepath
from convert2rhel.systeminfo import system_info


YUM_CONF_WITHOUT_DISTROVERPKG = """[main]
PKG_MANAGER_CONF_WITHOUT_DISTROVERPKG = """[main]
installonly_limit=3
# This is the default"""

YUM_CONF_WITH_DISTROVERPKG = """[main]
PKG_MANAGER_CONF_WITH_DISTROVERPKG = """[main]
installonly_limit=3
distroverpkg=centos-release
Expand All @@ -48,40 +48,42 @@
SUPPORTED_RHEL_VERSIONS = [7, 8]


def test_get_yum_conf_content(monkeypatch):
monkeypatch.setattr(redhatrelease.YumConf, "_yum_conf_path", unit_tests.DUMMY_FILE)
@pytest.fixture()
def pkg_manager_conf_instance():
return PkgManagerConf()

yum_conf = redhatrelease.YumConf()

assert "Dummy file to read" in yum_conf._yum_conf_content
def test_get_pkg_manager_conf_content(monkeypatch):
pkg_manager_conf = redhatrelease.PkgManagerConf(config_path=unit_tests.DUMMY_FILE)
assert "Dummy file to read" in pkg_manager_conf._pkg_manager_conf_content


@pytest.mark.parametrize("version", SUPPORTED_RHEL_VERSIONS)
def test_patch_yum_conf_missing_distroverpkg(version, monkeypatch):
monkeypatch.setattr(redhatrelease.YumConf, "_yum_conf_path", unit_tests.DUMMY_FILE)
def test_patch_pkg_manager_conf_missing_distroverpkg(version, monkeypatch, pkg_manager_conf_instance):

monkeypatch.setattr(system_info, "version", version)
yum_conf = redhatrelease.YumConf()
yum_conf._yum_conf_content = YUM_CONF_WITHOUT_DISTROVERPKG
pkg_manager_conf = pkg_manager_conf_instance
pkg_manager_conf._pkg_manager_conf_content = PKG_MANAGER_CONF_WITHOUT_DISTROVERPKG

# Call just this function to avoid unmockable built-in write func
yum_conf._comment_out_distroverpkg_tag()
pkg_manager_conf._comment_out_distroverpkg_tag()

assert "distroverpkg=" not in yum_conf._yum_conf_content
assert yum_conf._yum_conf_content.count("distroverpkg=") == 0
assert "distroverpkg=" not in pkg_manager_conf._pkg_manager_conf_content
assert pkg_manager_conf._pkg_manager_conf_content.count("distroverpkg=") == 0


@pytest.mark.parametrize("version", SUPPORTED_RHEL_VERSIONS)
def test_patch_yum_conf_existing_distroverpkg(version, monkeypatch):
monkeypatch.setattr(redhatrelease.YumConf, "_yum_conf_path", unit_tests.DUMMY_FILE)
def test_patch_pkg_manager_conf_existing_distroverpkg(version, monkeypatch, pkg_manager_conf_instance):

monkeypatch.setattr(system_info, "version", systeminfo.Version(version, 0))
yum_conf = redhatrelease.YumConf()
yum_conf._yum_conf_content = YUM_CONF_WITH_DISTROVERPKG
pkg_manager_conf = pkg_manager_conf_instance
pkg_manager_conf._pkg_manager_conf_content = PKG_MANAGER_CONF_WITH_DISTROVERPKG

# Call just this function to avoid unmockable built-in write func
yum_conf._comment_out_distroverpkg_tag()
pkg_manager_conf._comment_out_distroverpkg_tag()

assert "#distroverpkg=" in yum_conf._yum_conf_content
assert yum_conf._yum_conf_content.count("#distroverpkg=") == 1
assert "#distroverpkg=" in pkg_manager_conf._pkg_manager_conf_content
assert pkg_manager_conf._pkg_manager_conf_content.count("#distroverpkg=") == 1


@pytest.mark.parametrize(
Expand All @@ -96,36 +98,39 @@ def test_patch_yum_conf_existing_distroverpkg(version, monkeypatch):
("unknown", "anything", False),
),
)
def test_yum_is_modified(monkeypatch, pkg_type, subprocess_ret, expected_result):
def test_pkg_manager_is_modified(monkeypatch, pkg_type, subprocess_ret, expected_result):
monkeypatch.setattr(pkgmanager, "TYPE", value=pkg_type)

run_subprocess = unit_tests.RunSubprocessMocked(return_string=subprocess_ret)
monkeypatch.setattr(utils, "run_subprocess", value=run_subprocess)
pkg_manager_conf = redhatrelease.PkgManagerConf()

assert YumConf.is_modified() == expected_result
assert pkg_manager_conf.is_modified() == expected_result


@pytest.mark.parametrize("modified", (True, False))
def test_yum_patch(monkeypatch, modified, caplog):
def test_pkg_manager_patch(monkeypatch, modified, caplog, tmp_path):
is_modified = mock.Mock(return_value=modified)
monkeypatch.setattr(YumConf, "is_modified", value=is_modified)
monkeypatch.setattr(PkgManagerConf, "is_modified", value=is_modified)
_comment_out_distroverpkg_tag = mock.Mock()
monkeypatch.setattr(
YumConf,
PkgManagerConf,
"_comment_out_distroverpkg_tag",
value=_comment_out_distroverpkg_tag,
)
_write_altered_yum_conf = mock.Mock()
monkeypatch.setattr(YumConf, "_write_altered_yum_conf", value=_write_altered_yum_conf)

YumConf().patch()
monkeypatch.setattr(
PkgManagerConf,
"_pkg_manager_conf_path",
value=tmp_path,
)

PkgManagerConf(config_path=str(tmp_path / "yum.conf")).patch()
if modified:
_comment_out_distroverpkg_tag.assert_called_once()
assert "patched" in caplog.text
else:
_comment_out_distroverpkg_tag.assert_not_called()
assert "Skipping patching, yum configuration file not modified" in caplog.text
assert "Skipping patching, package manager configuration file has not been modified" in caplog.text


@pytest.mark.parametrize(("is_file", "exception"), ((True, False), (False, True)))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from conftest import TEST_VARS
from conftest import TEST_VARS, SystemInformationRelease


def test_yum_conf_patch(convert2rhel, shell):
Expand All @@ -9,6 +9,9 @@ def test_yum_conf_patch(convert2rhel, shell):
expanding the $releasever variable properly.
"""
shell("echo '#random text' >> /etc/yum.conf")
pkgmanager_conf = "/etc/yum.conf"
if SystemInformationRelease.version.major >= 8:
pkgmanager_conf = "/etc/dnf/dnf.conf"

with convert2rhel(
"-y --serverurl {} --username {} --password {} --pool {} --debug".format(
Expand All @@ -18,7 +21,7 @@ def test_yum_conf_patch(convert2rhel, shell):
TEST_VARS["RHSM_POOL"],
)
) as c2r:
c2r.expect("/etc/yum.conf patched.")
c2r.expect("{} patched.".format(pkgmanager_conf))
assert c2r.exitstatus == 0

# The tsflags will prevent updating the RHEL-8.5 versions to RHEL-8.6
Expand Down

0 comments on commit 7c12aa8

Please sign in to comment.