Live charm state manipulation with `jhack`

When developing a charm or an integration, you often find yourself in need of directly manipulating unit statuses, relation data, or other pieces of a charm’s state.

For example, if you’ve been fiddling with charm code or monkey-testing a development deployment and:

  • a relation ends up containing corrupted data
  • a charm ends up in an unexpected state
  • you want to on purpose corrupt some data to see if the charm can recover

Suppose you have a model called demo-1 containing a tester app. If you type jhack scenario snapshot tester/0 --format json, without arguments, you will obtain a printout containing the whole charm state: its relations (and their data), its networks, its stored state, its deferred events, containers, and so on.


To edit the relation data for some endpoint of this charm (or multiple at once, why not?), we pipe this out to a file and edit it in there:

jhack scenario snapshot tester/0 --format json --include r > ./tester-state.json

The --include r argument means we only want to collect relation state. This significantly speeds up execution.


We change the ‘host’ value of the demo-ingress relation’s local app databag and then run:

jhack scenario state-apply tester/0 ./tester-state.json

And the relation data gets updated!

If you want to see the commands that jhack state-apply is emitting on your behalf to do this magic, add a --dry-run flag and see:

Limitations and future work

Note that at the moment there are a few limitations to state-apply:

  • additive mutations only: in order to ‘remove’ a key-value pair from a databag, you need to explicitly, manually set the value to "" (the empty string).
  • I didn’t yet implement state-apply for secrets, (container) filesystem contents, deferred events and stored state.
  • you can only state-apply from the perspective of a single charm! This means that if you state-apply foo/0 ./mystate.json, you can only mutate what foo/0 could mutate. This means you have to respect leadership and general databag ownership rules: you can only write app data if you’re leader, and mutating databags that belong to remote units will have no effect: you need to state-apply on that unit in order to change its side of a relation.