Blame dnf/cli/aliases.py

Packit Service 21c75c
# aliases.py
Packit Service 21c75c
# Resolving aliases in CLI arguments.
Packit Service 21c75c
#
Packit Service 21c75c
# Copyright (C) 2018 Red Hat, Inc.
Packit Service 21c75c
#
Packit Service 21c75c
# This copyrighted material is made available to anyone wishing to use,
Packit Service 21c75c
# modify, copy, or redistribute it subject to the terms and conditions of
Packit Service 21c75c
# the GNU General Public License v.2, or (at your option) any later version.
Packit Service 21c75c
# This program is distributed in the hope that it will be useful, but WITHOUT
Packit Service 21c75c
# ANY WARRANTY expressed or implied, including the implied warranties of
Packit Service 21c75c
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
Packit Service 21c75c
# Public License for more details.  You should have received a copy of the
Packit Service 21c75c
# GNU General Public License along with this program; if not, write to the
Packit Service 21c75c
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
Packit Service 21c75c
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
Packit Service 21c75c
# source code or documentation are not subject to the GNU General Public
Packit Service 21c75c
# License and may only be used or replicated with the express permission of
Packit Service 21c75c
# Red Hat, Inc.
Packit Service 21c75c
#
Packit Service 21c75c
Packit Service 21c75c
from __future__ import absolute_import
Packit Service 21c75c
from __future__ import unicode_literals
Packit Service 21c75c
from dnf.i18n import _
Packit Service 21c75c
Packit Service 21c75c
import collections
Packit Service 21c75c
import dnf.cli
Packit Service 21c75c
from dnf.conf.config import PRIO_DEFAULT
Packit Service 21c75c
import dnf.exceptions
Packit Service 21c75c
import libdnf.conf
Packit Service 21c75c
import logging
Packit Service 21c75c
import os
Packit Service 21c75c
import os.path
Packit Service 21c75c
Packit Service 21c75c
logger = logging.getLogger('dnf')
Packit Service 21c75c
Packit Service 21c75c
ALIASES_DROPIN_DIR = '/etc/dnf/aliases.d/'
Packit Service 21c75c
ALIASES_CONF_PATH = os.path.join(ALIASES_DROPIN_DIR, 'ALIASES.conf')
Packit Service 21c75c
ALIASES_USER_PATH = os.path.join(ALIASES_DROPIN_DIR, 'USER.conf')
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
class AliasesConfig(object):
Packit Service 21c75c
    def __init__(self, path):
Packit Service 21c75c
        self._path = path
Packit Service 21c75c
        self._parser = libdnf.conf.ConfigParser()
Packit Service 21c75c
        self._parser.read(self._path)
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def enabled(self):
Packit Service 21c75c
        option = libdnf.conf.OptionBool(True)
Packit Service 21c75c
        try:
Packit Service 21c75c
            option.set(PRIO_DEFAULT, self._parser.getData()["main"]["enabled"])
Packit Service 21c75c
        except IndexError:
Packit Service 21c75c
            pass
Packit Service 21c75c
        return option.getValue()
Packit Service 21c75c
Packit Service 21c75c
    @property
Packit Service 21c75c
    def aliases(self):
Packit Service 21c75c
        result = collections.OrderedDict()
Packit Service 21c75c
        section = "aliases"
Packit Service 21c75c
        if not self._parser.hasSection(section):
Packit Service 21c75c
            return result
Packit Service 21c75c
        for key in self._parser.options(section):
Packit Service 21c75c
            value = self._parser.getValue(section, key)
Packit Service 21c75c
            if not value:
Packit Service 21c75c
                continue
Packit Service 21c75c
            result[key] = value.split()
Packit Service 21c75c
        return result
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
class Aliases(object):
Packit Service 21c75c
    def __init__(self):
Packit Service 21c75c
        self.aliases = collections.OrderedDict()
Packit Service 21c75c
        self.conf = None
Packit Service 21c75c
        self.enabled = True
Packit Service 21c75c
Packit Service 21c75c
        if self._disabled_by_environ():
Packit Service 21c75c
            self.enabled = False
Packit Service 21c75c
            return
Packit Service 21c75c
Packit Service 21c75c
        self._load_main()
Packit Service 21c75c
Packit Service 21c75c
        if not self.enabled:
Packit Service 21c75c
            return
Packit Service 21c75c
Packit Service 21c75c
        self._load_aliases()
Packit Service 21c75c
Packit Service 21c75c
    def _disabled_by_environ(self):
Packit Service 21c75c
        option = libdnf.conf.OptionBool(True)
Packit Service 21c75c
        try:
Packit Service 21c75c
            option.set(PRIO_DEFAULT, os.environ['DNF_DISABLE_ALIASES'])
Packit Service 21c75c
            return option.getValue()
Packit Service 21c75c
        except KeyError:
Packit Service 21c75c
            return False
Packit Service 21c75c
        except RuntimeError:
Packit Service 21c75c
            logger.warning(
Packit Service 21c75c
                _('Unexpected value of environment variable: '
Packit Service 21c75c
                  'DNF_DISABLE_ALIASES=%s'), os.environ['DNF_DISABLE_ALIASES'])
Packit Service 21c75c
            return True
Packit Service 21c75c
Packit Service 21c75c
    def _load_conf(self, path):
Packit Service 21c75c
        try:
Packit Service 21c75c
            return AliasesConfig(path)
Packit Service 21c75c
        except RuntimeError as e:
Packit Service 21c75c
            raise dnf.exceptions.ConfigError(
Packit Service 21c75c
                _('Parsing file "%s" failed: %s') % (path, e))
Packit Service 21c75c
        except IOError as e:
Packit Service 21c75c
            raise dnf.exceptions.ConfigError(
Packit Service 21c75c
                _('Cannot read file "%s": %s') % (path, e))
Packit Service 21c75c
Packit Service 21c75c
    def _load_main(self):
Packit Service 21c75c
        try:
Packit Service 21c75c
            self.conf = self._load_conf(ALIASES_CONF_PATH)
Packit Service 21c75c
            self.enabled = self.conf.enabled
Packit Service 21c75c
        except dnf.exceptions.ConfigError as e:
Packit Service 21c75c
            logger.debug(_('Config error: %s'), e)
Packit Service 21c75c
Packit Service 21c75c
    def _load_aliases(self, filenames=None):
Packit Service 21c75c
        if filenames is None:
Packit Service 21c75c
            try:
Packit Service 21c75c
                filenames = self._dropin_dir_filenames()
Packit Service 21c75c
            except dnf.exceptions.ConfigError:
Packit Service 21c75c
                return
Packit Service 21c75c
        for filename in filenames:
Packit Service 21c75c
            try:
Packit Service 21c75c
                conf = self._load_conf(filename)
Packit Service 21c75c
                if conf.enabled:
Packit Service 21c75c
                    self.aliases.update(conf.aliases)
Packit Service 21c75c
            except dnf.exceptions.ConfigError as e:
Packit Service 21c75c
                logger.warning(_('Config error: %s'), e)
Packit Service 21c75c
Packit Service 21c75c
    def _dropin_dir_filenames(self):
Packit Service 21c75c
        # Get default aliases config filenames:
Packit Service 21c75c
        #   all files from ALIASES_DROPIN_DIR,
Packit Service 21c75c
        #   and ALIASES_USER_PATH as the last one (-> override all others)
Packit Service 21c75c
        ignored_filenames = [os.path.basename(ALIASES_CONF_PATH),
Packit Service 21c75c
                             os.path.basename(ALIASES_USER_PATH)]
Packit Service 21c75c
Packit Service 21c75c
        def _ignore_filename(filename):
Packit Service 21c75c
            return filename in ignored_filenames or\
Packit Service 21c75c
                filename.startswith('.') or\
Packit Service 21c75c
                not filename.endswith(('.conf', '.CONF'))
Packit Service 21c75c
Packit Service 21c75c
        filenames = []
Packit Service 21c75c
        try:
Packit Service 21c75c
            if not os.path.exists(ALIASES_DROPIN_DIR):
Packit Service 21c75c
                os.mkdir(ALIASES_DROPIN_DIR)
Packit Service 21c75c
            for fn in sorted(os.listdir(ALIASES_DROPIN_DIR)):
Packit Service 21c75c
                if _ignore_filename(fn):
Packit Service 21c75c
                    continue
Packit Service 21c75c
                filenames.append(os.path.join(ALIASES_DROPIN_DIR, fn))
Packit Service 21c75c
        except (IOError, OSError) as e:
Packit Service 21c75c
            raise dnf.exceptions.ConfigError(e)
Packit Service 21c75c
        if os.path.exists(ALIASES_USER_PATH):
Packit Service 21c75c
            filenames.append(ALIASES_USER_PATH)
Packit Service 21c75c
        return filenames
Packit Service 21c75c
Packit Service 21c75c
    def _resolve(self, args):
Packit Service 21c75c
        stack = []
Packit Service 21c75c
        self.prefix_options = []
Packit Service 21c75c
Packit Service 21c75c
        def store_prefix(args):
Packit Service 21c75c
            num = 0
Packit Service 21c75c
            for arg in args:
Packit Service 21c75c
                if arg and arg[0] != '-':
Packit Service 21c75c
                    break
Packit Service 21c75c
                num += 1
Packit Service 21c75c
Packit Service 21c75c
            self.prefix_options += args[:num]
Packit Service 21c75c
Packit Service 21c75c
            return args[num:]
Packit Service 21c75c
Packit Service 21c75c
        def subresolve(args):
Packit Service 21c75c
            suffix = store_prefix(args)
Packit Service 21c75c
Packit Service 21c75c
            if (not suffix or  # Current alias on stack is resolved
Packit Service 21c75c
                    suffix[0] not in self.aliases or  # End resolving
Packit Service 21c75c
                    suffix[0].startswith('\\')):  # End resolving
Packit Service 21c75c
                try:
Packit Service 21c75c
                    stack.pop()
Packit Service 21c75c
Packit Service 21c75c
                    # strip the '\' if it exists
Packit Service 21c75c
                    if suffix[0].startswith('\\'):
Packit Service 21c75c
                        suffix[0] = suffix[0][1:]
Packit Service 21c75c
                except IndexError:
Packit Service 21c75c
                    pass
Packit Service 21c75c
Packit Service 21c75c
                return suffix
Packit Service 21c75c
Packit Service 21c75c
            if suffix[0] in stack:  # Infinite recursion detected
Packit Service 21c75c
                raise dnf.exceptions.Error(
Packit Service 21c75c
                    _('Aliases contain infinite recursion'))
Packit Service 21c75c
Packit Service 21c75c
            # Next word must be an alias
Packit Service 21c75c
            stack.append(suffix[0])
Packit Service 21c75c
            current_alias_result = subresolve(self.aliases[suffix[0]])
Packit Service 21c75c
            if current_alias_result:  # We reached non-alias or '\'
Packit Service 21c75c
                return current_alias_result + suffix[1:]
Packit Service 21c75c
            else:  # Need to resolve aliases in the rest
Packit Service 21c75c
                return subresolve(suffix[1:])
Packit Service 21c75c
Packit Service 21c75c
        suffix = subresolve(args)
Packit Service 21c75c
        return self.prefix_options + suffix
Packit Service 21c75c
Packit Service 21c75c
    def resolve(self, args):
Packit Service 21c75c
        if self.enabled:
Packit Service 21c75c
            try:
Packit Service 21c75c
                args = self._resolve(args)
Packit Service 21c75c
            except dnf.exceptions.Error as e:
Packit Service 21c75c
                logger.error(_('%s, using original arguments.'), e)
Packit Service 21c75c
        return args