Blame dnf-json

Packit Service 4d2de5
#!/usr/bin/python3
Packit Service 4d2de5
Packit Service 4d2de5
import datetime
Packit Service 4d2de5
import dnf
Packit Service 4d2de5
import hashlib
Packit Service 4d2de5
import hawkey
Packit Service 4d2de5
import json
Packit Service 4d2de5
import sys
Packit Service 4d2de5
import tempfile
Packit Service 4d2de5
Packit Service 4d2de5
DNF_ERROR_EXIT_CODE = 10
Packit Service 4d2de5
Packit Service 4d2de5
Packit Service 4d2de5
def timestamp_to_rfc3339(timestamp):
Packit Service 4d2de5
    d = datetime.datetime.utcfromtimestamp(package.buildtime)
Packit Service 4d2de5
    return d.strftime('%Y-%m-%dT%H:%M:%SZ')
Packit Service 4d2de5
Packit Service 4d2de5
Packit Service 4d2de5
def dnfrepo(desc, parent_conf=None):
Packit Service 4d2de5
    """Makes a dnf.repo.Repo out of a JSON repository description"""
Packit Service 4d2de5
Packit Service 4d2de5
    repo = dnf.repo.Repo(desc["id"], parent_conf)
Packit Service 4d2de5
Packit Service 4d2de5
    if "baseurl" in desc:
Packit Service 4d2de5
        repo.baseurl = desc["baseurl"]
Packit Service 4d2de5
    elif "metalink" in desc:
Packit Service 4d2de5
        repo.metalink = desc["metalink"]
Packit Service 4d2de5
    elif "mirrorlist" in desc:
Packit Service 4d2de5
        repo.mirrorlist = desc["mirrorlist"]
Packit Service 4d2de5
    else:
Packit Service 4d2de5
        assert False
Packit Service 4d2de5
Packit Service 4d2de5
    if desc.get("ignoressl", False):
Packit Service 4d2de5
        repo.sslverify = False
Packit Service 4d2de5
    if "sslcacert" in desc:
Packit Service 4d2de5
        repo.sslcacert = desc["sslcacert"]
Packit Service 4d2de5
    if "sslclientkey" in desc:
Packit Service 4d2de5
        repo.sslclientkey = desc["sslclientkey"]
Packit Service 4d2de5
    if "sslclientcert" in desc:
Packit Service 4d2de5
        repo.sslclientcert = desc["sslclientcert"]
Packit Service 4d2de5
Packit Service 4d2de5
    # In dnf, the default metadata expiration time is 48 hours. However,
Packit Service 4d2de5
    # some repositories never expire the metadata, and others expire it much
Packit Service 4d2de5
    # sooner than that. Therefore we must make this configurable. If nothing
Packit Service 4d2de5
    # is provided, we default to never expiring the metadata, as hardcoding
Packit Service 4d2de5
    # some arbitrary does not seem very helpful.
Packit Service 4d2de5
    repo.metadata_expire = desc.get("metadata_expire", "-1")
Packit Service 4d2de5
Packit Service 4d2de5
    return repo
Packit Service 4d2de5
Packit Service 4d2de5
Packit Service 4d2de5
def create_base(repos, module_platform_id, persistdir, cachedir, arch):
Packit Service 4d2de5
    base = dnf.Base()
Packit Service 4d2de5
Packit Service 4d2de5
    # Enable fastestmirror to ensure we choose the fastest mirrors for
Packit Service 4d2de5
    # downloading metadata (when depsolving) and downloading packages.
Packit Service 4d2de5
    base.conf.fastestmirror = True
Packit Service 4d2de5
Packit Service 4d2de5
    # Try another mirror if it takes longer than 5 seconds to connect.
Packit Service 4d2de5
    base.conf.timeout = 5
Packit Service 4d2de5
Packit Service 4d2de5
    # Set the rest of the dnf configuration.
Packit Service 4d2de5
    base.conf.module_platform_id = module_platform_id
Packit Service 4d2de5
    base.conf.config_file_path = "/dev/null"
Packit Service 4d2de5
    base.conf.persistdir = persistdir
Packit Service 4d2de5
    base.conf.cachedir = cachedir
Packit Service 4d2de5
    base.conf.substitutions['arch'] = arch
Packit Service 4d2de5
    base.conf.substitutions['basearch'] = dnf.rpm.basearch(arch)
Packit Service 4d2de5
Packit Service 4d2de5
    for repo in repos:
Packit Service 4d2de5
        base.repos.add(dnfrepo(repo, base.conf))
Packit Service 4d2de5
Packit Service 4d2de5
    base.fill_sack(load_system_repo=False)
Packit Service 4d2de5
    return base
Packit Service 4d2de5
Packit Service 4d2de5
Packit Service 4d2de5
def exit_with_dnf_error(kind: str, reason: str):
Packit Service 4d2de5
    json.dump({"kind": kind, "reason": reason}, sys.stdout)
Packit Service 4d2de5
    sys.exit(DNF_ERROR_EXIT_CODE)
Packit Service 4d2de5
Packit Service 4d2de5
Packit Service 4d2de5
def repo_checksums(base):
Packit Service 4d2de5
    checksums = {}
Packit Service 4d2de5
    for repo in base.repos.iter_enabled():
Packit Service 4d2de5
        # Uses the same algorithm as libdnf to find cache dir:
Packit Service 4d2de5
        #   https://github.com/rpm-software-management/libdnf/blob/master/libdnf/repo/Repo.cpp#L1288
Packit Service 4d2de5
        if repo.metalink:
Packit Service 4d2de5
            url = repo.metalink
Packit Service 4d2de5
        elif repo.mirrorlist:
Packit Service 4d2de5
            url = repo.mirrorlist
Packit Service 4d2de5
        elif repo.baseurl:
Packit Service 4d2de5
            url = repo.baseurl[0]
Packit Service 4d2de5
        else:
Packit Service 4d2de5
            assert False
Packit Service 4d2de5
Packit Service 4d2de5
        digest = hashlib.sha256(url.encode()).hexdigest()[:16]
Packit Service 4d2de5
Packit Service 4d2de5
        repomd_file = f"{repo.id}-{digest}/repodata/repomd.xml"
Packit Service 4d2de5
        with open(f"{base.conf.cachedir}/{repomd_file}", "rb") as f:
Packit Service 4d2de5
            repomd = f.read()
Packit Service 4d2de5
Packit Service 4d2de5
        checksums[repo.id] = "sha256:" + hashlib.sha256(repomd).hexdigest()
Packit Service 4d2de5
Packit Service 4d2de5
    return checksums
Packit Service 4d2de5
Packit Service 4d2de5
Packit Service 4d2de5
call = json.load(sys.stdin)
Packit Service 4d2de5
command = call["command"]
Packit Service 4d2de5
arguments = call["arguments"]
Packit Service 4d2de5
repos = arguments.get("repos", {})
Packit Service 4d2de5
arch = arguments["arch"]
Packit Service 4d2de5
cachedir = arguments["cachedir"]
Packit Service 4d2de5
module_platform_id = arguments["module_platform_id"]
Packit Service 4d2de5
Packit Service 4d2de5
with tempfile.TemporaryDirectory() as persistdir:
Packit Service 4d2de5
    try:
Packit Service 4d2de5
        base = create_base(
Packit Service 4d2de5
            repos,
Packit Service 4d2de5
            module_platform_id,
Packit Service 4d2de5
            persistdir,
Packit Service 4d2de5
            cachedir,
Packit Service 4d2de5
            arch
Packit Service 4d2de5
        )
Packit Service 4d2de5
    except dnf.exceptions.Error as e:
Packit Service 4d2de5
        exit_with_dnf_error(
Packit Service 4d2de5
            type(e).__name__,
Packit Service 4d2de5
            f"Error occurred when setting up repo: {e}"
Packit Service 4d2de5
        )
Packit Service 4d2de5
Packit Service 4d2de5
    if command == "dump":
Packit Service 4d2de5
        packages = []
Packit Service 4d2de5
        for package in base.sack.query().available():
Packit Service 4d2de5
            packages.append({
Packit Service 4d2de5
                "name": package.name,
Packit Service 4d2de5
                "summary": package.summary,
Packit Service 4d2de5
                "description": package.description,
Packit Service 4d2de5
                "url": package.url,
Packit Service 4d2de5
                "epoch": package.epoch,
Packit Service 4d2de5
                "version": package.version,
Packit Service 4d2de5
                "release": package.release,
Packit Service 4d2de5
                "arch": package.arch,
Packit Service 4d2de5
                "buildtime": timestamp_to_rfc3339(package.buildtime),
Packit Service 4d2de5
                "license": package.license
Packit Service 4d2de5
            })
Packit Service 4d2de5
        json.dump({
Packit Service 4d2de5
            "checksums": repo_checksums(base),
Packit Service 4d2de5
            "packages": packages
Packit Service 4d2de5
        }, sys.stdout)
Packit Service 4d2de5
Packit Service 4d2de5
    elif command == "depsolve":
Packit Service 4d2de5
        errors = []
Packit Service 4d2de5
Packit Service 4d2de5
        try:
Packit Service 4d2de5
            base.install_specs(
Packit Service 4d2de5
                arguments["package-specs"],
Packit Service 4d2de5
                exclude=arguments.get("exclude-specs", [])
Packit Service 4d2de5
            )
Packit Service 4d2de5
        except dnf.exceptions.MarkingErrors as e:
Packit Service 4d2de5
            exit_with_dnf_error(
Packit Service 4d2de5
                "MarkingErrors",
Packit Service 4d2de5
                f"Error occurred when marking packages for installation: {e}"
Packit Service 4d2de5
            )
Packit Service 4d2de5
Packit Service 4d2de5
        try:
Packit Service 4d2de5
            base.resolve()
Packit Service 4d2de5
        except dnf.exceptions.DepsolveError as e:
Packit Service 4d2de5
            exit_with_dnf_error(
Packit Service 4d2de5
                "DepsolveError",
Packit Service 4d2de5
                (
Packit Service 4d2de5
                    "There was a problem depsolving "
Packit Service 4d2de5
                    f"{arguments['package-specs']}: {e}"
Packit Service 4d2de5
                )
Packit Service 4d2de5
            )
Packit Service 4d2de5
Packit Service 4d2de5
        dependencies = []
Packit Service 4d2de5
        for tsi in base.transaction:
Packit Service 4d2de5
            # Avoid using the install_set() helper, as it does not guarantee
Packit Service 4d2de5
            # a stable order
Packit Service 4d2de5
            if tsi.action not in dnf.transaction.FORWARD_ACTIONS:
Packit Service 4d2de5
                continue
Packit Service 4d2de5
            package = tsi.pkg
Packit Service 4d2de5
Packit Service 4d2de5
            dependencies.append({
Packit Service 4d2de5
                "name": package.name,
Packit Service 4d2de5
                "epoch": package.epoch,
Packit Service 4d2de5
                "version": package.version,
Packit Service 4d2de5
                "release": package.release,
Packit Service 4d2de5
                "arch": package.arch,
Packit Service 4d2de5
                "repo_id": package.reponame,
Packit Service 4d2de5
                "path": package.relativepath,
Packit Service 4d2de5
                "remote_location": package.remote_location(),
Packit Service 4d2de5
                "checksum": (
Packit Service 4d2de5
                    f"{hawkey.chksum_name(package.chksum[0])}:"
Packit Service 4d2de5
                    f"{package.chksum[1].hex()}"
Packit Service 4d2de5
                )
Packit Service 4d2de5
            })
Packit Service 4d2de5
        json.dump({
Packit Service 4d2de5
            "checksums": repo_checksums(base),
Packit Service 4d2de5
            "dependencies": dependencies
Packit Service 4d2de5
        }, sys.stdout)