173
174
return os.environ.get('JUJU_RELATION', None)
177
"""The relation ID for the current relation hook"""
178
return os.environ.get('JUJU_RELATION_ID', None)
178
def relation_id(relation_name=None, service_or_unit=None):
179
"""The relation ID for the current or a specified relation"""
180
if not relation_name and not service_or_unit:
181
return os.environ.get('JUJU_RELATION_ID', None)
182
elif relation_name and service_or_unit:
183
service_name = service_or_unit.split('/')[0]
184
for relid in relation_ids(relation_name):
185
remote_service = remote_service_name(relid)
186
if remote_service == service_name:
189
raise ValueError('Must specify neither or both of relation_name and service_or_unit')
181
192
def local_unit():
193
204
return local_unit().split('/')[0]
208
def remote_service_name(relid=None):
209
"""The remote service name for a given relation-id (or the current relation)"""
213
units = related_units(relid)
214
unit = units[0] if units else None
215
return unit.split('/')[0] if unit else None
197
219
"""The name of the currently executing hook"""
198
return os.path.basename(sys.argv[0])
220
return os.environ.get('JUJU_HOOK_NAME', os.path.basename(sys.argv[0]))
201
223
class Config(dict):
494
def peer_relation_id():
495
'''Get the peers relation id if a peers relation has been joined, else None.'''
497
section = md.get('peers')
500
relids = relation_ids(key)
507
def relation_to_interface(relation_name):
509
Given the name of a relation, return the interface that relation uses.
511
:returns: The interface name, or ``None``.
513
return relation_to_role_and_interface(relation_name)[1]
517
def relation_to_role_and_interface(relation_name):
519
Given the name of a relation, return the role and the name of the interface
520
that relation uses (where role is one of ``provides``, ``requires``, or ``peers``).
522
:returns: A tuple containing ``(role, interface)``, or ``(None, None)``.
524
_metadata = metadata()
525
for role in ('provides', 'requires', 'peers'):
526
interface = _metadata.get(role, {}).get(relation_name, {}).get('interface')
528
return role, interface
533
def role_and_interface_to_relations(role, interface_name):
535
Given a role and interface name, return a list of relation names for the
536
current charm that use that interface under that role (where role is one
537
of ``provides``, ``requires``, or ``peers``).
539
:returns: A list of relation names.
541
_metadata = metadata()
543
for relation_name, relation in _metadata.get(role, {}).items():
544
if relation['interface'] == interface_name:
545
results.append(relation_name)
550
def interface_to_relations(interface_name):
552
Given an interface, return a list of relation names for the current
553
charm that use that interface.
555
:returns: A list of relation names.
558
for role in ('provides', 'requires', 'peers'):
559
results.extend(role_and_interface_to_relations(role, interface_name))
472
564
def charm_name():
473
565
"""Get the name of the current charm as is specified on metadata.yaml"""
474
566
return metadata().get('name')
544
636
return unit_get('private-address')
640
def storage_get(attribute=None, storage_id=None):
641
"""Get storage attributes"""
642
_args = ['storage-get', '--format=json']
644
_args.extend(('-s', storage_id))
646
_args.append(attribute)
648
return json.loads(subprocess.check_output(_args).decode('UTF-8'))
654
def storage_list(storage_name=None):
655
"""List the storage IDs for the unit"""
656
_args = ['storage-list', '--format=json']
658
_args.append(storage_name)
660
return json.loads(subprocess.check_output(_args).decode('UTF-8'))
665
if e.errno == errno.ENOENT:
666
# storage-list does not exist
547
671
class UnregisteredHookError(Exception):
548
672
"""Raised when an undefined hook is called"""
644
768
subprocess.check_call(['action-fail', message])
772
"""Get the name of the currently executing action."""
773
return os.environ.get('JUJU_ACTION_NAME')
777
"""Get the UUID of the currently executing action."""
778
return os.environ.get('JUJU_ACTION_UUID')
782
"""Get the tag for the currently executing action."""
783
return os.environ.get('JUJU_ACTION_TAG')
647
786
def status_set(workload_state, message):
648
787
"""Set the workload state with a message
675
814
def status_get():
676
"""Retrieve the previously set juju workload state
678
If the status-set command is not found then assume this is juju < 1.23 and
815
"""Retrieve the previously set juju workload state and message
817
If the status-get command is not found then assume this is juju < 1.23 and
821
cmd = ['status-get', "--format=json", "--include-data"]
683
raw_status = subprocess.check_output(cmd, universal_newlines=True)
684
status = raw_status.rstrip()
823
raw_status = subprocess.check_output(cmd)
686
824
except OSError as e:
687
825
if e.errno == errno.ENOENT:
826
return ('unknown', "")
830
status = json.loads(raw_status.decode("UTF-8"))
831
return (status["status"], status["message"])
693
834
def translate_exc(from_exc, to_exc):
694
835
def inner_translate_exc1(f):
695
837
def inner_translate_exc2(*args, **kwargs):
697
839
return f(*args, **kwargs)
736
878
subprocess.check_call(cmd)
881
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
882
def payload_register(ptype, klass, pid):
883
""" is used while a hook is running to let Juju know that a
884
payload has been started."""
885
cmd = ['payload-register']
886
for x in [ptype, klass, pid]:
888
subprocess.check_call(cmd)
891
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
892
def payload_unregister(klass, pid):
893
""" is used while a hook is running to let Juju know
894
that a payload has been manually stopped. The <class> and <id> provided
895
must match a payload that has been previously registered with juju using
897
cmd = ['payload-unregister']
898
for x in [klass, pid]:
900
subprocess.check_call(cmd)
903
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
904
def payload_status_set(klass, pid, status):
905
"""is used to update the current status of a registered payload.
906
The <class> and <id> provided must match a payload that has been previously
907
registered with juju using payload-register. The <status> must be one of the
908
follow: starting, started, stopping, stopped"""
909
cmd = ['payload-status-set']
910
for x in [klass, pid, status]:
912
subprocess.check_call(cmd)
915
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
916
def resource_get(name):
917
"""used to fetch the resource path of the given name.
919
<name> must match a name of defined resource in metadata.yaml
921
returns either a path or False if resource not available
926
cmd = ['resource-get', name]
928
return subprocess.check_output(cmd).decode('UTF-8')
929
except subprocess.CalledProcessError:
740
934
def juju_version():
741
935
"""Full version string (eg. '1.23.3.1-trusty-amd64')"""
800
994
for callback, args, kwargs in reversed(_atexit):
801
995
callback(*args, **kwargs)
999
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
1000
def network_get_primary_address(binding):
1002
Retrieve the primary network address for a named binding
1004
:param binding: string. The name of a relation of extra-binding
1005
:return: string. The primary IP address for the named binding
1006
:raise: NotImplementedError if run on Juju < 2.0
1008
cmd = ['network-get', '--primary-address', binding]
1009
return subprocess.check_output(cmd).strip()