|
Packit |
a20ca0 |
#!/usr/bin/python3
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
"""Manifest-Pre-Processor - Depsolving
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
This manifest-pre-processor consumes a manifest on stdin, processes it, and
|
|
Packit |
a20ca0 |
produces the resulting manifest on stdout.
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
This tool adjusts the `org.osbuild.rpm` stage. It consumes the `mpp-depsolve`
|
|
Packit |
a20ca0 |
option and produces a package-list and source-entries.
|
|
Packit |
a20ca0 |
|
|
Packit Service |
4f7b0c |
It supports version "1" and version "2" of the manifest description format.
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
The parameters for this pre-processor, version "1", look like this:
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
```
|
|
Packit |
a20ca0 |
...
|
|
Packit |
a20ca0 |
{
|
|
Packit |
a20ca0 |
"name": "org.osbuild.rpm",
|
|
Packit |
a20ca0 |
...
|
|
Packit |
a20ca0 |
"options": {
|
|
Packit |
a20ca0 |
...
|
|
Packit |
a20ca0 |
"mpp-depsolve": {
|
|
Packit |
a20ca0 |
"architecture": "x86_64",
|
|
Packit |
a20ca0 |
"module-platform-id": "f32",
|
|
Packit |
a20ca0 |
"baseurl": "http://mirrors.kernel.org/fedora/releases/32/Everything/x86_64/os",
|
|
Packit |
a20ca0 |
"repos": [
|
|
Packit |
a20ca0 |
{
|
|
Packit |
a20ca0 |
"id": "default",
|
|
Packit |
a20ca0 |
"metalink": "https://mirrors.fedoraproject.org/metalink?repo=fedora-32&arch=$basearch"
|
|
Packit |
a20ca0 |
}
|
|
Packit |
a20ca0 |
],
|
|
Packit |
a20ca0 |
"packages": [
|
|
Packit |
a20ca0 |
"@core",
|
|
Packit |
a20ca0 |
"dracut-config-generic",
|
|
Packit |
a20ca0 |
"grub2-pc",
|
|
Packit |
a20ca0 |
"kernel"
|
|
Packit |
a20ca0 |
],
|
|
Packit |
a20ca0 |
"excludes": [
|
|
Packit |
a20ca0 |
(optional excludes)
|
|
Packit |
a20ca0 |
]
|
|
Packit |
a20ca0 |
}
|
|
Packit |
a20ca0 |
}
|
|
Packit |
a20ca0 |
}
|
|
Packit |
a20ca0 |
...
|
|
Packit |
a20ca0 |
```
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
The parameters for this pre-processor, version "2", look like this:
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
```
|
|
Packit Service |
4f7b0c |
...
|
|
Packit Service |
4f7b0c |
{
|
|
Packit Service |
4f7b0c |
"name": "org.osbuild.rpm",
|
|
Packit Service |
4f7b0c |
...
|
|
Packit Service |
4f7b0c |
"inputs": {
|
|
Packit Service |
4f7b0c |
packages: {
|
|
Packit Service |
4f7b0c |
"mpp-depsolve": {
|
|
Packit Service |
4f7b0c |
see above
|
|
Packit Service |
4f7b0c |
}
|
|
Packit Service |
4f7b0c |
}
|
|
Packit Service |
4f7b0c |
}
|
|
Packit Service |
4f7b0c |
}
|
|
Packit Service |
4f7b0c |
...
|
|
Packit Service |
4f7b0c |
```
|
|
Packit |
a20ca0 |
"""
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
import argparse
|
|
Packit |
a20ca0 |
import contextlib
|
|
Packit |
a20ca0 |
import json
|
|
Packit |
a20ca0 |
import os
|
|
Packit |
a20ca0 |
import sys
|
|
Packit |
a20ca0 |
import tempfile
|
|
Packit Service |
2d981f |
import urllib.parse
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
import dnf
|
|
Packit |
a20ca0 |
import hawkey
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
class State:
|
|
Packit |
a20ca0 |
dnf_cache = None # DNF Cache Directory
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
manifest = None # Input/Working Manifest
|
|
Packit |
a20ca0 |
manifest_urls = None # Link to sources URL dict
|
|
Packit |
a20ca0 |
manifest_todo = [] # Array of links to RPM stages
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
def _dnf_repo(conf, desc):
|
|
Packit |
a20ca0 |
repo = dnf.repo.Repo(desc["id"], conf)
|
|
Packit Service |
2d981f |
if "baseurl" in desc:
|
|
Packit Service |
2d981f |
repo.baseurl = desc["baseurl"]
|
|
Packit Service |
2d981f |
elif "metalink" in desc:
|
|
Packit Service |
2d981f |
repo.metalink = desc["metalink"]
|
|
Packit Service |
2d981f |
elif "mirrorlist" in desc:
|
|
Packit Service |
2d981f |
repo.metalink = desc["mirrorlist"]
|
|
Packit Service |
2d981f |
else:
|
|
Packit Service |
2d981f |
raise ValueError("repo description does not contain baseurl, metalink, or mirrorlist keys")
|
|
Packit |
a20ca0 |
return repo
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
def _dnf_base(repos, module_platform_id, persistdir, cachedir, arch):
|
|
Packit |
a20ca0 |
base = dnf.Base()
|
|
Packit Service |
2d981f |
if cachedir:
|
|
Packit Service |
2d981f |
base.conf.cachedir = cachedir
|
|
Packit |
a20ca0 |
base.conf.config_file_path = "/dev/null"
|
|
Packit |
a20ca0 |
base.conf.module_platform_id = module_platform_id
|
|
Packit |
a20ca0 |
base.conf.persistdir = persistdir
|
|
Packit |
a20ca0 |
base.conf.substitutions['arch'] = arch
|
|
Packit |
a20ca0 |
base.conf.substitutions['basearch'] = dnf.rpm.basearch(arch)
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
for repo in repos:
|
|
Packit |
a20ca0 |
base.repos.add(_dnf_repo(base.conf, repo))
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
base.fill_sack(load_system_repo=False)
|
|
Packit |
a20ca0 |
return base
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
def _dnf_resolve(state, mpp_depsolve):
|
|
Packit |
a20ca0 |
deps = []
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
arch = mpp_depsolve["architecture"]
|
|
Packit |
a20ca0 |
mpid = mpp_depsolve["module-platform-id"]
|
|
Packit |
a20ca0 |
repos = mpp_depsolve.get("repos", [])
|
|
Packit |
a20ca0 |
packages = mpp_depsolve.get("packages", [])
|
|
Packit |
a20ca0 |
excludes = mpp_depsolve.get("excludes", [])
|
|
Packit Service |
4f7b0c |
baseurl = mpp_depsolve.get("baseurl")
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
baseurls = {
|
|
Packit Service |
4f7b0c |
repo["id"]: repo.get("baseurl", baseurl) for repo in repos
|
|
Packit Service |
4f7b0c |
}
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
if len(packages) > 0:
|
|
Packit |
a20ca0 |
with tempfile.TemporaryDirectory() as persistdir:
|
|
Packit |
a20ca0 |
base = _dnf_base(repos, mpid, persistdir, state.dnf_cache, arch)
|
|
Packit |
a20ca0 |
base.install_specs(packages, exclude=excludes)
|
|
Packit |
a20ca0 |
base.resolve()
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
for tsi in base.transaction:
|
|
Packit |
a20ca0 |
if tsi.action not in dnf.transaction.FORWARD_ACTIONS:
|
|
Packit |
a20ca0 |
continue
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
checksum_type = hawkey.chksum_name(tsi.pkg.chksum[0])
|
|
Packit |
a20ca0 |
checksum_hex = tsi.pkg.chksum[1].hex()
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
path = tsi.pkg.relativepath
|
|
Packit Service |
4f7b0c |
base = baseurls[tsi.pkg.reponame]
|
|
Packit Service |
4f7b0c |
# dep["path"] often starts with a "/", even though it's meant to be
|
|
Packit Service |
4f7b0c |
# relative to `baseurl`. Strip any leading slashes, but ensure there's
|
|
Packit Service |
4f7b0c |
# exactly one between `baseurl` and the path.
|
|
Packit Service |
4f7b0c |
url = urllib.parse.urljoin(base + "/", path.lstrip("/"))
|
|
Packit Service |
4f7b0c |
|
|
Packit |
a20ca0 |
pkg = {
|
|
Packit |
a20ca0 |
"checksum": f"{checksum_type}:{checksum_hex}",
|
|
Packit |
a20ca0 |
"name": tsi.pkg.name,
|
|
Packit Service |
4f7b0c |
"url": url,
|
|
Packit |
a20ca0 |
}
|
|
Packit |
a20ca0 |
deps.append(pkg)
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
return deps
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
def _manifest_enter(manifest, key, default):
|
|
Packit |
a20ca0 |
if key not in manifest:
|
|
Packit |
a20ca0 |
manifest[key] = default
|
|
Packit |
a20ca0 |
return manifest[key]
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
|
|
Packit Service |
4f7b0c |
def _manifest_parse_v1(state, data):
|
|
Packit |
a20ca0 |
manifest = data
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
# Resolve "sources"."org.osbuild.files"."url".
|
|
Packit |
a20ca0 |
manifest_sources = _manifest_enter(manifest, "sources", {})
|
|
Packit |
a20ca0 |
manifest_files = _manifest_enter(manifest_sources, "org.osbuild.files", {})
|
|
Packit |
a20ca0 |
manifest_urls = _manifest_enter(manifest_files, "urls", {})
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
# Resolve "pipeline"."stages".
|
|
Packit |
a20ca0 |
manifest_pipeline = _manifest_enter(manifest, "pipeline", {})
|
|
Packit |
a20ca0 |
manifest_stages = _manifest_enter(manifest_pipeline, "stages", [])
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
# Collect all stages of interest in `manifest_todo`.
|
|
Packit |
a20ca0 |
manifest_todo = []
|
|
Packit |
a20ca0 |
for stage in manifest_stages:
|
|
Packit |
a20ca0 |
if stage.get("name", "") != "org.osbuild.rpm":
|
|
Packit |
a20ca0 |
continue
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
stage_options = _manifest_enter(stage, "options", {})
|
|
Packit |
a20ca0 |
if "mpp-depsolve" not in stage_options:
|
|
Packit |
a20ca0 |
continue
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
manifest_todo.append(stage)
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
# Remember links of interest.
|
|
Packit |
a20ca0 |
state.manifest = manifest
|
|
Packit |
a20ca0 |
state.manifest_urls = manifest_urls
|
|
Packit |
a20ca0 |
state.manifest_todo = manifest_todo
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
|
|
Packit Service |
4f7b0c |
def _manifest_process_v1(state, stage):
|
|
Packit |
a20ca0 |
options = _manifest_enter(stage, "options", {})
|
|
Packit |
a20ca0 |
options_mpp = _manifest_enter(options, "mpp-depsolve", {})
|
|
Packit |
a20ca0 |
options_packages = _manifest_enter(options, "packages", [])
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
del(options["mpp-depsolve"])
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
deps = _dnf_resolve(state, options_mpp)
|
|
Packit |
a20ca0 |
for dep in deps:
|
|
Packit |
a20ca0 |
options_packages.append(dep["checksum"])
|
|
Packit Service |
4f7b0c |
state.manifest_urls[dep["checksum"]] = dep["url"]
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
def _manifest_depsolve_v1(state, src):
|
|
Packit Service |
4f7b0c |
_manifest_parse_v1(state, src)
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
for stage in state.manifest_todo:
|
|
Packit Service |
4f7b0c |
_manifest_process_v1(state, stage)
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
def _manifest_parse_v2(state, manifest):
|
|
Packit Service |
4f7b0c |
todo = []
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
for pipeline in manifest.get("pipelines", {}):
|
|
Packit Service |
4f7b0c |
for stage in pipeline.get("stages", []):
|
|
Packit Service |
4f7b0c |
if stage["type"] != "org.osbuild.rpm":
|
|
Packit Service |
4f7b0c |
continue
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
inputs = _manifest_enter(stage, "inputs", {})
|
|
Packit Service |
4f7b0c |
packages = _manifest_enter(inputs, "packages", {})
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
if "mpp-depsolve" not in packages:
|
|
Packit Service |
4f7b0c |
continue
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
todo.append(packages)
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
sources = _manifest_enter(manifest, "sources", {})
|
|
Packit Service |
4f7b0c |
files = _manifest_enter(sources, "org.osbuild.curl", {})
|
|
Packit Service |
4f7b0c |
urls = _manifest_enter(files, "items", {})
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
state.manifest = manifest
|
|
Packit Service |
4f7b0c |
state.manifest_todo = todo
|
|
Packit Service |
4f7b0c |
state.manifest_urls = urls
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
def _manifest_process_v2(state, ip):
|
|
Packit Service |
4f7b0c |
urls = state.manifest_urls
|
|
Packit Service |
4f7b0c |
refs = _manifest_enter(ip, "references", {})
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
mpp = ip["mpp-depsolve"]
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
deps = _dnf_resolve(state, mpp)
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
for dep in deps:
|
|
Packit Service |
4f7b0c |
checksum = dep["checksum"]
|
|
Packit Service |
4f7b0c |
refs[checksum] = {}
|
|
Packit Service |
4f7b0c |
urls[checksum] = dep["url"]
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
del ip["mpp-depsolve"]
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
def _manifest_depsolve_v2(state, src):
|
|
Packit Service |
4f7b0c |
_manifest_parse_v2(state, src)
|
|
Packit Service |
4f7b0c |
|
|
Packit Service |
4f7b0c |
for todo in state.manifest_todo:
|
|
Packit Service |
4f7b0c |
_manifest_process_v2(state, todo)
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
def _main_args(argv):
|
|
Packit |
a20ca0 |
parser = argparse.ArgumentParser(description="Generate Test Manifests")
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
parser.add_argument(
|
|
Packit |
a20ca0 |
"--dnf-cache",
|
|
Packit |
a20ca0 |
metavar="PATH",
|
|
Packit |
a20ca0 |
type=os.path.abspath,
|
|
Packit |
a20ca0 |
default=None,
|
|
Packit |
a20ca0 |
help="Path to DNF cache-directory to use",
|
|
Packit |
a20ca0 |
)
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
return parser.parse_args(argv[1:])
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
@contextlib.contextmanager
|
|
Packit |
a20ca0 |
def _main_state(args):
|
|
Packit |
a20ca0 |
state = State()
|
|
Packit Service |
2d981f |
if args.dnf_cache:
|
|
Packit Service |
2d981f |
state.dnf_cache = args.dnf_cache
|
|
Packit Service |
2d981f |
yield state
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
def _main_process(state):
|
|
Packit |
a20ca0 |
src = json.load(sys.stdin)
|
|
Packit Service |
4f7b0c |
version = src.get("version", "1")
|
|
Packit Service |
4f7b0c |
if version == "1":
|
|
Packit Service |
4f7b0c |
_manifest_depsolve_v1(state, src)
|
|
Packit Service |
4f7b0c |
elif version == "2":
|
|
Packit Service |
4f7b0c |
_manifest_depsolve_v2(state, src)
|
|
Packit Service |
4f7b0c |
else:
|
|
Packit Service |
4f7b0c |
print(f"Unknown manifest version {version}", file=sys.stderr)
|
|
Packit Service |
4f7b0c |
return 1
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
json.dump(state.manifest, sys.stdout, indent=2)
|
|
Packit |
a20ca0 |
sys.stdout.write("\n")
|
|
Packit Service |
4f7b0c |
return 0
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
def main() -> int:
|
|
Packit |
a20ca0 |
args = _main_args(sys.argv)
|
|
Packit |
a20ca0 |
with _main_state(args) as state:
|
|
Packit |
a20ca0 |
_main_process(state)
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
return 0
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
|
|
Packit |
a20ca0 |
if __name__ == "__main__":
|
|
Packit |
a20ca0 |
sys.exit(main())
|