Hook tool

See also: Hook, Ops

In Juju, a hook tool is a Bash script located in /var/lib/juju/tools/unit-<app name>-<unit ID> that a charm uses to communicate with its Juju unit agent in response to a hook.

In the charm SDK, in Ops, hook tools are accessed through Ops constructs, specifically, those constructs designed to be used in the definition of the event handlers associated with the Ops events that translate Juju hooks. For example, when your charm calls ops.Unit.is_leader, in the background this calls ~/hooks/unit-name/leader-get; its output is wrapped and returned as a Python True/False value.

Contents:

List of hook tools

This list replicates the output of juju help hook-tool and of juju help-tool <name of hook tool.

Informational

status-get

status-get allows charms to query the current workload status.

Further information

Without arguments, it just prints the status code e.g. ‘maintenance’. With --include-data specified, it prints YAML which contains the status value plus any data associated with the status.

Include the --application option to get the overall status for the application, rather than an individual unit.

Examples for accessing the unit’s status:

From bash
status-get

status-get --include-data

Examples for accessing the application’s status:

From bash
status-get --application

status-set

status-set changes what is displayed in juju status.

Further details

status-set allows charms to describe their current status. This places the responsibility on the charm to know its status, and set it accordingly using the status-set hook tool. Changes made via status-set are applied without waiting for a hook execution to end and are not rolled back if a hook execution fails.

The leader unit is responsible for setting the overall status of the application by using the --application option.

This hook tool takes 2 arguments. The first is the status code and the second is a message to report to the user.

Valid status codes are:

  • maintenance (the unit is not currently providing a service, but expects to be soon, e.g. when first installing)
  • blocked (the unit cannot continue without user input)
  • waiting (the unit itself is not in error and requires no intervention, but it is not currently in service as it depends on some external factor, e.g. an application to which it is related is not running)
  • active (This unit believes it is correctly offering all the services it is primarily installed to provide)

For more extensive explanations of these status codes, please see the status reference page.

The second argument is a user-facing message, which will be displayed to any users viewing the status, and will also be visible in the status history. This can contain any useful information.

In the case of a blocked status though the status message should tell the user explicitly how to unblock the unit insofar as possible, as this is primary way of indicating any action to be taken (and may be surfaced by other tools using Juju, e.g. the Juju GUI).

A unit in the active state with should not generally expect anyone to look at its status message, and often it is better not to set one at all. In the event of a degradation of service, this is a good place to surface an explanation for the degradation (load, hardware failure or other issue).

A unit in error state will have a message that is set by Juju and not the charm because the error state represents a crash in a charm hook - an unmanaged and uninterpretable situation. Juju will set the message to be a reflection of the hook which crashed. For example “Crashed installing the software” for an install hook crash, or “Crash establishing database link” for a crash in a relationship hook.

Examples for setting the unit’s status:

From bash
# Set the unit's workload status to "maintenance".
# This implies a short downtime that should self-resolve.
status-set maintenance "installing software"
status-set maintenance "formatting storage space, time left: 120s"

# Set the unit's workload status to "waiting"
# The workload is awaiting something else in the model to become active 
status-set waiting "waiting for database"

# Set the unit workload's status to "active"
# The workload is installed and running. Any messages should be informational. 
status-set active
status-set active "Storage 95% full"

# Set the unit's workload status to "blocked"
# This implies human intervention is required to unblock the unit.
# Messages should describe what is needed to resolve the problem.
status-set blocked "Add a database relation"
status-set blocked "Storage full"

Examples for setting the application’s status:

From bash
# From a unit, update its status
status-set maintenance "Upgrading to 4.1.1"

# From the leader, update the application's status line 
status-set --application maintenance "Application upgrade underway"

Non-leader units which attempt to use --application will receive an error

status-set --application maintenance "I'm not the leader."
error: this unit is not the leader

application-version-set

application-version-set allows you to specify which version of the application is deployed. This will be provided to users via juju status.

From bash
application-version-set 1.1.10

juju-log

juju-log writes messages directly to the unit’s log file. Valid levels are: INFO, WARN, ERROR, DEBUG

Examples

From bash
juju-log -l 'WARN' Something has transpired
From PowerShell
Import-Module CharmHelpers

# Basic logging
Write-JujuLog "Something has transpired"

# Logs the message and throws an error, stopping the script
Write-JujuError "Something has transpired. Throwing an error..."

Actions

action-fail

action-fail sets the action’s fail state with a given error message. Using action-fail without a failure message will set a default message indicating a problem with the action.

From bash
action-fail 'unable to contact remote service'

action-get

action-get will print the value of the parameter at the given key, serialized as YAML. If multiple keys are passed, action-get will recurse into the param map as needed. Read more about Juju Actions or how to write Juju Actions.

From bash
TIMEOUT=$(action-get timeout)

action-set

action-set adds the given values to the results map of the Action. This map is returned to the user after the completion of the Action. Keys must start and end with lowercase alphanumeric, and contain only lowercase alphanumeric, hyphens and periods.

From bash
action-set answer 42

Metrics

add-metric

add-metric may only be executed from the collect-metrics hook.

Records a measurement which will be forwarded to the Juju controller. The same metric may not be collected twice in the same command.

From bash
add-metric metric1=value1 [metric2=value2 
]

Networking

network-get

network-get reports hostnames, IP addresses and CIDR blocks related to endpoint bindings.

Further details

By default it lists three pieces of address information:

  • binding address(es)
  • ingress address(es)
  • egress subnets

See Network primitives for in-depth coverage.

open-port

Requires Juju 3.1+

open-port registers a port or range to open on the public-interface.

Further details

The behavior differs a little bit between machine charms and Kubernetes charms.

Machine charms. On public clouds the port will only be open while the application is exposed. It accepts a single port or range of ports with an optional protocol, which may be icmp, udp, or tcp. tcp is the default.

open-port will not have any effect if the application is not exposed, and may have a somewhat delayed effect even if it is. This operation is transactional, so changes will not be made unless the hook exits successfully.

Prior to Juju 2.9, when charms requested a particular port range to be opened, Juju would automatically mark that port range as opened for all defined application endpoints. As of Juju 2.9, charms can constrain opened port ranges to a set of application endpoints by providing the --endpoints flag followed by a comma-delimited list of application endpoints.

Kubernetes charms. The port will open directly regardless of whether the application is exposed or not. This connects to the fact that juju expose currently has no effect on sidecar charms. Additionally, it is currently not possible to designate a range of ports to open for Kubernetes charms; to open a range, you will have to run open-port multiple times.

Examples

From bash

Open port 80 to TCP traffic:

open-port 80/tcp

Open port 1234 to UDP traffic:

open-port 1234/udp

Open a range of ports to UDP traffic:
```bash
open-port 1000-2000/udp

Open a range of ports to TCP traffic for specific application endpoints (since Juju 2.9):

open-port 1000-2000/tcp --endpoints dmz,monitoring

close-port

close-port ensures a port, or port range, is not accessible from the public interface.

From bash
# Close single port
close-port 80

# Close a range of ports
close-port 9000-9999/udp

# Disable ICMP
close-port icmp

# Close a range of ports for a set of endpoints (since Juju 2.9)
close-port 80-90 --endpoints dmz,public

unit-get

unit-get is deprecated in favour of network-get hook tool. See Network primitives for details.

unit-get returns the IP address of the unit.

Further details

It accepts a single argument, which must be private-address or public-address. It is not affected by context.

Note that if a unit has been deployed with --bind space then the address returned from unit-get private-address will get the address from this space, not the ‘default’ space.

Examples:

From bash
unit-get public-address

opened-ports

opened-ports lists all ports or ranges opened by the unit. The opened-ports hook tool lists all the ports currently opened by the running charm. It does not, at the moment, include ports which may be opened by other charms co-hosted on the same machine lp#1427770.

Opening ports is transactional (i.e. will take place on successfully exiting the current hook), and therefore opened-ports will not return any values for pending open-port operations run from within the same hook.

From bash
opened-ports

Prior to Juju 2.9, when charms requested a particular port range to be opened, Juju would automatically mark that port range as opened for all defined application endpoints. As of Juju 2.9, charms can constrain opened port ranges to a set of application endpoints.

To ensure backwards compatibility, opened-ports will, by default, display the unique set of opened port ranges for all endpoints. To list of opened port ranges grouped by application endpoint can be obtained by running opened-ports --endpoints.

Configuration

config-get

config-get returns information about the application configuration (as defined by config.yaml). If called without arguments, it returns a dictionary containing all config settings that are either explicitly set, or which have a non-nil default value. If the --all flag is passed, it returns a dictionary containing all defined config settings including nil values (for those without defaults). If called with a single argument, it returns the value of that config key. Missing config keys are reported as nulls, and do not return an error.

From bash
INTERVAL=$(config-get interval)

config-get --all

Charm state

goal-state

goal-state queries information about charm deployment and returns it as structured data.

Further details

goal-state provides:

  • the details of other peer units have been deployed and their status
  • the details of remote units on the other end of each endpoint and their status

The output will be a subset of that produced by the juju status. There will be output for sibling (peer) units and relation state per unit.

The unit status values are the workload status of the (sibling) peer units. We also use a unit status value of dying when the unit’s life becomes dying. Thus unit status is one of:

allocating active waiting blocked error dying

The relation status values are determined per unit and depend on whether the unit has entered or left scope. The possible values are:

  • joining : a relation has been created, but no units are available. This occurs when the application on the other side of the relation is added to a model, but the machine hosting the first unit has not yet been provisioned. Calling relation-set will work correctly as that data will be passed through to the unit when it comes online, but relation-get will not provide any data.
  • joined : the relation is active. A unit has entered scope and is accessible to this one.
  • broken : unit has left, or is preparing to leave scope. Calling relation-get is not advised as the data will quickly out of date when the unit leaves.
  • suspended : parent cross model relation is suspended
  • error: an external error has been detected

By reporting error state, the charm has a chance to determine that goal state may not be reached due to some external cause. As with status, we will report the time since the status changed to allow the charm to empirically guess that a peer may have become stuck if it has not yet reached active state.

Examples

From bash
goal-state

Affecting the machine

juju-reboot

juju-reboot is not supported within actions

juju-reboot causes the host machine to reboot, after stopping all containers hosted on the machine.

Further details

An invocation without arguments will allow the current hook to complete, and will only cause a reboot if the hook completes successfully.

If the --now flag is passed, the current hook will terminate immediately, and be restarted from scratch after reboot. This allows charm authors to write hooks that need to reboot more than once in the course of installing software.

The --now flag cannot terminate a debug-hooks session; hooks using --now should be sure to terminate on unexpected errors, so as to guarantee expected behavior in all situations.

Examples:

From bash
# immediately reboot
juju-reboot --now

# Reboot after current hook exits
juju-reboot

Leadership

See also: Leadership hook tools

is-leader

is-leader indicates whether the current unit is the application leader.

Further details

is-leaderwill write "True" to STDOUT and return 0 if the unit is currently leader and can be guaranteed to remain so for 30 seconds.

Output can be expressed as --format json or --format yaml if desired.

Examples

From bash
LEADER=$(is-leader)
if [ "${LEADER}" == "True" ]; then
  # Do something a leader would do
fi

leader-get

leader-get prints the value of a leadership setting specified.

Further details

leader-get acts much like relation-get) but only reads from the leader settings. If no key is given, or if the key is “-”, all keys and values will be printed.

Examples:

From bash
ADDRESSS=$(leader-get cluster-leader-address)

leader-set

The functionality provided by leader data (leader-get and leader-set) is now being replaced by “application-level relation data”. See relation-get and relation-set.

leader-set immediately writes key/value pairs to the Juju controller. The controller will then propagate that data to other units via leader-get.

Further data

The hook tool will fail if called without arguments, or if called by a unit that is not currently application leader.

leader-set lets you distribute string key=value pairs to other units, but with the following differences:

  • there’s only one leader-settings bucket per application (not one per unit)
  • only the leader can write to the bucket
  • only minions are informed of changes to the bucket
  • changes are propagated instantly

The instant propagation may be surprising, but it exists to satisfy the use case where shared data can be chosen by the leader at the very beginning of the install hook.

It is strongly recommended that leader settings are always written as a self-consistent group leader-set one=one two=two three=three.

Examples:

From bash
leader-set cluster-leader-address=10.0.0.123

Relations

relation-get

relation-get reads the settings of the local unit, or of any remote unit, in a given relation (set with -r, defaulting to the current relation identifier, as in relation-set). The first argument specifies the settings key, and the second the remote unit, which may be omitted if a default is available (that is, when running a relation hook other than -relation-broken).

If the first argument is omitted, a dictionary of all current keys and values will be printed; all values are always plain strings without any interpretation. If you need to specify a remote unit but want to see all settings, use - for the first argument.

The environment variable JUJU_REMOTE_UNIT stores the default remote unit.

You should never depend upon the presence of any given key in relation-get output. Processing that depends on specific values (other than private-address) should be restricted to -relation-changed hooks for the relevant unit, and the absence of a remote unit’s value should never be treated as an error in the local unit.

In practice, it is common and encouraged for -relation-changed hooks to exit early, without error, after inspecting relation-get output and determining the data is inadequate; and for all other hooks to be resilient in the face of missing keys, such that -relation-changed hooks will be sufficient to complete all configuration that depends on remote unit settings.

Key value pairs for remote units that have departed remain accessible for the lifetime of the relation.

From bash
# Getting the settings of the default unit in the default relation is done with:
 relation-get
  username: jim
  password: "12345"

# To get a specific setting from the default remote unit in the default relation
  relation-get username
   jim

# To get all settings from a particular remote unit in a particular relation you
   relation-get -r database:7 - mongodb/5
    username: bob
    password: 2db673e81ffa264c

relation-ids

relation-ids outputs a list of the related applications with a relation name. Accepts a single argument (relation-name) which, in a relation hook, defaults to the name of the current relation. The output is useful as input to the relation-list, relation-get, and relation-set commands to read or write other relation values.

From bash
relation-ids database

relation-list

relation-list outputs a list of all the related units for a relation identifier. If not running in a relation hook context, -r needs to be specified with a relation identifier similar to therelation-get and relation-set commands.

From bash
relation-list 9

relation-set

relation-set writes the local unit’s settings for some relation. If it’s not running in a relation hook, -r needs to be specified. The value part of an argument is not inspected, and is stored directly as a string. Setting an empty string causes the setting to be removed.

relation-set is the tool for communicating information between units of related applications. By convention the charm that provides an interface is likely to set values, and a charm that requires that interface will read values; but there is nothing enforcing this. Whatever information you need to propagate for the remote charm to work must be propagated via relation-set, with the single exception of the private-address key, which is always set before the unit joins.

For some charms you may wish to overwrite the private-address setting, for example if you’re writing a charm that serves as a proxy for some external application. It is rarely a good idea to remove that key though, as most charms expect that value to exist unconditionally and may fail if it is not present.

All values are set in a transaction at the point when the hook terminates successfully (i.e. the hook exit code is 0). At that point all changed values will be communicated to the rest of the system, causing -changed hooks to run in all related units.

There is no way to write settings for any unit other than the local unit. However, any hook on the local unit can write settings for any relation which the local unit is participating in.

From bash
relation-set port=80 tuning=default

relation-set -r server:3 username=jim password=12345

Resources

resource-get

resource-get fetches a resource from the Juju controller or the Juju Charm store. The command returns a local path to the file for a named resource.

If resource-get has not been run for the named resource previously, then the resource is downloaded from the controller at the revision associated with the unit’s application. That file is stored in the unit’s local cache. If resource-get has been run before then each subsequent run synchronizes the resource with the controller. This ensures that the revision of the unit-local copy of the resource matches the revision of the resource associated with the unit’s application.

The path provided by resource-get references the up-to-date file for the resource. Note that the resource may get updated on the controller for the application at any time, meaning the cached copy may be out of date at any time after resource-get is called. Consequently, the command should be run at every point where it is critical for the resource be up to date.

# resource-get software
/var/lib/juju/agents/unit-resources-example-0/resources/software/software.zip

Storage

storage-add

storage-add adds storage volumes to the unit.

Further details

storage-add takes the name of the storage volume (as defined in the charm metadata), and optionally the number of storage instances to add. By default, it will add a single storage instance of the name.

Examples:

From bash
storage-add database-storage=1

storage-get

storage-get obtains information about storage being attached to, or detaching from, the unit.

Further details

If the executing hook is a storage hook, information about the storage related to the hook will be reported; this may be overridden by specifying the name of the storage as reported by storage-list, and must be specified for non-storage hooks.

storage-get can be used to identify the storage location during storage-attached and storage-detaching hooks. The exception to this is when the charm specifies a static location for singleton stores.

Examples:

From bash
# retrieve information by UUID
storage-get 21127934-8986-11e5-af63-feff819cdc9f

# retrieve information by name
storage-get -s data/0

storage-list

storage-list list storages instances that are attached to the unit.

Further details

The storage instance identifiers returned from storage-list may be passed through to the storage-get command using the -s option.

Examples:

Payloads

Please see payloads in Charm metadata for further details on how to use payloads within your charms.

payload-status-set

payload-status-set is used to update the current status of a registered payload. The class and id provided must match a payload that has been previously registered with juju using payload-register.

Valid payload status codes:

  • starting
  • started
  • stopping
  • stopped

Examples:

From bash
payload-status-set monitor abcd13asa32c starting

payload-register

payload-register informs Juju that a payload has started.

Further details

Used while a hook is running to let Juju know that a payload has been started. The information used to start the payload must be provided when “register” is run.

The payload class must correspond to one of the payloads defined in the charm’s metadata.yaml.

An example fragment from metadata.yaml:

payloads:
    monitoring:
        type: docker
    kvm-guest:
        type: kvm

Examples:

From bash
payload-register monitoring docker 0fcgaba

payload-unregister

payload-unregister reports that a payload has stopped.

Further details

It used while a hook is running to let Juju know that a payload has been manually stopped. The class and id provided must match a payload that has been previously registered with Juju using payload-register.

Examples:

From bash
payload-unregister monitoring 0fcgaba


Contributors: @achilleasa, @benhoyt, @charlie4284, @dmitrii, @jameinel, @pmatulis, @ppasotti, @timclicks, @tmihoc

1 Like

opened ports should certainly be under the Networking section.

I don’t know if there is a missing section to aggregate the relation-* functions like we do for Information/Actions/Metrics. Maybe Relations and Storage as sections. status-get status-set feel like they should be part of the Informational section.

Hi John, I repeatedly hit 502 errors when finishing this page. Looking to fix everything up now.

I’m trying to learn, digest and use relations for juju.

I’m now stuck at trying to figure out what documentation I should use. I tried first this one and found an error:

relation_set --> https://discourse.charmhub.io/t/hook-tools/1163

I’m trying the python code and end up in an error:

Code:

hookenv.relation_set({‘changed’ : “BOOOOM”})

Error:

2020-09-06 19:00:59 DEBUG jujuc server.go:211 running hook tool “relation-set”
2020-09-06 19:00:59 DEBUG master-relation-changed Traceback (most recent call last):
2020-09-06 19:00:59 DEBUG master-relation-changed File “/var/lib/juju/agents/unit-worker-4/charm/hooks/master-relation-changed”, line 29, in
2020-09-06 19:00:59 DEBUG master-relation-changed hooks.execute(sys.argv)
2020-09-06 19:00:59 DEBUG master-relation-changed File “/var/lib/juju/agents/unit-worker-4/charm/lib/charmhelpers/core/hookenv.py”, line 945, in execute
2020-09-06 19:00:59 DEBUG master-relation-changed self._hookshook_name
2020-09-06 19:00:59 DEBUG master-relation-changed File “/var/lib/juju/agents/unit-worker-4/charm/hooks/master-relation-changed”, line 24, in master_relation_changed
2020-09-06 19:00:59 DEBUG master-relation-changed hookenv.relation_set({‘changed’ : “BOOOOM”})
2020-09-06 19:00:59 DEBUG master-relation-changed File “/var/lib/juju/agents/unit-worker-4/charm/lib/charmhelpers/core/hookenv.py”, line 502, in relation_set
2020-09-06 19:00:59 DEBUG master-relation-changed relation_cmd_line + ["–file", settings_file.name])
2020-09-06 19:00:59 DEBUG master-relation-changed File “/usr/lib/python3.6/subprocess.py”, line 306, in check_call
2020-09-06 19:00:59 DEBUG master-relation-changed retcode = call(*popenargs, **kwargs)
2020-09-06 19:00:59 DEBUG master-relation-changed File “/usr/lib/python3.6/subprocess.py”, line 287, in call
2020-09-06 19:00:59 DEBUG master-relation-changed with Popen(*popenargs, **kwargs) as p:
2020-09-06 19:00:59 DEBUG master-relation-changed File “/usr/lib/python3.6/subprocess.py”, line 729, in init
2020-09-06 19:00:59 DEBUG master-relation-changed restore_signals, start_new_session)
2020-09-06 19:00:59 DEBUG master-relation-changed File “/usr/lib/python3.6/subprocess.py”, line 1295, in _execute_child
2020-09-06 19:00:59 DEBUG master-relation-changed restore_signals, start_new_session, preexec_fn)
2020-09-06 19:00:59 DEBUG master-relation-changed TypeError: expected str, bytes or os.PathLike object, not dict
2020-09-06 19:00:59 ERROR juju.worker.uniter.operation runhook.go:136 hook “master-relation-changed” (via explicit, bespoke hook script) failed: exit status 1

So, I’m not sure what I’m doing wrong. But the docs are not producing a working example.

I switched my attention therefore to this documentation for the same function:

relation_set —> https://charm-helpers.readthedocs.io/en/latest/api/charmhelpers.core.hookenv.html#charmhelpers.core.hookenv.relation_set

I see two documentation sources and they are not the same and I really cant see any example for the later one, which leaves me to a trial-and-error-reading-code-and-figuring-this-out-by-myself.

As I think that learning about relations and interfaces for juju is really KEY - I would consider updating the document very valuable and I’d love to help out here to get this properly in place so that we can train beginner charmers.

I’m working on a tutorial for this purpose, but the shape of the documentation is so bad at the moment its difficult to know what to reference for my links to documentation. So, yeah
 where should I turn here?

1 Like

You are not using the juju tool directly (relation-set) but the reactive python wrapper from charmhelpers:
https://github.com/juju/charm-helpers/blob/9b6222e1f22fecf883bc2ff98217277cdd268a43/charmhelpers/core/hookenv.py#L479

The python signature of that is:

def relation_set(relation_id=None, relation_settings=None, **kwargs):

I do think the docs on the function are a bit sparse, and don’t really tell you how to use it:

However, the expectation is that you would pass the relation data you want to set as part of either a dict to the “relation_settings” keyword, or just as kwargs.

eg either:

hookenv.relation_set(relation_settings={"changed": "BOOOM"})

or

hookenv.relation_set(changed="BOOOM")

The error you are getting is because relation_set is interpreting the first argument as the relation_id and trying to pass a dict to a subprocess that expects a string.

If you do want more information about Reactive charming then you’d want to look around here:

There is another python framework as well:

And some associated getting started docs:


I definitely agree that relations are key to the expressive power of Juju charms. I’m sorry that it wasn’t clearer how to interact with it correctly.

1 Like

Thanx @jameinel

I’m trying to put together a tutorial and would like to be able to reference good and working docs. This is essential for that work, since it otherwise need to contain alot of workaround instructions warnings, corrections etc.

The tutorial is about using hooks only to work with relations on a trivial level. Not using reactive or ops.

It proved to be much harder than I would have liked it to be, but it’s a good experience and I think it will be a good one following up on the other tutorials using hooks-only charms I’ve wrote up.

I would appreciate if some work could be offloaded to improving the docs for this purpose with more code examples and snippets since it really helps.

I found this list of hook tools partially overlapping this document Command hook-tools

Here is a third also overlapping The hook environment, hook tools and how hooks are run

A fourth source for hook-tools Juju documentation | Juju documentation

  • The above pages contains more hook-tools not documented here
  • This page is alot more useful.

Can this be merged?

1 Like