|
Packit |
3a9065 |
# reposync.py
|
|
Packit |
3a9065 |
# DNF plugin adding a command to download all packages from given remote repo.
|
|
Packit |
3a9065 |
#
|
|
Packit |
3a9065 |
# Copyright (C) 2014 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 |
|
|
Packit |
3a9065 |
import hawkey
|
|
Packit |
3a9065 |
import os
|
|
Packit |
3a9065 |
import shutil
|
|
Packit Service |
d89df3 |
import types
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
from dnfpluginscore import _, logger
|
|
Packit |
3a9065 |
from dnf.cli.option_parser import OptionParser
|
|
Packit |
3a9065 |
import dnf
|
|
Packit |
3a9065 |
import dnf.cli
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def _pkgdir(intermediate, target):
|
|
Packit |
3a9065 |
cwd = dnf.i18n.ucd(os.getcwd())
|
|
Packit |
3a9065 |
return os.path.realpath(os.path.join(cwd, intermediate, target))
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
class RPMPayloadLocation(dnf.repo.RPMPayload):
|
|
Packit |
3a9065 |
def __init__(self, pkg, progress, pkg_location):
|
|
Packit |
3a9065 |
super(RPMPayloadLocation, self).__init__(pkg, progress)
|
|
Packit |
3a9065 |
self.package_dir = os.path.dirname(pkg_location)
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def _target_params(self):
|
|
Packit |
3a9065 |
tp = super(RPMPayloadLocation, self)._target_params()
|
|
Packit |
3a9065 |
dnf.util.ensure_dir(self.package_dir)
|
|
Packit |
3a9065 |
tp['dest'] = self.package_dir
|
|
Packit |
3a9065 |
return tp
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
@dnf.plugin.register_command
|
|
Packit |
3a9065 |
class RepoSyncCommand(dnf.cli.Command):
|
|
Packit |
3a9065 |
aliases = ('reposync',)
|
|
Packit |
3a9065 |
summary = _('download all packages from remote repo')
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def __init__(self, cli):
|
|
Packit |
3a9065 |
super(RepoSyncCommand, self).__init__(cli)
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
@staticmethod
|
|
Packit |
3a9065 |
def set_argparser(parser):
|
|
Packit |
3a9065 |
parser.add_argument('-a', '--arch', dest='arches', default=[],
|
|
Packit |
3a9065 |
action=OptionParser._SplitCallback, metavar='[arch]',
|
|
Packit |
3a9065 |
help=_('download only packages for this ARCH'))
|
|
Packit |
3a9065 |
parser.add_argument('--delete', default=False, action='store_true',
|
|
Packit |
3a9065 |
help=_('delete local packages no longer present in repository'))
|
|
Packit Service |
1a5bdb |
parser.add_argument('--download-metadata', default=False, action='store_true',
|
|
Packit Service |
1a5bdb |
help=_('download all the metadata.'))
|
|
Packit Service |
d89df3 |
parser.add_argument('-g', '--gpgcheck', default=False, action='store_true',
|
|
Packit Service |
d89df3 |
help=_('Remove packages that fail GPG signature checking '
|
|
Packit Service |
d89df3 |
'after downloading'))
|
|
Packit Service |
d89df3 |
parser.add_argument('-m', '--downloadcomps', default=False, action='store_true',
|
|
Packit Service |
d89df3 |
help=_('also download and uncompress comps.xml'))
|
|
Packit Service |
d89df3 |
parser.add_argument('--metadata-path',
|
|
Packit Service |
d89df3 |
help=_('where to store downloaded repository metadata. '
|
|
Packit Service |
d89df3 |
'Defaults to the value of --download-path.'))
|
|
Packit |
3a9065 |
parser.add_argument('-n', '--newest-only', default=False, action='store_true',
|
|
Packit |
3a9065 |
help=_('download only newest packages per-repo'))
|
|
Packit Service |
1a5bdb |
parser.add_argument('--norepopath', default=False, action='store_true',
|
|
Packit Service |
1a5bdb |
help=_("Don't add the reponame to the download path."))
|
|
Packit Service |
d89df3 |
parser.add_argument('-p', '--download-path', default='./',
|
|
Packit Service |
d89df3 |
help=_('where to store downloaded repositories'))
|
|
Packit |
3a9065 |
parser.add_argument('--remote-time', default=False, action='store_true',
|
|
Packit |
3a9065 |
help=_('try to set local timestamps of local files by '
|
|
Packit |
3a9065 |
'the one on the server'))
|
|
Packit Service |
d89df3 |
parser.add_argument('--source', default=False, action='store_true',
|
|
Packit Service |
d89df3 |
help=_('operate on source packages'))
|
|
Packit |
3a9065 |
parser.add_argument('-u', '--urls', default=False, action='store_true',
|
|
Packit |
3a9065 |
help=_("Just list urls of what would be downloaded, "
|
|
Packit |
3a9065 |
"don't download"))
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def configure(self):
|
|
Packit |
3a9065 |
demands = self.cli.demands
|
|
Packit |
3a9065 |
demands.available_repos = True
|
|
Packit |
3a9065 |
demands.sack_activation = True
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
repos = self.base.repos
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
if self.opts.repo:
|
|
Packit |
3a9065 |
repos.all().disable()
|
|
Packit |
3a9065 |
for repoid in self.opts.repo:
|
|
Packit |
3a9065 |
try:
|
|
Packit |
3a9065 |
repo = repos[repoid]
|
|
Packit |
3a9065 |
except KeyError:
|
|
Packit |
3a9065 |
raise dnf.cli.CliError("Unknown repo: '%s'." % repoid)
|
|
Packit |
3a9065 |
repo.enable()
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
if self.opts.source:
|
|
Packit |
3a9065 |
repos.enable_source_repos()
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
if len(list(repos.iter_enabled())) > 1 and self.opts.norepopath:
|
|
Packit |
3a9065 |
raise dnf.cli.CliError(
|
|
Packit |
3a9065 |
_("Can't use --norepopath with multiple repositories"))
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
for repo in repos.iter_enabled():
|
|
Packit |
3a9065 |
repo._repo.expire()
|
|
Packit |
3a9065 |
repo.deltarpm = False
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def run(self):
|
|
Packit |
3a9065 |
self.base.conf.keepcache = True
|
|
Packit Service |
d89df3 |
gpgcheck_ok = True
|
|
Packit |
3a9065 |
for repo in self.base.repos.iter_enabled():
|
|
Packit |
3a9065 |
if self.opts.remote_time:
|
|
Packit |
3a9065 |
repo._repo.setPreserveRemoteTime(True)
|
|
Packit |
3a9065 |
if self.opts.download_metadata:
|
|
Packit |
3a9065 |
if self.opts.urls:
|
|
Packit |
3a9065 |
for md_type, md_location in repo._repo.getMetadataLocations():
|
|
Packit |
3a9065 |
url = repo.remote_location(md_location)
|
|
Packit |
3a9065 |
if url:
|
|
Packit |
3a9065 |
print(url)
|
|
Packit |
3a9065 |
else:
|
|
Packit |
3a9065 |
msg = _("Failed to get mirror for metadata: %s") % md_type
|
|
Packit |
3a9065 |
logger.warning(msg)
|
|
Packit |
3a9065 |
else:
|
|
Packit |
3a9065 |
self.download_metadata(repo)
|
|
Packit |
3a9065 |
if self.opts.downloadcomps:
|
|
Packit |
3a9065 |
if self.opts.urls:
|
|
Packit |
3a9065 |
mdl = dict(repo._repo.getMetadataLocations())
|
|
Packit |
3a9065 |
group_locations = [mdl[md_type]
|
|
Packit |
3a9065 |
for md_type in ('group', 'group_gz', 'group_gz_zck')
|
|
Packit |
3a9065 |
if md_type in mdl]
|
|
Packit |
3a9065 |
if group_locations:
|
|
Packit |
3a9065 |
for group_location in group_locations:
|
|
Packit |
3a9065 |
url = repo.remote_location(group_location)
|
|
Packit |
3a9065 |
if url:
|
|
Packit |
3a9065 |
print(url)
|
|
Packit |
3a9065 |
break
|
|
Packit |
3a9065 |
else:
|
|
Packit |
3a9065 |
msg = _("Failed to get mirror for the group file.")
|
|
Packit |
3a9065 |
logger.warning(msg)
|
|
Packit |
3a9065 |
else:
|
|
Packit |
3a9065 |
self.getcomps(repo)
|
|
Packit |
3a9065 |
pkglist = self.get_pkglist(repo)
|
|
Packit |
3a9065 |
if self.opts.urls:
|
|
Packit |
3a9065 |
self.print_urls(pkglist)
|
|
Packit |
3a9065 |
else:
|
|
Packit |
3a9065 |
self.download_packages(pkglist)
|
|
Packit Service |
d89df3 |
if self.opts.gpgcheck:
|
|
Packit Service |
d89df3 |
for pkg in pkglist:
|
|
Packit Service |
d89df3 |
local_path = self.pkg_download_path(pkg)
|
|
Packit Service |
d89df3 |
# base.package_signature_check uses pkg.localPkg() to determine
|
|
Packit Service |
d89df3 |
# the location of the package rpm file on the disk.
|
|
Packit Service |
d89df3 |
# Set it to the correct download path.
|
|
Packit Service |
d89df3 |
pkg.localPkg = types.MethodType(
|
|
Packit Service |
d89df3 |
lambda s, local_path=local_path: local_path, pkg)
|
|
Packit Service |
d89df3 |
result, error = self.base.package_signature_check(pkg)
|
|
Packit Service |
d89df3 |
if result != 0:
|
|
Packit Service |
d89df3 |
logger.warning(_("Removing {}: {}").format(
|
|
Packit Service |
d89df3 |
os.path.basename(local_path), error))
|
|
Packit Service |
d89df3 |
os.unlink(local_path)
|
|
Packit Service |
d89df3 |
gpgcheck_ok = False
|
|
Packit |
3a9065 |
if self.opts.delete:
|
|
Packit |
3a9065 |
self.delete_old_local_packages(repo, pkglist)
|
|
Packit Service |
d89df3 |
if not gpgcheck_ok:
|
|
Packit Service |
d89df3 |
raise dnf.exceptions.Error(_("GPG signature check failed."))
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def repo_target(self, repo):
|
|
Packit |
3a9065 |
return _pkgdir(self.opts.destdir or self.opts.download_path,
|
|
Packit |
3a9065 |
repo.id if not self.opts.norepopath else '')
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def metadata_target(self, repo):
|
|
Packit |
3a9065 |
if self.opts.metadata_path:
|
|
Packit |
3a9065 |
return _pkgdir(self.opts.metadata_path, repo.id)
|
|
Packit |
3a9065 |
else:
|
|
Packit |
3a9065 |
return self.repo_target(repo)
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def pkg_download_path(self, pkg):
|
|
Packit |
3a9065 |
repo_target = self.repo_target(pkg.repo)
|
|
Packit |
3a9065 |
pkg_download_path = os.path.realpath(
|
|
Packit |
3a9065 |
os.path.join(repo_target, pkg.location))
|
|
Packit |
3a9065 |
# join() ensures repo_target ends with a path separator (otherwise the
|
|
Packit |
3a9065 |
# check would pass if pkg_download_path was a "sibling" path component
|
|
Packit |
3a9065 |
# of repo_target that has the same prefix).
|
|
Packit |
3a9065 |
if not pkg_download_path.startswith(os.path.join(repo_target, '')):
|
|
Packit |
3a9065 |
raise dnf.exceptions.Error(
|
|
Packit |
3a9065 |
_("Download target '{}' is outside of download path '{}'.").format(
|
|
Packit |
3a9065 |
pkg_download_path, repo_target))
|
|
Packit |
3a9065 |
return pkg_download_path
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def delete_old_local_packages(self, repo, pkglist):
|
|
Packit |
3a9065 |
# delete any *.rpm file under target path, that was not downloaded from repository
|
|
Packit |
3a9065 |
downloaded_files = set(self.pkg_download_path(pkg) for pkg in pkglist)
|
|
Packit |
3a9065 |
for dirpath, dirnames, filenames in os.walk(self.repo_target(repo)):
|
|
Packit |
3a9065 |
for filename in filenames:
|
|
Packit |
3a9065 |
path = os.path.join(dirpath, filename)
|
|
Packit |
3a9065 |
if filename.endswith('.rpm') and os.path.isfile(path):
|
|
Packit |
3a9065 |
if path not in downloaded_files:
|
|
Packit |
3a9065 |
# Delete disappeared or relocated file
|
|
Packit |
3a9065 |
try:
|
|
Packit |
3a9065 |
os.unlink(path)
|
|
Packit |
3a9065 |
logger.info(_("[DELETED] %s"), path)
|
|
Packit |
3a9065 |
except OSError:
|
|
Packit |
3a9065 |
logger.error(_("failed to delete file %s"), path)
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def getcomps(self, repo):
|
|
Packit |
3a9065 |
comps_fn = repo._repo.getCompsFn()
|
|
Packit |
3a9065 |
if comps_fn:
|
|
Packit |
3a9065 |
dest_path = self.metadata_target(repo)
|
|
Packit |
3a9065 |
dnf.util.ensure_dir(dest_path)
|
|
Packit |
3a9065 |
dest = os.path.join(dest_path, 'comps.xml')
|
|
Packit |
3a9065 |
dnf.yum.misc.decompress(comps_fn, dest=dest)
|
|
Packit |
3a9065 |
logger.info(_("comps.xml for repository %s saved"), repo.id)
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def download_metadata(self, repo):
|
|
Packit |
3a9065 |
repo_target = self.metadata_target(repo)
|
|
Packit |
3a9065 |
repo._repo.downloadMetadata(repo_target)
|
|
Packit |
3a9065 |
return True
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def _get_latest(self, query):
|
|
Packit |
3a9065 |
"""
|
|
Packit Service |
6746a6 |
return union of these queries:
|
|
Packit Service |
6746a6 |
- the latest NEVRAs from non-modular packages
|
|
Packit Service |
6746a6 |
- all packages from stream version with the latest package NEVRA
|
|
Packit Service |
6746a6 |
(this should not be needed but the latest package NEVRAs might be
|
|
Packit Service |
6746a6 |
part of an older module version)
|
|
Packit Service |
6746a6 |
- all packages from the latest stream version
|
|
Packit |
3a9065 |
"""
|
|
Packit |
3a9065 |
if not dnf.base.WITH_MODULES:
|
|
Packit |
3a9065 |
return query.latest()
|
|
Packit Service |
6746a6 |
|
|
Packit |
3a9065 |
query.apply()
|
|
Packit |
3a9065 |
module_packages = self.base._moduleContainer.getModulePackages()
|
|
Packit |
3a9065 |
all_artifacts = set()
|
|
Packit |
3a9065 |
module_dict = {} # {NameStream: {Version: [modules]}}
|
|
Packit Service |
6746a6 |
artifact_version = {} # {artifact: {NameStream: [Version]}}
|
|
Packit |
3a9065 |
for module_package in module_packages:
|
|
Packit Service |
6746a6 |
artifacts = module_package.getArtifacts()
|
|
Packit Service |
6746a6 |
all_artifacts.update(artifacts)
|
|
Packit |
3a9065 |
module_dict.setdefault(module_package.getNameStream(), {}).setdefault(
|
|
Packit |
3a9065 |
module_package.getVersionNum(), []).append(module_package)
|
|
Packit Service |
6746a6 |
for artifact in artifacts:
|
|
Packit Service |
6746a6 |
artifact_version.setdefault(artifact, {}).setdefault(
|
|
Packit Service |
6746a6 |
module_package.getNameStream(), []).append(module_package.getVersionNum())
|
|
Packit Service |
6746a6 |
|
|
Packit Service |
6746a6 |
# the latest NEVRAs from non-modular packages
|
|
Packit Service |
6746a6 |
latest_query = query.filter(
|
|
Packit |
3a9065 |
pkg__neq=query.filter(nevra_strict=all_artifacts)).latest()
|
|
Packit Service |
6746a6 |
|
|
Packit Service |
6746a6 |
# artifacts from the newest version and those versions that contain an artifact
|
|
Packit Service |
6746a6 |
# with the highest NEVRA
|
|
Packit Service |
6746a6 |
latest_stream_artifacts = set()
|
|
Packit Service |
6746a6 |
for namestream, version_dict in module_dict.items():
|
|
Packit Service |
6746a6 |
# versions that will be synchronized
|
|
Packit Service |
6746a6 |
versions = set()
|
|
Packit Service |
6746a6 |
# add the newest stream version
|
|
Packit Service |
6746a6 |
versions.add(sorted(version_dict.keys(), reverse=True)[0])
|
|
Packit Service |
6746a6 |
# collect all artifacts in all stream versions
|
|
Packit Service |
6746a6 |
stream_artifacts = set()
|
|
Packit Service |
6746a6 |
for modules in version_dict.values():
|
|
Packit Service |
6746a6 |
for module in modules:
|
|
Packit Service |
6746a6 |
stream_artifacts.update(module.getArtifacts())
|
|
Packit Service |
6746a6 |
# find versions to which the packages with the highest NEVRAs belong
|
|
Packit Service |
6746a6 |
for latest_pkg in query.filter(nevra_strict=stream_artifacts).latest():
|
|
Packit Service |
6746a6 |
# here we depend on modules.yaml allways containing full NEVRA (including epoch)
|
|
Packit Service |
6746a6 |
nevra = "{0.name}-{0.epoch}:{0.version}-{0.release}.{0.arch}".format(latest_pkg)
|
|
Packit Service |
6746a6 |
# download only highest version containing the latest artifact
|
|
Packit Service |
6746a6 |
versions.add(max(artifact_version[nevra][namestream]))
|
|
Packit Service |
6746a6 |
# add all artifacts from selected versions for synchronization
|
|
Packit Service |
6746a6 |
for version in versions:
|
|
Packit Service |
6746a6 |
for module in version_dict[version]:
|
|
Packit Service |
6746a6 |
latest_stream_artifacts.update(module.getArtifacts())
|
|
Packit Service |
6746a6 |
latest_query = latest_query.union(query.filter(nevra_strict=latest_stream_artifacts))
|
|
Packit Service |
6746a6 |
|
|
Packit Service |
6746a6 |
return latest_query
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def get_pkglist(self, repo):
|
|
Packit |
3a9065 |
query = self.base.sack.query(flags=hawkey.IGNORE_MODULAR_EXCLUDES).available().filterm(
|
|
Packit |
3a9065 |
reponame=repo.id)
|
|
Packit |
3a9065 |
if self.opts.newest_only:
|
|
Packit |
3a9065 |
query = self._get_latest(query)
|
|
Packit |
3a9065 |
if self.opts.source:
|
|
Packit |
3a9065 |
query.filterm(arch='src')
|
|
Packit |
3a9065 |
elif self.opts.arches:
|
|
Packit |
3a9065 |
query.filterm(arch=self.opts.arches)
|
|
Packit |
3a9065 |
return query
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def download_packages(self, pkglist):
|
|
Packit |
3a9065 |
base = self.base
|
|
Packit |
3a9065 |
progress = base.output.progress
|
|
Packit |
3a9065 |
if progress is None:
|
|
Packit |
3a9065 |
progress = dnf.callback.NullDownloadProgress()
|
|
Packit |
3a9065 |
drpm = dnf.drpm.DeltaInfo(base.sack.query(flags=hawkey.IGNORE_MODULAR_EXCLUDES).installed(),
|
|
Packit |
3a9065 |
progress, 0)
|
|
Packit |
3a9065 |
payloads = [RPMPayloadLocation(pkg, progress, self.pkg_download_path(pkg))
|
|
Packit |
3a9065 |
for pkg in pkglist]
|
|
Packit |
3a9065 |
base._download_remote_payloads(payloads, drpm, progress, None)
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def print_urls(self, pkglist):
|
|
Packit |
3a9065 |
for pkg in pkglist:
|
|
Packit |
3a9065 |
url = pkg.remote_location()
|
|
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 |
logger.warning(msg)
|