~james-page/charms/trusty/rabbitmq-server/network-splits

« back to all changes in this revision

Viewing changes to hooks/charmhelpers/core/hookenv.py

  • Committer: james.page at ubuntu
  • Date: 2015-01-23 08:23:05 UTC
  • mfrom: (52.2.27 rabbitmq-server)
  • Revision ID: james.page@ubuntu.com-20150123082305-5uf1uk14iov78hl2
Rebase on next branch

Show diffs side-by-side

added added

removed removed

Lines of Context:
9
9
import yaml
10
10
import subprocess
11
11
import sys
12
 
import UserDict
13
12
from subprocess import CalledProcessError
14
13
 
 
14
import six
 
15
if not six.PY3:
 
16
    from UserDict import UserDict
 
17
else:
 
18
    from collections import UserDict
 
19
 
15
20
CRITICAL = "CRITICAL"
16
21
ERROR = "ERROR"
17
22
WARNING = "WARNING"
63
68
    command = ['juju-log']
64
69
    if level:
65
70
        command += ['-l', level]
 
71
    if not isinstance(message, six.string_types):
 
72
        message = repr(message)
66
73
    command += [message]
67
74
    subprocess.call(command)
68
75
 
69
76
 
70
 
class Serializable(UserDict.IterableUserDict):
 
77
class Serializable(UserDict):
71
78
    """Wrapper, an object that can be serialized to yaml or json"""
72
79
 
73
80
    def __init__(self, obj):
74
81
        # wrap the object
75
 
        UserDict.IterableUserDict.__init__(self)
 
82
        UserDict.__init__(self)
76
83
        self.data = obj
77
84
 
78
85
    def __getattr__(self, attr):
156
163
 
157
164
 
158
165
class Config(dict):
159
 
    """A Juju charm config dictionary that can write itself to
160
 
    disk (as json) and track which values have changed since
161
 
    the previous hook invocation.
162
 
 
163
 
    Do not instantiate this object directly - instead call
164
 
    ``hookenv.config()``
 
166
    """A dictionary representation of the charm's config.yaml, with some
 
167
    extra features:
 
168
 
 
169
    - See which values in the dictionary have changed since the previous hook.
 
170
    - For values that have changed, see what the previous value was.
 
171
    - Store arbitrary data for use in a later hook.
 
172
 
 
173
    NOTE: Do not instantiate this object directly - instead call
 
174
    ``hookenv.config()``, which will return an instance of :class:`Config`.
165
175
 
166
176
    Example usage::
167
177
 
170
180
        >>> config = hookenv.config()
171
181
        >>> config['foo']
172
182
        'bar'
 
183
        >>> # store a new key/value for later use
173
184
        >>> config['mykey'] = 'myval'
174
 
        >>> config.save()
175
185
 
176
186
 
177
187
        >>> # user runs `juju set mycharm foo=baz`
188
198
        >>> # keys/values that we add are preserved across hooks
189
199
        >>> config['mykey']
190
200
        'myval'
191
 
        >>> # don't forget to save at the end of hook!
192
 
        >>> config.save()
193
201
 
194
202
    """
195
203
    CONFIG_FILE_NAME = '.juju-persistent-config'
196
204
 
197
205
    def __init__(self, *args, **kw):
198
206
        super(Config, self).__init__(*args, **kw)
 
207
        self.implicit_save = True
199
208
        self._prev_dict = None
200
209
        self.path = os.path.join(charm_dir(), Config.CONFIG_FILE_NAME)
201
210
        if os.path.exists(self.path):
202
211
            self.load_previous()
203
212
 
 
213
    def __getitem__(self, key):
 
214
        """For regular dict lookups, check the current juju config first,
 
215
        then the previous (saved) copy. This ensures that user-saved values
 
216
        will be returned by a dict lookup.
 
217
 
 
218
        """
 
219
        try:
 
220
            return dict.__getitem__(self, key)
 
221
        except KeyError:
 
222
            return (self._prev_dict or {})[key]
 
223
 
 
224
    def keys(self):
 
225
        prev_keys = []
 
226
        if self._prev_dict is not None:
 
227
            prev_keys = self._prev_dict.keys()
 
228
        return list(set(prev_keys + list(dict.keys(self))))
 
229
 
204
230
    def load_previous(self, path=None):
205
 
        """Load previous copy of config from disk so that current values
206
 
        can be compared to previous values.
 
231
        """Load previous copy of config from disk.
 
232
 
 
233
        In normal usage you don't need to call this method directly - it
 
234
        is called automatically at object initialization.
207
235
 
208
236
        :param path:
209
237
 
218
246
            self._prev_dict = json.load(f)
219
247
 
220
248
    def changed(self, key):
221
 
        """Return true if the value for this key has changed since
222
 
        the last save.
 
249
        """Return True if the current value for this key is different from
 
250
        the previous value.
223
251
 
224
252
        """
225
253
        if self._prev_dict is None:
228
256
 
229
257
    def previous(self, key):
230
258
        """Return previous value for this key, or None if there
231
 
        is no "previous" value.
 
259
        is no previous value.
232
260
 
233
261
        """
234
262
        if self._prev_dict:
238
266
    def save(self):
239
267
        """Save this config to disk.
240
268
 
241
 
        Preserves items in _prev_dict that do not exist in self.
 
269
        If the charm is using the :mod:`Services Framework <services.base>`
 
270
        or :meth:'@hook <Hooks.hook>' decorator, this
 
271
        is called automatically at the end of successful hook execution.
 
272
        Otherwise, it should be called directly by user code.
 
273
 
 
274
        To disable automatic saves, set ``implicit_save=False`` on this
 
275
        instance.
242
276
 
243
277
        """
244
278
        if self._prev_dict:
245
 
            for k, v in self._prev_dict.iteritems():
 
279
            for k, v in six.iteritems(self._prev_dict):
246
280
                if k not in self:
247
281
                    self[k] = v
248
282
        with open(self.path, 'w') as f:
257
291
        config_cmd_line.append(scope)
258
292
    config_cmd_line.append('--format=json')
259
293
    try:
260
 
        config_data = json.loads(subprocess.check_output(config_cmd_line))
 
294
        config_data = json.loads(
 
295
            subprocess.check_output(config_cmd_line).decode('UTF-8'))
261
296
        if scope is not None:
262
297
            return config_data
263
298
        return Config(config_data)
276
311
    if unit:
277
312
        _args.append(unit)
278
313
    try:
279
 
        return json.loads(subprocess.check_output(_args))
 
314
        return json.loads(subprocess.check_output(_args).decode('UTF-8'))
280
315
    except ValueError:
281
316
        return None
282
 
    except CalledProcessError, e:
 
317
    except CalledProcessError as e:
283
318
        if e.returncode == 2:
284
319
            return None
285
320
        raise
286
321
 
287
322
 
288
 
def relation_set(relation_id=None, relation_settings={}, **kwargs):
 
323
def relation_set(relation_id=None, relation_settings=None, **kwargs):
289
324
    """Set relation information for the current unit"""
 
325
    relation_settings = relation_settings if relation_settings else {}
290
326
    relation_cmd_line = ['relation-set']
291
327
    if relation_id is not None:
292
328
        relation_cmd_line.extend(('-r', relation_id))
293
 
    for k, v in (relation_settings.items() + kwargs.items()):
 
329
    for k, v in (list(relation_settings.items()) + list(kwargs.items())):
294
330
        if v is None:
295
331
            relation_cmd_line.append('{}='.format(k))
296
332
        else:
307
343
    relid_cmd_line = ['relation-ids', '--format=json']
308
344
    if reltype is not None:
309
345
        relid_cmd_line.append(reltype)
310
 
        return json.loads(subprocess.check_output(relid_cmd_line)) or []
 
346
        return json.loads(
 
347
            subprocess.check_output(relid_cmd_line).decode('UTF-8')) or []
311
348
    return []
312
349
 
313
350
 
318
355
    units_cmd_line = ['relation-list', '--format=json']
319
356
    if relid is not None:
320
357
        units_cmd_line.extend(('-r', relid))
321
 
    return json.loads(subprocess.check_output(units_cmd_line)) or []
 
358
    return json.loads(
 
359
        subprocess.check_output(units_cmd_line).decode('UTF-8')) or []
322
360
 
323
361
 
324
362
@cached
358
396
 
359
397
 
360
398
@cached
 
399
def metadata():
 
400
    """Get the current charm metadata.yaml contents as a python object"""
 
401
    with open(os.path.join(charm_dir(), 'metadata.yaml')) as md:
 
402
        return yaml.safe_load(md)
 
403
 
 
404
 
 
405
@cached
361
406
def relation_types():
362
407
    """Get a list of relation types supported by this charm"""
363
 
    charmdir = os.environ.get('CHARM_DIR', '')
364
 
    mdf = open(os.path.join(charmdir, 'metadata.yaml'))
365
 
    md = yaml.safe_load(mdf)
366
408
    rel_types = []
 
409
    md = metadata()
367
410
    for key in ('provides', 'requires', 'peers'):
368
411
        section = md.get(key)
369
412
        if section:
370
413
            rel_types.extend(section.keys())
371
 
    mdf.close()
372
414
    return rel_types
373
415
 
374
416
 
375
417
@cached
 
418
def charm_name():
 
419
    """Get the name of the current charm as is specified on metadata.yaml"""
 
420
    return metadata().get('name')
 
421
 
 
422
 
 
423
@cached
376
424
def relations():
377
425
    """Get a nested dictionary of relation data for all related units"""
378
426
    rels = {}
427
475
    """Get the unit ID for the remote unit"""
428
476
    _args = ['unit-get', '--format=json', attribute]
429
477
    try:
430
 
        return json.loads(subprocess.check_output(_args))
 
478
        return json.loads(subprocess.check_output(_args).decode('UTF-8'))
431
479
    except ValueError:
432
480
        return None
433
481
 
464
512
            hooks.execute(sys.argv)
465
513
    """
466
514
 
467
 
    def __init__(self):
 
515
    def __init__(self, config_save=True):
468
516
        super(Hooks, self).__init__()
469
517
        self._hooks = {}
 
518
        self._config_save = config_save
470
519
 
471
520
    def register(self, name, function):
472
521
        """Register a hook"""
477
526
        hook_name = os.path.basename(args[0])
478
527
        if hook_name in self._hooks:
479
528
            self._hooks[hook_name]()
 
529
            if self._config_save:
 
530
                cfg = config()
 
531
                if cfg.implicit_save:
 
532
                    cfg.save()
480
533
        else:
481
534
            raise UnregisteredHookError(hook_name)
482
535