Blame SOURCES/BZ-1429831-yum-copr.patch

47f05a
diff -N -up -r a/docs/Makefile b/docs/Makefile
47f05a
--- a/docs/Makefile	2017-03-21 14:21:12.326518685 +0100
47f05a
+++ b/docs/Makefile	2017-03-21 14:24:34.711624331 +0100
47f05a
@@ -5,7 +5,7 @@ DOCS = repoquery package-cleanup repo-rs
47f05a
        find-repos-of-install needs-restarting repo-graph repoclosure \
47f05a
        repomanage repotrack verifytree yum-config-manager yum-ovl
47f05a
 DOCS5 = yum-changelog.conf yum-versionlock.conf yum-fs-snapshot.conf
47f05a
-DOCS8 = yum-complete-transaction yumdb
47f05a
+DOCS8 = yum-complete-transaction yumdb yum-copr
47f05a
 
47f05a
 all:
47f05a
 	echo "Nothing to do"
47f05a
diff -N -up -r a/docs/yum-copr.8 b/docs/yum-copr.8
47f05a
--- a/docs/yum-copr.8	1970-01-01 01:00:00.000000000 +0100
47f05a
+++ b/docs/yum-copr.8	2017-03-21 14:23:17.436729456 +0100
47f05a
@@ -0,0 +1,120 @@
47f05a
+.\" Man page generated from reStructuredText.
47f05a
+.
47f05a
+.TH "YUM-COPR" "8" "July 29, 2014" "0.1.1" "yum-plugin-copr"
47f05a
+.SH NAME
47f05a
+yum-plugin-copr \- YUM copr Plugin
47f05a
+.
47f05a
+.nr rst2man-indent-level 0
47f05a
+.
47f05a
+.de1 rstReportMargin
47f05a
+\\$1 \\n[an-margin]
47f05a
+level \\n[rst2man-indent-level]
47f05a
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
47f05a
+-
47f05a
+\\n[rst2man-indent0]
47f05a
+\\n[rst2man-indent1]
47f05a
+\\n[rst2man-indent2]
47f05a
+..
47f05a
+.de1 INDENT
47f05a
+.\" .rstReportMargin pre:
47f05a
+. RS \\$1
47f05a
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
47f05a
+. nr rst2man-indent-level +1
47f05a
+.\" .rstReportMargin post:
47f05a
+..
47f05a
+.de UNINDENT
47f05a
+. RE
47f05a
+.\" indent \\n[an-margin]
47f05a
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
47f05a
+.nr rst2man-indent-level -1
47f05a
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
47f05a
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
47f05a
+..
47f05a
+.
47f05a
+.nr rst2man-indent-level 0
47f05a
+.
47f05a
+.de1 rstReportMargin
47f05a
+\\$1 \\n[an-margin]
47f05a
+level \\n[rst2man-indent-level]
47f05a
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
47f05a
+-
47f05a
+\\n[rst2man-indent0]
47f05a
+\\n[rst2man-indent1]
47f05a
+\\n[rst2man-indent2]
47f05a
+..
47f05a
+.de1 INDENT
47f05a
+.\" .rstReportMargin pre:
47f05a
+. RS \\$1
47f05a
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
47f05a
+. nr rst2man-indent-level +1
47f05a
+.\" .rstReportMargin post:
47f05a
+..
47f05a
+.de UNINDENT
47f05a
+. RE
47f05a
+.\" indent \\n[an-margin]
47f05a
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
47f05a
+.nr rst2man-indent-level -1
47f05a
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
47f05a
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
47f05a
+..
47f05a
+.sp
47f05a
+Work with Copr & Playground repositories on the local system.
47f05a
+.INDENT 0.0
47f05a
+.IP \(bu 2
47f05a
+The \fBcopr\fP command is used to add or remove Copr repositories to the local system
47f05a
+.IP \(bu 2
47f05a
+The \fBplayground\fP is used to enable or disable the Playground repository
47f05a
+.UNINDENT
47f05a
+.SH SYNOPSIS
47f05a
+.sp
47f05a
+\fByum copr [enable|disable|list|search] <parameters>\fP
47f05a
+.sp
47f05a
+\fByum playground [enable|disable|upgrade]\fP
47f05a
+.SH ARGUMENTS (COPR)
47f05a
+.INDENT 0.0
47f05a
+.TP
47f05a
+.B \fBenable name/project [chroot]\fP
47f05a
+Enable the \fBname/project\fP Copr repository with the optional \fBchroot\fP\&.
47f05a
+.TP
47f05a
+.B \fBdisable name/project\fP
47f05a
+Disable the \fBname/project\fP Copr repository.
47f05a
+.TP
47f05a
+.B \fBlist name\fP
47f05a
+List available Copr repositories for a given \fBname\fP\&.
47f05a
+.TP
47f05a
+.B \fBsearch project\fP
47f05a
+Search for a given \fBproject\fP\&.
47f05a
+.UNINDENT
47f05a
+.SH ARGUMENTS (PLAYGROUND)
47f05a
+.INDENT 0.0
47f05a
+.TP
47f05a
+.B \fBenable\fP
47f05a
+Enable the Playground repository.
47f05a
+.TP
47f05a
+.B \fBdisable\fP
47f05a
+Disable the Playground repository.
47f05a
+.TP
47f05a
+.B \fBupgrade\fP
47f05a
+Upgrade the Playground repository settings (same as \fBdisable\fP and then \fBenable\fP).
47f05a
+.UNINDENT
47f05a
+.SH EXAMPLES
47f05a
+.INDENT 0.0
47f05a
+.TP
47f05a
+.B \fBcopr enable rhscl/perl516 epel\-6\-x86_64\fP
47f05a
+Enable the \fBrhscl/perl516\fP Copr repository, using the \fBepel\-6\-x86_64\fP chroot.
47f05a
+.TP
47f05a
+.B \fBcopr disable rhscl/perl516\fP
47f05a
+Disable the \fBrhscl/perl516\fP Copr repository
47f05a
+.TP
47f05a
+.B \fBcopr list rita\fP
47f05a
+List available Copr projects for user \fBrita\fP\&.
47f05a
+.TP
47f05a
+.B \fBcopr search tests\fP
47f05a
+Search for Copr projects named \fBtests\fP\&.
47f05a
+.UNINDENT
47f05a
+.SH AUTHOR
47f05a
+See AUTHORS in the Core DNF Plugins distribution
47f05a
+.SH COPYRIGHT
47f05a
+2014, Red Hat, Licensed under GPLv2+
47f05a
+.\" Generated by docutils manpage writer.
47f05a
+.
47f05a
diff -N -up -r a/plugins/copr/copr.conf b/plugins/copr/copr.conf
47f05a
--- a/plugins/copr/copr.conf	1970-01-01 01:00:00.000000000 +0100
47f05a
+++ b/plugins/copr/copr.conf	2017-03-21 14:22:49.094134790 +0100
47f05a
@@ -0,0 +1,2 @@
47f05a
+[main]
47f05a
+enabled=1
47f05a
diff -N -up -r a/plugins/copr/copr.py b/plugins/copr/copr.py
47f05a
--- a/plugins/copr/copr.py	1970-01-01 01:00:00.000000000 +0100
47f05a
+++ b/plugins/copr/copr.py	2017-03-21 14:22:49.095134776 +0100
47f05a
@@ -0,0 +1,327 @@
47f05a
+# supplies the 'copr' command.
47f05a
+#
47f05a
+# Copyright (C) 2014  Red Hat, Inc.
47f05a
+#
47f05a
+# This copyrighted material is made available to anyone wishing to use,
47f05a
+# modify, copy, or redistribute it subject to the terms and conditions of
47f05a
+# the GNU General Public License v.2, or (at your option) any later version.
47f05a
+# This program is distributed in the hope that it will be useful, but WITHOUT
47f05a
+# ANY WARRANTY expressed or implied, including the implied warranties of
47f05a
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
47f05a
+# Public License for more details.  You should have received a copy of the
47f05a
+# GNU General Public License along with this program; if not, write to the
47f05a
+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
47f05a
+# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
47f05a
+# source code or documentation are not subject to the GNU General Public
47f05a
+# License and may only be used or replicated with the express permission of
47f05a
+# Red Hat, Inc.
47f05a
+#
47f05a
+
47f05a
+"""YUM plugin supplying the 'copr' command."""
47f05a
+
47f05a
+from urlgrabber import grabber
47f05a
+
47f05a
+import yum
47f05a
+import glob
47f05a
+import json
47f05a
+import os
47f05a
+import platform
47f05a
+import requests
47f05a
+import urllib
47f05a
+
47f05a
+from yum.i18n import _
47f05a
+from yum.plugins import TYPE_INTERACTIVE
47f05a
+
47f05a
+requires_api_version = '2.5'
47f05a
+plugin_type = (TYPE_INTERACTIVE,)
47f05a
+
47f05a
+yes = set([_('yes'), _('y')])
47f05a
+no = set([_('no'), _('n'), ''])
47f05a
+
47f05a
+YError = yum.Errors.YumBaseError
47f05a
+YCliError = yum.Errors.MiscError
47f05a
+
47f05a
+def config_hook(conduit):
47f05a
+    conduit.registerCommand(CoprCommand())
47f05a
+    conduit.registerCommand(PlaygroundCommand())
47f05a
+
47f05a
+class CoprCommand:
47f05a
+    """ Copr plugin for DNF """
47f05a
+
47f05a
+    def getNames(self):
47f05a
+        return [self.aliases[0]]
47f05a
+
47f05a
+    def getUsage(self):
47f05a
+        return self.usage
47f05a
+
47f05a
+    def getSummary(self):
47f05a
+        return self.summary[1:]
47f05a
+
47f05a
+    def doCheck(self, base, basecmd, extcmds):
47f05a
+        self.base = base
47f05a
+
47f05a
+    copr_url = "https://copr.fedoraproject.org"
47f05a
+    aliases = ("copr",)
47f05a
+    summary = _("Interact with Copr repositories.")
47f05a
+    usage = _("""
47f05a
+  enable name/project [chroot]
47f05a
+  disable name/project
47f05a
+  list name
47f05a
+  search project
47f05a
+
47f05a
+  Examples:
47f05a
+  copr enable rhscl/perl516 epel-6-x86_64
47f05a
+  copr enable ignatenkobrain/ocltoys
47f05a
+  copr disable rhscl/perl516
47f05a
+  copr list ignatenkobrain
47f05a
+  copr search tests
47f05a
+    """)
47f05a
+
47f05a
+    def doCommand(self, base, basecmd, extcmds):
47f05a
+        try:
47f05a
+            subcommand = extcmds[0]
47f05a
+            project_name = extcmds[1]
47f05a
+        except (ValueError, IndexError):
47f05a
+            base.logger.critical(
47f05a
+                _('Error: ') +
47f05a
+                _('exactly two additional parameters to '
47f05a
+                  'copr command are required'))
47f05a
+            # FIXME
47f05a
+            # dnf.cli.commands.err_mini_usage(self.cli, self.cli.base.basecmd)
47f05a
+            raise YCliError(
47f05a
+                _('exactly two additional parameters to '
47f05a
+                  'copr command are required'))
47f05a
+        try:
47f05a
+            chroot = extcmds[2]
47f05a
+        except IndexError:
47f05a
+            chroot = self._guess_chroot()
47f05a
+        repo_filename = "/etc/yum.repos.d/_copr_{}.repo" \
47f05a
+                        .format(project_name.replace("/", "-"))
47f05a
+        if subcommand == "enable":
47f05a
+            self._need_root()
47f05a
+            self._ask_user("""
47f05a
+You are about to enable a Copr repository. Please note that this
47f05a
+repository is not part of the main Fedora distribution, and quality
47f05a
+may vary.
47f05a
+
47f05a
+The Fedora Project does not exercise any power over the contents of
47f05a
+this repository beyond the rules outlined in the Copr FAQ at
47f05a
+<https://fedorahosted.org/copr/wiki/UserDocs#WhatIcanbuildinCopr>, and
47f05a
+packages are not held to any quality or securty level.
47f05a
+
47f05a
+Please do not file bug reports about these packages in Fedora
47f05a
+Bugzilla. In case of problems, contact the owner of this repository.
47f05a
+
47f05a
+Do you want to continue? [y/N]: """)
47f05a
+            self._download_repo(project_name, repo_filename, chroot)
47f05a
+            base.logger.info(_("Repository successfully enabled."))
47f05a
+        elif subcommand == "disable":
47f05a
+            self._need_root()
47f05a
+            self._remove_repo(repo_filename)
47f05a
+            base.logger.info(_("Repository successfully disabled."))
47f05a
+        elif subcommand == "list":
47f05a
+            #http://copr.fedoraproject.org/api/coprs/ignatenkobrain/
47f05a
+            api_path = "/api/coprs/{}/".format(project_name)
47f05a
+
47f05a
+            opener = urllib.FancyURLopener({})
47f05a
+            res = opener.open(self.copr_url + api_path)
47f05a
+            try:
47f05a
+                json_parse = json.loads(res.read())
47f05a
+            except ValueError:
47f05a
+                raise YError(
47f05a
+                    _("Can't parse repositories for username '{}'.")
47f05a
+                    .format(project_name))
47f05a
+            section_text = _("List of {} coprs").format(project_name)
47f05a
+            self._print_match_section(section_text)
47f05a
+            i = 0
47f05a
+            while i < len(json_parse["repos"]):
47f05a
+                msg = "{0}/{1} : ".format(project_name,
47f05a
+                      json_parse["repos"][i]["name"])
47f05a
+                desc = json_parse["repos"][i]["description"]
47f05a
+                if not desc:
47f05a
+                    desc = _("No description given")
47f05a
+                msg = self.base.fmtKeyValFill(unicode(msg), desc)
47f05a
+                print(msg)
47f05a
+                i += 1
47f05a
+        elif subcommand == "search":
47f05a
+            #http://copr.fedoraproject.org/api/coprs/search/tests/
47f05a
+            api_path = "/api/coprs/search/{}/".format(project_name)
47f05a
+
47f05a
+            opener = urllib.FancyURLopener({})
47f05a
+            res = opener.open(self.copr_url + api_path)
47f05a
+            try:
47f05a
+                json_parse = json.loads(res.read())
47f05a
+            except ValueError:
47f05a
+                raise YError(_("Can't parse search for '{}'.").format(project_name))
47f05a
+            section_text = _("Matched: {}").format(project_name)
47f05a
+            self._print_match_section(section_text)
47f05a
+            i = 0
47f05a
+            while i < len(json_parse["repos"]):
47f05a
+                msg = "{0}/{1} : ".format(json_parse["repos"][i]["username"], json_parse["repos"][i]["coprname"])
47f05a
+                desc = json_parse["repos"][i]["description"]
47f05a
+                if not desc:
47f05a
+                    desc = _("No description given.")
47f05a
+                msg = self.base.fmtKeyValFill(unicode(msg), desc)
47f05a
+                print(msg)
47f05a
+                i += 1
47f05a
+        else:
47f05a
+            raise YError(
47f05a
+                _('Unknown subcommand {}.').format(subcommand))
47f05a
+
47f05a
+        return 0, [basecmd + ' done']
47f05a
+
47f05a
+    def _print_match_section(self, text):
47f05a
+        formatted = self.base.fmtSection(text)
47f05a
+        print(formatted)
47f05a
+
47f05a
+    def _ask_user(self, question):
47f05a
+        if self.base.conf.assumeyes and not self.base.conf.assumeno:
47f05a
+            return
47f05a
+        elif self.base.conf.assumeno and not self.base.conf.assumeyes:
47f05a
+            raise YError(_('Safe and good answer. Exiting.'))
47f05a
+
47f05a
+        answer = raw_input(question).lower()
47f05a
+        answer = _(answer)
47f05a
+        while not ((answer in yes) or (answer in no)):
47f05a
+            answer = raw_input(question).lower()
47f05a
+            answer = _(answer)
47f05a
+        if answer in yes:
47f05a
+            return
47f05a
+        else:
47f05a
+            raise YError(_('Safe and good answer. Exiting.'))
47f05a
+
47f05a
+    @classmethod
47f05a
+    def _need_root(cls):
47f05a
+        # FIXME this should do dnf itself (BZ#1062889)
47f05a
+        if os.geteuid() != 0:
47f05a
+            raise YError(
47f05a
+                _('This command has to be run under the root user.'))
47f05a
+
47f05a
+    @classmethod
47f05a
+    def _guess_chroot(cls):
47f05a
+        """ Guess which choot is equivalent to this machine """
47f05a
+        # FIXME Copr should generate non-specific arch repo
47f05a
+        dist = platform.linux_distribution()
47f05a
+        if "Fedora" in dist:
47f05a
+            # x86_64 because repo-file is same for all arch
47f05a
+            # ($basearch is used)
47f05a
+            if "Rawhide" in dist:
47f05a
+                chroot = ("fedora-rawhide-x86_64")
47f05a
+            else:
47f05a
+                chroot = ("fedora-{}-x86_64".format(dist[1]))
47f05a
+        else:
47f05a
+            chroot = ("epel-%s-x86_64" % dist[1].split(".", 1)[0])
47f05a
+        return chroot
47f05a
+
47f05a
+    @classmethod
47f05a
+    def _download_repo(cls, project_name, repo_filename, chroot=None):
47f05a
+        if chroot is None:
47f05a
+            chroot = cls._guess_chroot()
47f05a
+        #http://copr.fedoraproject.org/coprs/larsks/rcm/repo/epel-7-x86_64/
47f05a
+        api_path = "/coprs/{0}/repo/{1}/".format(project_name, chroot)
47f05a
+        ug = grabber.URLGrabber()
47f05a
+        # FIXME when we are full on python2 urllib.parse
47f05a
+        try:
47f05a
+            ug.urlgrab(cls.copr_url + api_path, filename=repo_filename)
47f05a
+        except grabber.URLGrabError as e:
47f05a
+            cls._remove_repo(repo_filename)
47f05a
+            raise YError(str(e))
47f05a
+
47f05a
+    @classmethod
47f05a
+    def _remove_repo(cls, repo_filename):
47f05a
+        # FIXME is it Copr repo ?
47f05a
+        try:
47f05a
+            os.remove(repo_filename)
47f05a
+        except OSError as e:
47f05a
+            raise YError(str(e))
47f05a
+
47f05a
+    @classmethod
47f05a
+    def _get_data(cls, req):
47f05a
+        """ Wrapper around response from server
47f05a
+
47f05a
+        check data and print nice error in case of some error (and return None)
47f05a
+        otherwise return json object.
47f05a
+        """
47f05a
+        try:
47f05a
+            output = json.loads(req.text)
47f05a
+        except ValueError:
47f05a
+            YCliError(_("Unknown response from server."))
47f05a
+            return
47f05a
+        if req.status_code != 200:
47f05a
+            YCliError(_(
47f05a
+                "Something went wrong:\n {0}\n".format(output["error"])))
47f05a
+            return
47f05a
+        return output
47f05a
+
47f05a
+
47f05a
+class PlaygroundCommand(CoprCommand):
47f05a
+    """ Playground plugin for DNF """
47f05a
+
47f05a
+    aliases = ("playground",)
47f05a
+    summary = _("Interact with Playground repository.")
47f05a
+    usage = " [enable|disable|upgrade]"
47f05a
+
47f05a
+    def _cmd_enable(self, chroot):
47f05a
+        self._need_root()
47f05a
+        self._ask_user("""
47f05a
+You are about to enable a Playground repository.
47f05a
+
47f05a
+Do you want to continue? [y/N]: """)
47f05a
+        api_url = "{0}/api/playground/list/".format(
47f05a
+            self.copr_url)
47f05a
+        req = requests.get(api_url)
47f05a
+        output = self._get_data(req)
47f05a
+        if output["output"] != "ok":
47f05a
+            raise YCliError(_("Unknown response from server."))
47f05a
+        for repo in output["repos"]:
47f05a
+            project_name = "{0}/{1}".format(repo["username"],
47f05a
+                repo["coprname"])
47f05a
+            repo_filename = "/etc/yum.repos.d/_playground_{}.repo" \
47f05a
+                    .format(project_name.replace("/", "-"))
47f05a
+            try:
47f05a
+                # check if that repo exist? but that will result in twice
47f05a
+                # up calls
47f05a
+                api_url = "{0}/api/coprs/{1}/detail/{2}/".format(
47f05a
+                    self.copr_url, project_name, chroot)
47f05a
+                req = requests.get(api_url)
47f05a
+                output2 = self._get_data(req)
47f05a
+                if output2 and ("output" in output2) and (output2["output"] == "ok"):
47f05a
+                    self._download_repo(project_name, repo_filename, chroot)
47f05a
+            except YError:
47f05a
+                # likely 404 and that repo does not exist
47f05a
+                pass
47f05a
+
47f05a
+    def _cmd_disable(self):
47f05a
+        self._need_root()
47f05a
+        for repo_filename in glob.glob('/etc/yum.repos.d/_playground_*.repo'):
47f05a
+            self._remove_repo(repo_filename)
47f05a
+
47f05a
+    def doCommand(self, base, basecmd, extcmds):
47f05a
+        try:
47f05a
+            subcommand = extcmds[0]
47f05a
+        except (ValueError, IndexError):
47f05a
+            base.logger.critical(
47f05a
+                _('Error: ') +
47f05a
+                _('exactly one parameter to '
47f05a
+                  'playground command are required'))
47f05a
+            # FIXME:
47f05a
+            # dnf.cli.commands.err_mini_usage(self.cli, self.cli.base.basecmd)
47f05a
+            raise YCliError(
47f05a
+                _('exactly one parameter to '
47f05a
+                  'playground command are required'))
47f05a
+        chroot = self._guess_chroot()
47f05a
+        if subcommand == "enable":
47f05a
+            self._cmd_enable(chroot)
47f05a
+            base.logger.info(_("Playground repositories successfully enabled."))
47f05a
+        elif subcommand == "disable":
47f05a
+            self._cmd_disable()
47f05a
+            base.logger.info(_("Playground repositories successfully disabled."))
47f05a
+        elif subcommand == "upgrade":
47f05a
+            self._cmd_disable()
47f05a
+            self._cmd_enable(chroot)
47f05a
+            base.logger.info(_("Playground repositories successfully updated."))
47f05a
+        else:
47f05a
+            raise YError(
47f05a
+                _('Unknown subcommand {}.').format(subcommand))
47f05a
+
47f05a
+        return 0, [basecmd + ' done']