Blame plugins/download.py

Packit 3a9065
# download.py, supplies the 'download' command.
Packit 3a9065
#
Packit 3a9065
# Copyright (C) 2013-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 absolute_import
Packit 3a9065
from __future__ import unicode_literals
Packit 3a9065
from dnfpluginscore import _, logger
Packit 3a9065
from dnf.cli.option_parser import OptionParser
Packit 3a9065
Packit 3a9065
import dnf
Packit 3a9065
import dnf.cli
Packit 3a9065
import dnf.exceptions
Packit 3a9065
import dnf.i18n
Packit 3a9065
import dnf.subject
Packit 3a9065
import dnf.util
Packit 3a9065
import hawkey
Packit 3a9065
import itertools
Packit 3a9065
import os
Packit 3a9065
import shutil
Packit 3a9065
Packit 3a9065
Packit 3a9065
@dnf.plugin.register_command
Packit 3a9065
class DownloadCommand(dnf.cli.Command):
Packit 3a9065
Packit 3a9065
    aliases = ['download']
Packit 3a9065
    summary = _('Download package to current directory')
Packit 3a9065
Packit 3a9065
    def __init__(self, cli):
Packit 3a9065
        super(DownloadCommand, self).__init__(cli)
Packit 3a9065
        self.opts = None
Packit 3a9065
        self.parser = None
Packit 3a9065
Packit 3a9065
    @staticmethod
Packit 3a9065
    def set_argparser(parser):
Packit 3a9065
        parser.add_argument('packages', nargs='+',
Packit 3a9065
                            help=_('packages to download'))
Packit 3a9065
        parser.add_argument("--source", action='store_true',
Packit 3a9065
                            help=_('download the src.rpm instead'))
Packit 3a9065
        parser.add_argument("--debuginfo", action='store_true',
Packit 3a9065
                            help=_('download the -debuginfo package instead'))
Packit 3a9065
        parser.add_argument("--debugsource", action='store_true',
Packit 3a9065
                            help=_('download the -debugsource package instead'))
Packit 3a9065
        parser.add_argument("--arch", '--archlist', dest='arches', default=[],
Packit 3a9065
                            action=OptionParser._SplitCallback, metavar='[arch]',
Packit 3a9065
                            help=_("limit  the  query to packages of given architectures."))
Packit 3a9065
        parser.add_argument('--resolve', action='store_true',
Packit 3a9065
                            help=_('resolve and download needed dependencies'))
Packit 3a9065
        parser.add_argument('--alldeps', action='store_true',
Packit 3a9065
                            help=_('when running with --resolve, download all dependencies '
Packit 3a9065
                                   '(do not exclude already installed ones)'))
Packit 3a9065
        parser.add_argument('--url', '--urls', action='store_true', dest='url',
Packit 3a9065
                            help=_('print list of urls where the rpms '
Packit 3a9065
                                   'can be downloaded instead of downloading'))
Packit 3a9065
        parser.add_argument('--urlprotocols', action='append',
Packit 3a9065
                            choices=['http', 'https', 'rsync', 'ftp'],
Packit 3a9065
                            default=[],
Packit 3a9065
                            help=_('when running with --url, '
Packit 3a9065
                                   'limit to specific protocols'))
Packit 3a9065
Packit 3a9065
    def configure(self):
Packit 3a9065
        # setup sack and populate it with enabled repos
Packit 3a9065
        demands = self.cli.demands
Packit 3a9065
        demands.sack_activation = True
Packit 3a9065
        demands.available_repos = True
Packit 3a9065
        if self.opts.resolve and self.opts.alldeps:
Packit 3a9065
            demands.load_system_repo = False
Packit 3a9065
Packit 3a9065
        if self.opts.source:
Packit 3a9065
            self.base.repos.enable_source_repos()
Packit 3a9065
Packit 3a9065
        if self.opts.debuginfo or self.opts.debugsource:
Packit 3a9065
            self.base.repos.enable_debug_repos()
Packit 3a9065
Packit 3a9065
        if self.opts.destdir:
Packit 3a9065
            self.base.conf.destdir = self.opts.destdir
Packit 3a9065
        else:
Packit 3a9065
            self.base.conf.destdir = dnf.i18n.ucd(os.getcwd())
Packit 3a9065
Packit 3a9065
    def run(self):
Packit 3a9065
        """Execute the util action here."""
Packit 3a9065
Packit 3a9065
        if (not self.opts.source
Packit 3a9065
                and not self.opts.debuginfo
Packit 3a9065
                and not self.opts.debugsource):
Packit 3a9065
            pkgs = self._get_pkg_objs_rpms(self.opts.packages)
Packit 3a9065
        else:
Packit 3a9065
            pkgs = []
Packit 3a9065
            if self.opts.source:
Packit 3a9065
                pkgs.extend(self._get_pkg_objs_source(self.opts.packages))
Packit 3a9065
Packit 3a9065
            if self.opts.debuginfo:
Packit 3a9065
                pkgs.extend(self._get_pkg_objs_debuginfo(self.opts.packages))
Packit 3a9065
Packit 3a9065
            if self.opts.debugsource:
Packit 3a9065
                pkgs.extend(self._get_pkg_objs_debugsource(self.opts.packages))
Packit 3a9065
Packit 3a9065
        # If user asked for just urls then print them and we're done
Packit 3a9065
        if self.opts.url:
Packit 3a9065
            for pkg in pkgs:
Packit 3a9065
                # command line repo packages do not have .remote_location
Packit 3a9065
                if pkg.repoid != hawkey.CMDLINE_REPO_NAME:
Packit 3a9065
                    url = pkg.remote_location(schemes=self.opts.urlprotocols)
Packit 3a9065
                    if url:
Packit 3a9065
                        print(url)
Packit 3a9065
                    else:
Packit 3a9065
                        msg = _("Failed to get mirror for package: %s") % pkg.name
Packit 3a9065
                        if self.base.conf.strict:
Packit 3a9065
                            raise dnf.exceptions.Error(msg)
Packit 3a9065
                        logger.warning(msg)
Packit 3a9065
            return
Packit 3a9065
        else:
Packit 3a9065
            self._do_downloads(pkgs)  # download rpms
Packit 3a9065
Packit 3a9065
    def _do_downloads(self, pkgs):
Packit 3a9065
        """
Packit 3a9065
        Perform the download for a list of packages
Packit 3a9065
        """
Packit 3a9065
        pkg_dict = {}
Packit 3a9065
        for pkg in pkgs:
Packit 3a9065
            pkg_dict.setdefault(str(pkg), []).append(pkg)
Packit 3a9065
Packit 3a9065
        to_download = []
Packit 3a9065
        cmdline = []
Packit 3a9065
        for pkg_list in pkg_dict.values():
Packit 3a9065
            pkgs_cmdline = [pkg for pkg in pkg_list
Packit 3a9065
                            if pkg.repoid == hawkey.CMDLINE_REPO_NAME]
Packit 3a9065
            if pkgs_cmdline:
Packit 3a9065
                cmdline.append(pkgs_cmdline[0])
Packit 3a9065
                continue
Packit 3a9065
            pkg_list.sort(key=lambda x: (x.repo.priority, x.repo.cost))
Packit 3a9065
            to_download.append(pkg_list[0])
Packit 3a9065
        if to_download:
Packit 3a9065
            self.base.download_packages(to_download, self.base.output.progress)
Packit 3a9065
        if cmdline:
Packit 3a9065
            # command line repo packages are either local files or already downloaded urls
Packit 3a9065
            # just copy them to the destination
Packit 3a9065
            for pkg in cmdline:
Packit 3a9065
                # python<3.4 shutil module does not raise SameFileError, check manually
Packit 3a9065
                src = pkg.localPkg()
Packit 3a9065
                dst = os.path.join(self.base.conf.destdir, os.path.basename(src))
Packit 3a9065
                if os.path.exists(dst) and os.path.samefile(src, dst):
Packit 3a9065
                    continue
Packit 3a9065
                shutil.copy(src, self.base.conf.destdir)
Packit 3a9065
        locations = sorted([pkg.localPkg() for pkg in to_download + cmdline])
Packit 3a9065
        return locations
Packit 3a9065
Packit 3a9065
    def _get_pkg_objs_rpms(self, pkg_specs):
Packit 3a9065
        """
Packit 3a9065
        Return a list of dnf.Package objects that represent the rpms
Packit 3a9065
        to download.
Packit 3a9065
        """
Packit 3a9065
        if self.opts.resolve:
Packit 3a9065
            pkgs = self._get_packages_with_deps(pkg_specs)
Packit 3a9065
        else:
Packit 3a9065
            pkgs = self._get_packages(pkg_specs)
Packit 3a9065
        return pkgs
Packit 3a9065
Packit 3a9065
    def _get_pkg_objs_source(self, pkg_specs):
Packit 3a9065
        """
Packit 3a9065
        Return a list of dnf.Package objects that represent the source
Packit 3a9065
        rpms to download.
Packit 3a9065
        """
Packit 3a9065
        pkgs = self._get_pkg_objs_rpms(pkg_specs)
Packit 3a9065
        source_pkgs = self._get_source_packages(pkgs)
Packit 3a9065
        pkgs = set(self._get_packages(source_pkgs, source=True))
Packit 3a9065
        return pkgs
Packit 3a9065
Packit 3a9065
    def _get_pkg_objs_debuginfo(self, pkg_specs):
Packit 3a9065
        """
Packit 3a9065
        Return a list of dnf.Package objects that represent the debuginfo
Packit 3a9065
        rpms to download.
Packit 3a9065
        """
Packit 3a9065
        dbg_pkgs = set()
Packit 3a9065
        q = self.base.sack.query().available()
Packit 3a9065
Packit 3a9065
        for pkg in self._get_packages(pkg_specs):
Packit 3a9065
            for dbg_name in [pkg.debug_name, pkg.source_debug_name]:
Packit 3a9065
                dbg_available = q.filter(
Packit 3a9065
                    name=dbg_name,
Packit 3a9065
                    epoch=int(pkg.epoch),
Packit 3a9065
                    version=pkg.version,
Packit 3a9065
                    release=pkg.release,
Packit 3a9065
                    arch=pkg.arch
Packit 3a9065
                )
Packit 3a9065
Packit 3a9065
                if not dbg_available:
Packit 3a9065
                    continue
Packit 3a9065
Packit 3a9065
                for p in dbg_available:
Packit 3a9065
                    dbg_pkgs.add(p)
Packit 3a9065
Packit 3a9065
                break
Packit 3a9065
Packit 3a9065
        return dbg_pkgs
Packit 3a9065
Packit 3a9065
    def _get_pkg_objs_debugsource(self, pkg_specs):
Packit 3a9065
        """
Packit 3a9065
        Return a list of dnf.Package objects that represent the debugsource
Packit 3a9065
        rpms to download.
Packit 3a9065
        """
Packit 3a9065
        dbg_pkgs = set()
Packit 3a9065
        q = self.base.sack.query().available()
Packit 3a9065
Packit 3a9065
        for pkg in self._get_packages(pkg_specs):
Packit 3a9065
            dbg_available = q.filter(
Packit 3a9065
                name=pkg.debugsource_name,
Packit 3a9065
                epoch=int(pkg.epoch),
Packit 3a9065
                version=pkg.version,
Packit 3a9065
                release=pkg.release,
Packit 3a9065
                arch=pkg.arch
Packit 3a9065
            )
Packit 3a9065
Packit 3a9065
            for p in dbg_available:
Packit 3a9065
                dbg_pkgs.add(p)
Packit 3a9065
Packit 3a9065
        return dbg_pkgs
Packit 3a9065
Packit 3a9065
    def _get_packages(self, pkg_specs, source=False):
Packit 3a9065
        """Get packages matching pkg_specs."""
Packit 3a9065
        func = self._get_query_source if source else self._get_query
Packit 3a9065
        queries = []
Packit 3a9065
        for pkg_spec in pkg_specs:
Packit 3a9065
            try:
Packit 3a9065
                queries.append(func(pkg_spec))
Packit 3a9065
            except dnf.exceptions.PackageNotFoundError as e:
Packit 3a9065
                logger.error(dnf.i18n.ucd(e))
Packit 3a9065
                if self.base.conf.strict:
Packit 3a9065
                    logger.error(_("Exiting due to strict setting."))
Packit 3a9065
                    raise dnf.exceptions.Error(e)
Packit 3a9065
Packit 3a9065
        pkgs = list(itertools.chain(*queries))
Packit 3a9065
        return pkgs
Packit 3a9065
Packit 3a9065
    def _get_packages_with_deps(self, pkg_specs, source=False):
Packit 3a9065
        """Get packages matching pkg_specs and the deps."""
Packit 3a9065
        pkgs = self._get_packages(pkg_specs)
Packit 3a9065
        pkg_set = set(pkgs)
Packit 3a9065
        for pkg in pkgs:
Packit 3a9065
            goal = hawkey.Goal(self.base.sack)
Packit 3a9065
            goal.install(pkg)
Packit 3a9065
            rc = goal.run()
Packit 3a9065
            if rc:
Packit 3a9065
                pkg_set.update(goal.list_installs())
Packit 3a9065
                pkg_set.update(goal.list_upgrades())
Packit 3a9065
            else:
Packit 3a9065
                msg = [_('Error in resolve of packages:')]
Packit 3a9065
                logger.warning("\n    ".join(msg + [str(pkg) for pkg in pkgs]))
Packit 3a9065
                logger.warning(dnf.util._format_resolve_problems(goal.problem_rules()))
Packit 3a9065
                return []
Packit 3a9065
        return pkg_set
Packit 3a9065
Packit 3a9065
    @staticmethod
Packit 3a9065
    def _get_source_packages(pkgs):
Packit 3a9065
        """Get list of source rpm names for a list of packages."""
Packit 3a9065
        source_pkgs = set()
Packit 3a9065
        for pkg in pkgs:
Packit 3a9065
            if pkg.sourcerpm:
Packit 3a9065
                source_pkgs.add(pkg.sourcerpm)
Packit 3a9065
                logger.debug('  --> Package : %s Source : %s',
Packit 3a9065
                             str(pkg), pkg.sourcerpm)
Packit 3a9065
            elif pkg.arch == 'src':
Packit 3a9065
                source_pkgs.add("%s-%s.src.rpm" % (pkg.name, pkg.evr))
Packit 3a9065
            else:
Packit 3a9065
                logger.info(_("No source rpm defined for %s"), str(pkg))
Packit 3a9065
        return list(source_pkgs)
Packit 3a9065
Packit 3a9065
    def _get_query(self, pkg_spec):
Packit 3a9065
        """Return a query to match a pkg_spec."""
Packit 3a9065
        schemes = dnf.pycomp.urlparse.urlparse(pkg_spec)[0]
Packit 3a9065
        is_url = schemes and schemes in ('http', 'ftp', 'file', 'https')
Packit 3a9065
        if is_url or (pkg_spec.endswith('.rpm') and os.path.isfile(pkg_spec)):
Packit 3a9065
            pkgs = self.base.add_remote_rpms([pkg_spec], progress=self.base.output.progress)
Packit 3a9065
            return self.base.sack.query().filterm(pkg=pkgs)
Packit 3a9065
        subj = dnf.subject.Subject(pkg_spec)
Packit 3a9065
        q = subj.get_best_query(self.base.sack, with_src=self.opts.source)
Packit 3a9065
        q = q.available()
Packit 3a9065
        q = q.filterm(latest_per_arch_by_priority=True)
Packit 3a9065
        if self.opts.arches:
Packit 3a9065
            q = q.filter(arch=self.opts.arches)
Packit 3a9065
        if len(q.run()) == 0:
Packit 3a9065
            msg = _("No package %s available.") % (pkg_spec)
Packit 3a9065
            raise dnf.exceptions.PackageNotFoundError(msg)
Packit 3a9065
        return q
Packit 3a9065
Packit 3a9065
    def _get_query_source(self, pkg_spec):
Packit 3a9065
        """Return a query to match a source rpm file name."""
Packit 3a9065
        pkg_spec = pkg_spec[:-4]  # skip the .rpm
Packit 3a9065
        subj = dnf.subject.Subject(pkg_spec)
Packit 3a9065
        for nevra_obj in subj.get_nevra_possibilities():
Packit 3a9065
            tmp_query = nevra_obj.to_query(self.base.sack).available()
Packit 3a9065
            if tmp_query:
Packit 3a9065
                return tmp_query.latest()
Packit 3a9065
Packit 3a9065
        msg = _("No package %s available.") % (pkg_spec)
Packit 3a9065
        raise dnf.exceptions.PackageNotFoundError(msg)