1
# Copyright 2006 Canonical Ltd. All rights reserved.
3
"""The processing of dist-upgrader tarballs."""
7
__all__ = ['process_dist_upgrader', 'DistUpgraderError']
14
from sourcerer.deb.version import Version as DebianVersion
17
class DistUpgraderError(Exception):
18
"""Base class for all errors associated with putting a dist-upgrader
19
tarball on disk in the archive."""
22
class DistUpgraderAlreadyExists(DistUpgraderError):
23
"""A build for this type, version already exists."""
24
def __init__(self, arch, version):
25
message = ('dist-upgrader build %s for architecture %s already exists'%
27
DistUpgradeError.__init__(self, message)
29
self.version = version
31
class DistUpgraderTarError(DistUpgraderError):
32
"""The tarfile module raised an exception."""
33
def __init__(self, tarfile_path, tar_error):
34
message = 'Problem reading tarfile %s: %s' % (tarfile_path, tar_error)
35
DistUpgraderError.__init__(self, message)
36
self.tarfile_path = tarfile_path
37
self.tar_error = tar_error
40
class DistUpgraderInvalidTarfile(DistUpgraderError):
41
"""The supplied tarfile did not contain the expected elements."""
42
def __init__(self, tarfile_path, expected_dir):
43
message = ('Tarfile %s did not contain expected file %s' %
44
(tarfile_path, expected_dir))
45
DistUpgraderError.__init__(self, message)
46
self.tarfile_path = tarfile_path
47
self.expected_dir = expected_dir
50
def extract_filename_parts(tarfile_path):
51
"""Extract the basename, version and arch of the supplied d-i tarfile."""
52
tarfile_base = os.path.basename(tarfile_path)
53
name, version, arch = tarfile_base.split('_')
54
arch = arch.split('.')[0]
55
return tarfile_base, version, arch
58
def process_dist_upgrader(archive_root, tarfile_path, distrorelease,
59
make_version=DebianVersion):
60
"""Process a raw-dist-upgrader tarfile, unpacking it into the given
61
archive for the given distrorelease.
63
make_version is a callable which converts version numbers into python
64
objects which can be compared nicely. This defaults to sourcerer's version
65
type for deb packages. It does exactly what we want for now.
67
Raises DistUpgraderError (or some subclass thereof) if anything goes
71
tarfile_base, version, arch = extract_filename_parts(tarfile_path)
73
target = os.path.join(archive_root, 'dists', distrorelease, 'main',
74
'dist-upgrader-%s' % arch)
75
unpack_dir = 'dist-upgrader-%s' % arch
77
# Make sure the target version doesn't already exist. If it does, raise
78
# DistUpgraderAlreadyExists.
79
if os.path.exists(os.path.join(target, version)):
80
raise DistUpgraderAlreadyExists(arch, version)
82
# Unpack the tarball directly into the archive. Skip anything outside
83
# unpack_dir/version, and skip the unpack_dir/current symlink (which
84
# we'll fix up ourselves in a moment). Make sure everything we extract
85
# is group-writable. If we didn't extract anything, raise
86
# DistUpgraderInvalidTarfile.
91
tar = tarfile.open(tarfile_path)
94
if tarinfo.name != os.path.join('current'):
95
tar.extract(tarinfo, target)
96
newpath = os.path.join(target, tarinfo.name)
97
mode = stat.S_IMODE(os.stat(newpath).st_mode)
98
os.chmod(newpath, mode | stat.S_IWGRP)
102
except tarfile.TarError, e:
103
raise DistUpgraderTarError(tarfile_path, e)
106
raise DistUpgraderInvalidTarfile(tarfile_path, expected_dir)
108
# Get an appropriately-sorted list of the dist-upgrader directories now
109
# present in the target.
110
versions = [inst for inst in os.listdir(target) if inst != 'current']
111
if make_version is not None:
112
versions.sort(key=make_version, reverse=True)
116
# Make sure the 'current' symlink points to the most recent version
117
# The most recent version is in versions[0]
118
current = os.path.join(target, 'current')
119
os.symlink(versions[0], '%s.new' % current)
120
os.rename('%s.new' % current, current)
122
# There may be some other unpacked dist-upgrader directories in the target
123
# already. We only keep the three with the highest version (plus the one
124
# we just extracted, if for some reason it's lower).
125
for oldversion in versions[3:]:
126
if oldversion != version:
127
shutil.rmtree(os.path.join(target, oldversion))