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/>.
18
from tempfile import NamedTemporaryFile
3
20
from yaml import safe_load
4
21
from charmhelpers.core.host import (
12
25
from charmhelpers.core.hookenv import (
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
56
74
'icehouse/proposed': 'precise-proposed/icehouse',
57
75
'precise-icehouse/proposed': 'precise-proposed/icehouse',
58
76
'precise-proposed/icehouse': 'precise-proposed/icehouse',
78
'juno': 'trusty-updates/juno',
79
'trusty-juno': 'trusty-updates/juno',
80
'trusty-juno/updates': 'trusty-updates/juno',
81
'trusty-updates/juno': 'trusty-updates/juno',
82
'juno/proposed': 'trusty-proposed/juno',
83
'trusty-juno/proposed': 'trusty-proposed/juno',
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',
61
95
# The order of this list is very important. Handlers should be listed in from
194
233
def add_source(source, key=None):
234
"""Add a package source to this system.
236
@param source: a URL or sources.list entry, as supported by
237
add-apt-repository(1). Examples::
240
deb https://stub:key@private.example.com/ubuntu trusty main
243
'proposed:' may be used to enable the standard 'proposed'
244
pocket for the release.
245
'cloud:' may be used to activate official cloud archive pockets,
246
such as 'cloud:icehouse'
247
'distro' may be used as a noop
249
@param key: A key to be added to the system's APT keyring and used
250
to verify the signatures on packages. Ideally, this should be an
251
ASCII format GPG public key including the block headers. A GPG key
252
id may also be used, but be aware that only insecure protocols are
253
available to retrieve the actual public key from a public keyserver
254
placing your Juju environment at risk. ppa and cloud archive keys
255
are securely added automtically, so sould not be provided.
195
257
if source is None:
196
258
log('Source is not present. Skipping')
216
278
release = lsb_release()['DISTRIB_CODENAME']
217
279
with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:
218
280
apt.write(PROPOSED_POCKET.format(release))
281
elif source == 'distro':
284
log("Unknown source: {!r}".format(source))
220
subprocess.check_call(['apt-key', 'adv', '--keyserver',
221
'hkp://keyserver.ubuntu.com:80', '--recv',
287
if '-----BEGIN PGP PUBLIC KEY BLOCK-----' in key:
288
with NamedTemporaryFile('w+') as key_file:
292
subprocess.check_call(['apt-key', 'add', '-'], stdin=key_file)
294
# Note that hkp: is in no way a secure protocol. Using a
295
# GPG key id is pointless from a security POV unless you
296
# absolutely trust your network and DNS.
297
subprocess.check_call(['apt-key', 'adv', '--keyserver',
298
'hkp://keyserver.ubuntu.com:80', '--recv',
225
302
def configure_sources(update=False,
226
303
sources_var='install_sources',
227
304
keys_var='install_keys'):
229
Configure multiple sources from charm configuration
306
Configure multiple sources from charm configuration.
308
The lists are encoded as yaml fragments in the configuration.
309
The frament needs to be included as a string. Sources and their
310
corresponding keys are of the types supported by add_source().
234
315
- "http://example.com/repo precise main"
239
320
Note that 'null' (a.k.a. None) should not be quoted.
241
sources = safe_load(config(sources_var))
242
keys = config(keys_var)
244
keys = safe_load(keys)
245
if isinstance(sources, basestring) and (
246
keys is None or isinstance(keys, basestring)):
247
add_source(sources, keys)
322
sources = safe_load((config(sources_var) or '').strip()) or []
323
keys = safe_load((config(keys_var) or '').strip()) or None
325
if isinstance(sources, six.string_types):
329
for source in sources:
330
add_source(source, None)
249
if not len(sources) == len(keys):
250
msg = 'Install sources and keys lists are different lengths'
251
raise SourceConfigError(msg)
252
for src_num in range(len(sources)):
253
add_source(sources[src_num], keys[src_num])
332
if isinstance(keys, six.string_types):
335
if len(sources) != len(keys):
336
raise SourceConfigError(
337
'Install sources and keys lists are different lengths')
338
for source, key in zip(sources, keys):
339
add_source(source, key)
255
341
apt_update(fatal=True)
258
def install_remote(source):
344
def install_remote(source, *args, **kwargs):
260
346
Install a file tree from a remote source
262
348
The specified source should be a url of the form:
263
349
scheme://[host]/path[#[option=value][&...]]
265
Schemes supported are based on this modules submodules
266
Options supported are submodule-specific"""
351
Schemes supported are based on this modules submodules.
352
Options supported are submodule-specific.
353
Additional arguments are passed through to the submodule.
357
dest = install_remote('http://example.com/archive.tgz',
361
This will download `archive.tgz`, validate it using SHA1 and, if
362
the file is ok, extract it and return the directory in which it
363
was extracted. If the checksum fails, it will raise
364
:class:`charmhelpers.core.host.ChecksumError`.
267
366
# We ONLY check for True here because can_handle may return a string
268
367
# explaining why it can't handle a given source.
269
368
handlers = [h for h in plugins() if h.can_handle(source) is True]
270
369
installed_to = None
271
370
for handler in handlers:
273
installed_to = handler.install(source)
372
installed_to = handler.install(source, *args, **kwargs)
274
373
except UnhandledSource:
276
375
if not installed_to: