Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pre-transaction-actions plugin implementation #522

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ DNF-PLUGINS-CORE CONTRIBUTORS
Vladan Kudlac <[email protected]>
Wieland Hoffmann <[email protected]>
Otto Urpelainen <[email protected]>
Matija Skrgulja <[email protected]>
45 changes: 45 additions & 0 deletions dnf-plugins-core.spec
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,34 @@ Post transaction actions Plugin for DNF, Python 3 version. Plugin runs actions
files.
%endif

%if %{with python2}
%package -n python2-dnf-plugin-pre-transaction-actions
Summary: Pre transaction actions Plugin for DNF
Requires: python2-%{name} = %{version}-%{release}
%if !%{with python3}
Provides: dnf-plugin-pre-transaction-actions = %{version}-%{release}
%endif
Conflicts: python3-dnf-plugin-pre-transaction-actions < %{version}-%{release}

%description -n python2-dnf-plugin-pre-transaction-actions
Pre transaction actions Plugin for DNF, Python 2 version. Plugin runs actions
(shell commands) before transaction is completed. Actions are defined in action
files.
%endif

%if %{with python3}
%package -n python3-dnf-plugin-pre-transaction-actions
Summary: Pre transaction actions Plugin for DNF
Requires: python3-%{name} = %{version}-%{release}
Provides: dnf-plugin-pre-transaction-actions = %{version}-%{release}
Conflicts: python2-dnf-plugin-pre-transaction-actions < %{version}-%{release}

%description -n python3-dnf-plugin-pre-transaction-actions
Pre transaction actions Plugin for DNF, Python 3 version. Plugin runs actions
(shell commands) before transaction is completed. Actions are defined in action
files.
%endif

%if 0%{?rhel} == 0 && %{with python2}
%package -n python2-dnf-plugin-show-leaves
Summary: Leaves Plugin for DNF
Expand Down Expand Up @@ -746,6 +774,23 @@ ln -sf %{_mandir}/man1/%{yum_utils_subpackage_name}.1.gz %{buildroot}%{_mandir}/
%{_mandir}/man8/dnf-post-transaction-actions.*
%endif

%if %{with python2}
%files -n python2-dnf-plugin-pre-transaction-actions
%config(noreplace) %{_sysconfdir}/dnf/plugins/pre-transaction-actions.conf
%config(noreplace) %{_sysconfdir}/dnf/plugins/pre-transaction-actions.d
%{python2_sitelib}/dnf-plugins/pre-transaction-actions.*
%{_mandir}/man8/dnf-pre-transaction-actions.*
%endif

%if %{with python3}
%files -n python3-dnf-plugin-pre-transaction-actions
%config(noreplace) %{_sysconfdir}/dnf/plugins/pre-transaction-actions.conf
%config(noreplace) %{_sysconfdir}/dnf/plugins/pre-transaction-actions.d
%{python3_sitelib}/dnf-plugins/pre-transaction-actions.*
%{python3_sitelib}/dnf-plugins/__pycache__/pre-transaction-actions.*
%{_mandir}/man8/dnf-pre-transaction-actions.*
%endif

%if 0%{?rhel} == 0

%if %{with python2}
Expand Down
1 change: 1 addition & 0 deletions doc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/dnf-builddep.8
${CMAKE_CURRENT_BINARY_DIR}/dnf-repomanage.8
${CMAKE_CURRENT_BINARY_DIR}/dnf-reposync.8
${CMAKE_CURRENT_BINARY_DIR}/dnf-post-transaction-actions.8
${CMAKE_CURRENT_BINARY_DIR}/dnf-pre-transaction-actions.8
${CMAKE_CURRENT_BINARY_DIR}/dnf-show-leaves.8
${CMAKE_CURRENT_BINARY_DIR}/dnf-system-upgrade.8
${CMAKE_CURRENT_BINARY_DIR}/dnf-versionlock.8
Expand Down
2 changes: 2 additions & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ def version_readout():
('reposync', 'dnf-reposync', u'DNF reposync Plugin', AUTHORS, 8),
('post-transaction-actions', 'dnf-post-transaction-actions',
u'DNF post transaction actions Plugin', AUTHORS, 8),
('pre-transaction-actions', 'dnf-pre-transaction-actions',
u'DNF pre transaction actions Plugin', AUTHORS, 8),
('show-leaves', 'dnf-show-leaves', u'DNF show-leaves Plugin', AUTHORS, 8),
('system-upgrade', 'dnf-system-upgrade', u'DNF system-upgrade Plugin', AUTHORS, 8),
('versionlock', 'dnf-versionlock', u'DNF versionlock Plugin', AUTHORS, 8),
Expand Down
1 change: 1 addition & 0 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ This documents core plugins of DNF:
modulesync
needs_restarting
post-transaction-actions
pre-transaction-actions
repoclosure
repodiff
repograph
Expand Down
96 changes: 96 additions & 0 deletions doc/pre-transaction-actions.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
..
Copyright (C) 2019 Red Hat, Inc.

This copyrighted material is made available to anyone wishing to use,
modify, copy, or redistribute it subject to the terms and conditions of
the GNU General Public License v.2, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY expressed or implied, including the implied warranties of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
Public License for more details. You should have received a copy of the
GNU General Public License along with this program; if not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA. Any Red Hat trademarks that are incorporated in the
source code or documentation are not subject to the GNU General Public
License and may only be used or replicated with the express permission of
Red Hat, Inc.

===================================
DNF pre-transaction-actions Plugin
===================================

-----------
Description
-----------

The plugin allows to define actions to be executed upon starting an RPM transaction. Each action
may define a (glob-like) filtering rule on the package NEVRA or package files, as well as whether
the package was installed or removed. Actions are defined in action files.

-------------
Configuration
-------------

The plugin configuration is in ``/etc/dnf/plugins/pre-transaction-actions.conf``. All configuration
options are in the ``[main]`` section.

``enabled``
Whether the plugin is enabled. Default value is ``True``.

``actiondir``
Path to the directory with action files. Action files must have the ".action" extension.
Default value is "/etc/dnf/plugins/pre-transaction-actions.d/".

------------------
Action file format
------------------

Empty lines and lines that start with a '#' character are ignored.
Each non-comment line defines an action and consists of three items separated by colons:
``package_filter:transaction_state:command``.

``package_filter``
A (glob-like) filtering rule applied on the package NEVRA (also in the shortened forms) or
package files.

``transaction_state``
Filters packages according to their state in the transaction.

* ``in`` - packages that will appeared on the system (downgrade, install, obsolete, reinstall, upgrade)
* ``out`` - packages that will disappeared from the system (downgraded, obsoleted, remove, upgraded)
* ``any`` - all packages

``command``
Any shell command.
The following variables in the command will be substituted:
* ``${name}``, ``$name`` - package name
* ``${arch}``, ``$arch`` - package arch
* ``${ver}``, ``$ver`` - package version
* ``${rel}``, ``$rel`` - package release
* ``${epoch}``, ``$epoch`` - package epoch
* ``${repoid}``, ``$repoid`` - package repository id
* ``${state}``, ``$state`` - the change of package state in the transaction:
"downgrade", "downgraded", "install", "obsolete", "obsoleted", "reinstall",
"reinstalled", "remove", "upgrade", "upgraded"

The shell command will be evaluated for each package that matched the ``package_filter`` and
the ``transaction_state``. However, after variable substitution, any duplicate commands will be
removed and each command will only be executed once per transaction. The order of execution
of the commands follows the order in the action files, but may differ from the order of
packages in the transaction. In other words, when you define several action lines for the
same ``package_filter`` these lines will be executed in the order they were defined in the
action file when the ``package_filter`` matches a package during the ``trasaction_state`` state.
However, the order of when a particular ``package_filter`` is invoked depends on the position
of the corresponding package in the transaction.

An example action file:
^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: none

# log all packages (state, nevra, repo) in transaction into a file.
*:any:echo '${state} ${name}-${epoch}:${ver}-${rel}.${arch} repo ${repoid}' >>/tmp/pre-trans-actions-trans.log

# The same shell command (after variables substitution) is executed only once per transaction.
*:any:echo '${repoid}' >>/tmp/pre-trans-actions-repos
# will write each repo only once to /tmp/pre-trans-actions-repos, even if multiple packages from
# the same repo were matched
2 changes: 2 additions & 0 deletions etc/dnf/plugins/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
ADD_SUBDIRECTORY (copr.d)
ADD_SUBDIRECTORY (post-transaction-actions.d)
ADD_SUBDIRECTORY (pre-transaction-actions.d)
INSTALL (FILES copr.conf DESTINATION ${SYSCONFDIR}/dnf/plugins)
INSTALL (FILES debuginfo-install.conf DESTINATION ${SYSCONFDIR}/dnf/plugins)
if (${WITHOUT_LOCAL} STREQUAL "0")
Expand All @@ -8,3 +9,4 @@ endif()
INSTALL (FILES versionlock.conf DESTINATION ${SYSCONFDIR}/dnf/plugins)
INSTALL (FILES versionlock.list DESTINATION ${SYSCONFDIR}/dnf/plugins)
INSTALL (FILES post-transaction-actions.conf DESTINATION ${SYSCONFDIR}/dnf/plugins)
INSTALL (FILES pre-transaction-actions.conf DESTINATION ${SYSCONFDIR}/dnf/plugins)
4 changes: 4 additions & 0 deletions etc/dnf/plugins/pre-transaction-actions.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[main]
enabled = 1
actiondir = /etc/dnf/plugins/pre-transaction-actions.d/

1 change: 1 addition & 0 deletions etc/dnf/plugins/pre-transaction-actions.d/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
INSTALL (DIRECTORY . DESTINATION ${SYSCONFDIR}/dnf/plugins/pre-transaction-actions.d FILES_MATCHING PATTERN "(NONE)")
1 change: 1 addition & 0 deletions plugins/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ INSTALL (FILES migrate.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
endif()
INSTALL (FILES needs_restarting.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
INSTALL (FILES post-transaction-actions.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
INSTALL (FILES pre-transaction-actions.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
INSTALL (FILES repoclosure.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
INSTALL (FILES repodiff.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
INSTALL (FILES repograph.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
Expand Down
150 changes: 150 additions & 0 deletions plugins/pre-transaction-actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
"""
This plugin is basically a POST-transaction-actions plugin
but modified (with minimall changes) to work as pre-transaction-actions plugin

The plugin allows to define actions to be executed before an RPM transaction. Each action
may define a (glob-like) filtering rule on the package NEVRA or package files, as well as whether
the package was installed or removed. Actions are defined in action files.

The action can start any shell command. Commands can contain variables. The same command (after
variables substitution) is executed only once per transaction. The order of execution
of the commands may differ from the order of the packages in the transaction.
"""

from dnfpluginscore import _, logger

import libdnf.conf
import dnf
import dnf.transaction
import glob
import os
import subprocess


class PreTransactionActions(dnf.Plugin):

name = "pre-transaction-actions"

def __init__(self, base, cli):
super(PreTransactionActions, self).__init__(base, cli)
self.actiondir = "/etc/dnf/plugins/pre-transaction-actions.d/"
self.base = base
self.logger = logger

def config(self):
conf = self.read_config(self.base.conf)
if conf.has_section("main"):
if conf.has_option("main", "actiondir"):
self.actiondir = conf.get("main", "actiondir")

def _parse_actions(self):
"""Parses *.action files from self.actiondir path.
Parsed actions are stored in a list of tuples."""

action_file_list = []
if os.access(self.actiondir, os.R_OK):
action_file_list.extend(glob.glob(self.actiondir + "*.action"))

action_tuples = [] # (action key, action_state, shell command)
for file_name in action_file_list:
for line in open(file_name).readlines():
line = line.strip()
if line and line[0] != "#":
try:
(action_key, action_state, action_command) = line.split(':', 2)
except ValueError as e:
self.logger.error(_('Bad Action Line "%s": %s') % (line, e))
continue
else:
action_tuples.append((action_key, action_state, action_command))

return action_tuples

def _replace_vars(self, ts_item, command):
"""Replaces variables in the command.
Variables: ${name}, ${arch}, ${ver}, ${rel}, ${epoch}, ${repoid}, ${state}
or $name, $arch, $ver, $rel, $epoch, $repoid, $state"""

action_dict = {dnf.transaction.PKG_DOWNGRADE: "downgrade",
dnf.transaction.PKG_DOWNGRADED: "downgraded",
dnf.transaction.PKG_INSTALL: "install",
dnf.transaction.PKG_OBSOLETE: "obsolete",
dnf.transaction.PKG_OBSOLETED: "obsoleted",
dnf.transaction.PKG_REINSTALL: "reinstall",
dnf.transaction.PKG_REINSTALLED: "reinstalled",
dnf.transaction.PKG_REMOVE: "remove",
dnf.transaction.PKG_UPGRADE: "upgrade",
dnf.transaction.PKG_UPGRADED: "upgraded"}

action = action_dict.get(ts_item.action, "unknown - %s" % ts_item.action)

vardict = {"name": ts_item.name,
"arch": ts_item.arch,
"ver": ts_item.version,
"rel": ts_item.release,
"epoch": str(ts_item.epoch),
"repoid": ts_item.from_repo,
"state": action}

result = libdnf.conf.ConfigParser.substitute(command, vardict)
return result

def pre_transaction(self):
action_tuples = self._parse_actions()

in_ts_items = []
out_ts_items = []
all_ts_items = []
for ts_item in self.base.transaction:
if ts_item.action in dnf.transaction.FORWARD_ACTIONS:
in_ts_items.append(ts_item)
elif ts_item.action in dnf.transaction.BACKWARD_ACTIONS:
out_ts_items.append(ts_item)
else:
# The action is not rpm change. It can be a reason change, therefore we can skip that item
continue
all_ts_items.append(ts_item)

commands_to_run = []
for (a_key, a_state, a_command) in action_tuples:
if a_state == "in":
ts_items = in_ts_items
elif a_state == "out":
ts_items = out_ts_items
elif a_state == "any":
ts_items = all_ts_items
else:
# unsupported state, skip it
self.logger.error(_("Bad Transaction State: %s") % a_state)
continue

subj = dnf.subject.Subject(a_key)
pkgs = [tsi.pkg for tsi in ts_items]
query = self.base.sack.query().filterm(pkg=pkgs)
query = subj.get_best_query(self.base.sack, with_nevra=True, with_provides=False,
with_filenames=True, query=query)

for pkg in query:
ts_item_list = [tsi for tsi in ts_items
if tsi.pkg == pkg and tsi.pkg.reponame == pkg.reponame]
ts_item = ts_item_list[0]
command = self._replace_vars(ts_item, a_command)
commands_to_run.append(command)

# de-dup the list
seen = set()
commands_to_run = [x for x in commands_to_run if x not in seen and not seen.add(x)]

for command in commands_to_run:
try:
proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, close_fds=True,
universal_newlines=True)
stdout, stderr = proc.communicate()
if stdout:
self.logger.info(_('pre-transaction-actions: %s') % (stdout))
if stderr:
self.logger.error(_('pre-transaction-actions: %s') % (stderr))
except Exception as e:
self.logger.error(_('pre-transaction-actions: Bad Command "%s": %s') %
(command, e))
Loading