pull-pkg: change pullpkg into class PullPkg

instead of pullpkg.py containing a simple method to call, change it
into a normal class PullPkg that callers can create and use.
This commit is contained in:
Dan Streetman 2018-07-10 14:20:44 -04:00
parent fb750e38bb
commit 3491b0cff9
2 changed files with 266 additions and 212 deletions

View File

@ -128,8 +128,7 @@ class SourcePackage(object):
spph_class = SourcePackagePublishingHistory
def __init__(self, package=None, version=None, component=None,
dscfile=None, lp=None, mirrors=(), workdir='.', quiet=False,
series=None, pocket=None, verify_signature=False):
*args, **kwargs):
"""Can be initialised using either package or dscfile.
If package is specified, either the version or series can also be
specified; using version will get the specific package version,
@ -137,6 +136,15 @@ class SourcePackage(object):
Specifying only the package with no version or series will get the
latest version from the development series.
"""
dscfile = kwargs.get('dscfile')
lp = kwargs.get('lp')
mirrors = kwargs.get('mirrors', ())
workdir = kwargs.get('workdir', '.')
quiet = kwargs.get('quiet', False)
series = kwargs.get('series')
pocket = kwargs.get('pocket')
verify_signature = kwargs.get('verify_signature', False)
assert (package is not None or dscfile is not None)
self.source = package

View File

@ -23,6 +23,8 @@
import re
import errno
from argparse import ArgumentParser
from distro_info import DebianDistroInfo
@ -63,221 +65,265 @@ class InvalidPullValueError(ValueError):
pass
def create_argparser(default_pull=None, default_distro=None, default_arch=None):
help_default_pull = "What to pull: " + ", ".join(VALID_PULLS)
if default_pull:
help_default_pull += (" (default: %s)" % default_pull)
help_default_distro = "Pull from: " + ", ".join(VALID_DISTROS)
if default_distro:
help_default_distro += (" (default: %s)" % default_distro)
if not default_arch:
default_arch = host_architecture()
help_default_arch = ("Get binary packages for arch (default: %s)" % default_arch)
class PullPkg(object):
"""Class used to pull file(s) associated with a specific package"""
@classmethod
def main(cls, *args, **kwargs):
"""For use by stand-alone cmdline scripts.
parser = ArgumentParser()
parser.add_argument('-v', '--verbose', action='store_true',
help="Print verbose/debug messages")
parser.add_argument('-d', '--download-only', action='store_true',
help="Do not extract the source package")
parser.add_argument('-m', '--mirror', action='append',
help='Preferred mirror(s)')
parser.add_argument('--no-conf', action='store_true',
help="Don't read config files or environment variables")
parser.add_argument('--no-verify-signature', action='store_true',
help="Don't fail if dsc signature can't be verified")
parser.add_argument('-a', '--arch', default=default_arch,
help=help_default_arch)
parser.add_argument('-p', '--pull', default=default_pull,
help=help_default_pull)
parser.add_argument('-D', '--distro', default=default_distro,
help=help_default_distro)
parser.add_argument('package', help="Package name to pull")
parser.add_argument('release', nargs='?', help="Release to pull from")
parser.add_argument('version', nargs='?', help="Package version to pull")
return parser
def parse_pull(pull):
if not pull:
raise InvalidPullValueError("Must specify --pull")
# allow 'dbgsym' as alias for 'ddebs'
if pull == 'dbgsym':
Logger.debug("Pulling '%s' for '%s'", PULL_DDEBS, pull)
pull = PULL_DDEBS
# assume anything starting with 'bin' means 'debs'
if str(pull).startswith('bin'):
Logger.debug("Pulling '%s' for '%s'", PULL_DEBS, pull)
pull = PULL_DEBS
# verify pull action is valid
if pull not in VALID_PULLS:
raise InvalidPullValueError("Invalid pull action '%s'" % pull)
return pull
def parse_distro(distro):
if not distro:
raise InvalidDistroValueError("Must specify --distro")
distro = distro.lower()
# allow 'lp' for 'ubuntu'
if distro == 'lp':
Logger.debug("Using distro '%s' for '%s'", DISTRO_UBUNTU, distro)
distro = DISTRO_UBUNTU
# assume anything with 'cloud' is UCA
if re.match(r'.*cloud.*', distro):
Logger.debug("Using distro '%s' for '%s'", DISTRO_UCA, distro)
distro = DISTRO_UCA
# verify distro is valid
if distro not in VALID_DISTROS:
raise InvalidDistroValueError("Invalid distro '%s'" % distro)
return distro
def parse_release(release, distro):
if distro == DISTRO_UCA:
# UCA is special; it is specified UBUNTURELEASE-UCARELEASE or just
# UCARELEASE. The user could also specify UCARELEASE-POCKET. But UCA
# archives always correspond to only one UBUNTURELEASE, and UCA archives
# have only the Release pocket, so only UCARELEASE matters to us.
for r in release.split('-'):
if r in UbuntuCloudArchiveSourcePackage.getReleaseNames():
Logger.debug("Using UCA release '%s'", r)
return (r, None)
raise SeriesNotFoundException('UCA release {} not found.'.format(release))
# Check if release[-pocket] is specified
(release, pocket) = split_release_pocket(release, default=None)
Logger.debug("Parsed release '%s' pocket '%s'", release, pocket)
if distro == DISTRO_DEBIAN:
# This converts from the aliases like 'unstable'
debian_info = DebianDistroInfo()
codename = debian_info.codename(release)
if codename:
Logger.normal("Using release '%s' for '%s'", codename, release)
release = codename
d = Distribution(distro)
# let SeriesNotFoundException flow up
d.getSeries(release)
Logger.debug("Using distro '%s' release '%s' pocket '%s'",
distro, release, pocket)
return (release, pocket)
def pull(options):
# required options asserted below
# 'release' and 'version' are optional strings
# 'mirror' is optional list of strings
# these are type bool
assert hasattr(options, 'verbose')
assert hasattr(options, 'download_only')
assert hasattr(options, 'no_conf')
assert hasattr(options, 'no_verify_signature')
# these are type string
assert hasattr(options, 'arch')
assert hasattr(options, 'pull')
assert hasattr(options, 'distro')
assert hasattr(options, 'package')
Logger.set_verbosity(options.verbose)
Logger.debug("pullpkg options: %s", options)
# Login anonymously to LP
Launchpad.login_anonymously()
pull = parse_pull(options.pull)
distro = parse_distro(options.distro)
config = UDTConfig(options.no_conf)
mirrors = []
if hasattr(options, 'mirror') and options.mirror:
mirrors += options.mirror
if pull == PULL_DDEBS:
ddebs_mirror = config.get_value(distro.upper() + '_DDEBS_MIRROR')
if ddebs_mirror:
mirrors.append(ddebs_mirror)
if mirrors:
Logger.debug("using mirrors %s", ", ".join(mirrors))
package = options.package
release = getattr(options, 'release', None)
version = getattr(options, 'version', None)
pocket = None
dscfile = None
if package.endswith('.dsc') and not release and not version:
dscfile = package
package = None
if release:
This will handle catching certain exceptions or kbd interrupts,
and printing out (via Logger) the error message, instead of
allowing the exception to flow up to the script. This does
not catch unexpected exceptions, such as internal errors.
On (expected) error, this will call sys.exit(error);
unexpected errors will flow up to the caller.
On success, this simply returns.
"""
try:
(release, pocket) = parse_release(release, distro)
cls(*args, **kwargs).pull()
return
except KeyboardInterrupt:
Logger.normal('User abort.')
except (PackageNotFoundException, SeriesNotFoundException,
PocketDoesNotExistError, InvalidDistroValueError) as e:
Logger.error(str(e))
sys.exit(errno.ENOENT)
def __init__(self, *args, **kwargs):
self._default_pull = kwargs.get('pull')
self._default_distro = kwargs.get('distro')
self._default_arch = kwargs.get('arch', host_architecture())
self._parser = None
@property
def argparser(self):
if self._parser:
return self._parser
help_default_pull = "What to pull: " + ", ".join(VALID_PULLS)
if self._default_pull:
help_default_pull += (" (default: %s)" % self._default_pull)
help_default_distro = "Pull from: " + ", ".join(VALID_DISTROS)
if self._default_distro:
help_default_distro += (" (default: %s)" % self._default_distro)
help_default_arch = ("Get binary packages for arch")
help_default_arch += ("(default: %s)" % self._default_arch)
parser = ArgumentParser()
parser.add_argument('-v', '--verbose', action='store_true',
help="Print verbose/debug messages")
parser.add_argument('-d', '--download-only', action='store_true',
help="Do not extract the source package")
parser.add_argument('-m', '--mirror', action='append',
help='Preferred mirror(s)')
parser.add_argument('--no-conf', action='store_true',
help="Don't read config files or environment variables")
parser.add_argument('--no-verify-signature', action='store_true',
help="Don't fail if dsc signature can't be verified")
parser.add_argument('-a', '--arch', default=self._default_arch,
help=help_default_arch)
parser.add_argument('-p', '--pull', default=self._default_pull,
help=help_default_pull)
parser.add_argument('-D', '--distro', default=self._default_distro,
help=help_default_distro)
parser.add_argument('package', help="Package name to pull")
parser.add_argument('release', nargs='?', help="Release to pull from")
parser.add_argument('version', nargs='?', help="Package version to pull")
self._parser = parser
return self._parser
def parse_pull(self, pull):
if not pull:
raise InvalidPullValueError("Must specify --pull")
# allow 'dbgsym' as alias for 'ddebs'
if pull == 'dbgsym':
Logger.debug("Pulling '%s' for '%s'", PULL_DDEBS, pull)
pull = PULL_DDEBS
# assume anything starting with 'bin' means 'debs'
if str(pull).startswith('bin'):
Logger.debug("Pulling '%s' for '%s'", PULL_DEBS, pull)
pull = PULL_DEBS
# verify pull action is valid
if pull not in VALID_PULLS:
raise InvalidPullValueError("Invalid pull action '%s'" % pull)
return pull
def parse_distro(self, distro):
if not distro:
raise InvalidDistroValueError("Must specify --distro")
distro = distro.lower()
# allow 'lp' for 'ubuntu'
if distro == 'lp':
Logger.debug("Using distro '%s' for '%s'", DISTRO_UBUNTU, distro)
distro = DISTRO_UBUNTU
# assume anything with 'cloud' is UCA
if re.match(r'.*cloud.*', distro):
Logger.debug("Using distro '%s' for '%s'", DISTRO_UCA, distro)
distro = DISTRO_UCA
# verify distro is valid
if distro not in VALID_DISTROS:
raise InvalidDistroValueError("Invalid distro '%s'" % distro)
return distro
def parse_release(self, distro, release):
if distro == DISTRO_UCA:
# UCA is special; it is specified UBUNTURELEASE-UCARELEASE or just
# UCARELEASE. The user could also specify UCARELEASE-POCKET. But UCA
# archives always correspond to only one UBUNTURELEASE, and UCA archives
# have only the Release pocket, so only UCARELEASE matters to us.
for r in release.split('-'):
if r in UbuntuCloudArchiveSourcePackage.getReleaseNames():
Logger.debug("Using UCA release '%s'", r)
return (r, None)
raise SeriesNotFoundException('UCA release {} not found.'.format(release))
# Check if release[-pocket] is specified
(release, pocket) = split_release_pocket(release, default=None)
Logger.debug("Parsed release '%s' pocket '%s'", release, pocket)
if distro == DISTRO_DEBIAN:
# This converts from the aliases like 'unstable'
debian_info = DebianDistroInfo()
codename = debian_info.codename(release)
if codename:
Logger.normal("Using release '%s' for '%s'", codename, release)
release = codename
d = Distribution(distro)
# let SeriesNotFoundException flow up
d.getSeries(release)
Logger.debug("Using distro '%s' release '%s' pocket '%s'",
distro, release, pocket)
return (release, pocket)
def parse_release_and_version(self, distro, release, version, try_swap=True):
# Verify specified release is valid, and params in correct order
pocket = None
try:
(release, pocket) = self.parse_release(distro, release)
except (SeriesNotFoundException, PocketDoesNotExistError):
Logger.debug("Param '%s' not valid series, must be version", release)
release, version = version, release
if release:
try:
(release, pocket) = parse_release(release, distro)
except (SeriesNotFoundException, PocketDoesNotExistError):
Logger.error("Can't find series for '%s' or '%s'",
release, version)
raise
if try_swap:
Logger.debug("Param '%s' not valid series, must be version", release)
release, version = version, release
if release:
return self.parse_release_and_version(distro, release, version, False)
else:
Logger.error("Can't find series for '%s' or '%s'", release, version)
raise
return (release, version, pocket)
try:
pkgcls = DISTRO_PKG_CLASS[distro]
srcpkg = pkgcls(package=package, version=version,
series=release, pocket=pocket,
mirrors=mirrors, dscfile=dscfile,
verify_signature=(not options.no_verify_signature))
def parse_options(self, options):
# if any of these fail, there is a problem with the parser
# they should all be provided, though the optional ones may be None
# type bool
assert 'download_only' in options
assert 'no_conf' in options
assert 'no_verify_signature' in options
# type string
assert 'pull' in options
assert 'distro' in options
assert 'arch' in options
assert 'package' in options
# type string, optional
assert 'release' in options
assert 'version' in options
# type list of strings, optional
assert 'mirror' in options
pull = self.parse_pull(options['pull'])
distro = self.parse_distro(options['distro'])
params = {}
params['package'] = options['package']
if options['release']:
(r, v, p) = self.parse_release_and_version(distro, options['release'],
options['version'])
params['series'] = r
params['version'] = v
params['pocket'] = p
if (params['package'].endswith('.dsc') and not params['series'] and not params['version']):
params['dscfile'] = params['package']
params.pop('package')
mirrors = []
if options['mirror']:
mirrors.append(options['mirror'])
if pull == PULL_DDEBS:
config = UDTConfig(options['no_conf'])
ddebs_mirror = config.get_value(distro.upper() + '_DDEBS_MIRROR')
if ddebs_mirror:
mirrors.append(ddebs_mirror)
if mirrors:
Logger.debug("using mirrors %s", ", ".join(mirrors))
params['mirrors'] = mirrors
params['verify_signature'] = not options['no_verify_signature']
return (pull, distro, params)
def pull(self, args=None):
"""Pull (download) specified package file(s)"""
options = vars(self.argparser.parse_args(args))
assert 'verbose' in options
if options['verbose'] is not None:
Logger.set_verbosity(options['verbose'])
Logger.debug("pullpkg options: %s", options)
# Login anonymously to LP
Launchpad.login_anonymously()
(pull, distro, params) = self.parse_options(options)
# call implementation, and allow exceptions to flow up to caller
srcpkg = DISTRO_PKG_CLASS[distro](**params)
spph = srcpkg.lp_spph
except PackageNotFoundException as e:
Logger.error(str(e))
raise
Logger.normal('Found %s', spph.display_name)
Logger.normal('Found %s', spph.display_name)
if pull == PULL_LIST:
Logger.normal("Source files:")
for f in srcpkg.dsc['Files']:
Logger.normal(" %s", f['name'])
Logger.normal("Binary files:")
for f in spph.getBinaries(options.arch):
Logger.normal(" %s", f.getFileName())
return
# allow DownloadError to flow up to caller
if pull == PULL_SOURCE:
srcpkg.pull()
if options.download_only:
Logger.debug("--download-only specified, not extracting")
if pull == PULL_LIST:
Logger.normal("Source files:")
for f in srcpkg.dsc['Files']:
Logger.normal(" %s", f['name'])
Logger.normal("Binary files:")
for f in spph.getBinaries(options['arch']):
Logger.normal(" %s", f.getFileName())
elif pull == PULL_SOURCE:
# allow DownloadError to flow up to caller
srcpkg.pull()
if options['download_only']:
Logger.debug("--download-only specified, not extracting")
else:
srcpkg.unpack()
else:
srcpkg.unpack()
else:
name = '.*'
if package != spph.getPackageName():
Logger.normal("Pulling only binary package '%s'", package)
Logger.normal("Use package name '%s' to pull all binary packages",
spph.getPackageName())
name = package
if pull == PULL_DEBS:
name = r'{}(?<!-di)(?<!-dbgsym)$'.format(name)
elif pull == PULL_DDEBS:
name += '-dbgsym$'
elif pull == PULL_UDEBS:
name += '-di$'
else:
raise InvalidPullValueError("Invalid pull value %s" % pull)
total = srcpkg.pull_binaries(name=name, arch=options.arch)
if total < 1:
Logger.error("No %s found for %s %s", pull,
package, spph.getVersion())
name = '.*'
if params['package'] != spph.getPackageName():
Logger.normal("Pulling only binary package '%s'", params['package'])
Logger.normal("Use package name '%s' to pull all binary packages",
spph.getPackageName())
name = params['package']
if pull == PULL_DEBS:
name = r'{}(?<!-di)(?<!-dbgsym)$'.format(name)
elif pull == PULL_DDEBS:
name += '-dbgsym$'
elif pull == PULL_UDEBS:
name += '-di$'
else:
raise InvalidPullValueError("Invalid pull value %s" % pull)
# allow DownloadError to flow up to caller
total = srcpkg.pull_binaries(name=name, arch=options['arch'])
if total < 1:
Logger.error("No %s found for %s %s", pull,
params['package'], spph.getVersion())