From 16a66f358879559d2366a71fbfc3b2f506ce6944 Mon Sep 17 00:00:00 2001 From: Mattia Verga Date: Sun, 8 Dec 2024 18:34:00 +0100 Subject: [PATCH] Use libdnf5 python bindings for repo sanity check if available Signed-off-by: Mattia Verga --- bodhi-server/bodhi-server.spec | 3 ++ bodhi-server/bodhi/server/util.py | 80 ++++++++++++++++++++++--------- bodhi-server/pyproject.toml | 1 + bodhi-server/tests/test_util.py | 22 ++++++++- devel/ci/Dockerfile-f41 | 1 + devel/ci/Dockerfile-pip | 1 + devel/ci/Dockerfile-rawhide | 1 + 7 files changed, 85 insertions(+), 24 deletions(-) diff --git a/bodhi-server/bodhi-server.spec b/bodhi-server/bodhi-server.spec index 944960b408..45a2912c46 100644 --- a/bodhi-server/bodhi-server.spec +++ b/bodhi-server/bodhi-server.spec @@ -26,6 +26,9 @@ BuildRequires: python3-pytest-mock BuildRequires: python3-sphinx BuildRequires: python3-responses BuildRequires: python3-webtest +%if 0%{?fedora} && 0%{?fedora} >= 41 +BuildRequires: python3-libdnf5 >= 5.2 +%endif BuildRequires: python3-librepo BuildRequires: python3dist(libcomps) >= 0.1.20 BuildRequires: python3dist(poetry-core) >= 1 diff --git a/bodhi-server/bodhi/server/util.py b/bodhi-server/bodhi/server/util.py index 581323ad29..7778e0f4fa 100644 --- a/bodhi-server/bodhi/server/util.py +++ b/bodhi-server/bodhi/server/util.py @@ -59,6 +59,11 @@ from bodhi.server.config import config from bodhi.server.exceptions import RepodataException +try: + import libdnf5 + use_libdnf5 = True +except ImportError: + use_libdnf5 = False _ = TranslationStringFactory('bodhi') @@ -354,29 +359,36 @@ def sanity_check_repodata(myurl, repo_type, drpms=True): if not ret: raise RepodataException('updateinfo.xml.gz contains empty ID tags') - # Now call out to DNF to check if the repo is usable - # "tests" is a list of tuples with (dnf args, expected output) to run. - # For every test, DNF is run with the arguments, and if the expected output is not found, - # an error is raised. - tests = [] - - if repo_type in ('yum', 'source'): - tests.append((['list', '--available'], 'testrepo')) - else: # repo_type == 'module', verified above - tests.append((['module', 'list'], '.*')) - - for test in tests: - dnfargs, expout = test - - # Make sure every DNF test runs in a new temp dir - testdir = tempfile.mkdtemp(dir=tmpdir) - output = sanity_check_repodata_dnf(testdir, myurl, *dnfargs) - if (expout == ".*" and len(output.strip()) != 0) or (expout in output): - continue - else: - raise RepodataException( - "DNF did not return expected output when running test!" - + f" Test: {dnfargs}, expected: {expout}, output: {output}") + if use_libdnf5: + try: + testdir = tempfile.mkdtemp(dir=tmpdir) + load_repo_libdnf5(testdir, myurl) + except Exception as e: + raise RepodataException(f'Error loading the repository: {e}') + else: + # Now call out to DNF to check if the repo is usable + # "tests" is a list of tuples with (dnf args, expected output) to run. + # For every test, DNF is run with the arguments, and if the expected output + # is not found, an error is raised. + tests = [] + + if repo_type in ('yum', 'source'): + tests.append((['list', '--available'], 'testrepo')) + else: # repo_type == 'module', verified above + tests.append((['module', 'list'], '.*')) + + for test in tests: + dnfargs, expout = test + + # Make sure every DNF test runs in a new temp dir + testdir = tempfile.mkdtemp(dir=tmpdir) + output = sanity_check_repodata_dnf(testdir, myurl, *dnfargs) + if (expout == ".*" and len(output.strip()) != 0) or (expout in output): + continue + else: + raise RepodataException( + "DNF did not return expected output when running test!" + + f" Test: {dnfargs}, expected: {expout}, output: {output}") def sanity_check_repodata_dnf(tempdir, myurl, *dnf_args): @@ -406,6 +418,28 @@ def sanity_check_repodata_dnf(tempdir, myurl, *dnf_args): return subprocess.check_output(cmd, encoding='utf-8', stderr=subprocess.STDOUT) +def load_repo_libdnf5(tempdir, myurl): + """ + Use libdnf5 python bindings to try to load a repository. + + Args: + tempdir (str): Temporary directory for libdnf cache. + myurl (str): A path to a repodata directory. + Raises: + Exception: If the repodata is not valid or does not exist. + """ + base = libdnf5.base.Base() + base_config = base.get_config() + base_config.plugins = False + base_config.cachedir = tempdir + base.setup() + repo_sack = base.get_repo_sack() + repo = repo_sack.create_repo("testrepo") + repo.get_config().baseurl = myurl + repo_sack.load_repos(libdnf5.repo.Repo.Type_AVAILABLE) + return True + + def age(context, date, only_distance=False): """ Return a human readable age since the given date. diff --git a/bodhi-server/pyproject.toml b/bodhi-server/pyproject.toml index a6a69dd72f..085619c3c3 100644 --- a/bodhi-server/pyproject.toml +++ b/bodhi-server/pyproject.toml @@ -102,6 +102,7 @@ Markdown = ">=3.3.6" munch = ">=2.5.0" koji = ">=1.27.1" libcomps ="^0.1.20" +libdnf5 = {version = "^5.2", optional = true} packaging = ">=21.3" prometheus-client = ">=0.13.1" psycopg2 = ">=2.8.6" diff --git a/bodhi-server/tests/test_util.py b/bodhi-server/tests/test_util.py index 38b22f7b9e..cc0196815f 100644 --- a/bodhi-server/tests/test_util.py +++ b/bodhi-server/tests/test_util.py @@ -22,6 +22,7 @@ import os import shutil import subprocess +import sys import tempfile from munch import munchify @@ -465,6 +466,17 @@ def test_correct_yum_repo_with_xz_compress(self): # No exception should be raised here. util.sanity_check_repodata(self.tempdir, repo_type='yum', drpms=True) + @mock.patch('bodhi.server.util.load_repo_libdnf5', side_effect=Exception("Exception message")) + def test_invalid_repo_exception(self, *args): + """An exception should be raised if repo data is invalid.""" + pytest.importorskip('libdnf5', reason='This tests correct behavior with libdnf5 ' + 'which is not installed') + base.mkmetadatadir(self.tempdir, compress_type='xz') + + with pytest.raises(util.RepodataException) as exc: + util.sanity_check_repodata(self.tempdir, repo_type='yum', drpms=True) + assert str(exc.value) == "Error loading the repository: Exception message" + def test_correct_yum_repo_with_gz_compress(self): """No Exception should be raised if the repo is normal. @@ -535,6 +547,10 @@ def _mkmetadatadir_w_modules(self): root.remove(data) repomd_tree.write(repomd_path, encoding='UTF-8', xml_declaration=True) + @pytest.mark.skipif( + "libdnf5" in sys.modules, + reason='This can only be tested if lidnf5 is not installed' + ) @mock.patch('subprocess.check_output', return_value='Some output') def test_correct_module_repo(self, *args): """No Exception should be raised if the repo is a normal module repo.""" @@ -542,9 +558,13 @@ def test_correct_module_repo(self, *args): # No exception should be raised here. util.sanity_check_repodata(self.tempdir, repo_type='module', drpms=True) + @pytest.mark.skipif( + "libdnf5" in sys.modules, + reason='This can only be tested if lidnf5 is not installed' + ) @mock.patch('subprocess.check_output', return_value='') def test_module_repo_no_dnf_output(self, *args): - """No Exception should be raised if the repo is a normal module repo.""" + """An Exception should be raised if the repo is invalid module repo.""" self._mkmetadatadir_w_modules() with pytest.raises(util.RepodataException) as exc: diff --git a/devel/ci/Dockerfile-f41 b/devel/ci/Dockerfile-f41 index 902a745f64..57b9ce1d22 100644 --- a/devel/ci/Dockerfile-f41 +++ b/devel/ci/Dockerfile-f41 @@ -34,6 +34,7 @@ RUN dnf --best install -y \ python3-jinja2 \ python3-koji \ python3-libcomps \ + python3-libdnf5 \ python3-librepo \ python3-markdown \ python3-munch \ diff --git a/devel/ci/Dockerfile-pip b/devel/ci/Dockerfile-pip index 846825b754..c7b4986b39 100644 --- a/devel/ci/Dockerfile-pip +++ b/devel/ci/Dockerfile-pip @@ -17,6 +17,7 @@ RUN dnf install -y \ poetry \ postgresql-devel \ python3-devel \ + python3-libdnf5 \ python3-librepo \ redhat-rpm-config \ python3-libcomps \ diff --git a/devel/ci/Dockerfile-rawhide b/devel/ci/Dockerfile-rawhide index a36dcd8f10..e580b56ae1 100644 --- a/devel/ci/Dockerfile-rawhide +++ b/devel/ci/Dockerfile-rawhide @@ -34,6 +34,7 @@ RUN dnf --best install -y \ python3-jinja2 \ python3-koji \ python3-libcomps \ + python3-libdnf5 \ python3-librepo \ python3-markdown \ python3-munch \