Blame dnf/plugin.py

Packit 6f3914
# plugin.py
Packit 6f3914
# The interface for building DNF plugins.
Packit 6f3914
#
Packit 6f3914
# Copyright (C) 2012-2016 Red Hat, Inc.
Packit 6f3914
#
Packit 6f3914
# This copyrighted material is made available to anyone wishing to use,
Packit 6f3914
# modify, copy, or redistribute it subject to the terms and conditions of
Packit 6f3914
# the GNU General Public License v.2, or (at your option) any later version.
Packit 6f3914
# This program is distributed in the hope that it will be useful, but WITHOUT
Packit 6f3914
# ANY WARRANTY expressed or implied, including the implied warranties of
Packit 6f3914
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
Packit 6f3914
# Public License for more details.  You should have received a copy of the
Packit 6f3914
# GNU General Public License along with this program; if not, write to the
Packit 6f3914
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
Packit 6f3914
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
Packit 6f3914
# source code or documentation are not subject to the GNU General Public
Packit 6f3914
# License and may only be used or replicated with the express permission of
Packit 6f3914
# Red Hat, Inc.
Packit 6f3914
#
Packit 6f3914
Packit 6f3914
from __future__ import absolute_import
Packit 6f3914
from __future__ import print_function
Packit 6f3914
from __future__ import unicode_literals
Packit 6f3914
Packit 6f3914
import fnmatch
Packit 6f3914
import glob
Packit 6f3914
import importlib
Packit 6f3914
import inspect
Packit 6f3914
import logging
Packit 6f3914
import operator
Packit 6f3914
import os
Packit 6f3914
import sys
Packit 6f3914
import traceback
Packit 6f3914
Packit 6f3914
import libdnf
Packit 6f3914
import dnf.logging
Packit 6f3914
import dnf.pycomp
Packit 6f3914
import dnf.util
Packit 6f3914
from dnf.i18n import _
Packit 6f3914
Packit 6f3914
logger = logging.getLogger('dnf')
Packit 6f3914
Packit 6f3914
DYNAMIC_PACKAGE = 'dnf.plugin.dynamic'
Packit 6f3914
Packit 6f3914
Packit 6f3914
class Plugin(object):
Packit 6f3914
    """The base class custom plugins must derive from. #:api"""
Packit 6f3914
Packit 6f3914
    name = '<invalid>'
Packit 6f3914
    config_name = None
Packit 6f3914
Packit 6f3914
    @classmethod
Packit 6f3914
    def read_config(cls, conf):
Packit 6f3914
        # :api
Packit 6f3914
        parser = libdnf.conf.ConfigParser()
Packit 6f3914
        name = cls.config_name if cls.config_name else cls.name
Packit 6f3914
        files = ['%s/%s.conf' % (path, name) for path in conf.pluginconfpath]
Packit 6f3914
        for file in files:
Packit 6f3914
            if os.path.isfile(file):
Packit 6f3914
                try:
Packit 6f3914
                    parser.read(file)
Packit 6f3914
                except Exception as e:
Packit 6f3914
                    raise dnf.exceptions.ConfigError(_("Parsing file failed: %s") % str(e))
Packit 6f3914
        return parser
Packit 6f3914
Packit 6f3914
    def __init__(self, base, cli):
Packit 6f3914
        # :api
Packit 6f3914
        self.base = base
Packit 6f3914
        self.cli = cli
Packit 6f3914
Packit 6f3914
    def pre_config(self):
Packit 6f3914
        # :api
Packit 6f3914
        pass
Packit 6f3914
Packit 6f3914
    def config(self):
Packit 6f3914
        # :api
Packit 6f3914
        pass
Packit 6f3914
Packit 6f3914
    def resolved(self):
Packit 6f3914
        # :api
Packit 6f3914
        pass
Packit 6f3914
Packit 6f3914
    def sack(self):
Packit 6f3914
        # :api
Packit 6f3914
        pass
Packit 6f3914
Packit 6f3914
    def pre_transaction(self):
Packit 6f3914
        # :api
Packit 6f3914
        pass
Packit 6f3914
Packit 6f3914
    def transaction(self):
Packit 6f3914
        # :api
Packit 6f3914
        pass
Packit 6f3914
Packit 6f3914
Packit 6f3914
class Plugins(object):
Packit 6f3914
    def __init__(self):
Packit 6f3914
        self.plugin_cls = []
Packit 6f3914
        self.plugins = []
Packit 6f3914
Packit 6f3914
    def _caller(self, method):
Packit 6f3914
        for plugin in self.plugins:
Packit 6f3914
            try:
Packit 6f3914
                getattr(plugin, method)()
Packit 6f3914
            except dnf.exceptions.Error:
Packit 6f3914
                raise
Packit 6f3914
            except Exception:
Packit 6f3914
                exc_type, exc_value, exc_traceback = sys.exc_info()
Packit 6f3914
                except_list = traceback.format_exception(exc_type, exc_value, exc_traceback)
Packit 6f3914
                logger.critical(''.join(except_list))
Packit 6f3914
Packit 6f3914
    def _check_enabled(self, conf, enable_plugins):
Packit 6f3914
        """Checks whether plugins are enabled or disabled in configuration files
Packit 6f3914
           and removes disabled plugins from list"""
Packit 6f3914
        for plug_cls in self.plugin_cls[:]:
Packit 6f3914
            name = plug_cls.name
Packit 6f3914
            if any(fnmatch.fnmatch(name, pattern) for pattern in enable_plugins):
Packit 6f3914
                continue
Packit 6f3914
            parser = plug_cls.read_config(conf)
Packit 6f3914
            # has it enabled = False?
Packit 6f3914
            disabled = (parser.has_section('main')
Packit 6f3914
                        and parser.has_option('main', 'enabled')
Packit 6f3914
                        and not parser.getboolean('main', 'enabled'))
Packit 6f3914
            if disabled:
Packit 6f3914
                self.plugin_cls.remove(plug_cls)
Packit 6f3914
Packit 6f3914
    def _load(self, conf, skips, enable_plugins):
Packit 6f3914
        """Dynamically load relevant plugin modules."""
Packit 6f3914
Packit 6f3914
        if DYNAMIC_PACKAGE in sys.modules:
Packit 6f3914
            raise RuntimeError("load_plugins() called twice")
Packit 6f3914
        sys.modules[DYNAMIC_PACKAGE] = package = dnf.pycomp.ModuleType(DYNAMIC_PACKAGE)
Packit 6f3914
        package.__path__ = []
Packit 6f3914
Packit 6f3914
        files = _get_plugins_files(conf.pluginpath, skips, enable_plugins)
Packit 6f3914
        _import_modules(package, files)
Packit 6f3914
        self.plugin_cls = _plugin_classes()[:]
Packit 6f3914
        self._check_enabled(conf, enable_plugins)
Packit 6f3914
        if len(self.plugin_cls) > 0:
Packit 6f3914
            names = sorted(plugin.name for plugin in self.plugin_cls)
Packit 6f3914
            logger.debug(_('Loaded plugins: %s'), ', '.join(names))
Packit 6f3914
Packit 6f3914
    def _run_pre_config(self):
Packit 6f3914
        self._caller('pre_config')
Packit 6f3914
Packit 6f3914
    def _run_config(self):
Packit 6f3914
        self._caller('config')
Packit 6f3914
Packit 6f3914
    def _run_init(self, base, cli=None):
Packit 6f3914
        for p_cls in self.plugin_cls:
Packit 6f3914
            plugin = p_cls(base, cli)
Packit 6f3914
            self.plugins.append(plugin)
Packit 6f3914
Packit 6f3914
    def run_sack(self):
Packit 6f3914
        self._caller('sack')
Packit 6f3914
Packit 6f3914
    def run_resolved(self):
Packit 6f3914
        self._caller('resolved')
Packit 6f3914
Packit 6f3914
    def run_pre_transaction(self):
Packit 6f3914
        self._caller('pre_transaction')
Packit 6f3914
Packit 6f3914
    def run_transaction(self):
Packit 6f3914
        self._caller('transaction')
Packit 6f3914
Packit 6f3914
    def _unload(self):
Packit 6f3914
        del sys.modules[DYNAMIC_PACKAGE]
Packit 6f3914
Packit 6f3914
    def unload_removed_plugins(self, transaction):
Packit 6f3914
        erased = set([package.name for package in transaction.remove_set])
Packit 6f3914
        if not erased:
Packit 6f3914
            return
Packit 6f3914
        installed = set([package.name for package in transaction.install_set])
Packit 6f3914
        transaction_diff = erased - installed
Packit 6f3914
        if not transaction_diff:
Packit 6f3914
            return
Packit 6f3914
        files_erased = set()
Packit 6f3914
        for pkg in transaction.remove_set:
Packit 6f3914
            if pkg.name in transaction_diff:
Packit 6f3914
                files_erased.update(pkg.files)
Packit 6f3914
        for plugin in self.plugins[:]:
Packit 6f3914
            if inspect.getfile(plugin.__class__) in files_erased:
Packit 6f3914
                self.plugins.remove(plugin)
Packit 6f3914
Packit 6f3914
Packit 6f3914
def _plugin_classes():
Packit 6f3914
    return Plugin.__subclasses__()
Packit 6f3914
Packit 6f3914
Packit 6f3914
def _import_modules(package, py_files):
Packit 6f3914
    for fn in py_files:
Packit 6f3914
        path, module = os.path.split(fn)
Packit 6f3914
        package.__path__.append(path)
Packit 6f3914
        (module, ext) = os.path.splitext(module)
Packit 6f3914
        name = '%s.%s' % (package.__name__, module)
Packit 6f3914
        try:
Packit 6f3914
            module = importlib.import_module(name)
Packit 6f3914
        except Exception as e:
Packit 6f3914
            logger.error(_('Failed loading plugin "%s": %s'), module, e)
Packit 6f3914
            logger.log(dnf.logging.SUBDEBUG, '', exc_info=True)
Packit 6f3914
Packit 6f3914
Packit 6f3914
def _get_plugins_files(paths, disable_plugins, enable_plugins):
Packit 6f3914
    plugins = []
Packit 6f3914
    disable_plugins = set(disable_plugins)
Packit 6f3914
    enable_plugins = set(enable_plugins)
Packit 6f3914
    pattern_enable_found = set()
Packit 6f3914
    pattern_disable_found = set()
Packit 6f3914
    for p in paths:
Packit 6f3914
        for fn in glob.glob('%s/*.py' % p):
Packit 6f3914
            (plugin_name, dummy) = os.path.splitext(os.path.basename(fn))
Packit 6f3914
            matched = True
Packit 6f3914
            enable_pattern_tested = False
Packit 6f3914
            for pattern_skip in disable_plugins:
Packit 6f3914
                if fnmatch.fnmatch(plugin_name, pattern_skip):
Packit 6f3914
                    pattern_disable_found.add(pattern_skip)
Packit 6f3914
                    matched = False
Packit 6f3914
                    for pattern_enable in enable_plugins:
Packit 6f3914
                        if fnmatch.fnmatch(plugin_name, pattern_enable):
Packit 6f3914
                            matched = True
Packit 6f3914
                            pattern_enable_found.add(pattern_enable)
Packit 6f3914
                    enable_pattern_tested = True
Packit 6f3914
            if not enable_pattern_tested:
Packit 6f3914
                for pattern_enable in enable_plugins:
Packit 6f3914
                    if fnmatch.fnmatch(plugin_name, pattern_enable):
Packit 6f3914
                        pattern_enable_found.add(pattern_enable)
Packit 6f3914
            if matched:
Packit 6f3914
                plugins.append(fn)
Packit 6f3914
    enable_not_found = enable_plugins.difference(pattern_enable_found)
Packit 6f3914
    if enable_not_found:
Packit 6f3914
        logger.warning(_("No matches found for the following enable plugin patterns: {}").format(
Packit 6f3914
            ", ".join(sorted(enable_not_found))))
Packit 6f3914
    disable_not_found = disable_plugins.difference(pattern_disable_found)
Packit 6f3914
    if disable_not_found:
Packit 6f3914
        logger.warning(_("No matches found for the following disable plugin patterns: {}").format(
Packit 6f3914
            ", ".join(sorted(disable_not_found))))
Packit 6f3914
    return plugins
Packit 6f3914
Packit 6f3914
Packit 6f3914
def register_command(command_class):
Packit 6f3914
    # :api
Packit 6f3914
    """A class decorator for automatic command registration."""
Packit 6f3914
    def __init__(self, base, cli):
Packit 6f3914
        if cli:
Packit 6f3914
            cli.register_command(command_class)
Packit 6f3914
    plugin_class = type(str(command_class.__name__ + 'Plugin'),
Packit 6f3914
                        (dnf.Plugin,),
Packit 6f3914
                        {"__init__": __init__,
Packit 6f3914
                         "name": command_class.aliases[0]})
Packit 6f3914
    command_class._plugin = plugin_class
Packit 6f3914
    return command_class