Blame plugins/copr.py

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