63
68
command = ['juju-log']
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)
70
class Serializable(UserDict.IterableUserDict):
77
class Serializable(UserDict):
71
78
"""Wrapper, an object that can be serialized to yaml or json"""
73
80
def __init__(self, obj):
75
UserDict.IterableUserDict.__init__(self)
82
UserDict.__init__(self)
78
85
def __getattr__(self, attr):
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.
163
Do not instantiate this object directly - instead call
166
"""A dictionary representation of the charm's config.yaml, with some
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.
173
NOTE: Do not instantiate this object directly - instead call
174
``hookenv.config()``, which will return an instance of :class:`Config`.
188
198
>>> # keys/values that we add are preserved across hooks
189
199
>>> config['mykey']
191
>>> # don't forget to save at the end of hook!
195
203
CONFIG_FILE_NAME = '.juju-persistent-config'
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()
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.
220
return dict.__getitem__(self, key)
222
return (self._prev_dict or {})[key]
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))))
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.
233
In normal usage you don't need to call this method directly - it
234
is called automatically at object initialization.
239
267
"""Save this config to disk.
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.
274
To disable automatic saves, set ``implicit_save=False`` on this
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:
248
282
with open(self.path, 'w') as f:
257
291
config_cmd_line.append(scope)
258
292
config_cmd_line.append('--format=json')
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)
277
312
_args.append(unit)
279
return json.loads(subprocess.check_output(_args))
314
return json.loads(subprocess.check_output(_args).decode('UTF-8'))
280
315
except ValueError:
282
except CalledProcessError, e:
317
except CalledProcessError as e:
283
318
if e.returncode == 2:
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())):
295
331
relation_cmd_line.append('{}='.format(k))
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 []
347
subprocess.check_output(relid_cmd_line).decode('UTF-8')) or []
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 []
359
subprocess.check_output(units_cmd_line).decode('UTF-8')) or []
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)
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)
367
410
for key in ('provides', 'requires', 'peers'):
368
411
section = md.get(key)
370
413
rel_types.extend(section.keys())
419
"""Get the name of the current charm as is specified on metadata.yaml"""
420
return metadata().get('name')
377
425
"""Get a nested dictionary of relation data for all related units"""
427
475
"""Get the unit ID for the remote unit"""
428
476
_args = ['unit-get', '--format=json', attribute]
430
return json.loads(subprocess.check_output(_args))
478
return json.loads(subprocess.check_output(_args).decode('UTF-8'))
431
479
except ValueError:
464
512
hooks.execute(sys.argv)
515
def __init__(self, config_save=True):
468
516
super(Hooks, self).__init__()
518
self._config_save = config_save
471
520
def register(self, name, function):
472
521
"""Register a hook"""