Blame plugins/copr.py

Packit 3a9065
# supplies the 'copr' command.
Packit 3a9065
#
Packit 3a9065
# Copyright (C) 2014-2015  Red Hat, Inc.
Packit 3a9065
#
Packit 3a9065
# This copyrighted material is made available to anyone wishing to use,
Packit 3a9065
# modify, copy, or redistribute it subject to the terms and conditions of
Packit 3a9065
# the GNU General Public License v.2, or (at your option) any later version.
Packit 3a9065
# This program is distributed in the hope that it will be useful, but WITHOUT
Packit 3a9065
# ANY WARRANTY expressed or implied, including the implied warranties of
Packit 3a9065
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
Packit 3a9065
# Public License for more details.  You should have received a copy of the
Packit 3a9065
# GNU General Public License along with this program; if not, write to the
Packit 3a9065
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
Packit 3a9065
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
Packit 3a9065
# source code or documentation are not subject to the GNU General Public
Packit 3a9065
# License and may only be used or replicated with the express permission of
Packit 3a9065
# Red Hat, Inc.
Packit 3a9065
#
Packit 3a9065
Packit 3a9065
from __future__ import print_function
Packit 3a9065
Packit 3a9065
import glob
Packit 3a9065
import itertools
Packit 3a9065
import json
Packit 3a9065
import os
Packit 3a9065
import re
Packit 3a9065
import shutil
Packit 3a9065
import stat
Packit 3a9065
import sys
Packit 3a9065
Packit 3a9065
from dnfpluginscore import _, logger
Packit 3a9065
import dnf
Packit 3a9065
from dnf.pycomp import PY3
Packit 3a9065
from dnf.i18n import ucd
Packit 3a9065
import rpm
Packit 3a9065
Packit 3a9065
# Attempt importing the linux_distribution function from distro
Packit 3a9065
# If that fails, attempt to import the deprecated implementation
Packit 3a9065
# from the platform module.
Packit 3a9065
try:
Packit 3a9065
    from distro import linux_distribution, os_release_attr
Packit 3a9065
except ImportError:
Packit 3a9065
    def os_release_attr(_):
Packit 3a9065
        return ""
Packit 3a9065
    try:
Packit 3a9065
        from platform import linux_distribution
Packit 3a9065
    except ImportError:
Packit 3a9065
        # Simple fallback for distributions that lack an implementation
Packit 3a9065
        def linux_distribution():
Packit 3a9065
            with open('/etc/os-release') as os_release_file:
Packit 3a9065
                os_release_data = {}
Packit 3a9065
                for line in os_release_file:
Packit 3a9065
                    os_release_key, os_release_value = line.rstrip.split('=')
Packit 3a9065
                    os_release_data[os_release_key] = os_release_value.strip('"')
Packit 3a9065
                return (os_release_data['NAME'], os_release_data['VERSION_ID'], None)
Packit 3a9065
Packit 3a9065
PLUGIN_CONF = 'copr'
Packit 3a9065
Packit 3a9065
YES = set([_('yes'), _('y')])
Packit 3a9065
NO = set([_('no'), _('n'), ''])
Packit 3a9065
Packit 3a9065
if PY3:
Packit 3a9065
    from configparser import ConfigParser, NoOptionError, NoSectionError
Packit 3a9065
else:
Packit 3a9065
    from ConfigParser import ConfigParser, NoOptionError, NoSectionError
Packit 3a9065
Packit 3a9065
@dnf.plugin.register_command
Packit 3a9065
class CoprCommand(dnf.cli.Command):
Packit 3a9065
    """ Copr plugin for DNF """
Packit 3a9065
Packit 3a9065
    chroot_config = None
Packit 3a9065
Packit 3a9065
    default_hostname = "copr.fedorainfracloud.org"
Packit 3a9065
    default_hub = "fedora"
Packit 3a9065
    default_protocol = "https"
Packit 3a9065
    default_port = 443
Packit 3a9065
    default_url = default_protocol + "://" + default_hostname
Packit 3a9065
    aliases = ("copr",)
Packit 3a9065
    summary = _("Interact with Copr repositories.")
Packit 3a9065
    first_warning = True
Packit 3a9065
    usage = _("""
Packit 3a9065
  enable name/project [chroot]
Packit 3a9065
  disable name/project
Packit 3a9065
  remove name/project
Packit 3a9065
  list --installed/enabled/disabled
Packit 3a9065
  list --available-by-user=NAME
Packit 3a9065
  search project
Packit 3a9065
Packit 3a9065
  Examples:
Packit 3a9065
  copr enable rhscl/perl516 epel-6-x86_64
Packit 3a9065
  copr enable ignatenkobrain/ocltoys
Packit 3a9065
  copr disable rhscl/perl516
Packit 3a9065
  copr remove rhscl/perl516
Packit 3a9065
  copr list --enabled
Packit 3a9065
  copr list --available-by-user=ignatenkobrain
Packit 3a9065
  copr search tests
Packit 3a9065
    """)
Packit 3a9065
Packit 3a9065
    @staticmethod
Packit 3a9065
    def set_argparser(parser):
Packit 3a9065
        parser.add_argument('subcommand', nargs=1,
Packit 3a9065
                            choices=['help', 'enable', 'disable',
Packit 3a9065
                                     'remove', 'list', 'search'])
Packit 3a9065
Packit 3a9065
        list_option = parser.add_mutually_exclusive_group()
Packit 3a9065
        list_option.add_argument('--installed', action='store_true',
Packit 3a9065
                                 help=_('List all installed Copr repositories (default)'))
Packit 3a9065
        list_option.add_argument('--enabled', action='store_true',
Packit 3a9065
                                 help=_('List enabled Copr repositories'))
Packit 3a9065
        list_option.add_argument('--disabled', action='store_true',
Packit 3a9065
                                 help=_('List disabled Copr repositories'))
Packit 3a9065
        list_option.add_argument('--available-by-user', metavar='NAME',
Packit 3a9065
                                 help=_('List available Copr repositories by user NAME'))
Packit 3a9065
Packit 3a9065
        parser.add_argument('--hub', help=_('Specify an instance of Copr to work with'))
Packit 3a9065
Packit 3a9065
        parser.add_argument('arg', nargs='*')
Packit 3a9065
Packit 3a9065
    def configure(self):
Packit 3a9065
        copr_hub = None
Packit 3a9065
        copr_plugin_config = ConfigParser()
Packit 3a9065
        config_files = []
Packit 3a9065
        config_path = self.base.conf.pluginconfpath[0]
Packit 3a9065
Packit 3a9065
        default_config_file = os.path.join(config_path, PLUGIN_CONF + ".conf")
Packit 3a9065
        if os.path.isfile(default_config_file):
Packit 3a9065
            config_files.append(default_config_file)
Packit 3a9065
Packit 3a9065
            copr_plugin_config.read(default_config_file)
Packit 3a9065
            if copr_plugin_config.has_option('main', 'distribution') and\
Packit 3a9065
                    copr_plugin_config.has_option('main', 'releasever'):
Packit 3a9065
                distribution = copr_plugin_config.get('main', 'distribution')
Packit 3a9065
                releasever = copr_plugin_config.get('main', 'releasever')
Packit 3a9065
                self.chroot_config = [distribution, releasever]
Packit 3a9065
            else:
Packit 3a9065
                self.chroot_config = [False, False]
Packit 3a9065
Packit 3a9065
        for filename in os.listdir(os.path.join(config_path, PLUGIN_CONF + ".d")):
Packit 3a9065
            if filename.endswith('.conf'):
Packit 3a9065
                config_file = os.path.join(config_path, PLUGIN_CONF + ".d", filename)
Packit 3a9065
                config_files.append(config_file)
Packit 3a9065
Packit 3a9065
        project = []
Packit 3a9065
        if len(self.opts.arg):
Packit 3a9065
            project = self.opts.arg[0].split("/")
Packit 3a9065
Packit 3a9065
        if len(project) == 3 and self.opts.hub:
Packit 3a9065
            logger.critical(
Packit 3a9065
                _('Error: ') +
Packit 3a9065
                _('specify Copr hub either with `--hub` or using '
Packit 3a9065
                  '`copr_hub/copr_username/copr_projectname` format')
Packit 3a9065
            )
Packit 3a9065
            raise dnf.cli.CliError(_('multiple hubs specified'))
Packit 3a9065
Packit 3a9065
        # Copr hub was not specified, using default hub `fedora`
Packit 3a9065
        elif not self.opts.hub and len(project) != 3:
Packit 3a9065
            self.copr_hostname = self.default_hostname
Packit 3a9065
            self.copr_url = self.default_url
Packit 3a9065
Packit 3a9065
        # Copr hub specified with hub/user/project format
Packit 3a9065
        elif len(project) == 3:
Packit 3a9065
            copr_hub = project[0]
Packit 3a9065
Packit 3a9065
        else:
Packit 3a9065
            copr_hub = self.opts.hub
Packit 3a9065
Packit 3a9065
        # Try to find hub in a config file
Packit 3a9065
        if config_files and copr_hub:
Packit 3a9065
            self.copr_url = None
Packit 3a9065
            copr_plugin_config.read(sorted(config_files, reverse=True))
Packit 3a9065
            hostname = self._read_config_item(copr_plugin_config, copr_hub, 'hostname', None)
Packit 3a9065
Packit 3a9065
            if hostname:
Packit 3a9065
                protocol = self._read_config_item(copr_plugin_config, copr_hub, 'protocol',
Packit 3a9065
                                                  self.default_protocol)
Packit 3a9065
                port = self._read_config_item(copr_plugin_config, copr_hub, 'port',
Packit 3a9065
                                              self.default_port)
Packit 3a9065
Packit 3a9065
                self.copr_hostname = hostname
Packit 3a9065
                self.copr_url = protocol + "://" + hostname
Packit 3a9065
                if int(port) != self.default_port:
Packit 3a9065
                    self.copr_url += ":" + port
Packit 3a9065
                    self.copr_hostname += ":" + port
Packit 3a9065
Packit 3a9065
        if not self.copr_url:
Packit 3a9065
            self.copr_hostname = copr_hub
Packit 3a9065
            self.copr_url = self.default_protocol + "://" + copr_hub
Packit 3a9065
Packit 3a9065
    def _read_config_item(self, config, hub, section, default):
Packit 3a9065
        try:
Packit 3a9065
            return config.get(hub, section)
Packit 3a9065
        except (NoOptionError, NoSectionError):
Packit 3a9065
            return default
Packit 3a9065
Packit 3a9065
    def _user_warning_before_prompt(self, text):
Packit 3a9065
        sys.stderr.write("{0}\n".format(text.strip()))
Packit 3a9065
Packit 3a9065
    def run(self):
Packit 3a9065
        subcommand = self.opts.subcommand[0]
Packit 3a9065
Packit 3a9065
        if subcommand == "help":
Packit 3a9065
            self.cli.optparser.print_help(self)
Packit 3a9065
            return 0
Packit 3a9065
        if subcommand == "list":
Packit 3a9065
            if self.opts.available_by_user:
Packit 3a9065
                self._list_user_projects(self.opts.available_by_user)
Packit 3a9065
                return
Packit 3a9065
            else:
Packit 3a9065
                self._list_installed_repositories(self.base.conf.reposdir[0],
Packit 3a9065
                                                  self.opts.enabled, self.opts.disabled)
Packit 3a9065
                return
Packit 3a9065
Packit 3a9065
        try:
Packit 3a9065
            project_name = self.opts.arg[0]
Packit 3a9065
        except (ValueError, IndexError):
Packit 3a9065
            logger.critical(
Packit 3a9065
                _('Error: ') +
Packit 3a9065
                _('exactly two additional parameters to '
Packit 3a9065
                  'copr command are required'))
Packit 3a9065
            self.cli.optparser.print_help(self)
Packit 3a9065
            raise dnf.cli.CliError(
Packit 3a9065
                _('exactly two additional parameters to '
Packit 3a9065
                  'copr command are required'))
Packit 3a9065
        try:
Packit 3a9065
            chroot = self.opts.arg[1]
Packit 3a9065
        except IndexError:
Packit 3a9065
            chroot = self._guess_chroot()
Packit 3a9065
Packit 3a9065
        # commands without defined copr_username/copr_projectname
Packit 3a9065
        if subcommand == "search":
Packit 3a9065
            self._search(project_name)
Packit 3a9065
            return
Packit 3a9065
Packit 3a9065
        project = project_name.split("/")
Packit 3a9065
        if len(project) not in [2, 3]:
Packit 3a9065
            logger.critical(
Packit 3a9065
                _('Error: ') +
Packit 3a9065
                _('use format `copr_username/copr_projectname` '
Packit 3a9065
                  'to reference copr project'))
Packit 3a9065
            raise dnf.cli.CliError(_('bad copr project format'))
Packit 3a9065
        elif len(project) == 2:
Packit 3a9065
            copr_username = project[0]
Packit 3a9065
            copr_projectname = project[1]
Packit 3a9065
        else:
Packit 3a9065
            copr_username = project[1]
Packit 3a9065
            copr_projectname = project[2]
Packit 3a9065
            project_name = copr_username + "/" + copr_projectname
Packit 3a9065
Packit 3a9065
        repo_filename = "{0}/_copr:{1}:{2}:{3}.repo".format(
Packit 3a9065
            self.base.conf.get_reposdir, self.copr_hostname,
Packit 3a9065
            self._sanitize_username(copr_username), copr_projectname)
Packit 3a9065
        if subcommand == "enable":
Packit 3a9065
            self._need_root()
Packit 3a9065
            info = _("""
Packit 3a9065
Enabling a Copr repository. Please note that this repository is not part
Packit 3a9065
of the main distribution, and quality may vary.
Packit 3a9065
Packit 3a9065
The Fedora Project does not exercise any power over the contents of
Packit 3a9065
this repository beyond the rules outlined in the Copr FAQ at
Packit 3a9065
<https://docs.pagure.org/copr.copr/user_documentation.html#what-i-can-build-in-copr>,
Packit 3a9065
and packages are not held to any quality or security level.
Packit 3a9065
Packit 3a9065
Please do not file bug reports about these packages in Fedora
Packit 3a9065
Bugzilla. In case of problems, contact the owner of this repository.
Packit 3a9065
""")
Packit 3a9065
            project = '/'.join([self.copr_hostname, copr_username,
Packit 3a9065
                                copr_projectname])
Packit 3a9065
            msg = "Do you really want to enable {0}?".format(project)
Packit 3a9065
            self._ask_user(info, msg)
Packit 3a9065
            self._download_repo(project_name, repo_filename, chroot)
Packit 3a9065
            logger.info(_("Repository successfully enabled."))
Packit 3a9065
            self._runtime_deps_warning(copr_username, copr_projectname)
Packit 3a9065
        elif subcommand == "disable":
Packit 3a9065
            self._need_root()
Packit 3a9065
            self._disable_repo(copr_username, copr_projectname)
Packit 3a9065
            logger.info(_("Repository successfully disabled."))
Packit 3a9065
        elif subcommand == "remove":
Packit 3a9065
            self._need_root()
Packit 3a9065
            self._remove_repo(copr_username, copr_projectname)
Packit 3a9065
            logger.info(_("Repository successfully removed."))
Packit 3a9065
Packit 3a9065
        else:
Packit 3a9065
            raise dnf.exceptions.Error(
Packit 3a9065
                _('Unknown subcommand {}.').format(subcommand))
Packit 3a9065
Packit 3a9065
    def _list_repo_file(self, repo_id, repo, enabled_only, disabled_only):
Packit 3a9065
        file_name = repo.repofile.split('/')[-1]
Packit 3a9065
Packit 3a9065
        match_new = re.match("_copr:" + self.copr_hostname, file_name)
Packit 3a9065
        match_old = self.copr_url == self.default_url and re.match("_copr_", file_name)
Packit 3a9065
        match_any = re.match("_copr:|^_copr_", file_name)
Packit 3a9065
Packit 3a9065
        if self.opts.hub:
Packit 3a9065
            if not match_new and not match_old:
Packit 3a9065
                return
Packit 3a9065
        elif not match_any:
Packit 3a9065
            return
Packit 3a9065
Packit 3a9065
        if re.match('copr:.*:.*:.*:ml', repo_id):
Packit 3a9065
            # We skip multilib repositories
Packit 3a9065
            return
Packit 3a9065
Packit Service 6746a6
        if re.match('coprdep:.*', repo_id):
Packit Service 6746a6
            # Runtime dependencies are not listed.
Packit Service 6746a6
            return
Packit Service 6746a6
Packit 3a9065
        enabled = repo.enabled
Packit 3a9065
        if (enabled and disabled_only) or (not enabled and enabled_only):
Packit 3a9065
            return
Packit 3a9065
Packit 3a9065
        old_repo = False
Packit 3a9065
        # repo ID has copr:<hostname>:<user>:<copr_dir> format, while <copr_dir>
Packit 3a9065
        # can contain more colons
Packit 3a9065
        if re.match("copr:", repo_id):
Packit 3a9065
            _, copr_hostname, copr_owner, copr_dir = repo_id.split(':', 3)
Packit 3a9065
            msg = copr_hostname + '/' + copr_owner + "/" + copr_dir
Packit 3a9065
        # repo ID has <user>-<project> format, try to get hub from file name
Packit 3a9065
        elif re.match("_copr:", file_name):
Packit 3a9065
            copr_name = repo_id.split('-', 1)
Packit 3a9065
            copr_hostname = file_name.rsplit(':', 2)[0].split(':', 1)[1]
Packit 3a9065
            msg = copr_hostname + '/' + copr_name[0] + '/' + copr_name[1]
Packit 3a9065
        # no information about hub, assume the default one
Packit 3a9065
        else:
Packit 3a9065
            copr_name = repo_id.split('-', 1)
Packit 3a9065
            msg = self.default_hostname + '/' + copr_name[0] + '/' + copr_name[1]
Packit 3a9065
            old_repo = True
Packit 3a9065
        if not enabled:
Packit 3a9065
            msg += " (disabled)"
Packit 3a9065
        if old_repo:
Packit 3a9065
            msg += " *"
Packit 3a9065
Packit 3a9065
        print(msg)
Packit 3a9065
        return old_repo
Packit 3a9065
Packit 3a9065
    def _list_installed_repositories(self, directory, enabled_only, disabled_only):
Packit 3a9065
        old_repo = False
Packit 3a9065
        for repo_id, repo in self.base.repos.items():
Packit 3a9065
            if self._list_repo_file(repo_id, repo, enabled_only, disabled_only):
Packit 3a9065
                old_repo = True
Packit 3a9065
        if old_repo:
Packit 3a9065
            print(_("* These coprs have repo file with an old format that contains "
Packit 3a9065
                    "no information about Copr hub - the default one was assumed. "
Packit 3a9065
                    "Re-enable the project to fix this."))
Packit 3a9065
Packit 3a9065
    def _list_user_projects(self, user_name):
Packit 3a9065
        # http://copr.fedorainfracloud.org/api/coprs/ignatenkobrain/
Packit 3a9065
        api_path = "/api/coprs/{}/".format(user_name)
Packit 3a9065
        res = self.base.urlopen(self.copr_url + api_path, mode='w+')
Packit 3a9065
        try:
Packit 3a9065
            json_parse = json.loads(res.read())
Packit 3a9065
        except ValueError:
Packit 3a9065
            raise dnf.exceptions.Error(
Packit 3a9065
                _("Can't parse repositories for username '{}'.")
Packit 3a9065
                .format(user_name))
Packit 3a9065
        self._check_json_output(json_parse)
Packit 3a9065
        section_text = _("List of {} coprs").format(user_name)
Packit 3a9065
        self._print_match_section(section_text)
Packit 3a9065
        i = 0
Packit 3a9065
        while i < len(json_parse["repos"]):
Packit 3a9065
            msg = "{0}/{1} : ".format(user_name,
Packit 3a9065
                                      json_parse["repos"][i]["name"])
Packit 3a9065
            desc = json_parse["repos"][i]["description"]
Packit 3a9065
            if not desc:
Packit 3a9065
                desc = _("No description given")
Packit 3a9065
            msg = self.base.output.fmtKeyValFill(ucd(msg), desc)
Packit 3a9065
            print(msg)
Packit 3a9065
            i += 1
Packit 3a9065
Packit 3a9065
    def _search(self, query):
Packit 3a9065
        # http://copr.fedorainfracloud.org/api/coprs/search/tests/
Packit 3a9065
        api_path = "/api/coprs/search/{}/".format(query)
Packit 3a9065
        res = self.base.urlopen(self.copr_url + api_path, mode='w+')
Packit 3a9065
        try:
Packit 3a9065
            json_parse = json.loads(res.read())
Packit 3a9065
        except ValueError:
Packit 3a9065
            raise dnf.exceptions.Error(_("Can't parse search for '{}'."
Packit 3a9065
                                         ).format(query))
Packit 3a9065
        self._check_json_output(json_parse)
Packit 3a9065
        section_text = _("Matched: {}").format(query)
Packit 3a9065
        self._print_match_section(section_text)
Packit 3a9065
        i = 0
Packit 3a9065
        while i < len(json_parse["repos"]):
Packit 3a9065
            msg = "{0}/{1} : ".format(json_parse["repos"][i]["username"],
Packit 3a9065
                                      json_parse["repos"][i]["coprname"])
Packit 3a9065
            desc = json_parse["repos"][i]["description"]
Packit 3a9065
            if not desc:
Packit 3a9065
                desc = _("No description given.")
Packit 3a9065
            msg = self.base.output.fmtKeyValFill(ucd(msg), desc)
Packit 3a9065
            print(msg)
Packit 3a9065
            i += 1
Packit 3a9065
Packit 3a9065
    def _print_match_section(self, text):
Packit 3a9065
        formatted = self.base.output.fmtSection(text)
Packit 3a9065
        print(formatted)
Packit 3a9065
Packit 3a9065
    def _ask_user_no_raise(self, info, msg):
Packit 3a9065
        if not self.first_warning:
Packit 3a9065
            sys.stderr.write("\n")
Packit 3a9065
        self.first_warning = False
Packit 3a9065
        sys.stderr.write("{0}\n".format(info.strip()))
Packit 3a9065
Packit 3a9065
        if self.base._promptWanted():
Packit 3a9065
            if self.base.conf.assumeno or not self.base.output.userconfirm(
Packit 3a9065
                    msg='\n{} [y/N]: '.format(msg), defaultyes_msg='\n{} [Y/n]: '.format(msg)):
Packit 3a9065
                return False
Packit 3a9065
        return True
Packit 3a9065
Packit 3a9065
    def _ask_user(self, info, msg):
Packit 3a9065
        if not self._ask_user_no_raise(info, msg):
Packit 3a9065
            raise dnf.exceptions.Error(_('Safe and good answer. Exiting.'))
Packit 3a9065
Packit 3a9065
    @classmethod
Packit 3a9065
    def _need_root(cls):
Packit 3a9065
        # FIXME this should do dnf itself (BZ#1062889)
Packit 3a9065
        if os.geteuid() != 0:
Packit 3a9065
            raise dnf.exceptions.Error(
Packit 3a9065
                _('This command has to be run under the root user.'))
Packit 3a9065
Packit 3a9065
    def _guess_chroot(self):
Packit 3a9065
        """ Guess which chroot is equivalent to this machine """
Packit 3a9065
        # FIXME Copr should generate non-specific arch repo
Packit 3a9065
        dist = self.chroot_config
Packit 3a9065
        if dist is None or (dist[0] is False) or (dist[1] is False):
Packit 3a9065
            dist = linux_distribution()
Packit 3a9065
        # Get distribution architecture
Packit 3a9065
        distarch = self.base.conf.substitutions['basearch']
Packit 3a9065
        if "Fedora" in dist:
Packit 3a9065
            if "Rawhide" in dist:
Packit 3a9065
                chroot = ("fedora-rawhide-" + distarch)
Packit 3a9065
            # workaround for enabling repos in Rawhide when VERSION in os-release
Packit 3a9065
            # contains a name other than Rawhide
Packit 3a9065
            elif "rawhide" in os_release_attr("redhat_support_product_version"):
Packit 3a9065
                chroot = ("fedora-rawhide-" + distarch)
Packit 3a9065
            else:
Packit 3a9065
                chroot = ("fedora-{0}-{1}".format(dist[1], distarch))
Packit 3a9065
        elif "Mageia" in dist:
Packit 3a9065
            # Get distribution architecture (Mageia does not use $basearch)
Packit 3a9065
            distarch = rpm.expandMacro("%{distro_arch}")
Packit 3a9065
            # Set the chroot
Packit 3a9065
            if "Cauldron" in dist:
Packit 3a9065
                chroot = ("mageia-cauldron-{}".format(distarch))
Packit 3a9065
            else:
Packit 3a9065
                chroot = ("mageia-{0}-{1}".format(dist[1], distarch))
Packit 3a9065
        elif "openSUSE" in dist:
Packit 3a9065
            # Get distribution architecture (openSUSE does not use $basearch)
Packit 3a9065
            distarch = rpm.expandMacro("%{_target_cpu}")
Packit 3a9065
            # Set the chroot
Packit 3a9065
            if "Tumbleweed" in dist:
Packit 3a9065
                chroot = ("opensuse-tumbleweed-{}".format(distarch))
Packit 3a9065
            else:
Packit 3a9065
                chroot = ("opensuse-leap-{0}-{1}".format(dist[1], distarch))
Packit 3a9065
        else:
Packit 3a9065
            chroot = ("epel-%s-x86_64" % dist[1].split(".", 1)[0])
Packit 3a9065
        return chroot
Packit 3a9065
Packit 3a9065
    def _download_repo(self, project_name, repo_filename, chroot=None):
Packit 3a9065
        if chroot is None:
Packit 3a9065
            chroot = self._guess_chroot()
Packit 3a9065
        short_chroot = '-'.join(chroot.split('-')[:2])
Packit 3a9065
        arch = chroot.split('-')[2]
Packit 3a9065
        api_path = "/coprs/{0}/repo/{1}/dnf.repo?arch={2}".format(project_name, short_chroot, arch)
Packit 3a9065
Packit 3a9065
        try:
Packit 3a9065
            f = self.base.urlopen(self.copr_url + api_path, mode='w+')
Packit 3a9065
        except IOError as e:
Packit 3a9065
            if os.path.exists(repo_filename):
Packit 3a9065
                os.remove(repo_filename)
Packit 3a9065
            if '404' in str(e):
Packit 3a9065
                if PY3:
Packit 3a9065
                    import urllib.request
Packit 3a9065
                    try:
Packit 3a9065
                        res = urllib.request.urlopen(self.copr_url + "/coprs/" + project_name)
Packit 3a9065
                        status_code = res.getcode()
Packit 3a9065
                    except urllib.error.HTTPError as e:
Packit 3a9065
                        status_code = e.getcode()
Packit 3a9065
                else:
Packit 3a9065
                    import urllib
Packit 3a9065
                    res = urllib.urlopen(self.copr_url + "/coprs/" + project_name)
Packit 3a9065
                    status_code = res.getcode()
Packit 3a9065
                if str(status_code) != '404':
Packit 3a9065
                    raise dnf.exceptions.Error(_("This repository does not have"
Packit 3a9065
                                                 " any builds yet so you cannot enable it now."))
Packit 3a9065
                else:
Packit 3a9065
                    raise dnf.exceptions.Error(_("Such repository does not exist."))
Packit 3a9065
            raise
Packit 3a9065
Packit 3a9065
        for line in f:
Packit 3a9065
            if re.match(r"\[copr:", line):
Packit 3a9065
                repo_filename = os.path.join(self.base.conf.get_reposdir,
Packit 3a9065
                                             "_" + line[1:-2] + ".repo")
Packit 3a9065
            break
Packit 3a9065
Packit 3a9065
        # if using default hub, remove possible old repofile
Packit 3a9065
        if self.copr_url == self.default_url:
Packit 3a9065
            # copr:hub:user:project.repo => _copr_user_project.repo
Packit 3a9065
            old_repo_filename = repo_filename.replace("_copr:", "_copr", 1)\
Packit 3a9065
                .replace(self.copr_hostname, "").replace(":", "_", 1).replace(":", "-")\
Packit 3a9065
                .replace("group_", "@")
Packit 3a9065
            if os.path.exists(old_repo_filename):
Packit 3a9065
                os.remove(old_repo_filename)
Packit 3a9065
Packit 3a9065
        shutil.copy2(f.name, repo_filename)
Packit 3a9065
        os.chmod(repo_filename, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
Packit 3a9065
Packit 3a9065
    def _runtime_deps_warning(self, copr_username, copr_projectname):
Packit 3a9065
        """
Packit 3a9065
        In addition to the main copr repo (that has repo ID prefixed with
Packit 3a9065
        `copr:`), the repofile might contain additional repositories that
Packit 3a9065
        serve as runtime dependencies. This method informs the user about
Packit 3a9065
        the additional repos and provides an option to disable them.
Packit 3a9065
        """
Packit 3a9065
Packit 3a9065
        self.base.reset(repos=True)
Packit 3a9065
        self.base.read_all_repos()
Packit 3a9065
Packit 3a9065
        repo = self._get_copr_repo(self._sanitize_username(copr_username), copr_projectname)
Packit 3a9065
Packit 3a9065
        runtime_deps = []
Packit 3a9065
        for repo_id in repo.cfg.sections():
Packit 3a9065
            if repo_id.startswith("copr:"):
Packit 3a9065
                continue
Packit 3a9065
            runtime_deps.append(repo_id)
Packit 3a9065
Packit 3a9065
        if not runtime_deps:
Packit 3a9065
            return
Packit 3a9065
Packit 3a9065
        info = _(
Packit 3a9065
            "Maintainer of the enabled Copr repository decided to make\n"
Packit 3a9065
            "it dependent on other repositories. Such repositories are\n"
Packit 3a9065
            "usually necessary for successful installation of RPMs from\n"
Packit 3a9065
            "the main Copr repository (they provide runtime dependencies).\n\n"
Packit 3a9065
Packit 3a9065
            "Be aware that the note about quality and bug-reporting\n"
Packit 3a9065
            "above applies here too, Fedora Project doesn't control the\n"
Packit 3a9065
            "content. Please review the list:\n\n"
Packit 3a9065
            "{0}\n\n"
Packit 3a9065
            "These repositories have been enabled automatically."
Packit 3a9065
        )
Packit 3a9065
Packit 3a9065
        counter = itertools.count(1)
Packit 3a9065
        info = info.format("\n\n".join([
Packit 3a9065
            "{num:2}. [{repoid}]\n    baseurl={baseurl}".format(
Packit 3a9065
                num=next(counter),
Packit 3a9065
                repoid=repoid,
Packit 3a9065
                baseurl=repo.cfg.getValue(repoid, "baseurl"))
Packit 3a9065
            for repoid in runtime_deps
Packit 3a9065
        ]))
Packit 3a9065
Packit 3a9065
        if not self._ask_user_no_raise(info, _("Do you want to keep them enabled?")):
Packit 3a9065
            for dep in runtime_deps:
Packit 3a9065
                self.base.conf.write_raw_configfile(repo.repofile, dep,
Packit 3a9065
                                                    self.base.conf.substitutions,
Packit 3a9065
                                                    {"enabled": "0"})
Packit 3a9065
Packit 3a9065
    def _get_copr_repo(self, copr_username, copr_projectname):
Packit 3a9065
        repo_id = "copr:{0}:{1}:{2}".format(self.copr_hostname.rsplit(':', 1)[0],
Packit 3a9065
                                            self._sanitize_username(copr_username),
Packit 3a9065
                                            copr_projectname)
Packit 3a9065
        if repo_id not in self.base.repos:
Packit 3a9065
            # check if there is a repo with old ID format
Packit 3a9065
            repo_id = repo_id = "{0}-{1}".format(self._sanitize_username(copr_username),
Packit 3a9065
                                                 copr_projectname)
Packit 3a9065
            if repo_id in self.base.repos and "_copr" in self.base.repos[repo_id].repofile:
Packit 3a9065
                file_name = self.base.repos[repo_id].repofile.split('/')[-1]
Packit 3a9065
                try:
Packit 3a9065
                    copr_hostname = file_name.rsplit(':', 2)[0].split(':', 1)[1]
Packit 3a9065
                    if copr_hostname != self.copr_hostname:
Packit 3a9065
                        return None
Packit 3a9065
                except IndexError:
Packit 3a9065
                    # old filename format without hostname
Packit 3a9065
                    pass
Packit 3a9065
            else:
Packit 3a9065
                return None
Packit 3a9065
Packit 3a9065
        return self.base.repos[repo_id]
Packit 3a9065
Packit 3a9065
    def _remove_repo(self, copr_username, copr_projectname):
Packit 3a9065
        # FIXME is it Copr repo ?
Packit 3a9065
        repo = self._get_copr_repo(copr_username, copr_projectname)
Packit 3a9065
        if not repo:
Packit 3a9065
            raise dnf.exceptions.Error(
Packit 3a9065
                _("Failed to remove copr repo {0}/{1}/{2}"
Packit 3a9065
                  .format(self.copr_hostname, copr_username, copr_projectname)))
Packit 3a9065
        try:
Packit 3a9065
            os.remove(repo.repofile)
Packit 3a9065
        except OSError as e:
Packit 3a9065
            raise dnf.exceptions.Error(str(e))
Packit 3a9065
Packit 3a9065
    def _disable_repo(self, copr_username, copr_projectname):
Packit 3a9065
        repo = self._get_copr_repo(copr_username, copr_projectname)
Packit 3a9065
        if repo is None:
Packit 3a9065
            raise dnf.exceptions.Error(
Packit 3a9065
                _("Failed to disable copr repo {}/{}"
Packit 3a9065
                  .format(copr_username, copr_projectname)))
Packit 3a9065
Packit 3a9065
        # disable all repos provided by the repo file
Packit 3a9065
        for repo_id in repo.cfg.sections():
Packit 3a9065
            self.base.conf.write_raw_configfile(repo.repofile, repo_id,
Packit 3a9065
                                                self.base.conf.substitutions, {"enabled": "0"})
Packit 3a9065
Packit 3a9065
    @classmethod
Packit 3a9065
    def _get_data(cls, f):
Packit 3a9065
        """ Wrapper around response from server
Packit 3a9065
Packit 3a9065
        check data and print nice error in case of some error (and return None)
Packit 3a9065
        otherwise return json object.
Packit 3a9065
        """
Packit 3a9065
        try:
Packit 3a9065
            output = json.loads(f.read())
Packit 3a9065
        except ValueError:
Packit 3a9065
            dnf.cli.CliError(_("Unknown response from server."))
Packit 3a9065
            return
Packit 3a9065
        return output
Packit 3a9065
Packit 3a9065
    @classmethod
Packit 3a9065
    def _check_json_output(cls, json_obj):
Packit 3a9065
        if json_obj["output"] != "ok":
Packit 3a9065
            raise dnf.exceptions.Error("{}".format(json_obj["error"]))
Packit 3a9065
Packit 3a9065
    @classmethod
Packit 3a9065
    def _sanitize_username(cls, copr_username):
Packit 3a9065
        if copr_username[0] == "@":
Packit 3a9065
            return "group_{}".format(copr_username[1:])
Packit 3a9065
        else:
Packit 3a9065
            return copr_username
Packit 3a9065
Packit 3a9065
Packit 3a9065
@dnf.plugin.register_command
Packit 3a9065
class PlaygroundCommand(CoprCommand):
Packit 3a9065
    """ Playground plugin for DNF """
Packit 3a9065
Packit 3a9065
    aliases = ("playground",)
Packit 3a9065
    summary = _("Interact with Playground repository.")
Packit 3a9065
    usage = " [enable|disable|upgrade]"
Packit 3a9065
Packit 3a9065
    def _cmd_enable(self, chroot):
Packit 3a9065
        self._need_root()
Packit 3a9065
        self._ask_user(
Packit 3a9065
            _("Enabling a Playground repository."),
Packit 3a9065
            _("Do you want to continue?"),
Packit 3a9065
        )
Packit 3a9065
        api_url = "{0}/api/playground/list/".format(
Packit 3a9065
            self.copr_url)
Packit 3a9065
        f = self.base.urlopen(api_url, mode="w+")
Packit 3a9065
        output = self._get_data(f)
Packit 3a9065
        f.close()
Packit 3a9065
        if output["output"] != "ok":
Packit 3a9065
            raise dnf.cli.CliError(_("Unknown response from server."))
Packit 3a9065
        for repo in output["repos"]:
Packit 3a9065
            project_name = "{0}/{1}".format(repo["username"],
Packit 3a9065
                                            repo["coprname"])
Packit 3a9065
            repo_filename = "{}/_playground_{}.repo".format(self.base.conf.get_reposdir, project_name.replace("/", "-"))
Packit 3a9065
            try:
Packit 3a9065
                if chroot not in repo["chroots"]:
Packit 3a9065
                    continue
Packit 3a9065
                api_url = "{0}/api/coprs/{1}/detail/{2}/".format(
Packit 3a9065
                    self.copr_url, project_name, chroot)
Packit 3a9065
                f = self.base.urlopen(api_url, mode='w+')
Packit 3a9065
                output2 = self._get_data(f)
Packit 3a9065
                f.close()
Packit 3a9065
                if (output2 and ("output" in output2)
Packit 3a9065
                        and (output2["output"] == "ok")):
Packit 3a9065
                    self._download_repo(project_name, repo_filename, chroot)
Packit 3a9065
            except dnf.exceptions.Error:
Packit 3a9065
                # likely 404 and that repo does not exist
Packit 3a9065
                pass
Packit 3a9065
Packit 3a9065
    def _cmd_disable(self):
Packit 3a9065
        self._need_root()
Packit 3a9065
        for repo_filename in glob.glob("{}/_playground_*.repo".format(self.base.conf.get_reposdir)):
Packit 3a9065
            self._remove_repo(repo_filename)
Packit 3a9065
Packit 3a9065
    @staticmethod
Packit 3a9065
    def set_argparser(parser):
Packit 3a9065
        parser.add_argument('subcommand', nargs=1,
Packit 3a9065
                            choices=['enable', 'disable', 'upgrade'])
Packit 3a9065
Packit 3a9065
    def run(self):
Packit 3a9065
        subcommand = self.opts.subcommand[0]
Packit 3a9065
        chroot = self._guess_chroot()
Packit 3a9065
        if subcommand == "enable":
Packit 3a9065
            self._cmd_enable(chroot)
Packit 3a9065
            logger.info(_("Playground repositories successfully enabled."))
Packit 3a9065
        elif subcommand == "disable":
Packit 3a9065
            self._cmd_disable()
Packit 3a9065
            logger.info(_("Playground repositories successfully disabled."))
Packit 3a9065
        elif subcommand == "upgrade":
Packit 3a9065
            self._cmd_disable()
Packit 3a9065
            self._cmd_enable(chroot)
Packit 3a9065
            logger.info(_("Playground repositories successfully updated."))
Packit 3a9065
        else:
Packit 3a9065
            raise dnf.exceptions.Error(
Packit 3a9065
                _('Unknown subcommand {}.').format(subcommand))