1
"Interactions with the Juju environment"
2
# Copyright 2013 Canonical Ltd.
5
# Charm Helpers Developers <juju@lists.ubuntu.com>
24
''' Cache return values for multiple executions of func + args
29
def unit_get(attribute):
34
will cache the result of unit_get + 'test' for future calls.
36
def wrapper(*args, **kwargs):
38
key = str((func, args, kwargs))
42
res = func(*args, **kwargs)
49
''' Flushes any entries from function cache where the
50
key is found in the function+args '''
54
flush_list.append(item)
55
for item in flush_list:
59
def log(message, level=None):
60
"Write a message to the juju log"
61
command = ['juju-log']
63
command += ['-l', level]
65
subprocess.call(command)
68
class Serializable(UserDict.IterableUserDict):
69
"Wrapper, an object that can be serialized to yaml or json"
71
def __init__(self, obj):
73
UserDict.IterableUserDict.__init__(self)
76
def __getattr__(self, attr):
77
# See if this object has attribute.
78
if attr in ("json", "yaml", "data"):
79
return self.__dict__[attr]
80
# Check for attribute in wrapped object.
81
got = getattr(self.data, attr, MARKER)
84
# Proxy to the wrapped object via dict interface.
86
return self.data[attr]
88
raise AttributeError(attr)
90
def __getstate__(self):
91
# Pickle as a standard dictionary.
94
def __setstate__(self, state):
95
# Unpickle into our wrapper.
99
"Serialize the object to json"
100
return json.dumps(self.data)
103
"Serialize the object to yaml"
104
return yaml.dump(self.data)
107
def execution_environment():
108
"""A convenient bundling of the current execution context"""
110
context['conf'] = config()
112
context['reltype'] = relation_type()
113
context['relid'] = relation_id()
114
context['rel'] = relation_get()
115
context['unit'] = local_unit()
116
context['rels'] = relations()
117
context['env'] = os.environ
121
def in_relation_hook():
122
"Determine whether we're running in a relation hook"
123
return 'JUJU_RELATION' in os.environ
127
"The scope for the current relation hook"
128
return os.environ.get('JUJU_RELATION', None)
132
"The relation ID for the current relation hook"
133
return os.environ.get('JUJU_RELATION_ID', None)
138
return os.environ['JUJU_UNIT_NAME']
142
"The remote unit for the current relation hook"
143
return os.environ['JUJU_REMOTE_UNIT']
147
"The name service group this unit belongs to"
148
return local_unit().split('/')[0]
152
def config(scope=None):
153
"Juju charm configuration"
154
config_cmd_line = ['config-get']
155
if scope is not None:
156
config_cmd_line.append(scope)
157
config_cmd_line.append('--format=json')
159
return json.loads(subprocess.check_output(config_cmd_line))
165
def relation_get(attribute=None, unit=None, rid=None):
166
_args = ['relation-get', '--format=json']
170
_args.append(attribute or '-')
174
return json.loads(subprocess.check_output(_args))
179
def relation_set(relation_id=None, relation_settings={}, **kwargs):
180
relation_cmd_line = ['relation-set']
181
if relation_id is not None:
182
relation_cmd_line.extend(('-r', relation_id))
183
for k, v in (relation_settings.items() + kwargs.items()):
185
relation_cmd_line.append('{}='.format(k))
187
relation_cmd_line.append('{}={}'.format(k, v))
188
subprocess.check_call(relation_cmd_line)
189
# Flush cache of any relation-gets for local unit
194
def relation_ids(reltype=None):
195
"A list of relation_ids"
196
reltype = reltype or relation_type()
197
relid_cmd_line = ['relation-ids', '--format=json']
198
if reltype is not None:
199
relid_cmd_line.append(reltype)
200
return json.loads(subprocess.check_output(relid_cmd_line)) or []
205
def related_units(relid=None):
206
"A list of related units"
207
relid = relid or relation_id()
208
units_cmd_line = ['relation-list', '--format=json']
209
if relid is not None:
210
units_cmd_line.extend(('-r', relid))
211
return json.loads(subprocess.check_output(units_cmd_line)) or []
215
def relation_for_unit(unit=None, rid=None):
216
"Get the json represenation of a unit's relation"
217
unit = unit or remote_unit()
218
relation = relation_get(unit=unit, rid=rid)
220
if key.endswith('-list'):
221
relation[key] = relation[key].split()
222
relation['__unit__'] = unit
227
def relations_for_id(relid=None):
228
"Get relations of a specific relation ID"
230
relid = relid or relation_ids()
231
for unit in related_units(relid):
232
unit_data = relation_for_unit(unit, relid)
233
unit_data['__relid__'] = relid
234
relation_data.append(unit_data)
239
def relations_of_type(reltype=None):
240
"Get relations of a specific type"
242
reltype = reltype or relation_type()
243
for relid in relation_ids(reltype):
244
for relation in relations_for_id(relid):
245
relation['__relid__'] = relid
246
relation_data.append(relation)
251
def relation_types():
252
"Get a list of relation types supported by this charm"
253
charmdir = os.environ.get('CHARM_DIR', '')
254
mdf = open(os.path.join(charmdir, 'metadata.yaml'))
255
md = yaml.safe_load(mdf)
257
for key in ('provides', 'requires', 'peers'):
258
section = md.get(key)
260
rel_types.extend(section.keys())
268
for reltype in relation_types():
270
for relid in relation_ids(reltype):
271
units = {local_unit(): relation_get(unit=local_unit(), rid=relid)}
272
for unit in related_units(relid):
273
reldata = relation_get(unit=unit, rid=relid)
274
units[unit] = reldata
275
relids[relid] = units
276
rels[reltype] = relids
280
def open_port(port, protocol="TCP"):
281
"Open a service network port"
282
_args = ['open-port']
283
_args.append('{}/{}'.format(port, protocol))
284
subprocess.check_call(_args)
287
def close_port(port, protocol="TCP"):
288
"Close a service network port"
289
_args = ['close-port']
290
_args.append('{}/{}'.format(port, protocol))
291
subprocess.check_call(_args)
295
def unit_get(attribute):
296
_args = ['unit-get', '--format=json', attribute]
298
return json.loads(subprocess.check_output(_args))
303
def unit_private_ip():
304
return unit_get('private-address')
307
class UnregisteredHookError(Exception):
313
super(Hooks, self).__init__()
316
def register(self, name, function):
317
self._hooks[name] = function
319
def execute(self, args):
320
hook_name = os.path.basename(args[0])
321
if hook_name in self._hooks:
322
self._hooks[hook_name]()
324
raise UnregisteredHookError(hook_name)
326
def hook(self, *hook_names):
327
def wrapper(decorated):
328
for hook_name in hook_names:
329
self.register(hook_name, decorated)
331
self.register(decorated.__name__, decorated)
332
if '_' in decorated.__name__:
334
decorated.__name__.replace('_', '-'), decorated)
340
return os.environ.get('CHARM_DIR')