~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/canonical/archivepublisher/debian_installer.py

  • Committer: David Allouche
  • Date: 2005-11-03 02:06:06 UTC
  • mfrom: (63.1.161)
  • mto: (1102.1.157)
  • mto: This revision was merged to the branch mainline in revision 2836.
  • Revision ID: david.allouche@canonical.com-20051103020606-db5d5e3f2d8ad88e
merge robertc

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2004-2005 Canonical Ltd.  All rights reserved.
 
2
 
 
3
"""The processing of debian installer tarballs."""
 
4
 
 
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.
 
7
 
 
8
__metaclass__ = type
 
9
 
 
10
__all__ = ['process_debian_installer', 'DebianInstallerError']
 
11
 
 
12
import os
 
13
import tarfile
 
14
import stat
 
15
import shutil
 
16
 
 
17
from sourcerer.deb.version import Version as DebianVersion
 
18
 
 
19
 
 
20
class DebianInstallerError(Exception):
 
21
    """Base class for all errors associated with putting a d-i tarball on
 
22
    disk in the archive."""
 
23
 
 
24
 
 
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
 
32
        self.arch = arch
 
33
        self.version = version
 
34
 
 
35
 
 
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
 
43
 
 
44
 
 
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
 
53
 
 
54
 
 
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
 
62
 
 
63
 
 
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.
 
68
 
 
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.
 
72
 
 
73
    Raises DebianInstallerError (or some subclass thereof) if anything goes
 
74
    wrong.
 
75
    """
 
76
 
 
77
    tarfile_base, version, arch = extract_filename_parts(tarfile_path)
 
78
 
 
79
    # Is this a full build or a daily build?
 
80
    if '.0.' not in version:
 
81
        build_type = 'installer'
 
82
    else:
 
83
        build_type = 'daily-installer'
 
84
 
 
85
    target = os.path.join(archive_root, 'dists', distrorelease, 'main',
 
86
                          '%s-%s' % (build_type, arch))
 
87
    unpack_dir = 'installer-%s' % arch
 
88
 
 
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)
 
93
 
 
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)
 
100
    tar = None
 
101
    extracted = False
 
102
    try:
 
103
        try:
 
104
            tar = tarfile.open(tarfile_path)
 
105
            for tarinfo in tar:
 
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)
 
112
                    extracted = True
 
113
        except tarfile.TarError, e:
 
114
            raise DebianInstallerTarError(tarfile_path, e)
 
115
    finally:
 
116
        if tar is not None:
 
117
            tar.close()
 
118
    if not extracted:
 
119
        raise DebianInstallerInvalidTarfile(tarfile_path, expected_dir)
 
120
 
 
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))
 
126
 
 
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)
 
132
    else:
 
133
        versions.reverse()
 
134
 
 
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)
 
140
 
 
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))