Blame dnf/cli/aliases.py

Packit 6f3914
# aliases.py
Packit 6f3914
# Resolving aliases in CLI arguments.
Packit 6f3914
#
Packit 6f3914
# Copyright (C) 2018 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 unicode_literals
Packit 6f3914
from dnf.i18n import _
Packit 6f3914
Packit 6f3914
import collections
Packit 6f3914
import dnf.cli
Packit 6f3914
from dnf.conf.config import PRIO_DEFAULT
Packit 6f3914
import dnf.exceptions
Packit 6f3914
import libdnf.conf
Packit 6f3914
import logging
Packit 6f3914
import os
Packit 6f3914
import os.path
Packit 6f3914
Packit 6f3914
logger = logging.getLogger('dnf')
Packit 6f3914
Packit 6f3914
ALIASES_DROPIN_DIR = '/etc/dnf/aliases.d/'
Packit 6f3914
ALIASES_CONF_PATH = os.path.join(ALIASES_DROPIN_DIR, 'ALIASES.conf')
Packit 6f3914
ALIASES_USER_PATH = os.path.join(ALIASES_DROPIN_DIR, 'USER.conf')
Packit 6f3914
Packit 6f3914
Packit 6f3914
class AliasesConfig(object):
Packit 6f3914
    def __init__(self, path):
Packit 6f3914
        self._path = path
Packit 6f3914
        self._parser = libdnf.conf.ConfigParser()
Packit 6f3914
        self._parser.read(self._path)
Packit 6f3914
Packit 6f3914
    @property
Packit 6f3914
    def enabled(self):
Packit 6f3914
        option = libdnf.conf.OptionBool(True)
Packit 6f3914
        try:
Packit 6f3914
            option.set(PRIO_DEFAULT, self._parser.getData()["main"]["enabled"])
Packit 6f3914
        except IndexError:
Packit 6f3914
            pass
Packit 6f3914
        return option.getValue()
Packit 6f3914
Packit 6f3914
    @property
Packit 6f3914
    def aliases(self):
Packit 6f3914
        result = collections.OrderedDict()
Packit 6f3914
        section = "aliases"
Packit 6f3914
        if not self._parser.hasSection(section):
Packit 6f3914
            return result
Packit 6f3914
        for key in self._parser.options(section):
Packit 6f3914
            value = self._parser.getValue(section, key)
Packit 6f3914
            if not value:
Packit 6f3914
                continue
Packit 6f3914
            result[key] = value.split()
Packit 6f3914
        return result
Packit 6f3914
Packit 6f3914
Packit 6f3914
class Aliases(object):
Packit 6f3914
    def __init__(self):
Packit 6f3914
        self.aliases = collections.OrderedDict()
Packit 6f3914
        self.conf = None
Packit 6f3914
        self.enabled = True
Packit 6f3914
Packit 6f3914
        if self._disabled_by_environ():
Packit 6f3914
            self.enabled = False
Packit 6f3914
            return
Packit 6f3914
Packit 6f3914
        self._load_main()
Packit 6f3914
Packit 6f3914
        if not self.enabled:
Packit 6f3914
            return
Packit 6f3914
Packit 6f3914
        self._load_aliases()
Packit 6f3914
Packit 6f3914
    def _disabled_by_environ(self):
Packit 6f3914
        option = libdnf.conf.OptionBool(True)
Packit 6f3914
        try:
Packit 6f3914
            option.set(PRIO_DEFAULT, os.environ['DNF_DISABLE_ALIASES'])
Packit 6f3914
            return option.getValue()
Packit 6f3914
        except KeyError:
Packit 6f3914
            return False
Packit 6f3914
        except RuntimeError:
Packit 6f3914
            logger.warning(
Packit 6f3914
                _('Unexpected value of environment variable: '
Packit 6f3914
                  'DNF_DISABLE_ALIASES=%s'), os.environ['DNF_DISABLE_ALIASES'])
Packit 6f3914
            return True
Packit 6f3914
Packit 6f3914
    def _load_conf(self, path):
Packit 6f3914
        try:
Packit 6f3914
            return AliasesConfig(path)
Packit 6f3914
        except RuntimeError as e:
Packit 6f3914
            raise dnf.exceptions.ConfigError(
Packit 6f3914
                _('Parsing file "%s" failed: %s') % (path, e))
Packit 6f3914
        except IOError as e:
Packit 6f3914
            raise dnf.exceptions.ConfigError(
Packit 6f3914
                _('Cannot read file "%s": %s') % (path, e))
Packit 6f3914
Packit 6f3914
    def _load_main(self):
Packit 6f3914
        try:
Packit 6f3914
            self.conf = self._load_conf(ALIASES_CONF_PATH)
Packit 6f3914
            self.enabled = self.conf.enabled
Packit 6f3914
        except dnf.exceptions.ConfigError as e:
Packit 6f3914
            logger.debug(_('Config error: %s'), e)
Packit 6f3914
Packit 6f3914
    def _load_aliases(self, filenames=None):
Packit 6f3914
        if filenames is None:
Packit 6f3914
            try:
Packit 6f3914
                filenames = self._dropin_dir_filenames()
Packit 6f3914
            except dnf.exceptions.ConfigError:
Packit 6f3914
                return
Packit 6f3914
        for filename in filenames:
Packit 6f3914
            try:
Packit 6f3914
                conf = self._load_conf(filename)
Packit 6f3914
                self.aliases.update(conf.aliases)
Packit 6f3914
            except dnf.exceptions.ConfigError as e:
Packit 6f3914
                logger.warning(_('Config error: %s'), e)
Packit 6f3914
Packit 6f3914
    def _dropin_dir_filenames(self):
Packit 6f3914
        # Get default aliases config filenames:
Packit 6f3914
        #   all files from ALIASES_DROPIN_DIR,
Packit 6f3914
        #   and ALIASES_USER_PATH as the last one (-> override all others)
Packit 6f3914
        ignored_filenames = [os.path.basename(ALIASES_CONF_PATH),
Packit 6f3914
                             os.path.basename(ALIASES_USER_PATH)]
Packit 6f3914
Packit 6f3914
        def _ignore_filename(filename):
Packit 6f3914
            return filename in ignored_filenames or\
Packit 6f3914
                filename.startswith('.') or\
Packit 6f3914
                not filename.endswith(('.conf', '.CONF'))
Packit 6f3914
Packit 6f3914
        filenames = []
Packit 6f3914
        try:
Packit 6f3914
            if not os.path.exists(ALIASES_DROPIN_DIR):
Packit 6f3914
                os.mkdir(ALIASES_DROPIN_DIR)
Packit 6f3914
            for fn in os.listdir(ALIASES_DROPIN_DIR):
Packit 6f3914
                if _ignore_filename(fn):
Packit 6f3914
                    continue
Packit 6f3914
                filenames.append(os.path.join(ALIASES_DROPIN_DIR, fn))
Packit 6f3914
        except (IOError, OSError) as e:
Packit 6f3914
            raise dnf.exceptions.ConfigError(e)
Packit 6f3914
        if os.path.exists(ALIASES_USER_PATH):
Packit 6f3914
            filenames.append(ALIASES_USER_PATH)
Packit 6f3914
        return filenames
Packit 6f3914
Packit 6f3914
    def _resolve(self, args):
Packit 6f3914
        stack = []
Packit 6f3914
        self.prefix_options = []
Packit 6f3914
Packit 6f3914
        def store_prefix(args):
Packit 6f3914
            num = 0
Packit 6f3914
            for arg in args:
Packit 6f3914
                if arg and arg[0] != '-':
Packit 6f3914
                    break
Packit 6f3914
                num += 1
Packit 6f3914
Packit 6f3914
            self.prefix_options += args[:num]
Packit 6f3914
Packit 6f3914
            return args[num:]
Packit 6f3914
Packit 6f3914
        def subresolve(args):
Packit 6f3914
            suffix = store_prefix(args)
Packit 6f3914
Packit 6f3914
            if (not suffix or  # Current alias on stack is resolved
Packit 6f3914
                    suffix[0] not in self.aliases or  # End resolving
Packit 6f3914
                    suffix[0].startswith('\\')):  # End resolving
Packit 6f3914
                try:
Packit 6f3914
                    stack.pop()
Packit 6f3914
                except IndexError:
Packit 6f3914
                    pass
Packit 6f3914
                return suffix
Packit 6f3914
Packit 6f3914
            if suffix[0] in stack:  # Infinite recursion detected
Packit 6f3914
                raise dnf.exceptions.Error(
Packit 6f3914
                    _('Aliases contain infinite recursion'))
Packit 6f3914
Packit 6f3914
            # Next word must be an alias
Packit 6f3914
            stack.append(suffix[0])
Packit 6f3914
            current_alias_result = subresolve(self.aliases[suffix[0]])
Packit 6f3914
            if current_alias_result:  # We reached non-alias or '\'
Packit 6f3914
                return current_alias_result + suffix[1:]
Packit 6f3914
            else:  # Need to resolve aliases in the rest
Packit 6f3914
                return subresolve(suffix[1:])
Packit 6f3914
Packit 6f3914
        suffix = subresolve(args)
Packit 6f3914
        return self.prefix_options + suffix
Packit 6f3914
Packit 6f3914
    def resolve(self, args):
Packit 6f3914
        if self.enabled:
Packit 6f3914
            try:
Packit 6f3914
                args = self._resolve(args)
Packit 6f3914
            except dnf.exceptions.Error as e:
Packit 6f3914
                logger.error(_('%s, using original arguments.'), e)
Packit 6f3914
        return args