This command used to be called jhack crpc
but has been renamed to the more apt script
.
the jhack crpc
name has been assigned to a different command.
Say you’re developing a charm, or you’re facing an issue with a deployment that you want to inspect a bit better.
It’s often useful to know what data a charm has access to. What relations can it see? What’s the result of calling that one method? What’s the self.foo.library_is_ready()
method returning?
Also, it can be useful to manipulate some of that data by hand.
The traditional ways of accessing this information, in order of hackiness, include:
- adding a debug log to the charm file, repacking the charm, redeploy/refresh, read the
juju debug-log
to see what’s up (orjhack sync
+jhack fire
as a faster alternative) - using
juju debug-code
topdb
into the process and inspect the variables live - Using
juju exec
to issue direct hook tool calls such asjuju exec --unit foo/0 relation-set ...
Enter jhack script
jhack script
is my take on how to quickly execute something charm-side, without having to submit to Juju event’s model (aka without having to wait) and without having to resort to low-level manual hook tool calls.
The base idea is: you write a script, for example:
def main(charm):
relations = charm.model.relations['bar']
for relation in relations:
print(relation.app, relation.data[relation.app]['key'])
relation.data[relation.app]['other-key'] = 'value'
then you do jhack script myapp/0 --input ./path/to/script.py
and you see the output of that script.
Easy!
How about executing a method on a charm? Also easy.
def main(charm):
relations = charm.model.relations['bar']
for relation in relations:
charm._do_something_with(relation, arg=True, kwarg=False)
What’s really going on
Normally, a charm executes when it receives an event. Or when you juju exec ./dispatch
. In this case, jhack
is generating an alternative dispatch script, one that sets up the framework and instantiates the charm, but does not fire any event on it. Instead of passing it to ops.main.main
, it passes the charm instance to the function you defined!
jhack
scp’s this script and the modified dispatch to the unit, juju exec
s the modified dispatch script and the rest is history (also known as stdout
).
Use cases
Inspiration for this command was me wanting to prototype a generic debugging action for charms, that is, a script that would collect data using the same API that a charm uses to work with it in the first place. Why use juju show-unit
when you already know ops’ API and can do relation.data[relation.app]
instead?
Secondly, I also see a role for a battery of custom scripts that we can co-host with our charm repositories to use when things go south. For example a ‘get this charm unstuck’ script for when you’re developing a feature…
Finally, think about how you can use this when you’re developing for example a charm library, or a routine that needs to take the charm instance as input.
class MyCharmLib(ops.Object):
...
def main(charm):
lib = MyCharmLib(charm)
if lib.some_api_call(...):
return 42
return 41
Or whatever.
Either way, it’s pretty nice.
Oh, and did I mention that if you do jhack script appname --input foo.py
it will run the script on all units of that application?
Impress your friends with pipes
As a side note, jhack script
can read scripts from stdin, for the quick and dirty party tricks / demo hacks.
Thoughts on future work
It would be interesting to add functionality to make scripts ‘sticky’, i.e. upload them to the unit and have them run as a pre/post event hook on every subsequent dispatch call. This would however mean adding some serious complexity and potential for errors.
Also see: Introducing `jhack crpc`: aka python evaluation on live charms