Blame plugins/post-transaction-actions.py

Packit Service 27f74b
# Copyright (C) 2019 Red Hat, Inc.
Packit Service 27f74b
#
Packit Service 27f74b
# Licensed under the GNU Lesser General Public License Version 2.1
Packit Service 27f74b
#
Packit Service 27f74b
# This library is free software; you can redistribute it and/or
Packit Service 27f74b
# modify it under the terms of the GNU Lesser General Public
Packit Service 27f74b
# License as published by the Free Software Foundation; either
Packit Service 27f74b
# version 2.1 of the License, or (at your option) any later version.
Packit Service 27f74b
#
Packit Service 27f74b
# This library is distributed in the hope that it will be useful,
Packit Service 27f74b
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 27f74b
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Packit Service 27f74b
# Lesser General Public License for more details.
Packit Service 27f74b
#
Packit Service 27f74b
# You should have received a copy of the GNU Lesser General Public
Packit Service 27f74b
# License along with this library; if not, write to the Free Software
Packit Service 27f74b
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Packit Service 27f74b
Packit Service 27f74b
"""
Packit Service 27f74b
The plugin allows to define actions to be executed upon completing an RPM transaction. Each action
Packit Service 27f74b
may define a (glob-like) filtering rule on the package NEVRA or package files, as well as whether
Packit Service 27f74b
the package was installed or removed. Actions are defined in action files.
Packit Service 27f74b
Packit Service 27f74b
The action can start any shell command. Commands can contain variables. The same command (after
Packit Service 27f74b
variables substitution) is executed only once per transaction. The order of execution
Packit Service 27f74b
of the commands may differ from the order of the packages in the transaction.
Packit Service 27f74b
"""
Packit Service 27f74b
Packit Service 27f74b
from dnfpluginscore import _, logger
Packit Service 27f74b
Packit Service 27f74b
import libdnf.conf
Packit Service 27f74b
import dnf
Packit Service 27f74b
import dnf.transaction
Packit Service 27f74b
import glob
Packit Service 27f74b
import os
Packit Service 27f74b
import subprocess
Packit Service 27f74b
Packit Service 27f74b
Packit Service 27f74b
class PostTransactionActions(dnf.Plugin):
Packit Service 27f74b
Packit Service 27f74b
    name = "post-transaction-actions"
Packit Service 27f74b
Packit Service 27f74b
    def __init__(self, base, cli):
Packit Service 27f74b
        super(PostTransactionActions, self).__init__(base, cli)
Packit Service 27f74b
        self.actiondir = "/etc/dnf/plugins/post-transaction-actions.d/"
Packit Service 27f74b
        self.base = base
Packit Service 27f74b
        self.logger = logger
Packit Service 27f74b
Packit Service 27f74b
    def config(self):
Packit Service 27f74b
        conf = self.read_config(self.base.conf)
Packit Service 27f74b
        if conf.has_section("main"):
Packit Service 27f74b
            if conf.has_option("main", "actiondir"):
Packit Service 27f74b
                self.actiondir = conf.get("main", "actiondir")
Packit Service 27f74b
Packit Service 27f74b
    def _parse_actions(self):
Packit Service 27f74b
        """Parses *.action files from self.actiondir path.
Packit Service 27f74b
        Parsed actions are stored in a list of tuples."""
Packit Service 27f74b
Packit Service 27f74b
        action_file_list = []
Packit Service 27f74b
        if os.access(self.actiondir, os.R_OK):
Packit Service 27f74b
            action_file_list.extend(glob.glob(self.actiondir + "*.action"))
Packit Service 27f74b
Packit Service 27f74b
        action_tuples = []  # (action key, action_state, shell command)
Packit Service 27f74b
        for file_name in action_file_list:
Packit Service 27f74b
            for line in open(file_name).readlines():
Packit Service 27f74b
                line = line.strip()
Packit Service 27f74b
                if line and line[0] != "#":
Packit Service 27f74b
                    try:
Packit Service 27f74b
                        (action_key, action_state, action_command) = line.split(':', 2)
Packit Service 27f74b
                    except ValueError as e:
Packit Service 27f74b
                        self.logger.error(_('Bad Action Line "%s": %s') % (line, e))
Packit Service 27f74b
                        continue
Packit Service 27f74b
                    else:
Packit Service 27f74b
                        action_tuples.append((action_key, action_state, action_command))
Packit Service 27f74b
Packit Service 27f74b
        return action_tuples
Packit Service 27f74b
Packit Service 27f74b
    def _replace_vars(self, ts_item, command):
Packit Service 27f74b
        """Replaces variables in the command.
Packit Service 27f74b
        Variables: ${name}, ${arch}, ${ver}, ${rel}, ${epoch}, ${repoid}, ${state}
Packit Service 27f74b
        or $name, $arch, $ver, $rel, $epoch, $repoid, $state"""
Packit Service 27f74b
Packit Service 27f74b
        action_dict = {dnf.transaction.PKG_DOWNGRADE: "downgrade",
Packit Service 27f74b
                       dnf.transaction.PKG_DOWNGRADED: "downgraded",
Packit Service 27f74b
                       dnf.transaction.PKG_INSTALL: "install",
Packit Service 27f74b
                       dnf.transaction.PKG_OBSOLETE: "obsolete",
Packit Service 27f74b
                       dnf.transaction.PKG_OBSOLETED: "obsoleted",
Packit Service 27f74b
                       dnf.transaction.PKG_REINSTALL: "reinstall",
Packit Service 27f74b
                       dnf.transaction.PKG_REINSTALLED: "reinstalled",
Packit Service 27f74b
                       dnf.transaction.PKG_REMOVE: "remove",
Packit Service 27f74b
                       dnf.transaction.PKG_UPGRADE: "upgrade",
Packit Service 27f74b
                       dnf.transaction.PKG_UPGRADED: "upgraded"}
Packit Service 27f74b
Packit Service 27f74b
        action = action_dict.get(ts_item.action, "unknown - %s" % ts_item.action)
Packit Service 27f74b
Packit Service 27f74b
        vardict = {"name": ts_item.name,
Packit Service 27f74b
                   "arch": ts_item.arch,
Packit Service 27f74b
                   "ver": ts_item.version,
Packit Service 27f74b
                   "rel": ts_item.release,
Packit Service 27f74b
                   "epoch": str(ts_item.epoch),
Packit Service 27f74b
                   "repoid": ts_item.from_repo,
Packit Service 27f74b
                   "state": action}
Packit Service 27f74b
Packit Service 27f74b
        result = libdnf.conf.ConfigParser_substitute(command, vardict)
Packit Service 27f74b
        return result
Packit Service 27f74b
Packit Service 27f74b
    def transaction(self):
Packit Service 27f74b
        action_tuples = self._parse_actions()
Packit Service 27f74b
Packit Service 27f74b
        in_ts_items = []
Packit Service 27f74b
        out_ts_items = []
Packit Service 27f74b
        all_ts_items = []
Packit Service 27f74b
        for ts_item in self.base.transaction:
Packit Service 27f74b
            if ts_item.action in dnf.transaction.FORWARD_ACTIONS:
Packit Service 27f74b
                in_ts_items.append(ts_item)
Packit Service 27f74b
            elif ts_item.action in dnf.transaction.BACKWARD_ACTIONS:
Packit Service 27f74b
                out_ts_items.append(ts_item)
Packit Service 27f74b
            all_ts_items.append(ts_item)
Packit Service 27f74b
Packit Service 27f74b
        commands_to_run = set()
Packit Service 27f74b
        for (a_key, a_state, a_command) in action_tuples:
Packit Service 27f74b
            if a_state == "in":
Packit Service 27f74b
                ts_items = in_ts_items
Packit Service 27f74b
            elif a_state == "out":
Packit Service 27f74b
                ts_items = out_ts_items
Packit Service 27f74b
            elif a_state == "any":
Packit Service 27f74b
                ts_items = all_ts_items
Packit Service 27f74b
            else:
Packit Service 27f74b
                # unsupported state, skip it
Packit Service 27f74b
                self.logger.error(_("Bad Transaction State: %s") % a_state)
Packit Service 27f74b
                continue
Packit Service 27f74b
Packit Service 27f74b
            subj = dnf.subject.Subject(a_key)
Packit Service 27f74b
            pkgs = [tsi.pkg for tsi in ts_items]
Packit Service 27f74b
            query = self.base.sack.query().filterm(pkg=pkgs)
Packit Service 27f74b
            query = subj.get_best_query(self.base.sack, with_nevra=True, with_provides=False,
Packit Service 27f74b
                                        with_filenames=True, query=query)
Packit Service 27f74b
Packit Service 27f74b
            for pkg in query:
Packit Service 27f74b
                ts_item_list = [tsi for tsi in ts_items
Packit Service 27f74b
                                if tsi.pkg == pkg and tsi.pkg.reponame == pkg.reponame]
Packit Service 27f74b
                ts_item = ts_item_list[0]
Packit Service 27f74b
                command = self._replace_vars(ts_item, a_command)
Packit Service 27f74b
                commands_to_run.add(command)
Packit Service 27f74b
Packit Service 27f74b
        for command in commands_to_run:
Packit Service 27f74b
            try:
Packit Service 27f74b
                proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE,
Packit Service 27f74b
                                        stderr=subprocess.PIPE, close_fds=True,
Packit Service 27f74b
                                        universal_newlines=True)
Packit Service 27f74b
                stdout, stderr = proc.communicate()
Packit Service 27f74b
                if stdout:
Packit Service 27f74b
                    self.logger.info(_('post-transaction-actions: %s') % (stdout))
Packit Service 27f74b
                if stderr:
Packit Service 27f74b
                    self.logger.error(_('post-transaction-actions: %s') % (stderr))
Packit Service 27f74b
            except Exception as e:
Packit Service 27f74b
                self.logger.error(_('post-transaction-actions: Bad Command "%s": %s') %
Packit Service 27f74b
                                  (command, e))