~lutostag/ubuntu/trusty/maas/1.5.2+packagefix

« back to all changes in this revision

Viewing changes to src/provisioningserver/boot/utils.py

  • Committer: Package Import Robot
  • Author(s): Andres Rodriguez
  • Date: 2014-03-28 10:43:53 UTC
  • mto: This revision was merged to the branch mainline in revision 57.
  • Revision ID: package-import@ubuntu.com-20140328104353-ekpolg0pm5xnvq2s
Tags: upstream-1.5+bzr2204
ImportĀ upstreamĀ versionĀ 1.5+bzr2204

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2014 Canonical Ltd.  This software is licensed under the
 
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
"""Utilities that BootMethod's can use."""
 
5
 
 
6
from __future__ import (
 
7
    absolute_import,
 
8
    print_function,
 
9
    unicode_literals,
 
10
    )
 
11
 
 
12
str = None
 
13
 
 
14
__metaclass__ = type
 
15
__all__ = [
 
16
    'get_distro_release',
 
17
    'get_package',
 
18
    'get_updates_package',
 
19
    ]
 
20
 
 
21
import gzip
 
22
import hashlib
 
23
import os
 
24
from platform import linux_distribution
 
25
import re
 
26
import StringIO
 
27
import subprocess
 
28
import urllib2
 
29
 
 
30
from provisioningserver.utils import (
 
31
    call_capture_and_check,
 
32
    tempdir,
 
33
    )
 
34
 
 
35
 
 
36
def urljoin(*args):
 
37
    return '/'.join(s.strip('/') for s in args)
 
38
 
 
39
 
 
40
def get_distro_release():
 
41
    """Returns the release name for the current distribution."""
 
42
    distname, version, codename = linux_distribution()
 
43
    return codename
 
44
 
 
45
 
 
46
def get_file(url):
 
47
    """Downloads the file from the URL.
 
48
 
 
49
    :param url: URL to download file
 
50
    :returns: File data, or None
 
51
    """
 
52
    response = urllib2.urlopen(url)
 
53
    return response.read()
 
54
 
 
55
 
 
56
def get_md5sum(data):
 
57
    """Returns the md5sum for the provided data."""
 
58
    md5 = hashlib.md5()
 
59
    md5.update(data)
 
60
    return md5.hexdigest()
 
61
 
 
62
 
 
63
def gpg_verify_data(signature, data_file):
 
64
    """Verify's data using the signature."""
 
65
    with tempdir() as tmp:
 
66
        sig_out = os.path.join(tmp, 'verify.gpg')
 
67
        with open(sig_out, 'wb') as stream:
 
68
            stream.write(signature)
 
69
 
 
70
        data_out = os.path.join(tmp, 'verify')
 
71
        with open(data_out, 'wb') as stream:
 
72
            stream.write(data_file)
 
73
 
 
74
        args = [
 
75
            "gpgv",
 
76
            "--keyring",
 
77
            "/etc/apt/trusted.gpg",
 
78
            sig_out,
 
79
            data_out
 
80
            ]
 
81
        call_capture_and_check(args, stderr=subprocess.STDOUT)
 
82
 
 
83
 
 
84
def decompress_packages(packages):
 
85
    compressed = StringIO.StringIO(packages)
 
86
    decompressed = gzip.GzipFile(fileobj=compressed)
 
87
    return unicode(decompressed.read(), errors='ignore')
 
88
 
 
89
 
 
90
def get_packages(archive, component, architecture, release=None):
 
91
    """Gets the packages list from the archive."""
 
92
    release = get_distro_release() if release is None else release
 
93
    url = urljoin(archive, 'dists', release)
 
94
    release_url = urljoin(url, 'Release')
 
95
    release_file = get_file(release_url)
 
96
    release_file_gpg = get_file('%s.gpg' % release_url)
 
97
    gpg_verify_data(release_file_gpg, release_file)
 
98
 
 
99
    # Download the packages and verify that md5sum matches
 
100
    path = '%s/binary-%s/Packages.gz' % (component, architecture)
 
101
    packages_url = urljoin(url, path)
 
102
    packages = get_file(packages_url)
 
103
    md5sum = re.search(
 
104
        r"^\s*?([a-zA-Z0-9]{32})\s+?[0-9]+\s+%s$" % path,
 
105
        release_file,
 
106
        re.MULTILINE).group(1)
 
107
    if get_md5sum(packages) != md5sum:
 
108
        raise ValueError("%s failed checksum." % packages_url)
 
109
 
 
110
    return decompress_packages(packages)
 
111
 
 
112
 
 
113
def get_package_info(package, archive, component, architecture, release=None):
 
114
    """Gets the package information."""
 
115
    release = get_distro_release() if release is None else release
 
116
    packages = get_packages(archive, component, architecture, release=release)
 
117
 
 
118
    info = re.search(
 
119
        r"^(Package: %s.*?)\n\n" % package,
 
120
        packages,
 
121
        re.MULTILINE | re.DOTALL)
 
122
    if info is None:
 
123
        return None
 
124
    info = info.group(1)
 
125
 
 
126
    data = {}
 
127
    for line in info.splitlines():
 
128
        key, value = line.split(':', 1)
 
129
        data[key] = value.strip()
 
130
    return data
 
131
 
 
132
 
 
133
def get_package(package, archive, component, architecture, release=None):
 
134
    """Downloads the package from the archive."""
 
135
    release = get_distro_release() if release is None else release
 
136
    package = get_package_info(
 
137
        package, archive, component, architecture, release=release)
 
138
    if package is None:
 
139
        return None, None
 
140
 
 
141
    # Download the package and check checksum
 
142
    path = package['Filename']
 
143
    filename = os.path.basename(path)
 
144
    url = urljoin(archive, path)
 
145
    deb = get_file(url)
 
146
    md5 = get_md5sum(deb)
 
147
    if md5 != package['MD5sum']:
 
148
        raise ValueError("%s failed checksum." % filename)
 
149
    return deb, filename
 
150
 
 
151
 
 
152
def get_updates_package(package, archive, component, architecture,
 
153
                        release=None):
 
154
    """Downloads the package from the {release}-updates if it exists, if not
 
155
    fails back to {release} archive.
 
156
    """
 
157
    release = get_distro_release() if release is None else release
 
158
    releases = ['%s-updates' % release, release]
 
159
    for release in releases:
 
160
        deb, filename = get_package(
 
161
            package, archive, component, architecture, release=release)
 
162
        if deb is not None:
 
163
            return deb, filename
 
164
    return None, None