Little known feature: workload => juju communication

Most people that have used Juju for a little while are quite familiar with the concept of the juju agents executing hooks in the charms to respond to juju lifecycle events.

What fewer people know is a command available on the workload machines to interact back with juju.

juju-run [options] [<unit-name>] <commands>

The juju-run command executes the command in a hook context of the specified unit.

How this works

The unit agent has an abstract domain socket open named after the unit itself. The juju-run executable connects to this socket and passes through the commands to run.

The agent then acquires the hook execution lock and creates a hook context, and then shells back out in that context to execute the commands using bash -s.

Note: Since juju-run executes the command in a hook context, the command runs as root. As such, the juju-run comand must be executed by root or through sudo.

Status Updates

One initial use-case for this was to be able to provide out of band status updates from the workload. As an example, letā€™s say I have an application deployed called foo, and there is a script that was installed by the charm that has a cron job that wants to set the status based on external events to the server.

juju-run foo/0 "set-status maintenance 'updating the frobulator'"

This status would be observable in juju status as soon as it is run. This allows charms to provide more immediate feedback rather than waiting for the periodic update-status hook call from the agent.

Webhooks

An additional use-case is for the charm to set up a webhook. For example providing updates to a charm on an external github update.

Letā€™s say we have a charm that runs a simple webhook service. The charms makes the webhook endpoint available to the public internet and is then registered with an external provider, like github.

When the webhook is called, the data payload of the webhook can be used to set configuration for the charm. The key here would be to use the leadership data bag. Only the leader can write into it, so some care is needed on deploy and messaging.

# hash variable set from data payload
juju-run foo/0 "leader-set hash=$hash"

This change would then cause the leader-settings-changed hook to be executed and the normal charm operations could then use that new hash value to go and pull from the upstream branch.

4 Likes

Couple of examples in the wild:

1 Like

Great post, @thumper!

One of the things I use juju-run for regularly when troubleshooting issue is introspecting and managing relation data, as using juju run from the juju client requires many obscure environment variables to be set in order to make relation hooks work.

Letā€™s say you want to introspect the data that application foo unit 1 is sharing to unit 0 via itā€™s relation with an ID of ā€œ2ā€. First find the relation ID (I like to use the /var/lib/juju/agents/unit-X/state/relations directory as a shortcut to find the relation IDs and related units on the machine running the unit, but if there are multiple relations between apps, you may need to use ā€˜juju-run unit/X ā€œrelation-ids $interface_nameā€ā€™ and ā€˜juju-run unit/x "relation-list -r "ā€™ commands to find the specific relation to the unit you want) then you can run:

juju ssh foo/0
sudo juju-run foo/0 -r 2 --remote-unit foo/1 'relation-get [optional_key]'

Similarly, if you need to update relation data (perhaps during a debugging/troubleshooting session):

juju ssh foo/0
sudo juju-run foo/0 -r 2 --remote-unit foo/1 'relation-set key=value'

This is great, thanks! :slight_smile:

I want to note, too, for others that might not know yet ( like I didnā€™t a second ago ), that this doc, especially the ā€œHook Toolsā€, section tells you all of the commands that you have access to in the juju-run context.

2 Likes

I have two requests:

  1. Add this to the hook tools reference page (probably why itā€™s little-known :wink:)

  2. Add support to juju-run for the {unit}/leader syntax that the external CLI supports (e.g., juju-run foo/leader "leader-set hash=$hash"). Since juju-run canā€™t actually invoke something on another machine, presumably this would just turn into a no-op on non-leaders, but it would still be useful since the background service or whatever canā€™t actually tell if itā€™s running on the leader without a hook context. (Otherwise, the charm potentially has to add and remove services every time leadership changes, or I guess create a local cache of the leadership flag.)

Good point. Iā€™ll get @timClicks on it.

I think if you just used the unit name, and called leader-set, it would just fail on those units that arenā€™t the leader. If you donā€™t care about the return value, that is up to the caller.

You could always do it as:

juju-run "is-leader || leader-set hash..."

If is-leader returns false, then leader-set wonā€™t be run. I always find the syntax a bit odd, but it does work, you can also do:

juju-run "if is-leader; then leader-set hash; fi"
2 Likes