|
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))
|