1
# Copyright 2004-2005 Canonical Ltd. All rights reserved.
3
"""The processing of debian installer tarballs."""
5
# This code is mostly owned by Colin Watson and is partly refactored by
6
# Daniel Silverstone who should be the first point of contact for it.
10
__all__ = ['process_debian_installer', 'DebianInstallerError']
17
from sourcerer.deb.version import Version as DebianVersion
20
class DebianInstallerError(Exception):
21
"""Base class for all errors associated with putting a d-i tarball on
22
disk in the archive."""
25
class DebianInstallerAlreadyExists(DebianInstallerError):
26
"""A build for this type, architecture, and version already exists."""
27
def __init__(self, build_type, arch, version):
28
message = ('%s build %s for architecture %s already exists' %
29
(build_type, arch, version))
30
DebianInstallerError.__init__(self, message)
31
self.build_type = build_type
33
self.version = version
36
class DebianInstallerTarError(DebianInstallerError):
37
"""The tarfile module raised an exception."""
38
def __init__(self, tarfile_path, tar_error):
39
message = 'Problem reading tarfile %s: %s' % (tarfile_path, tar_error)
40
DebianInstallerError.__init__(self, message)
41
self.tarfile_path = tarfile_path
42
self.tar_error = tar_error
45
class DebianInstallerInvalidTarfile(DebianInstallerError):
46
"""The supplied tarfile did not contain the expected elements."""
47
def __init__(self, tarfile_path, expected_dir):
48
message = ('Tarfile %s did not contain expected directory %s' %
49
(tarfile_path, expected_dir))
50
DebianInstallerError.__init__(self, message)
51
self.tarfile_path = tarfile_path
52
self.expected_dir = expected_dir
55
def extract_filename_parts(tarfile_path):
56
"""Extract the basename, version and arch of the supplied d-i tarfile."""
57
tarfile_base = os.path.basename(tarfile_path)
58
components = tarfile_base.split('_')
59
version = components[1]
60
arch = components[2].split('.')[0]
61
return tarfile_base, version, arch
64
def process_debian_installer(archive_root, tarfile_path, distrorelease,
65
make_version=DebianVersion):
66
"""Process a raw-installer tarfile, unpacking it into the given archive
67
for the given distrorelease.
69
make_version is a callable which converts version numbers into python
70
objects which can be compared nicely. This defaults to sourcerer's version
71
type for deb packages. It does exactly what we want for now.
73
Raises DebianInstallerError (or some subclass thereof) if anything goes
77
tarfile_base, version, arch = extract_filename_parts(tarfile_path)
79
# Is this a full build or a daily build?
80
if '.0.' not in version:
81
build_type = 'installer'
83
build_type = 'daily-installer'
85
target = os.path.join(archive_root, 'dists', distrorelease, 'main',
86
'%s-%s' % (build_type, arch))
87
unpack_dir = 'installer-%s' % arch
89
# Make sure the target version doesn't already exist. If it does, raise
90
# DebianInstallerAlreadyExists.
91
if os.path.exists(os.path.join(target, version)):
92
raise DebianInstallerAlreadyExists(build_type, arch, version)
94
# Unpack the tarball directly into the archive. Skip anything outside
95
# unpack_dir/version, and skip the unpack_dir/current symlink (which
96
# we'll fix up ourselves in a moment). Make sure everything we extract
97
# is group-writable. If we didn't extract anything, raise
98
# DebianInstallerInvalidTarfile.
99
expected_dir = os.path.join(unpack_dir, version)
104
tar = tarfile.open(tarfile_path)
106
if (tarinfo.name.startswith('%s/' % expected_dir) and
107
tarinfo.name != os.path.join(unpack_dir, 'current')):
108
tar.extract(tarinfo, target)
109
newpath = os.path.join(target, tarinfo.name)
110
mode = stat.S_IMODE(os.stat(newpath).st_mode)
111
os.chmod(newpath, mode | stat.S_IWGRP)
113
except tarfile.TarError, e:
114
raise DebianInstallerTarError(tarfile_path, e)
119
raise DebianInstallerInvalidTarfile(tarfile_path, expected_dir)
121
# We now have a valid unpacked installer directory, but it's one level
122
# deeper than it should be. Move it up and remove the debris.
123
shutil.move(os.path.join(target, expected_dir),
124
os.path.join(target, version))
125
shutil.rmtree(os.path.join(target, unpack_dir))
127
# Get an appropriately-sorted list of the installer directories now
128
# present in the target.
129
versions = [inst for inst in os.listdir(target) if inst != 'current']
130
if make_version is not None:
131
versions.sort(key=make_version, reverse=True)
135
# Make sure the 'current' symlink points to the most recent version
136
# The most recent version is in versions[0]
137
current = os.path.join(target, 'current')
138
os.symlink(versions[0], '%s.new' % current)
139
os.rename('%s.new' % current, current)
141
# There may be some other unpacked installer directories in the target
142
# already. We only keep the three with the highest version (plus the one
143
# we just extracted, if for some reason it's lower).
144
for oldversion in versions[3:]:
145
if oldversion != version:
146
shutil.rmtree(os.path.join(target, oldversion))