353
388
"""Set relation information for the current unit"""
354
389
relation_settings = relation_settings if relation_settings else {}
355
390
relation_cmd_line = ['relation-set']
391
accepts_file = "--file" in subprocess.check_output(
392
relation_cmd_line + ["--help"], universal_newlines=True)
356
393
if relation_id is not None:
357
394
relation_cmd_line.extend(('-r', relation_id))
358
for k, v in (list(relation_settings.items()) + list(kwargs.items())):
360
relation_cmd_line.append('{}='.format(k))
362
relation_cmd_line.append('{}={}'.format(k, v))
363
subprocess.check_call(relation_cmd_line)
395
settings = relation_settings.copy()
396
settings.update(kwargs)
397
for key, value in settings.items():
398
# Force value to be a string: it always should, but some call
399
# sites pass in things like dicts or numbers.
400
if value is not None:
401
settings[key] = "{}".format(value)
403
# --file was introduced in Juju 1.23.2. Use it by default if
404
# available, since otherwise we'll break if the relation data is
405
# too big. Ideally we should tell relation-set to read the data from
406
# stdin, but that feature is broken in 1.23.2: Bug #1454678.
407
with tempfile.NamedTemporaryFile(delete=False) as settings_file:
408
settings_file.write(yaml.safe_dump(settings).encode("utf-8"))
409
subprocess.check_call(
410
relation_cmd_line + ["--file", settings_file.name])
411
os.remove(settings_file.name)
413
for key, value in settings.items():
415
relation_cmd_line.append('{}='.format(key))
417
relation_cmd_line.append('{}={}'.format(key, value))
418
subprocess.check_call(relation_cmd_line)
364
419
# Flush cache of any relation-gets for local unit
365
420
flush(local_unit())
423
def relation_clear(r_id=None):
424
''' Clears any relation data already set on relation r_id '''
425
settings = relation_get(rid=r_id,
427
for setting in settings:
428
if setting not in ['public-address', 'private-address']:
429
settings[setting] = None
430
relation_set(relation_id=r_id,
369
435
def relation_ids(reltype=None):
370
436
"""A list of relation_ids"""
513
def relation_to_interface(relation_name):
515
Given the name of a relation, return the interface that relation uses.
517
:returns: The interface name, or ``None``.
519
return relation_to_role_and_interface(relation_name)[1]
523
def relation_to_role_and_interface(relation_name):
525
Given the name of a relation, return the role and the name of the interface
526
that relation uses (where role is one of ``provides``, ``requires``, or ``peer``).
528
:returns: A tuple containing ``(role, interface)``, or ``(None, None)``.
530
_metadata = metadata()
531
for role in ('provides', 'requires', 'peer'):
532
interface = _metadata.get(role, {}).get(relation_name, {}).get('interface')
534
return role, interface
539
def role_and_interface_to_relations(role, interface_name):
541
Given a role and interface name, return a list of relation names for the
542
current charm that use that interface under that role (where role is one
543
of ``provides``, ``requires``, or ``peer``).
545
:returns: A list of relation names.
547
_metadata = metadata()
549
for relation_name, relation in _metadata.get(role, {}).items():
550
if relation['interface'] == interface_name:
551
results.append(relation_name)
556
def interface_to_relations(interface_name):
558
Given an interface, return a list of relation names for the current
559
charm that use that interface.
561
:returns: A list of relation names.
564
for role in ('provides', 'requires', 'peer'):
565
results.extend(role_and_interface_to_relations(role, interface_name))
447
570
def charm_name():
448
571
"""Get the name of the current charm as is specified on metadata.yaml"""
449
572
return metadata().get('name')
606
741
The results set by action_set are preserved."""
607
742
subprocess.check_call(['action-fail', message])
746
"""Get the name of the currently executing action."""
747
return os.environ.get('JUJU_ACTION_NAME')
751
"""Get the UUID of the currently executing action."""
752
return os.environ.get('JUJU_ACTION_UUID')
756
"""Get the tag for the currently executing action."""
757
return os.environ.get('JUJU_ACTION_TAG')
760
def status_set(workload_state, message):
761
"""Set the workload state with a message
763
Use status-set to set the workload state with a message which is visible
764
to the user via juju status. If the status-set command is not found then
765
assume this is juju < 1.23 and juju-log the message unstead.
767
workload_state -- valid juju workload state.
768
message -- status update message
770
valid_states = ['maintenance', 'blocked', 'waiting', 'active']
771
if workload_state not in valid_states:
773
'{!r} is not a valid workload state'.format(workload_state)
775
cmd = ['status-set', workload_state, message]
777
ret = subprocess.call(cmd)
781
if e.errno != errno.ENOENT:
783
log_message = 'status-set failed: {} {}'.format(workload_state,
785
log(log_message, level='INFO')
789
"""Retrieve the previously set juju workload state
791
If the status-set command is not found then assume this is juju < 1.23 and
796
raw_status = subprocess.check_output(cmd, universal_newlines=True)
797
status = raw_status.rstrip()
800
if e.errno == errno.ENOENT:
806
def translate_exc(from_exc, to_exc):
807
def inner_translate_exc1(f):
808
def inner_translate_exc2(*args, **kwargs):
810
return f(*args, **kwargs)
814
return inner_translate_exc2
816
return inner_translate_exc1
819
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
821
"""Does the current unit hold the juju leadership
823
Uses juju to determine whether the current unit is the leader of its peers
825
cmd = ['is-leader', '--format=json']
826
return json.loads(subprocess.check_output(cmd).decode('UTF-8'))
829
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
830
def leader_get(attribute=None):
831
"""Juju leader get value(s)"""
832
cmd = ['leader-get', '--format=json'] + [attribute or '-']
833
return json.loads(subprocess.check_output(cmd).decode('UTF-8'))
836
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
837
def leader_set(settings=None, **kwargs):
838
"""Juju leader set value(s)"""
840
# log("Juju leader-set '%s'" % (settings), level=DEBUG)
842
settings = settings or {}
843
settings.update(kwargs)
844
for k, v in settings.items():
846
cmd.append('{}='.format(k))
848
cmd.append('{}={}'.format(k, v))
849
subprocess.check_call(cmd)
854
"""Full version string (eg. '1.23.3.1-trusty-amd64')"""
855
# Per https://bugs.launchpad.net/juju-core/+bug/1455368/comments/1
856
jujud = glob.glob('/var/lib/juju/tools/machine-*/jujud')[0]
857
return subprocess.check_output([jujud, 'version'],
858
universal_newlines=True).strip()
862
def has_juju_version(minimum_version):
863
"""Return True if the Juju version is at least the provided version"""
864
return LooseVersion(juju_version()) >= LooseVersion(minimum_version)
871
def atstart(callback, *args, **kwargs):
872
'''Schedule a callback to run before the main hook.
874
Callbacks are run in the order they were added.
876
This is useful for modules and classes to perform initialization
877
and inject behavior. In particular:
879
- Run common code before all of your hooks, such as logging
880
the hook name or interesting relation data.
881
- Defer object or module initialization that requires a hook
882
context until we know there actually is a hook context,
883
making testing easier.
884
- Rather than requiring charm authors to include boilerplate to
885
invoke your helper's behavior, have it run automatically if
886
your object is instantiated or module imported.
888
This is not at all useful after your hook framework as been launched.
891
_atstart.append((callback, args, kwargs))
894
def atexit(callback, *args, **kwargs):
895
'''Schedule a callback to run on successful hook completion.
897
Callbacks are run in the reverse order that they were added.'''
898
_atexit.append((callback, args, kwargs))
902
'''Hook frameworks must invoke this before running the main hook body.'''
904
for callback, args, kwargs in _atstart:
905
callback(*args, **kwargs)
910
'''Hook frameworks must invoke this after the main hook body has
911
successfully completed. Do not invoke it if the hook fails.'''
913
for callback, args, kwargs in reversed(_atexit):
914
callback(*args, **kwargs)