121
123
def in_relation_hook():
122
"Determine whether we're running in a relation hook"
124
"""Determine whether we're running in a relation hook"""
123
125
return 'JUJU_RELATION' in os.environ
126
128
def relation_type():
127
"The scope for the current relation hook"
129
"""The scope for the current relation hook"""
128
130
return os.environ.get('JUJU_RELATION', None)
131
133
def relation_id():
132
"The relation ID for the current relation hook"
134
"""The relation ID for the current relation hook"""
133
135
return os.environ.get('JUJU_RELATION_ID', None)
136
138
def local_unit():
138
140
return os.environ['JUJU_UNIT_NAME']
141
143
def remote_unit():
142
"The remote unit for the current relation hook"
144
"""The remote unit for the current relation hook"""
143
145
return os.environ['JUJU_REMOTE_UNIT']
146
148
def service_name():
147
"The name service group this unit belongs to"
149
"""The name service group this unit belongs to"""
148
150
return local_unit().split('/')[0]
154
"""The name of the currently executing hook"""
155
return os.path.basename(sys.argv[0])
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
169
>>> from charmhelpers.core import hookenv
170
>>> config = hookenv.config()
173
>>> config['mykey'] = 'myval'
177
>>> # user runs `juju set mycharm foo=baz`
178
>>> # now we're inside subsequent config-changed hook
179
>>> config = hookenv.config()
182
>>> # test to see if this val has changed since last hook
183
>>> config.changed('foo')
185
>>> # what was the previous value?
186
>>> config.previous('foo')
188
>>> # keys/values that we add are preserved across hooks
191
>>> # don't forget to save at the end of hook!
195
CONFIG_FILE_NAME = '.juju-persistent-config'
197
def __init__(self, *args, **kw):
198
super(Config, self).__init__(*args, **kw)
199
self._prev_dict = None
200
self.path = os.path.join(charm_dir(), Config.CONFIG_FILE_NAME)
201
if os.path.exists(self.path):
204
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.
210
File path from which to load the previous config. If `None`,
211
config is loaded from the default location. If `path` is
212
specified, subsequent `save()` calls will write to the same
216
self.path = path or self.path
217
with open(self.path) as f:
218
self._prev_dict = json.load(f)
220
def changed(self, key):
221
"""Return true if the value for this key has changed since
225
if self._prev_dict is None:
227
return self.previous(key) != self.get(key)
229
def previous(self, key):
230
"""Return previous value for this key, or None if there
231
is no "previous" value.
235
return self._prev_dict.get(key)
239
"""Save this config to disk.
241
Preserves items in _prev_dict that do not exist in self.
245
for k, v in self._prev_dict.iteritems():
248
with open(self.path, 'w') as f:
152
253
def config(scope=None):
153
"Juju charm configuration"
254
"""Juju charm configuration"""
154
255
config_cmd_line = ['config-get']
155
256
if scope is not None:
156
257
config_cmd_line.append(scope)
157
258
config_cmd_line.append('--format=json')
159
return json.loads(subprocess.check_output(config_cmd_line))
260
config_data = json.loads(subprocess.check_output(config_cmd_line))
261
if scope is not None:
263
return Config(config_data)
160
264
except ValueError:
165
269
def relation_get(attribute=None, unit=None, rid=None):
270
"""Get relation information"""
166
271
_args = ['relation-get', '--format=json']
168
273
_args.append('-r')
392
def is_relation_made(relation, keys='private-address'):
394
Determine whether a relation is established by checking for
395
presence of key(s). If a list of keys is provided, they
396
must all be present for the relation to be identified as made
398
if isinstance(keys, str):
400
for r_id in relation_ids(relation):
401
for unit in related_units(r_id):
404
context[k] = relation_get(k, rid=r_id,
406
if None not in context.values():
280
411
def open_port(port, protocol="TCP"):
281
"Open a service network port"
412
"""Open a service network port"""
282
413
_args = ['open-port']
283
414
_args.append('{}/{}'.format(port, protocol))
284
415
subprocess.check_call(_args)
287
418
def close_port(port, protocol="TCP"):
288
"Close a service network port"
419
"""Close a service network port"""
289
420
_args = ['close-port']
290
421
_args.append('{}/{}'.format(port, protocol))
291
422
subprocess.check_call(_args)
303
435
def unit_private_ip():
436
"""Get this unit's private IP address"""
304
437
return unit_get('private-address')
307
440
class UnregisteredHookError(Exception):
441
"""Raised when an undefined hook is called"""
311
445
class Hooks(object):
446
"""A convenient handler for hook functions.
451
# register a hook, taking its name from the function name
456
# register a hook, providing a custom hook name
457
@hooks.hook("config-changed")
458
def config_changed():
461
if __name__ == "__main__":
462
# execute a hook based on the name the program is called by
463
hooks.execute(sys.argv)
312
466
def __init__(self):
313
467
super(Hooks, self).__init__()
316
470
def register(self, name, function):
471
"""Register a hook"""
317
472
self._hooks[name] = function
319
474
def execute(self, args):
475
"""Execute a registered hook based on args[0]"""
320
476
hook_name = os.path.basename(args[0])
321
477
if hook_name in self._hooks:
322
478
self._hooks[hook_name]()