~launchpad-pqm/launchpad/devel

« back to all changes in this revision

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

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-03-17 20:41:13 UTC
  • mfrom: (3277.1.4 launchpad-foobar2)
  • Revision ID: pqm@pqm.ubuntu.com-20060317204113-9841a4470db3611b
[r=jamesh] Mainline soyuz

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2006 Canonical Ltd.  All rights reserved.
 
2
 
 
3
"""The processing of dist-upgrader tarballs."""
 
4
 
 
5
__metaclass__ = type
 
6
 
 
7
__all__ = ['process_dist_upgrader', 'DistUpgraderError']
 
8
 
 
9
import os
 
10
import tarfile
 
11
import stat
 
12
import shutil
 
13
 
 
14
from sourcerer.deb.version import Version as DebianVersion
 
15
 
 
16
 
 
17
class DistUpgraderError(Exception):
 
18
    """Base class for all errors associated with putting a dist-upgrader
 
19
       tarball on disk in the archive."""
 
20
 
 
21
 
 
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'%
 
26
                   (arch, version))
 
27
        DistUpgradeError.__init__(self, message)
 
28
        self.arch = arch
 
29
        self.version = version
 
30
 
 
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
 
38
 
 
39
 
 
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
 
48
 
 
49
 
 
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
 
56
 
 
57
 
 
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.
 
62
 
 
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.
 
66
 
 
67
    Raises DistUpgraderError (or some subclass thereof) if anything goes
 
68
    wrong.
 
69
    """
 
70
 
 
71
    tarfile_base, version, arch = extract_filename_parts(tarfile_path)
 
72
 
 
73
    target = os.path.join(archive_root, 'dists', distrorelease, 'main',
 
74
                          'dist-upgrader-%s' % arch)
 
75
    unpack_dir = 'dist-upgrader-%s' % arch
 
76
 
 
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)
 
81
 
 
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.
 
87
    tar = None
 
88
    extracted = False
 
89
 
 
90
    try:
 
91
        tar = tarfile.open(tarfile_path)
 
92
        try:
 
93
            for tarinfo in tar:
 
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)
 
99
                    extracted = True
 
100
        finally:
 
101
            tar.close()
 
102
    except tarfile.TarError, e:
 
103
        raise DistUpgraderTarError(tarfile_path, e)
 
104
 
 
105
    if not extracted:
 
106
        raise DistUpgraderInvalidTarfile(tarfile_path, expected_dir)
 
107
 
 
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)
 
113
    else:
 
114
        versions.reverse()
 
115
 
 
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)
 
121
 
 
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))