Charming isn’t easy without the operator framework: hook tools abstractions, pebble bindings, charm libraries, custom events, stored state, test harness, … all gone. But it’s interesting to see where juju “ends” and operator framework “begins”.
Charm’s execution environment
Charms are run with the juju hook tools in the PATH
and with a bunch of environment variables that provide the juju context (try printing os.environ
from within a charm; see Charm environment variables).
To quickly experiment with some juju hook tools, try the following on a deployed charm:
juju run --unit my-app/0 -- ls /var/lib/juju/tools/unit-my-app-0 juju run --unit my-app/0 -- config-get juju run --unit my-app/0 -- status-get juju run --unit my-app/0 -- relation-get --help
The *.charm
file
If you take a bare charm,
import os
import sys
from subprocess import call
if __name__ == "__main__":
# /var/lib/juju/tools/unit-bare-0/ is already in the PATH,
# so can call hooks without full path.
# (os.environ is inherited to the callee.)
if hook_name := os.environ.get("JUJU_HOOK_NAME"):
call(["juju-log", "-l", "INFO", f"Hook: {hook_name}"])
elif action_name := os.environ.get("JUJU_ACTION_NAME"):
call(["juju-log", "-l", "INFO", f"Action: {action_name}"])
else:
call([
"juju-log",
"-l",
"ERROR",
"This is odd: JUJU_HOOK_NAME nor JUJU_ACTION_NAME are set!"
])
call(["status-set", "active", "Woohoo!"])
and pack it, you’d get a *.charm
file:
$ charmcraft pack
Packing the charm
Created 'bare_ubuntu-20.04-amd64.charm'.
Charms packed:
bare_ubuntu-20.04-amd64.charm
which is actually a zip file:
$ unzip -l bare_ubuntu-20.04-amd64.charm | grep -Ev 'venv/|__pycache__/'
Archive: bare_ubuntu-20.04-amd64.charm
Length Date Time Name
--------- ---------- ----- ----
102 2022-05-14 23:56 dispatch
140 2022-05-14 23:39 actions.yaml
250 2022-05-14 23:57 manifest.yaml
616 2022-05-14 23:45 metadata.yaml
697 2022-05-14 23:45 README.md
11358 2022-05-14 23:39 LICENSE
151 2022-05-14 23:39 config.yaml
102 2022-05-14 23:56 hooks/upgrade-charm
102 2022-05-14 23:56 hooks/start
102 2022-05-14 23:56 hooks/install
707 2022-05-14 23:55 src/charm.py
--------- -------
5547259 560 files
Packing introduces several new files (which are there for historical reasons),
dispatch
hooks/install
hooks/start
hooks/upgrade-charm
all of which have the exact same contents:
#!/bin/sh
JUJU_DISPATCH_PATH="${JUJU_DISPATCH_PATH:-$0}" PYTHONPATH=lib:venv \
exec ./src/charm.py
which is the charm-entrypoint
(ref) of the charm.
Empirically testing events emission sequence
If you deploy two instances of the bare charm:
juju deploy ./bare_ubuntu-20.04-amd64.charm bare1 --num-units 2
juju deploy ./bare_ubuntu-20.04-amd64.charm bare2 --num-units 2
and relate them
juju relate bare1:some-regular-provider bare2:some-regular-requirer
followed by cleanup:
juju remove-application bare1 bare2
then your log should say something along the lines of:
$ juju debug-log --ms --include unit-bare1-0 --replay
01:00:52.331 Hook: install
01:00:52.751 replicas:32: Hook: replicas-relation-created
01:00:53.121 Hook: leader-elected
01:00:53.507 Hook: config-changed
01:00:53.863 Hook: start
01:00:54.235 replicas:32: Hook: replicas-relation-joined
01:00:54.596 replicas:32: Hook: replicas-relation-changed
01:02:21.910 some-regular-provider:34: Hook: some-regular-provider-relation-created
01:02:22.273 some-regular-provider:34: Hook: some-regular-provider-relation-joined
01:02:22.617 some-regular-provider:34: Hook: some-regular-provider-relation-changed
01:02:22.970 some-regular-provider:34: Hook: some-regular-provider-relation-joined
01:02:23.357 some-regular-provider:34: Hook: some-regular-provider-relation-changed
01:09:08.241 replicas:32: Hook: replicas-relation-departed
01:09:08.632 some-regular-provider:34: Hook: some-regular-provider-relation-departed
01:09:08.965 some-regular-provider:34: Hook: some-regular-provider-relation-departed
01:09:09.310 some-regular-provider:34: Hook: some-regular-provider-relation-broken
01:09:09.731 Hook: stop
01:09:10.092 Hook: remove
where you can empirically convince yourself that, for example:
- “peer relation created” may fire as early as next after install.
- “peer relation depated” never fires
- “regular relation departed” fires per related unit
- “regular relation broken” fires per app
Not necessarily python operators
Since the juju context is provided via environment variables, operators don’t have to be written in python:
but modern charms are all written in python using the operator framework.