1
# Copyright 2014 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
"""Utilities that BootMethod's can use."""
6
from __future__ import (
18
'get_updates_package',
24
from platform import linux_distribution
30
from provisioningserver.utils import (
31
call_capture_and_check,
37
return '/'.join(s.strip('/') for s in args)
40
def get_distro_release():
41
"""Returns the release name for the current distribution."""
42
distname, version, codename = linux_distribution()
47
"""Downloads the file from the URL.
49
:param url: URL to download file
50
:returns: File data, or None
52
response = urllib2.urlopen(url)
53
return response.read()
57
"""Returns the md5sum for the provided data."""
60
return md5.hexdigest()
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)
70
data_out = os.path.join(tmp, 'verify')
71
with open(data_out, 'wb') as stream:
72
stream.write(data_file)
77
"/etc/apt/trusted.gpg",
81
call_capture_and_check(args, stderr=subprocess.STDOUT)
84
def decompress_packages(packages):
85
compressed = StringIO.StringIO(packages)
86
decompressed = gzip.GzipFile(fileobj=compressed)
87
return unicode(decompressed.read(), errors='ignore')
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)
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)
104
r"^\s*?([a-zA-Z0-9]{32})\s+?[0-9]+\s+%s$" % path,
106
re.MULTILINE).group(1)
107
if get_md5sum(packages) != md5sum:
108
raise ValueError("%s failed checksum." % packages_url)
110
return decompress_packages(packages)
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)
119
r"^(Package: %s.*?)\n\n" % package,
121
re.MULTILINE | re.DOTALL)
127
for line in info.splitlines():
128
key, value = line.split(':', 1)
129
data[key] = value.strip()
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)
141
# Download the package and check checksum
142
path = package['Filename']
143
filename = os.path.basename(path)
144
url = urljoin(archive, path)
146
md5 = get_md5sum(deb)
147
if md5 != package['MD5sum']:
148
raise ValueError("%s failed checksum." % filename)
152
def get_updates_package(package, archive, component, architecture,
154
"""Downloads the package from the {release}-updates if it exists, if not
155
fails back to {release} archive.
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)