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"]