~psivaa/uci-engine/rabbitmq-restish-with-proxy

« back to all changes in this revision

Viewing changes to charms/precise/webui/hooks/charmhelpers/fetch/__init__.py

  • Committer: Joe Talbott
  • Date: 2014-01-23 17:11:17 UTC
  • mto: (126.2.10 webui)
  • mto: This revision was merged to the branch mainline in revision 161.
  • Revision ID: joe.talbott@canonical.com-20140123171117-82s9buwmb4lazj6f
Add webui charm.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import importlib
 
2
from yaml import safe_load
 
3
from charmhelpers.core.host import (
 
4
    lsb_release
 
5
)
 
6
from urlparse import (
 
7
    urlparse,
 
8
    urlunparse,
 
9
)
 
10
import subprocess
 
11
from charmhelpers.core.hookenv import (
 
12
    config,
 
13
    log,
 
14
)
 
15
import apt_pkg
 
16
import os
 
17
 
 
18
CLOUD_ARCHIVE = """# Ubuntu Cloud Archive
 
19
deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
 
20
"""
 
21
PROPOSED_POCKET = """# Proposed
 
22
deb http://archive.ubuntu.com/ubuntu {}-proposed main universe multiverse restricted
 
23
"""
 
24
CLOUD_ARCHIVE_POCKETS = {
 
25
    # Folsom
 
26
    'folsom': 'precise-updates/folsom',
 
27
    'precise-folsom': 'precise-updates/folsom',
 
28
    'precise-folsom/updates': 'precise-updates/folsom',
 
29
    'precise-updates/folsom': 'precise-updates/folsom',
 
30
    'folsom/proposed': 'precise-proposed/folsom',
 
31
    'precise-folsom/proposed': 'precise-proposed/folsom',
 
32
    'precise-proposed/folsom': 'precise-proposed/folsom',
 
33
    # Grizzly
 
34
    'grizzly': 'precise-updates/grizzly',
 
35
    'precise-grizzly': 'precise-updates/grizzly',
 
36
    'precise-grizzly/updates': 'precise-updates/grizzly',
 
37
    'precise-updates/grizzly': 'precise-updates/grizzly',
 
38
    'grizzly/proposed': 'precise-proposed/grizzly',
 
39
    'precise-grizzly/proposed': 'precise-proposed/grizzly',
 
40
    'precise-proposed/grizzly': 'precise-proposed/grizzly',
 
41
    # Havana
 
42
    'havana': 'precise-updates/havana',
 
43
    'precise-havana': 'precise-updates/havana',
 
44
    'precise-havana/updates': 'precise-updates/havana',
 
45
    'precise-updates/havana': 'precise-updates/havana',
 
46
    'havana/proposed': 'precise-proposed/havana',
 
47
    'precies-havana/proposed': 'precise-proposed/havana',
 
48
    'precise-proposed/havana': 'precise-proposed/havana',
 
49
}
 
50
 
 
51
 
 
52
def filter_installed_packages(packages):
 
53
    """Returns a list of packages that require installation"""
 
54
    apt_pkg.init()
 
55
    cache = apt_pkg.Cache()
 
56
    _pkgs = []
 
57
    for package in packages:
 
58
        try:
 
59
            p = cache[package]
 
60
            p.current_ver or _pkgs.append(package)
 
61
        except KeyError:
 
62
            log('Package {} has no installation candidate.'.format(package),
 
63
                level='WARNING')
 
64
            _pkgs.append(package)
 
65
    return _pkgs
 
66
 
 
67
 
 
68
def apt_install(packages, options=None, fatal=False):
 
69
    """Install one or more packages"""
 
70
    if options is None:
 
71
        options = ['--option=Dpkg::Options::=--force-confold']
 
72
 
 
73
    cmd = ['apt-get', '--assume-yes']
 
74
    cmd.extend(options)
 
75
    cmd.append('install')
 
76
    if isinstance(packages, basestring):
 
77
        cmd.append(packages)
 
78
    else:
 
79
        cmd.extend(packages)
 
80
    log("Installing {} with options: {}".format(packages,
 
81
                                                options))
 
82
    env = os.environ.copy()
 
83
    if 'DEBIAN_FRONTEND' not in env:
 
84
        env['DEBIAN_FRONTEND'] = 'noninteractive'
 
85
 
 
86
    if fatal:
 
87
        subprocess.check_call(cmd, env=env)
 
88
    else:
 
89
        subprocess.call(cmd, env=env)
 
90
 
 
91
 
 
92
def apt_update(fatal=False):
 
93
    """Update local apt cache"""
 
94
    cmd = ['apt-get', 'update']
 
95
    if fatal:
 
96
        subprocess.check_call(cmd)
 
97
    else:
 
98
        subprocess.call(cmd)
 
99
 
 
100
 
 
101
def apt_purge(packages, fatal=False):
 
102
    """Purge one or more packages"""
 
103
    cmd = ['apt-get', '--assume-yes', 'purge']
 
104
    if isinstance(packages, basestring):
 
105
        cmd.append(packages)
 
106
    else:
 
107
        cmd.extend(packages)
 
108
    log("Purging {}".format(packages))
 
109
    if fatal:
 
110
        subprocess.check_call(cmd)
 
111
    else:
 
112
        subprocess.call(cmd)
 
113
 
 
114
 
 
115
def apt_hold(packages, fatal=False):
 
116
    """Hold one or more packages"""
 
117
    cmd = ['apt-mark', 'hold']
 
118
    if isinstance(packages, basestring):
 
119
        cmd.append(packages)
 
120
    else:
 
121
        cmd.extend(packages)
 
122
    log("Holding {}".format(packages))
 
123
    if fatal:
 
124
        subprocess.check_call(cmd)
 
125
    else:
 
126
        subprocess.call(cmd)
 
127
 
 
128
 
 
129
def add_source(source, key=None):
 
130
    if (source.startswith('ppa:') or
 
131
        source.startswith('http:') or
 
132
        source.startswith('deb ') or
 
133
            source.startswith('cloud-archive:')):
 
134
        subprocess.check_call(['add-apt-repository', '--yes', source])
 
135
    elif source.startswith('cloud:'):
 
136
        apt_install(filter_installed_packages(['ubuntu-cloud-keyring']),
 
137
                    fatal=True)
 
138
        pocket = source.split(':')[-1]
 
139
        if pocket not in CLOUD_ARCHIVE_POCKETS:
 
140
            raise SourceConfigError(
 
141
                'Unsupported cloud: source option %s' %
 
142
                pocket)
 
143
        actual_pocket = CLOUD_ARCHIVE_POCKETS[pocket]
 
144
        with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt:
 
145
            apt.write(CLOUD_ARCHIVE.format(actual_pocket))
 
146
    elif source == 'proposed':
 
147
        release = lsb_release()['DISTRIB_CODENAME']
 
148
        with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:
 
149
            apt.write(PROPOSED_POCKET.format(release))
 
150
    if key:
 
151
        subprocess.check_call(['apt-key', 'import', key])
 
152
 
 
153
 
 
154
class SourceConfigError(Exception):
 
155
    pass
 
156
 
 
157
 
 
158
def configure_sources(update=False,
 
159
                      sources_var='install_sources',
 
160
                      keys_var='install_keys'):
 
161
    """
 
162
    Configure multiple sources from charm configuration
 
163
 
 
164
    Example config:
 
165
        install_sources:
 
166
          - "ppa:foo"
 
167
          - "http://example.com/repo precise main"
 
168
        install_keys:
 
169
          - null
 
170
          - "a1b2c3d4"
 
171
 
 
172
    Note that 'null' (a.k.a. None) should not be quoted.
 
173
    """
 
174
    sources = safe_load(config(sources_var))
 
175
    keys = config(keys_var)
 
176
    if keys is not None:
 
177
        keys = safe_load(keys)
 
178
    if isinstance(sources, basestring) and (
 
179
            keys is None or isinstance(keys, basestring)):
 
180
        add_source(sources, keys)
 
181
    else:
 
182
        if not len(sources) == len(keys):
 
183
            msg = 'Install sources and keys lists are different lengths'
 
184
            raise SourceConfigError(msg)
 
185
        for src_num in range(len(sources)):
 
186
            add_source(sources[src_num], keys[src_num])
 
187
    if update:
 
188
        apt_update(fatal=True)
 
189
 
 
190
# The order of this list is very important. Handlers should be listed in from
 
191
# least- to most-specific URL matching.
 
192
FETCH_HANDLERS = (
 
193
    'charmhelpers.fetch.archiveurl.ArchiveUrlFetchHandler',
 
194
    'charmhelpers.fetch.bzrurl.BzrUrlFetchHandler',
 
195
)
 
196
 
 
197
 
 
198
class UnhandledSource(Exception):
 
199
    pass
 
200
 
 
201
 
 
202
def install_remote(source):
 
203
    """
 
204
    Install a file tree from a remote source
 
205
 
 
206
    The specified source should be a url of the form:
 
207
        scheme://[host]/path[#[option=value][&...]]
 
208
 
 
209
    Schemes supported are based on this modules submodules
 
210
    Options supported are submodule-specific"""
 
211
    # We ONLY check for True here because can_handle may return a string
 
212
    # explaining why it can't handle a given source.
 
213
    handlers = [h for h in plugins() if h.can_handle(source) is True]
 
214
    installed_to = None
 
215
    for handler in handlers:
 
216
        try:
 
217
            installed_to = handler.install(source)
 
218
        except UnhandledSource:
 
219
            pass
 
220
    if not installed_to:
 
221
        raise UnhandledSource("No handler found for source {}".format(source))
 
222
    return installed_to
 
223
 
 
224
 
 
225
def install_from_config(config_var_name):
 
226
    charm_config = config()
 
227
    source = charm_config[config_var_name]
 
228
    return install_remote(source)
 
229
 
 
230
 
 
231
class BaseFetchHandler(object):
 
232
 
 
233
    """Base class for FetchHandler implementations in fetch plugins"""
 
234
 
 
235
    def can_handle(self, source):
 
236
        """Returns True if the source can be handled. Otherwise returns
 
237
        a string explaining why it cannot"""
 
238
        return "Wrong source type"
 
239
 
 
240
    def install(self, source):
 
241
        """Try to download and unpack the source. Return the path to the
 
242
        unpacked files or raise UnhandledSource."""
 
243
        raise UnhandledSource("Wrong source type {}".format(source))
 
244
 
 
245
    def parse_url(self, url):
 
246
        return urlparse(url)
 
247
 
 
248
    def base_url(self, url):
 
249
        """Return url without querystring or fragment"""
 
250
        parts = list(self.parse_url(url))
 
251
        parts[4:] = ['' for i in parts[4:]]
 
252
        return urlunparse(parts)
 
253
 
 
254
 
 
255
def plugins(fetch_handlers=None):
 
256
    if not fetch_handlers:
 
257
        fetch_handlers = FETCH_HANDLERS
 
258
    plugin_list = []
 
259
    for handler_name in fetch_handlers:
 
260
        package, classname = handler_name.rsplit('.', 1)
 
261
        try:
 
262
            handler_class = getattr(
 
263
                importlib.import_module(package),
 
264
                classname)
 
265
            plugin_list.append(handler_class())
 
266
        except (ImportError, AttributeError):
 
267
            # Skip missing plugins so that they can be ommitted from
 
268
            # installation if desired
 
269
            log("FetchHandler {} not found, skipping plugin".format(
 
270
                handler_name))
 
271
    return plugin_list