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