tools/osbuild-depsolve-dnf: load repos from dir

Support loading repositories from a root tree instead of supplying them
with the request.  The repositories should be in the standard yum repo
format.  Both repository sources can be defined simultaneously, but at
least one is required.

The root_dir is expected to contain files necessary for depsolving in
the standard paths.

These files are:
- Repository (.repo) configurations in <root_dir>/etc/yum.repos.d/
- GPG key files in <root_dir>/etc/pki/rpm-gpg/
    - This will be used to resolve gpg key paths specified in the .repo
      files that are relative to the root_dir.
- (Optional) Custom dnf config variables in <root_dir>/etc/dnf/vars or
  <root_dir>/etc/yum/vars.
    - This is used by CentOS Stream to set the value of $stream.

Custom repository configurations in arbitrary (non-root) paths will have
to follow this directory structure.

A new variable is added to the request, `releasever`, which is mandatory
when using `root_dir`.  This variable is used in repository URLs and GPG
key paths.  In the default case, dnf reads this variable by inspecting
the rpm database.  We will override it in the Solver the same way we
override the arch and basearch for variable substitution.  In the
future, we will make this variable mandatory in all cases, which will
make the variable available for repo configs defined in the request as
well.

The root_dir is used in two ways:
- Set the base.conf.reposdir to <root_dir>/etc/yum.repos.d.
- Call update_from_etc() with root_dir to read custom variables in
  <root_dir>/etc/yum/vars and <root_dir>/etc/dnf/vars.
This commit is contained in:
Achilleas Koutsou 2024-03-20 13:45:18 +01:00 committed by Brian C. Lane
parent 06c8aca871
commit 38f5964205

View file

@ -20,8 +20,15 @@ import hawkey
class Solver():
# pylint: disable=too-many-arguments
def __init__(self, repos, module_platform_id, persistdir, cachedir, arch):
def __init__(self, request, persistdir, cache_dir):
arch = request["arch"]
releasever = request.get("releasever")
module_platform_id = request["module_platform_id"]
arguments = request["arguments"]
repos = arguments.get("repos", [])
root_dir = arguments.get("root_dir")
self.base = dnf.Base()
# Enable fastestmirror to ensure we choose the fastest mirrors for
@ -49,12 +56,24 @@ class Solver():
self.base.conf.module_platform_id = module_platform_id
self.base.conf.config_file_path = "/dev/null"
self.base.conf.persistdir = persistdir
self.base.conf.cachedir = cachedir
self.base.conf.cachedir = cache_dir
self.base.conf.substitutions['arch'] = arch
self.base.conf.substitutions['basearch'] = dnf.rpm.basearch(arch)
if releasever:
self.base.conf.substitutions['releasever'] = releasever
for repo in repos:
self.base.repos.add(self._dnfrepo(repo, self.base.conf))
if root_dir:
# This sets the varsdir to ("{root_dir}/etc/yum/vars/", "{root_dir}/etc/dnf/vars/") for custom variable
# substitution (e.g. CentOS Stream 9's $stream variable)
self.base.conf.substitutions.update_from_etc(root_dir)
repos_dir = os.path.join(root_dir, "etc/yum.repos.d")
self.base.conf.reposdir = repos_dir
self.base.read_all_repos()
self.base.fill_sack(load_system_repo=False)
# pylint: disable=too-many-branches
@ -271,20 +290,12 @@ def setup_cachedir(request):
def solve(request, cache_dir):
command = request["command"]
arch = request["arch"]
module_platform_id = request["module_platform_id"]
arguments = request["arguments"]
transactions = arguments.get("transactions")
with tempfile.TemporaryDirectory() as persistdir:
try:
solver = Solver(
arguments["repos"],
module_platform_id,
persistdir,
cache_dir,
arch
)
solver = Solver(request, persistdir, cache_dir)
if command == "dump":
result = solver.dump()
elif command == "depsolve":
@ -336,6 +347,7 @@ def respond(result):
print(json.dumps(result))
# pylint: disable=too-many-return-statements
def validate_request(request):
command = request.get("command")
valid_cmds = ("depsolve", "dump", "search")
@ -356,6 +368,7 @@ def validate_request(request):
"kind": "InvalidRequest",
"reason": "no 'module_platform_id' specified"
}
arguments = request.get("arguments")
if not arguments:
return {
@ -363,10 +376,20 @@ def validate_request(request):
"reason": "empty 'arguments'"
}
if not arguments.get("repos"):
if not arguments.get("repos") and not arguments.get("root_dir"):
return {
"kind": "InvalidRequest",
"reason": "no 'repos' specified"
"reason": "no 'repos' or 'root_dir' specified"
}
# if root_dir is used, we also need releasever
# pylint: disable=fixme
# TODO: Make releasever mandatory in all cases.
# We temporarily keep it tied to root_dir for short-term backwards compatibility
if arguments.get("root_dir") and not request.get("releasever"):
return {
"kind": "InvalidRequest",
"reason": "'root_dir' requires setting 'releasever'"
}
return None