From d068c6d91f670d39446865b0e368c33d79adbfcb Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Mon, 12 May 2025 12:49:35 +0200 Subject: [PATCH] dnfjson: detect/error if no repositories are defined This commit adds an error message if no repositories are defined in the dnfjson query. We had the issue in https://github.com/osbuild/bootc-image-builder/issues/922 that in a RHEL bootc-container no repositories are defined. Here the error is quite confusing, as it complains about error marking packages which is technically correct but hides the root of the problem. With this detect we can construct a more useful error message in the higher layers. --- osbuild/solver/__init__.py | 4 ++++ osbuild/solver/dnf.py | 13 ++++++++++++- osbuild/solver/dnf5.py | 10 ++++++++++ tools/osbuild-depsolve-dnf | 7 ++++++- tools/test/test_depsolve.py | 25 +++++++++++++++++++++++++ 5 files changed, 57 insertions(+), 2 deletions(-) diff --git a/osbuild/solver/__init__.py b/osbuild/solver/__init__.py index 64fd2778..70ec264d 100755 --- a/osbuild/solver/__init__.py +++ b/osbuild/solver/__init__.py @@ -40,6 +40,10 @@ class RepoError(SolverException): pass +class NoReposError(SolverException): + pass + + class MarkingError(SolverException): pass diff --git a/osbuild/solver/dnf.py b/osbuild/solver/dnf.py index 5918e712..3fc01872 100755 --- a/osbuild/solver/dnf.py +++ b/osbuild/solver/dnf.py @@ -11,7 +11,15 @@ from typing import Dict, List import dnf import hawkey -from osbuild.solver import DepsolveError, MarkingError, RepoError, SolverBase, modify_rootdir_path, read_keys +from osbuild.solver import ( + DepsolveError, + MarkingError, + NoReposError, + RepoError, + SolverBase, + modify_rootdir_path, + read_keys, +) from osbuild.util.sbom.dnf import dnf_pkgset_to_sbom_pkgset from osbuild.util.sbom.spdx import sbom_pkgset_to_spdx2_doc @@ -92,6 +100,9 @@ class DNF(SolverBase): except dnf.exceptions.Error as e: raise RepoError(e) from e + if not self.base.repos._any_enabled(): + raise NoReposError("There are no enabled repositories") + # enable module resolving self.base_module = dnf.module.module_base.ModuleBase(self.base) diff --git a/osbuild/solver/dnf5.py b/osbuild/solver/dnf5.py index 3a6ed401..5b75bcfe 100755 --- a/osbuild/solver/dnf5.py +++ b/osbuild/solver/dnf5.py @@ -14,6 +14,7 @@ from libdnf5.common import QueryCmp_GLOB as GLOB from osbuild.solver import ( DepsolveError, MarkingError, + NoReposError, RepoError, SolverBase, modify_rootdir_path, @@ -48,6 +49,12 @@ def _invert(dct): return {v: k for k in dct for v in dct[k]} +def any_repos_enabled(base): + """Return true if any repositories are enabled""" + rq = dnf5.repo.RepoQuery(base) + return rq.begin() != rq.end() + + class DNF5(SolverBase): """Solver implements package related actions @@ -168,6 +175,9 @@ class DNF5(SolverBase): except RuntimeError as e: raise RepoError(e) from e + if not any_repos_enabled(self.base): + raise NoReposError("There are no enabled repositories") + # Custom license index file path use for SBOM generation self.license_index_path = license_index_path diff --git a/tools/osbuild-depsolve-dnf b/tools/osbuild-depsolve-dnf index a38fc250..4c6b81a9 100755 --- a/tools/osbuild-depsolve-dnf +++ b/tools/osbuild-depsolve-dnf @@ -13,7 +13,7 @@ import os.path import sys import tempfile -from osbuild.solver import GPGKeyReadError, MarkingError, DepsolveError, RepoError, InvalidRequestError +from osbuild.solver import GPGKeyReadError, MarkingError, DepsolveError, NoReposError, RepoError, InvalidRequestError # Load the solver configuration config = {"use_dnf5": False} @@ -77,6 +77,11 @@ def solve(request, cache_dir): "kind": "RepoError", "reason": f"There was a problem reading a repository: {e}" } + except NoReposError as e: + return None, { + "kind": "NoReposError", + "reason": f"There was a problem finding repositories: {e}" + } except MarkingError as e: printe("error install_specs") return None, { diff --git a/tools/test/test_depsolve.py b/tools/test/test_depsolve.py index 8727c4ba..348ff3d5 100644 --- a/tools/test/test_depsolve.py +++ b/tools/test/test_depsolve.py @@ -1865,3 +1865,28 @@ def test_depsolve_result_api(tmp_path, repo_servers): "data", "path", ] + + +@pytest.mark.parametrize("dnf_config, detect_fn", [ + ({}, assert_dnf), + ({"use_dnf5": False}, assert_dnf), + ({"use_dnf5": True}, assert_dnf5), +], ids=["no-config", "dnf4", "dnf5"]) +def test_depsolve_no_repos(tmp_path, dnf_config, detect_fn): + try: + detect_fn() + except RuntimeError as e: + pytest.skip(str(e)) + + transactions = [ + { + "package-specs": [ + "filesystem", + "pkg-with-no-deps" + ], + }, + ] + res, exit_code = depsolve(transactions, tmp_path.as_posix(), dnf_config, root_dir=tmp_path.as_posix()) + assert exit_code == 1 + assert res["kind"] == "NoReposError" + assert "There are no enabled repositories" in res["reason"]