340
369
"""Set relation information for the current unit"""
341
370
relation_settings = relation_settings if relation_settings else {}
342
371
relation_cmd_line = ['relation-set']
372
accepts_file = "--file" in subprocess.check_output(
373
relation_cmd_line + ["--help"], universal_newlines=True)
343
374
if relation_id is not None:
344
375
relation_cmd_line.extend(('-r', relation_id))
345
for k, v in (list(relation_settings.items()) + list(kwargs.items())):
347
relation_cmd_line.append('{}='.format(k))
349
relation_cmd_line.append('{}={}'.format(k, v))
350
subprocess.check_call(relation_cmd_line)
376
settings = relation_settings.copy()
377
settings.update(kwargs)
378
for key, value in settings.items():
379
# Force value to be a string: it always should, but some call
380
# sites pass in things like dicts or numbers.
381
if value is not None:
382
settings[key] = "{}".format(value)
384
# --file was introduced in Juju 1.23.2. Use it by default if
385
# available, since otherwise we'll break if the relation data is
386
# too big. Ideally we should tell relation-set to read the data from
387
# stdin, but that feature is broken in 1.23.2: Bug #1454678.
388
with tempfile.NamedTemporaryFile(delete=False) as settings_file:
389
settings_file.write(yaml.safe_dump(settings).encode("utf-8"))
390
subprocess.check_call(
391
relation_cmd_line + ["--file", settings_file.name])
392
os.remove(settings_file.name)
394
for key, value in settings.items():
396
relation_cmd_line.append('{}='.format(key))
398
relation_cmd_line.append('{}={}'.format(key, value))
399
subprocess.check_call(relation_cmd_line)
351
400
# Flush cache of any relation-gets for local unit
352
401
flush(local_unit())
404
def relation_clear(r_id=None):
405
''' Clears any relation data already set on relation r_id '''
406
settings = relation_get(rid=r_id,
408
for setting in settings:
409
if setting not in ['public-address', 'private-address']:
410
settings[setting] = None
411
relation_set(relation_id=r_id,
356
416
def relation_ids(reltype=None):
357
417
"""A list of relation_ids"""
494
def relation_to_interface(relation_name):
496
Given the name of a relation, return the interface that relation uses.
498
:returns: The interface name, or ``None``.
500
return relation_to_role_and_interface(relation_name)[1]
504
def relation_to_role_and_interface(relation_name):
506
Given the name of a relation, return the role and the name of the interface
507
that relation uses (where role is one of ``provides``, ``requires``, or ``peer``).
509
:returns: A tuple containing ``(role, interface)``, or ``(None, None)``.
511
_metadata = metadata()
512
for role in ('provides', 'requires', 'peer'):
513
interface = _metadata.get(role, {}).get(relation_name, {}).get('interface')
515
return role, interface
520
def role_and_interface_to_relations(role, interface_name):
522
Given a role and interface name, return a list of relation names for the
523
current charm that use that interface under that role (where role is one
524
of ``provides``, ``requires``, or ``peer``).
526
:returns: A list of relation names.
528
_metadata = metadata()
530
for relation_name, relation in _metadata.get(role, {}).items():
531
if relation['interface'] == interface_name:
532
results.append(relation_name)
537
def interface_to_relations(interface_name):
539
Given an interface, return a list of relation names for the current
540
charm that use that interface.
542
:returns: A list of relation names.
545
for role in ('provides', 'requires', 'peer'):
546
results.extend(role_and_interface_to_relations(role, interface_name))
434
551
def charm_name():
435
552
"""Get the name of the current charm as is specified on metadata.yaml"""
436
553
return metadata().get('name')
567
696
"""Return the root directory of the current charm"""
568
697
return os.environ.get('CHARM_DIR')
701
def action_get(key=None):
702
"""Gets the value of an action parameter, or all key/value param pairs"""
706
cmd.append('--format=json')
707
action_data = json.loads(subprocess.check_output(cmd).decode('UTF-8'))
711
def action_set(values):
712
"""Sets the values to be returned after the action finishes"""
714
for k, v in list(values.items()):
715
cmd.append('{}={}'.format(k, v))
716
subprocess.check_call(cmd)
719
def action_fail(message):
720
"""Sets the action status to failed and sets the error message.
722
The results set by action_set are preserved."""
723
subprocess.check_call(['action-fail', message])
727
"""Get the name of the currently executing action."""
728
return os.environ.get('JUJU_ACTION_NAME')
732
"""Get the UUID of the currently executing action."""
733
return os.environ.get('JUJU_ACTION_UUID')
737
"""Get the tag for the currently executing action."""
738
return os.environ.get('JUJU_ACTION_TAG')
741
def status_set(workload_state, message):
742
"""Set the workload state with a message
744
Use status-set to set the workload state with a message which is visible
745
to the user via juju status. If the status-set command is not found then
746
assume this is juju < 1.23 and juju-log the message unstead.
748
workload_state -- valid juju workload state.
749
message -- status update message
751
valid_states = ['maintenance', 'blocked', 'waiting', 'active']
752
if workload_state not in valid_states:
754
'{!r} is not a valid workload state'.format(workload_state)
756
cmd = ['status-set', workload_state, message]
758
ret = subprocess.call(cmd)
762
if e.errno != errno.ENOENT:
764
log_message = 'status-set failed: {} {}'.format(workload_state,
766
log(log_message, level='INFO')
770
"""Retrieve the previously set juju workload state and message
772
If the status-get command is not found then assume this is juju < 1.23 and
776
cmd = ['status-get', "--format=json", "--include-data"]
778
raw_status = subprocess.check_output(cmd)
780
if e.errno == errno.ENOENT:
781
return ('unknown', "")
785
status = json.loads(raw_status.decode("UTF-8"))
786
return (status["status"], status["message"])
789
def translate_exc(from_exc, to_exc):
790
def inner_translate_exc1(f):
791
def inner_translate_exc2(*args, **kwargs):
793
return f(*args, **kwargs)
797
return inner_translate_exc2
799
return inner_translate_exc1
802
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
804
"""Does the current unit hold the juju leadership
806
Uses juju to determine whether the current unit is the leader of its peers
808
cmd = ['is-leader', '--format=json']
809
return json.loads(subprocess.check_output(cmd).decode('UTF-8'))
812
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
813
def leader_get(attribute=None):
814
"""Juju leader get value(s)"""
815
cmd = ['leader-get', '--format=json'] + [attribute or '-']
816
return json.loads(subprocess.check_output(cmd).decode('UTF-8'))
819
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
820
def leader_set(settings=None, **kwargs):
821
"""Juju leader set value(s)"""
823
# log("Juju leader-set '%s'" % (settings), level=DEBUG)
825
settings = settings or {}
826
settings.update(kwargs)
827
for k, v in settings.items():
829
cmd.append('{}='.format(k))
831
cmd.append('{}={}'.format(k, v))
832
subprocess.check_call(cmd)
837
"""Full version string (eg. '1.23.3.1-trusty-amd64')"""
838
# Per https://bugs.launchpad.net/juju-core/+bug/1455368/comments/1
839
jujud = glob.glob('/var/lib/juju/tools/machine-*/jujud')[0]
840
return subprocess.check_output([jujud, 'version'],
841
universal_newlines=True).strip()
845
def has_juju_version(minimum_version):
846
"""Return True if the Juju version is at least the provided version"""
847
return LooseVersion(juju_version()) >= LooseVersion(minimum_version)
854
def atstart(callback, *args, **kwargs):
855
'''Schedule a callback to run before the main hook.
857
Callbacks are run in the order they were added.
859
This is useful for modules and classes to perform initialization
860
and inject behavior. In particular:
862
- Run common code before all of your hooks, such as logging
863
the hook name or interesting relation data.
864
- Defer object or module initialization that requires a hook
865
context until we know there actually is a hook context,
866
making testing easier.
867
- Rather than requiring charm authors to include boilerplate to
868
invoke your helper's behavior, have it run automatically if
869
your object is instantiated or module imported.
871
This is not at all useful after your hook framework as been launched.
874
_atstart.append((callback, args, kwargs))
877
def atexit(callback, *args, **kwargs):
878
'''Schedule a callback to run on successful hook completion.
880
Callbacks are run in the reverse order that they were added.'''
881
_atexit.append((callback, args, kwargs))
885
'''Hook frameworks must invoke this before running the main hook body.'''
887
for callback, args, kwargs in _atstart:
888
callback(*args, **kwargs)
893
'''Hook frameworks must invoke this after the main hook body has
894
successfully completed. Do not invoke it if the hook fails.'''
896
for callback, args, kwargs in reversed(_atexit):
897
callback(*args, **kwargs)