Blame dnf/transaction_sr.py

Packit Service 21c75c
# Copyright (C) 2020 Red Hat, Inc.
Packit Service 21c75c
#
Packit Service 21c75c
# This program is free software; you can redistribute it and/or modify
Packit Service 21c75c
# it under the terms of the GNU General Public License as published by
Packit Service 21c75c
# the Free Software Foundation; either version 2 of the License, or
Packit Service 21c75c
# (at your option) any later version.
Packit Service 21c75c
#
Packit Service 21c75c
# This program is distributed in the hope that it will be useful,
Packit Service 21c75c
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 21c75c
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Service 21c75c
# GNU Library General Public License for more details.
Packit Service 21c75c
#
Packit Service 21c75c
# You should have received a copy of the GNU General Public License
Packit Service 21c75c
# along with this program; if not, write to the Free Software
Packit Service 21c75c
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Packit Service 21c75c
Packit Service 21c75c
from __future__ import absolute_import
Packit Service 21c75c
from __future__ import print_function
Packit Service 21c75c
from __future__ import unicode_literals
Packit Service 21c75c
Packit Service 21c75c
import libdnf
Packit Service 21c75c
import hawkey
Packit Service 21c75c
Packit Service 21c75c
from dnf.i18n import _
Packit Service 21c75c
import dnf.exceptions
Packit Service 21c75c
Packit Service 21c75c
import json
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
VERSION_MAJOR = 0
Packit Service 21c75c
VERSION_MINOR = 0
Packit Service 21c75c
VERSION = "%s.%s" % (VERSION_MAJOR, VERSION_MINOR)
Packit Service 21c75c
"""
Packit Service 21c75c
The version of the stored transaction.
Packit Service 21c75c
Packit Service 21c75c
MAJOR version denotes backwards incompatible changes (old dnf won't work with
Packit Service 21c75c
new transaction JSON).
Packit Service 21c75c
Packit Service 21c75c
MINOR version denotes extending the format without breaking backwards
Packit Service 21c75c
compatibility (old dnf can work with new transaction JSON). Forwards
Packit Service 21c75c
compatibility needs to be handled by being able to process the old format as
Packit Service 21c75c
well as the new one.
Packit Service 21c75c
"""
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
class TransactionError(dnf.exceptions.Error):
Packit Service 21c75c
    def __init__(self, msg):
Packit Service 21c75c
        super(TransactionError, self).__init__(msg)
Packit Service 21c75c
Packit Service 21c75c
Packit Service 2bb387
class TransactionReplayError(dnf.exceptions.Error):
Packit Service 21c75c
    def __init__(self, filename, errors):
Packit Service 21c75c
        """
Packit Service 21c75c
        :param filename: The name of the transaction file being replayed
Packit Service 21c75c
        :param errors: a list of error classes or a string with an error description
Packit Service 21c75c
        """
Packit Service 21c75c
Packit Service 2bb387
        # store args in case someone wants to read them from a caught exception
Packit Service 2bb387
        self.filename = filename
Packit Service 21c75c
        if isinstance(errors, (list, tuple)):
Packit Service 2bb387
            self.errors = errors
Packit Service 2bb387
        else:
Packit Service 2bb387
            self.errors = [errors]
Packit Service 21c75c
Packit Service 2bb387
        if filename:
Packit Service 2bb387
            msg = _('The following problems occurred while replaying the transaction from file "{filename}":').format(filename=filename)
Packit Service 2bb387
        else:
Packit Service 2bb387
            msg = _('The following problems occurred while running a transaction:')
Packit Service 21c75c
Packit Service 2bb387
        for error in self.errors:
Packit Service 2bb387
            msg += "\n  " + str(error)
Packit Service 21c75c
Packit Service 2bb387
        super(TransactionReplayError, self).__init__(msg)
Packit Service 21c75c
Packit Service 21c75c
Packit Service 2bb387
class IncompatibleTransactionVersionError(TransactionReplayError):
Packit Service 21c75c
    def __init__(self, filename, msg):
Packit Service 21c75c
        super(IncompatibleTransactionVersionError, self).__init__(filename, msg)
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
def _check_version(version, filename):
Packit Service 21c75c
    major, minor = version.split('.')
Packit Service 21c75c
Packit Service 21c75c
    try:
Packit Service 21c75c
        major = int(major)
Packit Service 21c75c
    except ValueError as e:
Packit Service 2bb387
        raise TransactionReplayError(
Packit Service 21c75c
            filename,
Packit Service 21c75c
            _('Invalid major version "{major}", number expected.').format(major=major)
Packit Service 21c75c
        )
Packit Service 21c75c
Packit Service 21c75c
    try:
Packit Service 21c75c
        int(minor)  # minor is unused, just check it's a number
Packit Service 21c75c
    except ValueError as e:
Packit Service 2bb387
        raise TransactionReplayError(
Packit Service 21c75c
            filename,
Packit Service 21c75c
            _('Invalid minor version "{minor}", number expected.').format(minor=minor)
Packit Service 21c75c
        )
Packit Service 21c75c
Packit Service 21c75c
    if major != VERSION_MAJOR:
Packit Service 21c75c
        raise IncompatibleTransactionVersionError(
Packit Service 21c75c
            filename,
Packit Service 21c75c
            _('Incompatible major version "{major}", supported major version is "{major_supp}".')
Packit Service 21c75c
                .format(major=major, major_supp=VERSION_MAJOR)
Packit Service 21c75c
        )
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
def serialize_transaction(transaction):
Packit Service 21c75c
    """
Packit Service 21c75c
    Serializes a transaction to a data structure that is equivalent to the stored JSON format.
Packit Service 21c75c
    :param transaction: the transaction to serialize (an instance of dnf.db.history.TransactionWrapper)
Packit Service 21c75c
    """
Packit Service 21c75c
Packit Service 21c75c
    data = {
Packit Service 21c75c
        "version": VERSION,
Packit Service 21c75c
    }
Packit Service 21c75c
    rpms = []
Packit Service 21c75c
    groups = []
Packit Service 21c75c
    environments = []
Packit Service 21c75c
Packit Service 2bb387
    if transaction is None:
Packit Service 2bb387
        return data
Packit Service 2bb387
Packit Service 21c75c
    for tsi in transaction.packages():
Packit Service 21c75c
        if tsi.is_package():
Packit Service 21c75c
            rpms.append({
Packit Service 21c75c
                "action": tsi.action_name,
Packit Service 21c75c
                "nevra": tsi.nevra,
Packit Service 21c75c
                "reason": libdnf.transaction.TransactionItemReasonToString(tsi.reason),
Packit Service 21c75c
                "repo_id": tsi.from_repo
Packit Service 21c75c
            })
Packit Service 21c75c
Packit Service 21c75c
        elif tsi.is_group():
Packit Service 21c75c
            group = tsi.get_group()
Packit Service 21c75c
Packit Service 21c75c
            group_data = {
Packit Service 21c75c
                "action": tsi.action_name,
Packit Service 21c75c
                "id": group.getGroupId(),
Packit Service 21c75c
                "packages": [],
Packit Service 21c75c
                "package_types": libdnf.transaction.compsPackageTypeToString(group.getPackageTypes())
Packit Service 21c75c
            }
Packit Service 21c75c
Packit Service 21c75c
            for pkg in group.getPackages():
Packit Service 21c75c
                group_data["packages"].append({
Packit Service 21c75c
                    "name": pkg.getName(),
Packit Service 21c75c
                    "installed": pkg.getInstalled(),
Packit Service 21c75c
                    "package_type": libdnf.transaction.compsPackageTypeToString(pkg.getPackageType())
Packit Service 21c75c
                })
Packit Service 21c75c
Packit Service 21c75c
            groups.append(group_data)
Packit Service 21c75c
Packit Service 21c75c
        elif tsi.is_environment():
Packit Service 21c75c
            env = tsi.get_environment()
Packit Service 21c75c
Packit Service 21c75c
            env_data = {
Packit Service 21c75c
                "action": tsi.action_name,
Packit Service 21c75c
                "id": env.getEnvironmentId(),
Packit Service 21c75c
                "groups": [],
Packit Service 21c75c
                "package_types": libdnf.transaction.compsPackageTypeToString(env.getPackageTypes())
Packit Service 21c75c
            }
Packit Service 21c75c
Packit Service 21c75c
            for grp in env.getGroups():
Packit Service 21c75c
                env_data["groups"].append({
Packit Service 21c75c
                    "id": grp.getGroupId(),
Packit Service 21c75c
                    "installed": grp.getInstalled(),
Packit Service 21c75c
                    "group_type": libdnf.transaction.compsPackageTypeToString(grp.getGroupType())
Packit Service 21c75c
                })
Packit Service 21c75c
Packit Service 21c75c
            environments.append(env_data)
Packit Service 21c75c
Packit Service 21c75c
    if rpms:
Packit Service 21c75c
        data["rpms"] = rpms
Packit Service 21c75c
Packit Service 21c75c
    if groups:
Packit Service 21c75c
        data["groups"] = groups
Packit Service 21c75c
Packit Service 21c75c
    if environments:
Packit Service 21c75c
        data["environments"] = environments
Packit Service 21c75c
Packit Service 21c75c
    return data
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
class TransactionReplay(object):
Packit Service 21c75c
    """
Packit Service 21c75c
    A class that encapsulates replaying a transaction. The transaction data are
Packit Service 21c75c
    loaded and stored when the class is initialized. The transaction is run by
Packit Service 21c75c
    calling the `run()` method, after the transaction is created (but before it is
Packit Service 21c75c
    performed), the `post_transaction()` method needs to be called to verify no
Packit Service 21c75c
    extra packages were pulled in and also to fix the reasons.
Packit Service 21c75c
    """
Packit Service 21c75c
Packit Service 21c75c
    def __init__(
Packit Service 21c75c
        self,
Packit Service 21c75c
        base,
Packit Service 2bb387
        filename="",
Packit Service 2bb387
        data=None,
Packit Service 21c75c
        ignore_extras=False,
Packit Service 21c75c
        ignore_installed=False,
Packit Service 21c75c
        skip_unavailable=False
Packit Service 21c75c
    ):
Packit Service 21c75c
        """
Packit Service 21c75c
        :param base: the dnf base
Packit Service 2bb387
        :param filename: the filename to load the transaction from (conflicts with the 'data' argument)
Packit Service 2bb387
        :param data: the dictionary to load the transaction from (conflicts with the 'filename' argument)
Packit Service 21c75c
        :param ignore_extras: whether to ignore extra package pulled into the transaction
Packit Service 21c75c
        :param ignore_installed: whether to ignore installed versions of packages
Packit Service 21c75c
        :param skip_unavailable: whether to skip transaction packages that aren't available
Packit Service 21c75c
        """
Packit Service 21c75c
Packit Service 21c75c
        self._base = base
Packit Service 2bb387
        self._filename = filename
Packit Service 21c75c
        self._ignore_installed = ignore_installed
Packit Service 21c75c
        self._ignore_extras = ignore_extras
Packit Service 21c75c
        self._skip_unavailable = skip_unavailable
Packit Service 21c75c
Packit Service 21c75c
        if not self._base.conf.strict:
Packit Service 21c75c
            self._skip_unavailable = True
Packit Service 21c75c
Packit Service 21c75c
        self._nevra_cache = set()
Packit Service 21c75c
        self._nevra_reason_cache = {}
Packit Service 21c75c
        self._warnings = []
Packit Service 21c75c
Packit Service 2bb387
        if filename and data:
Packit Service 2bb387
            raise ValueError(_("Conflicting TransactionReplay arguments have been specified: filename, data"))
Packit Service 2bb387
        elif filename:
Packit Service 2bb387
            self._load_from_file(filename)
Packit Service 2bb387
        else:
Packit Service 2bb387
            self._load_from_data(data)
Packit Service 2bb387
Packit Service 2bb387
Packit Service 2bb387
    def _load_from_file(self, fn):
Packit Service 2bb387
        self._filename = fn
Packit Service 21c75c
        with open(fn, "r") as f:
Packit Service 21c75c
            try:
Packit Service 2bb387
                replay_data = json.load(f)
Packit Service 21c75c
            except json.decoder.JSONDecodeError as e:
Packit Service 2bb387
                raise TransactionReplayError(fn, str(e) + ".")
Packit Service 21c75c
Packit Service 21c75c
        try:
Packit Service 2bb387
            self._load_from_data(replay_data)
Packit Service 2bb387
        except TransactionError as e:
Packit Service 2bb387
            raise TransactionReplayError(fn, e)
Packit Service 21c75c
Packit Service 2bb387
    def _load_from_data(self, data):
Packit Service 2bb387
        self._replay_data = data
Packit Service 2bb387
        self._verify_toplevel_json(self._replay_data)
Packit Service 21c75c
Packit Service 2bb387
        self._rpms = self._replay_data.get("rpms", [])
Packit Service 2bb387
        self._assert_type(self._rpms, list, "rpms", "array")
Packit Service f4ad1f
Packit Service 2bb387
        self._groups = self._replay_data.get("groups", [])
Packit Service 2bb387
        self._assert_type(self._groups, list, "groups", "array")
Packit Service 2bb387
Packit Service 2bb387
        self._environments = self._replay_data.get("environments", [])
Packit Service 2bb387
        self._assert_type(self._environments, list, "environments", "array")
Packit Service 21c75c
Packit Service 21c75c
    def _raise_or_warn(self, warn_only, msg):
Packit Service 21c75c
        if warn_only:
Packit Service 21c75c
            self._warnings.append(msg)
Packit Service 21c75c
        else:
Packit Service 21c75c
            raise TransactionError(msg)
Packit Service 21c75c
Packit Service 21c75c
    def _assert_type(self, value, t, id, expected):
Packit Service 21c75c
        if not isinstance(value, t):
Packit Service 21c75c
            raise TransactionError(_('Unexpected type of "{id}", {exp} expected.').format(id=id, exp=expected))
Packit Service 21c75c
Packit Service 21c75c
    def _verify_toplevel_json(self, replay_data):
Packit Service 21c75c
        fn = self._filename
Packit Service 21c75c
Packit Service 21c75c
        if "version" not in replay_data:
Packit Service 2bb387
            raise TransactionReplayError(fn, _('Missing key "{key}".'.format(key="version")))
Packit Service 21c75c
Packit Service 21c75c
        self._assert_type(replay_data["version"], str, "version", "string")
Packit Service 21c75c
Packit Service 21c75c
        _check_version(replay_data["version"], fn)
Packit Service 21c75c
Packit Service 21c75c
    def _replay_pkg_action(self, pkg_data):
Packit Service 21c75c
        try:
Packit Service 21c75c
            action = pkg_data["action"]
Packit Service 21c75c
            nevra = pkg_data["nevra"]
Packit Service 2bb387
            repo_id = pkg_data["repo_id"]
Packit Service 21c75c
            reason = libdnf.transaction.StringToTransactionItemReason(pkg_data["reason"])
Packit Service 21c75c
        except KeyError as e:
Packit Service 21c75c
            raise TransactionError(
Packit Service 21c75c
                _('Missing object key "{key}" in an rpm.').format(key=e.args[0])
Packit Service 21c75c
            )
Packit Service 21c75c
        except IndexError as e:
Packit Service 21c75c
            raise TransactionError(
Packit Service 21c75c
                _('Unexpected value of package reason "{reason}" for rpm nevra "{nevra}".')
Packit Service 21c75c
                    .format(reason=pkg_data["reason"], nevra=nevra)
Packit Service 21c75c
            )
Packit Service 21c75c
Packit Service 21c75c
        subj = hawkey.Subject(nevra)
Packit Service 21c75c
        parsed_nevras = subj.get_nevra_possibilities(forms=[hawkey.FORM_NEVRA])
Packit Service 21c75c
Packit Service 21c75c
        if len(parsed_nevras) != 1:
Packit Service 21c75c
            raise TransactionError(_('Cannot parse NEVRA for package "{nevra}".').format(nevra=nevra))
Packit Service 21c75c
Packit Service 21c75c
        parsed_nevra = parsed_nevras[0]
Packit Service 21c75c
        na = "%s.%s" % (parsed_nevra.name, parsed_nevra.arch)
Packit Service 21c75c
Packit Service 21c75c
        query_na = self._base.sack.query().filter(name=parsed_nevra.name, arch=parsed_nevra.arch)
Packit Service 21c75c
Packit Service 21c75c
        epoch = parsed_nevra.epoch if parsed_nevra.epoch is not None else 0
Packit Service 21c75c
        query = query_na.filter(epoch=epoch, version=parsed_nevra.version, release=parsed_nevra.release)
Packit Service 21c75c
Packit Service 2bb387
        # In case the package is found in the same repo as in the original
Packit Service 2bb387
        # transaction, limit the query to that plus installed packages. IOW
Packit Service 2bb387
        # remove packages with the same NEVRA in case they are found in
Packit Service 2bb387
        # multiple repos and the repo the package came from originally is one
Packit Service 2bb387
        # of them.
Packit Service 2bb387
        # This can e.g. make a difference in the system-upgrade plugin, in case
Packit Service 2bb387
        # the same NEVRA is in two repos, this makes sure the same repo is used
Packit Service 2bb387
        # for both download and upgrade steps of the plugin.
Packit Service 2bb387
        if repo_id:
Packit Service 2bb387
            query_repo = query.filter(reponame=repo_id)
Packit Service 2bb387
            if query_repo:
Packit Service 2bb387
                query = query_repo.union(query.installed())
Packit Service 2bb387
Packit Service 21c75c
        if not query:
Packit Service 21c75c
            self._raise_or_warn(self._skip_unavailable, _('Cannot find rpm nevra "{nevra}".').format(nevra=nevra))
Packit Service 21c75c
            return
Packit Service 21c75c
Packit Service 21c75c
        # a cache to check no extra packages were pulled into the transaction
Packit Service 21c75c
        if action != "Reason Change":
Packit Service 21c75c
            self._nevra_cache.add(nevra)
Packit Service 21c75c
Packit Service 21c75c
        # store reasons for forward actions and "Removed", the rest of the
Packit Service 21c75c
        # actions reasons should stay as they were determined by the transaction
Packit Service 21c75c
        if action in ("Install", "Upgrade", "Downgrade", "Reinstall", "Removed"):
Packit Service 21c75c
            self._nevra_reason_cache[nevra] = reason
Packit Service 21c75c
Packit Service 21c75c
        if action in ("Install", "Upgrade", "Downgrade"):
Packit Service 21c75c
            if action == "Install" and query_na.installed() and not self._base._get_installonly_query(query_na):
Packit Service 21c75c
                self._raise_or_warn(self._ignore_installed,
Packit Service 21c75c
                    _('Package "{na}" is already installed for action "{action}".').format(na=na, action=action))
Packit Service 21c75c
Packit Service 21c75c
            sltr = dnf.selector.Selector(self._base.sack).set(pkg=query)
Packit Service 21c75c
            self._base.goal.install(select=sltr, optional=not self._base.conf.strict)
Packit Service 21c75c
        elif action == "Reinstall":
Packit Service 21c75c
            query = query.available()
Packit Service 21c75c
Packit Service 21c75c
            if not query:
Packit Service 21c75c
                self._raise_or_warn(self._skip_unavailable,
Packit Service 21c75c
                    _('Package nevra "{nevra}" not available in repositories for action "{action}".')
Packit Service 21c75c
                    .format(nevra=nevra, action=action))
Packit Service 21c75c
                return
Packit Service 21c75c
Packit Service 21c75c
            sltr = dnf.selector.Selector(self._base.sack).set(pkg=query)
Packit Service 21c75c
            self._base.goal.install(select=sltr, optional=not self._base.conf.strict)
Packit Service 21c75c
        elif action in ("Upgraded", "Downgraded", "Reinstalled", "Removed", "Obsoleted"):
Packit Service 21c75c
            query = query.installed()
Packit Service 21c75c
Packit Service 21c75c
            if not query:
Packit Service 21c75c
                self._raise_or_warn(self._ignore_installed,
Packit Service 21c75c
                    _('Package nevra "{nevra}" not installed for action "{action}".').format(nevra=nevra, action=action))
Packit Service 21c75c
                return
Packit Service 21c75c
Packit Service 21c75c
            # erasing the original version (the reverse part of an action like
Packit Service 21c75c
            # e.g. upgrade) is more robust, but we can't do it if
Packit Service 21c75c
            # skip_unavailable is True, because if the forward part of the
Packit Service 21c75c
            # action is skipped, we would simply remove the package here
Packit Service 21c75c
            if not self._skip_unavailable or action == "Removed":
Packit Service 21c75c
                for pkg in query:
Packit Service 21c75c
                    self._base.goal.erase(pkg, clean_deps=False)
Packit Service 21c75c
        elif action == "Reason Change":
Packit Service 21c75c
            self._base.history.set_reason(query[0], reason)
Packit Service 21c75c
        else:
Packit Service 21c75c
            raise TransactionError(
Packit Service 21c75c
                _('Unexpected value of package action "{action}" for rpm nevra "{nevra}".')
Packit Service 21c75c
                    .format(action=action, nevra=nevra)
Packit Service 21c75c
            )
Packit Service 21c75c
Packit Service 21c75c
    def _create_swdb_group(self, group_id, pkg_types, pkgs):
Packit Service 21c75c
        comps_group = self._base.comps._group_by_id(group_id)
Packit Service 21c75c
        if not comps_group:
Packit Service 21c75c
            self._raise_or_warn(self._skip_unavailable, _("Group id '%s' is not available.") % group_id)
Packit Service 21c75c
            return None
Packit Service 21c75c
Packit Service 21c75c
        swdb_group = self._base.history.group.new(group_id, comps_group.name, comps_group.ui_name, pkg_types)
Packit Service 21c75c
Packit Service 21c75c
        try:
Packit Service 21c75c
            for pkg in pkgs:
Packit Service 21c75c
                name = pkg["name"]
Packit Service 21c75c
                self._assert_type(name, str, "groups.packages.name", "string")
Packit Service 21c75c
                installed = pkg["installed"]
Packit Service 21c75c
                self._assert_type(installed, bool, "groups.packages.installed", "boolean")
Packit Service 21c75c
                package_type = pkg["package_type"]
Packit Service 21c75c
                self._assert_type(package_type, str, "groups.packages.package_type", "string")
Packit Service 21c75c
Packit Service 21c75c
                try:
Packit Service 21c75c
                    swdb_group.addPackage(name, installed, libdnf.transaction.stringToCompsPackageType(package_type))
Packit Service 21c75c
                except libdnf.error.Error as e:
Packit Service 21c75c
                    raise TransactionError(str(e))
Packit Service 21c75c
Packit Service 21c75c
        except KeyError as e:
Packit Service 21c75c
            raise TransactionError(
Packit Service 21c75c
                _('Missing object key "{key}" in groups.packages.').format(key=e.args[0])
Packit Service 21c75c
            )
Packit Service 21c75c
Packit Service 21c75c
        return swdb_group
Packit Service 21c75c
Packit Service 21c75c
    def _swdb_group_install(self, group_id, pkg_types, pkgs):
Packit Service 21c75c
        swdb_group = self._create_swdb_group(group_id, pkg_types, pkgs)
Packit Service 21c75c
Packit Service 21c75c
        if swdb_group is not None:
Packit Service 21c75c
            self._base.history.group.install(swdb_group)
Packit Service 21c75c
Packit Service 21c75c
    def _swdb_group_upgrade(self, group_id, pkg_types, pkgs):
Packit Service 21c75c
        if not self._base.history.group.get(group_id):
Packit Service 21c75c
            self._raise_or_warn( self._ignore_installed, _("Group id '%s' is not installed.") % group_id)
Packit Service 21c75c
            return
Packit Service 21c75c
Packit Service 21c75c
        swdb_group = self._create_swdb_group(group_id, pkg_types, pkgs)
Packit Service 21c75c
Packit Service 21c75c
        if swdb_group is not None:
Packit Service 21c75c
            self._base.history.group.upgrade(swdb_group)
Packit Service 21c75c
Packit Service 21c75c
    def _swdb_group_remove(self, group_id, pkg_types, pkgs):
Packit Service 21c75c
        if not self._base.history.group.get(group_id):
Packit Service 21c75c
            self._raise_or_warn(self._ignore_installed, _("Group id '%s' is not installed.") % group_id)
Packit Service 21c75c
            return
Packit Service 21c75c
Packit Service 21c75c
        swdb_group = self._create_swdb_group(group_id, pkg_types, pkgs)
Packit Service 21c75c
Packit Service 21c75c
        if swdb_group is not None:
Packit Service 21c75c
            self._base.history.group.remove(swdb_group)
Packit Service 21c75c
Packit Service 21c75c
    def _create_swdb_environment(self, env_id, pkg_types, groups):
Packit Service 21c75c
        comps_env = self._base.comps._environment_by_id(env_id)
Packit Service 21c75c
        if not comps_env:
Packit Service 21c75c
            self._raise_or_warn(self._skip_unavailable, _("Environment id '%s' is not available.") % env_id)
Packit Service 21c75c
            return None
Packit Service 21c75c
Packit Service 21c75c
        swdb_env = self._base.history.env.new(env_id, comps_env.name, comps_env.ui_name, pkg_types)
Packit Service 21c75c
Packit Service 21c75c
        try:
Packit Service 21c75c
            for grp in groups:
Packit Service 21c75c
                id = grp["id"]
Packit Service 21c75c
                self._assert_type(id, str, "environments.groups.id", "string")
Packit Service 21c75c
                installed = grp["installed"]
Packit Service 21c75c
                self._assert_type(installed, bool, "environments.groups.installed", "boolean")
Packit Service 21c75c
                group_type = grp["group_type"]
Packit Service 21c75c
                self._assert_type(group_type, str, "environments.groups.group_type", "string")
Packit Service 21c75c
Packit Service 21c75c
                try:
Packit Service 21c75c
                    group_type = libdnf.transaction.stringToCompsPackageType(group_type)
Packit Service 21c75c
                except libdnf.error.Error as e:
Packit Service 21c75c
                    raise TransactionError(str(e))
Packit Service 21c75c
Packit Service 21c75c
                if group_type not in (
Packit Service 21c75c
                    libdnf.transaction.CompsPackageType_MANDATORY,
Packit Service 21c75c
                    libdnf.transaction.CompsPackageType_OPTIONAL
Packit Service 21c75c
                ):
Packit Service 21c75c
                    raise TransactionError(
Packit Service 21c75c
                        _('Invalid value "{group_type}" of environments.groups.group_type, '
Packit Service 21c75c
                            'only "mandatory" or "optional" is supported.'
Packit Service 21c75c
                        ).format(group_type=grp["group_type"])
Packit Service 21c75c
                    )
Packit Service 21c75c
Packit Service 21c75c
                swdb_env.addGroup(id, installed, group_type)
Packit Service 21c75c
        except KeyError as e:
Packit Service 21c75c
            raise TransactionError(
Packit Service 21c75c
                _('Missing object key "{key}" in environments.groups.').format(key=e.args[0])
Packit Service 21c75c
            )
Packit Service 21c75c
Packit Service 21c75c
        return swdb_env
Packit Service 21c75c
Packit Service 21c75c
    def _swdb_environment_install(self, env_id, pkg_types, groups):
Packit Service 21c75c
        swdb_env = self._create_swdb_environment(env_id, pkg_types, groups)
Packit Service 21c75c
Packit Service 21c75c
        if swdb_env is not None:
Packit Service 21c75c
            self._base.history.env.install(swdb_env)
Packit Service 21c75c
Packit Service 21c75c
    def _swdb_environment_upgrade(self, env_id, pkg_types, groups):
Packit Service 21c75c
        if not self._base.history.env.get(env_id):
Packit Service 21c75c
            self._raise_or_warn(self._ignore_installed,_("Environment id '%s' is not installed.") % env_id)
Packit Service 21c75c
            return
Packit Service 21c75c
Packit Service 21c75c
        swdb_env = self._create_swdb_environment(env_id, pkg_types, groups)
Packit Service 21c75c
Packit Service 21c75c
        if swdb_env is not None:
Packit Service 21c75c
            self._base.history.env.upgrade(swdb_env)
Packit Service 21c75c
Packit Service 21c75c
    def _swdb_environment_remove(self, env_id, pkg_types, groups):
Packit Service 21c75c
        if not self._base.history.env.get(env_id):
Packit Service 21c75c
            self._raise_or_warn(self._ignore_installed, _("Environment id '%s' is not installed.") % env_id)
Packit Service 21c75c
            return
Packit Service 21c75c
Packit Service 21c75c
        swdb_env = self._create_swdb_environment(env_id, pkg_types, groups)
Packit Service 21c75c
Packit Service 21c75c
        if swdb_env is not None:
Packit Service 21c75c
            self._base.history.env.remove(swdb_env)
Packit Service 21c75c
Packit Service 21c75c
    def get_data(self):
Packit Service 21c75c
        """
Packit Service 21c75c
        :returns: the loaded data of the transaction
Packit Service 21c75c
        """
Packit Service 21c75c
Packit Service 21c75c
        return self._replay_data
Packit Service 21c75c
Packit Service 21c75c
    def get_warnings(self):
Packit Service 21c75c
        """
Packit Service 21c75c
        :returns: an array of warnings gathered during the transaction replay
Packit Service 21c75c
        """
Packit Service 21c75c
Packit Service 21c75c
        return self._warnings
Packit Service 21c75c
Packit Service 21c75c
    def run(self):
Packit Service 21c75c
        """
Packit Service 21c75c
        Replays the transaction.
Packit Service 21c75c
        """
Packit Service 21c75c
Packit Service 21c75c
        fn = self._filename
Packit Service 21c75c
        errors = []
Packit Service 21c75c
Packit Service 21c75c
        for pkg_data in self._rpms:
Packit Service 21c75c
            try:
Packit Service 21c75c
                self._replay_pkg_action(pkg_data)
Packit Service 21c75c
            except TransactionError as e:
Packit Service 21c75c
                errors.append(e)
Packit Service 21c75c
Packit Service 21c75c
        for group_data in self._groups:
Packit Service 21c75c
            try:
Packit Service 21c75c
                action = group_data["action"]
Packit Service 21c75c
                group_id = group_data["id"]
Packit Service 21c75c
Packit Service 21c75c
                try:
Packit Service 21c75c
                    pkg_types = libdnf.transaction.stringToCompsPackageType(group_data["package_types"])
Packit Service 21c75c
                except libdnf.error.Error as e:
Packit Service 21c75c
                    errors.append(TransactionError(str(e)))
Packit Service 21c75c
                    continue
Packit Service 21c75c
Packit Service 21c75c
                if action == "Install":
Packit Service 21c75c
                    self._swdb_group_install(group_id, pkg_types, group_data["packages"])
Packit Service 21c75c
                elif action == "Upgrade":
Packit Service 21c75c
                    self._swdb_group_upgrade(group_id, pkg_types, group_data["packages"])
Packit Service 21c75c
                elif action == "Removed":
Packit Service 21c75c
                    self._swdb_group_remove(group_id, pkg_types, group_data["packages"])
Packit Service 21c75c
                else:
Packit Service 21c75c
                    errors.append(TransactionError(
Packit Service 21c75c
                        _('Unexpected value of group action "{action}" for group "{group}".')
Packit Service 21c75c
                            .format(action=action, group=group_id)
Packit Service 21c75c
                    ))
Packit Service 21c75c
            except KeyError as e:
Packit Service 21c75c
                errors.append(TransactionError(
Packit Service 21c75c
                    _('Missing object key "{key}" in a group.').format(key=e.args[0])
Packit Service 21c75c
                ))
Packit Service 21c75c
            except TransactionError as e:
Packit Service 21c75c
                errors.append(e)
Packit Service 21c75c
Packit Service 21c75c
        for env_data in self._environments:
Packit Service 21c75c
            try:
Packit Service 21c75c
                action = env_data["action"]
Packit Service 21c75c
                env_id = env_data["id"]
Packit Service 21c75c
Packit Service 21c75c
                try:
Packit Service 21c75c
                    pkg_types = libdnf.transaction.stringToCompsPackageType(env_data["package_types"])
Packit Service 21c75c
                except libdnf.error.Error as e:
Packit Service 21c75c
                    errors.append(TransactionError(str(e)))
Packit Service 21c75c
                    continue
Packit Service 21c75c
Packit Service 21c75c
                if action == "Install":
Packit Service 21c75c
                    self._swdb_environment_install(env_id, pkg_types, env_data["groups"])
Packit Service 21c75c
                elif action == "Upgrade":
Packit Service 21c75c
                    self._swdb_environment_upgrade(env_id, pkg_types, env_data["groups"])
Packit Service 21c75c
                elif action == "Removed":
Packit Service 21c75c
                    self._swdb_environment_remove(env_id, pkg_types, env_data["groups"])
Packit Service 21c75c
                else:
Packit Service 21c75c
                    errors.append(TransactionError(
Packit Service 21c75c
                        _('Unexpected value of environment action "{action}" for environment "{env}".')
Packit Service 21c75c
                            .format(action=action, env=env_id)
Packit Service 21c75c
                    ))
Packit Service 21c75c
            except KeyError as e:
Packit Service 21c75c
                errors.append(TransactionError(
Packit Service 21c75c
                    _('Missing object key "{key}" in an environment.').format(key=e.args[0])
Packit Service 21c75c
                ))
Packit Service 21c75c
            except TransactionError as e:
Packit Service 21c75c
                errors.append(e)
Packit Service 21c75c
Packit Service 21c75c
        if errors:
Packit Service 2bb387
            raise TransactionReplayError(fn, errors)
Packit Service 21c75c
Packit Service 21c75c
    def post_transaction(self):
Packit Service 21c75c
        """
Packit Service 21c75c
        Sets reasons in the transaction history to values from the stored transaction.
Packit Service 21c75c
Packit Service 21c75c
        Also serves to check whether additional packages were pulled in by the
Packit Service 21c75c
        transaction, which results in an error (unless ignore_extras is True).
Packit Service 21c75c
        """
Packit Service 21c75c
Packit Service 21c75c
        if not self._base.transaction:
Packit Service 21c75c
            return
Packit Service 21c75c
Packit Service 21c75c
        errors = []
Packit Service 21c75c
Packit Service 21c75c
        for tsi in self._base.transaction:
Packit Service 21c75c
            try:
Packit Service 21c75c
                pkg = tsi.pkg
Packit Service 21c75c
            except KeyError as e:
Packit Service 21c75c
                # the transaction item has no package, happens for action == "Reason Change"
Packit Service 21c75c
                continue
Packit Service 21c75c
Packit Service 21c75c
            nevra = str(pkg)
Packit Service 21c75c
Packit Service 21c75c
            if nevra not in self._nevra_cache:
Packit Service 21c75c
                # if ignore_installed is True, we don't want to check for
Packit Service 21c75c
                # Upgraded/Downgraded/Reinstalled extras in the transaction,
Packit Service 21c75c
                # basically those may be installed and we are ignoring them
Packit Service 21c75c
                if not self._ignore_installed or not tsi.action in (
Packit Service 21c75c
                    libdnf.transaction.TransactionItemAction_UPGRADED,
Packit Service 21c75c
                    libdnf.transaction.TransactionItemAction_DOWNGRADED,
Packit Service 21c75c
                    libdnf.transaction.TransactionItemAction_REINSTALLED
Packit Service 21c75c
                ):
Packit Service 21c75c
                    msg = _('Package nevra "{nevra}", which is not present in the transaction file, was pulled '
Packit Service 21c75c
                        'into the transaction.'
Packit Service 21c75c
                    ).format(nevra=nevra)
Packit Service 21c75c
Packit Service 21c75c
                    if not self._ignore_extras:
Packit Service 21c75c
                        errors.append(TransactionError(msg))
Packit Service 21c75c
                    else:
Packit Service 21c75c
                        self._warnings.append(msg)
Packit Service 21c75c
Packit Service 21c75c
            try:
Packit Service 21c75c
                replay_reason = self._nevra_reason_cache[nevra]
Packit Service 21c75c
Packit Service 21c75c
                if tsi.action in (
Packit Service 21c75c
                    libdnf.transaction.TransactionItemAction_INSTALL,
Packit Service 21c75c
                    libdnf.transaction.TransactionItemAction_REMOVE
Packit Service 21c75c
                ) or libdnf.transaction.TransactionItemReasonCompare(replay_reason, tsi.reason) > 0:
Packit Service 21c75c
                    tsi.reason = replay_reason
Packit Service 21c75c
            except KeyError as e:
Packit Service 21c75c
                # if the pkg nevra wasn't found, we don't want to change the reason
Packit Service 21c75c
                pass
Packit Service 21c75c
Packit Service 21c75c
        if errors:
Packit Service 2bb387
            raise TransactionReplayError(self._filename, errors)