2
from yaml import safe_load
3
from charmhelpers.core.host import (
11
from charmhelpers.core.hookenv import (
17
CLOUD_ARCHIVE = """# Ubuntu Cloud Archive
18
deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
20
PROPOSED_POCKET = """# Proposed
21
deb http://archive.ubuntu.com/ubuntu {}-proposed main universe multiverse restricted
25
def filter_installed_packages(packages):
26
"""Returns a list of packages that require installation"""
28
cache = apt_pkg.Cache()
30
for package in packages:
33
p.current_ver or _pkgs.append(package)
35
log('Package {} has no installation candidate.'.format(package),
41
def apt_install(packages, options=None, fatal=False):
42
"""Install one or more packages"""
43
options = options or []
44
cmd = ['apt-get', '-y']
47
if isinstance(packages, basestring):
51
log("Installing {} with options: {}".format(packages,
54
subprocess.check_call(cmd)
59
def apt_update(fatal=False):
60
"""Update local apt cache"""
61
cmd = ['apt-get', 'update']
63
subprocess.check_call(cmd)
68
def apt_purge(packages, fatal=False):
69
"""Purge one or more packages"""
70
cmd = ['apt-get', '-y', 'purge']
71
if isinstance(packages, basestring):
75
log("Purging {}".format(packages))
77
subprocess.check_call(cmd)
82
def add_source(source, key=None):
83
if ((source.startswith('ppa:') or
84
source.startswith('http:'))):
85
subprocess.check_call(['add-apt-repository', '--yes', source])
86
elif source.startswith('cloud:'):
87
apt_install(filter_installed_packages(['ubuntu-cloud-keyring']),
89
pocket = source.split(':')[-1]
90
with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt:
91
apt.write(CLOUD_ARCHIVE.format(pocket))
92
elif source == 'proposed':
93
release = lsb_release()['DISTRIB_CODENAME']
94
with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:
95
apt.write(PROPOSED_POCKET.format(release))
97
subprocess.check_call(['apt-key', 'import', key])
100
class SourceConfigError(Exception):
104
def configure_sources(update=False,
105
sources_var='install_sources',
106
keys_var='install_keys'):
108
Configure multiple sources from charm configuration
113
- "http://example.com/repo precise main"
118
Note that 'null' (a.k.a. None) should not be quoted.
120
sources = safe_load(config(sources_var))
121
keys = safe_load(config(keys_var))
122
if isinstance(sources, basestring) and isinstance(keys, basestring):
123
add_source(sources, keys)
125
if not len(sources) == len(keys):
126
msg = 'Install sources and keys lists are different lengths'
127
raise SourceConfigError(msg)
128
for src_num in range(len(sources)):
129
add_source(sources[src_num], keys[src_num])
131
apt_update(fatal=True)
133
# The order of this list is very important. Handlers should be listed in from
134
# least- to most-specific URL matching.
136
'charmhelpers.fetch.archiveurl.ArchiveUrlFetchHandler',
137
'charmhelpers.fetch.bzrurl.BzrUrlFetchHandler',
141
class UnhandledSource(Exception):
145
def install_remote(source):
147
Install a file tree from a remote source
149
The specified source should be a url of the form:
150
scheme://[host]/path[#[option=value][&...]]
152
Schemes supported are based on this modules submodules
153
Options supported are submodule-specific"""
154
# We ONLY check for True here because can_handle may return a string
155
# explaining why it can't handle a given source.
156
handlers = [h for h in plugins() if h.can_handle(source) is True]
158
for handler in handlers:
160
installed_to = handler.install(source)
161
except UnhandledSource:
164
raise UnhandledSource("No handler found for source {}".format(source))
168
def install_from_config(config_var_name):
169
charm_config = config()
170
source = charm_config[config_var_name]
171
return install_remote(source)
174
class BaseFetchHandler(object):
175
"""Base class for FetchHandler implementations in fetch plugins"""
176
def can_handle(self, source):
177
"""Returns True if the source can be handled. Otherwise returns
178
a string explaining why it cannot"""
179
return "Wrong source type"
181
def install(self, source):
182
"""Try to download and unpack the source. Return the path to the
183
unpacked files or raise UnhandledSource."""
184
raise UnhandledSource("Wrong source type {}".format(source))
186
def parse_url(self, url):
189
def base_url(self, url):
190
"""Return url without querystring or fragment"""
191
parts = list(self.parse_url(url))
192
parts[4:] = ['' for i in parts[4:]]
193
return urlunparse(parts)
196
def plugins(fetch_handlers=None):
197
if not fetch_handlers:
198
fetch_handlers = FETCH_HANDLERS
200
for handler_name in fetch_handlers:
201
package, classname = handler_name.rsplit('.', 1)
203
handler_class = getattr(importlib.import_module(package), classname)
204
plugin_list.append(handler_class())
205
except (ImportError, AttributeError):
206
# Skip missing plugins so that they can be ommitted from
207
# installation if desired
208
log("FetchHandler {} not found, skipping plugin".format(handler_name))