Blame plugins/config_manager.py

Packit Service 27f74b
#
Packit Service 27f74b
# Copyright (C) 2015  Red Hat, Inc.
Packit Service 27f74b
#
Packit Service 27f74b
# This copyrighted material is made available to anyone wishing to use,
Packit Service 27f74b
# modify, copy, or redistribute it subject to the terms and conditions of
Packit Service 27f74b
# the GNU General Public License v.2, or (at your option) any later version.
Packit Service 27f74b
# This program is distributed in the hope that it will be useful, but WITHOUT
Packit Service 27f74b
# ANY WARRANTY expressed or implied, including the implied warranties of
Packit Service 27f74b
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
Packit Service 27f74b
# Public License for more details.  You should have received a copy of the
Packit Service 27f74b
# GNU General Public License along with this program; if not, write to the
Packit Service 27f74b
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
Packit Service 27f74b
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
Packit Service 27f74b
# source code or documentation are not subject to the GNU General Public
Packit Service 27f74b
# License and may only be used or replicated with the express permission of
Packit Service 27f74b
# Red Hat, Inc.
Packit Service 27f74b
#
Packit Service 27f74b
Packit Service 27f74b
from __future__ import absolute_import
Packit Service 27f74b
from __future__ import unicode_literals
Packit Service 27f74b
from dnfpluginscore import _, logger, P_
Packit Service 27f74b
Packit Service 27f74b
import dnf
Packit Service 27f74b
import dnf.cli
Packit Service 27f74b
import dnf.pycomp
Packit Service 27f74b
import dnf.util
Packit Service 27f74b
import fnmatch
Packit Service 27f74b
import os
Packit Service 27f74b
import re
Packit Service 27f74b
import shutil
Packit Service 27f74b
Packit Service 27f74b
Packit Service 27f74b
@dnf.plugin.register_command
Packit Service 27f74b
class ConfigManagerCommand(dnf.cli.Command):
Packit Service 27f74b
Packit Service 27f74b
    aliases = ['config-manager']
Packit Service 27f74b
    summary = _('manage {prog} configuration options and repositories').format(
Packit Service 27f74b
        prog=dnf.util.MAIN_PROG)
Packit Service 27f74b
Packit Service 27f74b
    @staticmethod
Packit Service 27f74b
    def set_argparser(parser):
Packit Service 27f74b
        parser.add_argument(
Packit Service 27f74b
            'crepo', nargs='*', metavar='repo',
Packit Service 27f74b
            help=_('repo to modify'))
Packit Service 27f74b
        parser.add_argument(
Packit Service 27f74b
            '--save', default=False, action='store_true',
Packit Service 27f74b
            help=_('save the current options (useful with --setopt)'))
Packit Service 27f74b
        parser.add_argument(
Packit Service 27f74b
            '--add-repo', default=[], action='append', metavar='URL',
Packit Service 27f74b
            help=_('add (and enable) the repo from the specified file or url'))
Packit Service 27f74b
        parser.add_argument(
Packit Service 27f74b
            '--dump', default=False, action='store_true',
Packit Service 27f74b
            help=_('print current configuration values to stdout'))
Packit Service 27f74b
        parser.add_argument(
Packit Service 27f74b
            '--dump-variables', default=False, action='store_true',
Packit Service 27f74b
            help=_('print variable values to stdout'))
Packit Service 27f74b
        enable_group = parser.add_mutually_exclusive_group()
Packit Service 27f74b
        enable_group.add_argument("--set-enabled", default=False,
Packit Service 27f74b
                                  dest="set_enabled", action="store_true",
Packit Service 27f74b
                                  help=_("enable repos (automatically saves)"))
Packit Service 27f74b
        enable_group.add_argument("--set-disabled", default=False,
Packit Service 27f74b
                                  dest="set_disabled", action="store_true",
Packit Service 27f74b
                                  help=_("disable repos (automatically saves)"))
Packit Service 27f74b
Packit Service 27f74b
    def configure(self):
Packit Service 27f74b
        # setup sack and populate it with enabled repos
Packit Service 27f74b
        demands = self.cli.demands
Packit Service 27f74b
        demands.available_repos = True
Packit Service 27f74b
Packit Service 27f74b
        # if no argument was passed then error
Packit Service 27f74b
        if (not (self.opts.add_repo != [] or
Packit Service 27f74b
                 self.opts.save or
Packit Service 27f74b
                 self.opts.dump or
Packit Service 27f74b
                 self.opts.dump_variables or
Packit Service 27f74b
                 self.opts.set_disabled or
Packit Service 27f74b
                 self.opts.set_enabled) ):
Packit Service 27f74b
            self.cli.optparser.error(_("one of the following arguments is required: {}")
Packit Service 27f74b
                                     .format(' '.join([
Packit Service 27f74b
                                         "--save", "--add-repo",
Packit Service 27f74b
                                         "--dump", "--dump-variables",
Packit Service 27f74b
                                         "--set-enabled", "--enable",
Packit Service 27f74b
                                         "--set-disabled", "--disable"])))
Packit Service 27f74b
Packit Service 27f74b
        # warn with hint if --enablerepo or --disablerepo argument was passed
Packit Service 27f74b
        if self.opts.repos_ed != []:
Packit Service 27f74b
            logger.warning(_("Warning: --enablerepo/--disablerepo arguments have no meaning"
Packit Service 27f74b
                             "with config manager. Use --set-enabled/--set-disabled instead."))
Packit Service 27f74b
Packit Service 27f74b
        if (self.opts.save or self.opts.set_enabled or
Packit Service 27f74b
                self.opts.set_disabled or self.opts.add_repo):
Packit Service 27f74b
            demands.root_user = True
Packit Service 27f74b
Packit Service 27f74b
Packit Service 27f74b
    def run(self):
Packit Service 27f74b
        """Execute the util action here."""
Packit Service 27f74b
        if self.opts.add_repo:
Packit Service 27f74b
            self.add_repo()
Packit Service 27f74b
        else:
Packit Service 27f74b
            self.modify_repo()
Packit Service 27f74b
Packit Service 27f74b
    def modify_repo(self):
Packit Service 27f74b
        """ process --set-enabled, --set-disabled and --setopt options """
Packit Service 27f74b
Packit Service 27f74b
        matching_repos = []         # list of matched repositories
Packit Service 27f74b
        not_matching_repos_id = set()  # IDs of not matched repositories
Packit Service 27f74b
Packit Service 27f74b
        def match_repos(key, add_matching_repos):
Packit Service 27f74b
            matching = self.base.repos.get_matching(key)
Packit Service 27f74b
            if not matching:
Packit Service 27f74b
                not_matching_repos_id.add(name)
Packit Service 27f74b
            elif add_matching_repos:
Packit Service 27f74b
                matching_repos.extend(matching)
Packit Service 27f74b
Packit Service 27f74b
        if self.opts.crepo:
Packit Service 27f74b
            for name in self.opts.crepo:
Packit Service 27f74b
                match_repos(name, True)
Packit Service 27f74b
            if hasattr(self.opts, 'repo_setopts'):
Packit Service 27f74b
                for name in self.opts.repo_setopts.keys():
Packit Service 27f74b
                    match_repos(name, False)
Packit Service 27f74b
        else:
Packit Service 27f74b
            if hasattr(self.opts, 'repo_setopts'):
Packit Service 27f74b
                for name in self.opts.repo_setopts.keys():
Packit Service 27f74b
                    match_repos(name, True)
Packit Service 27f74b
Packit Service 27f74b
        if not_matching_repos_id:
Packit Service 27f74b
            raise dnf.exceptions.Error(_("No matching repo to modify: %s.")
Packit Service 27f74b
                                       % ', '.join(not_matching_repos_id))
Packit Service 27f74b
Packit Service 27f74b
        sbc = self.base.conf
Packit Service 27f74b
        modify = {}
Packit Service 27f74b
        if hasattr(self.opts, 'main_setopts') and self.opts.main_setopts:
Packit Service 27f74b
            modify = self.opts.main_setopts
Packit Service 27f74b
        if self.opts.dump_variables:
Packit Service 27f74b
            for name, val in self.base.conf.substitutions.items():
Packit Service 27f74b
                print("%s = %s" % (name, val))
Packit Service 27f74b
        if not self.opts.crepo or 'main' in self.opts.crepo:
Packit Service 27f74b
            if self.opts.save and modify:
Packit Service 27f74b
                # modify [main] in global configuration file
Packit Service 27f74b
                self.base.conf.write_raw_configfile(self.base.conf.config_file_path, 'main',
Packit Service 27f74b
                                                    sbc.substitutions, modify)
Packit Service 27f74b
            if self.opts.dump:
Packit Service 27f74b
                print(self.base.output.fmtSection('main'))
Packit Service 27f74b
                print(self.base.conf.dump())
Packit Service 27f74b
Packit Service 27f74b
        if not matching_repos:
Packit Service 27f74b
            return
Packit Service 27f74b
Packit Service 27f74b
        if self.opts.set_enabled or self.opts.set_disabled:
Packit Service 27f74b
            self.opts.save = True
Packit Service 27f74b
Packit Service 27f74b
        for repo in sorted(matching_repos):
Packit Service 27f74b
            repo_modify = {}
Packit Service 27f74b
            if self.opts.set_enabled:
Packit Service 27f74b
                repo_modify['enabled'] = "1"
Packit Service 27f74b
            elif self.opts.set_disabled:
Packit Service 27f74b
                repo_modify['enabled'] = "0"
Packit Service 27f74b
            if hasattr(self.opts, 'repo_setopts'):
Packit Service 27f74b
                for repoid, setopts in self.opts.repo_setopts.items():
Packit Service 27f74b
                    if fnmatch.fnmatch(repo.id, repoid):
Packit Service 27f74b
                        repo_modify.update(setopts)
Packit Service 27f74b
            if self.opts.save and repo_modify:
Packit Service 27f74b
                self.base.conf.write_raw_configfile(repo.repofile, repo.id, sbc.substitutions, repo_modify)
Packit Service 27f74b
            if self.opts.dump:
Packit Service 27f74b
                print(self.base.output.fmtSection('repo: ' + repo.id))
Packit Service 27f74b
                print(repo.dump())
Packit Service 27f74b
Packit Service 27f74b
    def add_repo(self):
Packit Service 27f74b
        """ process --add-repo option """
Packit Service 27f74b
Packit Service 27f74b
        # Get the reposdir location
Packit Service 27f74b
        myrepodir = self.base.conf.get_reposdir
Packit Service 27f74b
        errors_count = 0
Packit Service 27f74b
Packit Service 27f74b
        for url in self.opts.add_repo:
Packit Service 27f74b
            if dnf.pycomp.urlparse.urlparse(url).scheme == '':
Packit Service 27f74b
                url = 'file://' + os.path.abspath(url)
Packit Service 27f74b
            logger.info(_('Adding repo from: %s'), url)
Packit Service 27f74b
            if url.endswith('.repo'):
Packit Service 27f74b
                # .repo file - download, put into reposdir and enable it
Packit Service 27f74b
                destname = os.path.basename(url)
Packit Service 27f74b
                destname = os.path.join(myrepodir, destname)
Packit Service 27f74b
                try:
Packit Service 27f74b
                    f = self.base.urlopen(url, mode='w+')
Packit Service 27f74b
                    shutil.copy2(f.name, destname)
Packit Service 27f74b
                    os.chmod(destname, 0o644)
Packit Service 27f74b
                    f.close()
Packit Service 27f74b
                except IOError as e:
Packit Service 27f74b
                    errors_count += 1
Packit Service 27f74b
                    logger.error(e)
Packit Service 27f74b
                    continue
Packit Service 27f74b
            else:
Packit Service 27f74b
                # just url to repo, create .repo file on our own
Packit Service 27f74b
                repoid = sanitize_url_to_fs(url)
Packit Service 27f74b
                reponame = 'created by {} config-manager from {}'.format(dnf.util.MAIN_PROG, url)
Packit Service 27f74b
                destname = os.path.join(myrepodir, "%s.repo" % repoid)
Packit Service 27f74b
                content = "[%s]\nname=%s\nbaseurl=%s\nenabled=1\n" % \
Packit Service 27f74b
                                                (repoid, reponame, url)
Packit Service 27f74b
                if not save_to_file(destname, content):
Packit Service 27f74b
                    continue
Packit Service 27f74b
        if errors_count:
Packit Service 27f74b
            raise dnf.exceptions.Error(P_("Configuration of repo failed",
Packit Service 27f74b
                                          "Configuration of repos failed", errors_count))
Packit Service 27f74b
Packit Service 27f74b
Packit Service 27f74b
def save_to_file(filename, content):
Packit Service 27f74b
    try:
Packit Service 27f74b
        with open(filename, 'w+') as fd:
Packit Service 27f74b
            dnf.pycomp.write_to_file(fd, content)
Packit Service 27f74b
            os.chmod(filename, 0o644)
Packit Service 27f74b
    except (IOError, OSError) as e:
Packit Service 27f74b
        logger.error(_('Could not save repo to repofile %s: %s'),
Packit Service 27f74b
                     filename, e)
Packit Service 27f74b
        return False
Packit Service 27f74b
    return True
Packit Service 27f74b
Packit Service 27f74b
# Regular expressions to sanitise cache filenames
Packit Service 27f74b
RE_SCHEME = re.compile(r'^\w+:/*(\w+:|www\.)?')
Packit Service 27f74b
RE_SLASH = re.compile(r'[?/:&#|~\*\[\]\(\)\'\\]+')
Packit Service 27f74b
RE_BEGIN = re.compile(r'^[,.]*')
Packit Service 27f74b
RE_FINAL = re.compile(r'[,.]*$')
Packit Service 27f74b
Packit Service 27f74b
def sanitize_url_to_fs(url):
Packit Service 27f74b
    """Return a filename suitable for the filesystem and for repo id
Packit Service 27f74b
Packit Service 27f74b
    Strips dangerous and common characters to create a filename we
Packit Service 27f74b
    can use to store the cache in.
Packit Service 27f74b
    """
Packit Service 27f74b
Packit Service 27f74b
    try:
Packit Service 27f74b
        if RE_SCHEME.match(url):
Packit Service 27f74b
            if dnf.pycomp.PY3:
Packit Service 27f74b
                url = url.encode('idna').decode('utf-8')
Packit Service 27f74b
            else:
Packit Service 27f74b
                if isinstance(url, str):
Packit Service 27f74b
                    url = url.decode('utf-8').encode('idna')
Packit Service 27f74b
                else:
Packit Service 27f74b
                    url = url.encode('idna')
Packit Service 27f74b
                if isinstance(url, unicode):
Packit Service 27f74b
                    url = url.encode('utf-8')
Packit Service 27f74b
    except (UnicodeDecodeError, UnicodeEncodeError, UnicodeError, TypeError):
Packit Service 27f74b
        pass
Packit Service 27f74b
    url = RE_SCHEME.sub("", url)
Packit Service 27f74b
    url = RE_SLASH.sub("_", url)
Packit Service 27f74b
    url = RE_BEGIN.sub("", url)
Packit Service 27f74b
    url = RE_FINAL.sub("", url)
Packit Service 27f74b
Packit Service 27f74b
    # limit length of url
Packit Service 27f74b
    if len(url) > 250:
Packit Service 27f74b
        parts = url[:185].split('_')
Packit Service 27f74b
        lastindex = 185-len(parts[-1])
Packit Service 27f74b
        csum = dnf.yum.misc.Checksums(['sha256'])
Packit Service 27f74b
        csum.update(url[lastindex:])
Packit Service 27f74b
        url = url[:lastindex] + '_' + csum.hexdigest()
Packit Service 27f74b
    # remove all not allowed characters
Packit Service 27f74b
    allowed_regex = "[^abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.:-]"
Packit Service 27f74b
    return re.sub(allowed_regex, '', url)