1
# Copyright 2014-2015 Canonical Limited.
3
# This file is part of charm-helpers.
5
# charm-helpers is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU Lesser General Public License version 3 as
7
# published by the Free Software Foundation.
9
# charm-helpers is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU Lesser General Public License for more details.
14
# You should have received a copy of the GNU Lesser General Public License
15
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
2
18
from tempfile import NamedTemporaryFile
33
from urllib.parse import urlparse, urlunparse
35
from urlparse import urlparse, urlunparse
20
38
CLOUD_ARCHIVE = """# Ubuntu Cloud Archive
21
39
deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
62
80
'trusty-juno/updates': 'trusty-updates/juno',
63
81
'trusty-updates/juno': 'trusty-updates/juno',
64
82
'juno/proposed': 'trusty-proposed/juno',
65
'juno/proposed': 'trusty-proposed/juno',
66
83
'trusty-juno/proposed': 'trusty-proposed/juno',
67
84
'trusty-proposed/juno': 'trusty-proposed/juno',
86
'kilo': 'trusty-updates/kilo',
87
'trusty-kilo': 'trusty-updates/kilo',
88
'trusty-kilo/updates': 'trusty-updates/kilo',
89
'trusty-updates/kilo': 'trusty-updates/kilo',
90
'kilo/proposed': 'trusty-proposed/kilo',
91
'trusty-kilo/proposed': 'trusty-proposed/kilo',
92
'trusty-proposed/kilo': 'trusty-proposed/kilo',
94
'liberty': 'trusty-updates/liberty',
95
'trusty-liberty': 'trusty-updates/liberty',
96
'trusty-liberty/updates': 'trusty-updates/liberty',
97
'trusty-updates/liberty': 'trusty-updates/liberty',
98
'liberty/proposed': 'trusty-proposed/liberty',
99
'trusty-liberty/proposed': 'trusty-proposed/liberty',
100
'trusty-proposed/liberty': 'trusty-proposed/liberty',
102
'mitaka': 'trusty-updates/mitaka',
103
'trusty-mitaka': 'trusty-updates/mitaka',
104
'trusty-mitaka/updates': 'trusty-updates/mitaka',
105
'trusty-updates/mitaka': 'trusty-updates/mitaka',
106
'mitaka/proposed': 'trusty-proposed/mitaka',
107
'trusty-mitaka/proposed': 'trusty-proposed/mitaka',
108
'trusty-proposed/mitaka': 'trusty-proposed/mitaka',
70
111
# The order of this list is very important. Handlers should be listed in from
72
113
FETCH_HANDLERS = (
73
114
'charmhelpers.fetch.archiveurl.ArchiveUrlFetchHandler',
74
115
'charmhelpers.fetch.bzrurl.BzrUrlFetchHandler',
116
'charmhelpers.fetch.giturl.GitUrlFetchHandler',
77
119
APT_NO_LOCK = 100 # The return code for "couldn't acquire lock" in APT.
181
223
def apt_purge(packages, fatal=False):
182
224
"""Purge one or more packages"""
183
225
cmd = ['apt-get', '--assume-yes', 'purge']
184
if isinstance(packages, basestring):
226
if isinstance(packages, six.string_types):
185
227
cmd.append(packages)
187
229
cmd.extend(packages)
189
231
_run_apt_command(cmd, fatal)
234
def apt_mark(packages, mark, fatal=False):
235
"""Flag one or more packages using apt-mark"""
236
log("Marking {} as {}".format(packages, mark))
237
cmd = ['apt-mark', mark]
238
if isinstance(packages, six.string_types):
244
subprocess.check_call(cmd, universal_newlines=True)
246
subprocess.call(cmd, universal_newlines=True)
192
249
def apt_hold(packages, fatal=False):
193
"""Hold one or more packages"""
194
cmd = ['apt-mark', 'hold']
195
if isinstance(packages, basestring):
199
log("Holding {}".format(packages))
202
subprocess.check_call(cmd)
250
return apt_mark(packages, 'hold', fatal=fatal)
253
def apt_unhold(packages, fatal=False):
254
return apt_mark(packages, 'unhold', fatal=fatal)
207
257
def add_source(source, key=None):
218
268
pocket for the release.
219
269
'cloud:' may be used to activate official cloud archive pockets,
220
270
such as 'cloud:icehouse'
271
'distro' may be used as a noop
222
273
@param key: A key to be added to the system's APT keyring and used
223
274
to verify the signatures on packages. Ideally, this should be an
251
302
release = lsb_release()['DISTRIB_CODENAME']
252
303
with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:
253
304
apt.write(PROPOSED_POCKET.format(release))
305
elif source == 'distro':
255
raise SourceConfigError("Unknown source: {!r}".format(source))
308
log("Unknown source: {!r}".format(source))
258
311
if '-----BEGIN PGP PUBLIC KEY BLOCK-----' in key:
259
with NamedTemporaryFile() as key_file:
312
with NamedTemporaryFile('w+') as key_file:
260
313
key_file.write(key)
293
346
sources = safe_load((config(sources_var) or '').strip()) or []
294
347
keys = safe_load((config(keys_var) or '').strip()) or None
296
if isinstance(sources, basestring):
349
if isinstance(sources, six.string_types):
297
350
sources = [sources]
300
353
for source in sources:
301
354
add_source(source, None)
303
if isinstance(keys, basestring):
356
if isinstance(keys, six.string_types):
306
359
if len(sources) != len(keys):
341
394
for handler in handlers:
343
396
installed_to = handler.install(source, *args, **kwargs)
344
except UnhandledSource:
397
except UnhandledSource as e:
398
log('Install source attempt unsuccessful: {}'.format(e),
346
400
if not installed_to:
347
401
raise UnhandledSource("No handler found for source {}".format(source))
348
402
return installed_to
365
419
importlib.import_module(package),
367
421
plugin_list.append(handler_class())
368
except (ImportError, AttributeError):
422
except NotImplementedError:
369
423
# Skip missing plugins so that they can be ommitted from
370
424
# installation if desired
371
425
log("FetchHandler {} not found, skipping plugin".format(
397
451
while result is None or result == APT_NO_LOCK:
399
453
result = subprocess.check_call(cmd, env=env)
400
except subprocess.CalledProcessError, e:
454
except subprocess.CalledProcessError as e:
401
455
retry_count = retry_count + 1
402
456
if retry_count > APT_NO_LOCK_RETRY_COUNT: