Sometimes you want to have a simple charm to test some behaviour or juju interaction; but going through the whole
charmcraft init --> edit --> charmcraft pack --> deploy is just a little bit too much trouble.
Suppose you could, instead, write this:
import functional @functional.charm def foo(self: CharmBase, logger: logging.Logger = None): from ops.model import ActiveStatus return ActiveStatus('welcome to functional charms')
And then run:
jhack charm func /path/to/my-charm.py -d foo
The script will inject the code you decorated with
@functional.charm into a templated charm’s
__init__ method and deploy it under application name
The unit takes the usual time to come up, but deploying the charm (including packing) are reduced to less than a second!
This hack is made with a specific use case in mind, namely testing very simple logic and relation data, and generating once a reusable template. As a consequence there are several limitations.
At the moment the charm template is static and so is the venv attached to it, meaning that you can’t use in the functional charm any dependencies that don’t come with the template.
Of course you can
charmcraft pack a charm that you wish to promote to template, and then do:
jhack charm func /path/to/my-charm.py -t /path/to/template.charm -d foo
Which again is a one-off investment.
Secondly, this design implies that any modules you’ll require in the charm will need to be imported inside the function.
import bar @functional.charm def foo(self: CharmBase, logger: logging.Logger = None): from ops.model import ActiveStatus bar.do_things() # will raise NameError('bar') return ActiveStatus('welcome to functional charms') @functional.charm def foo(self: CharmBase, logger: logging.Logger = None): import bar from ops.model import ActiveStatus bar.do_things() # will work! return ActiveStatus('welcome to functional charms')
We could work on automating this part, or facilitating injecting a whole charm.py file in the template instead of a single function.
The metadata is also static: so if you wish to access any event except the core lifecycle ones (e.g. relation events, actions, pebble-ready) you’ll have to get the necessary yaml specs manually in place in the running unit or the packed charm, or tweak the template.
I think adding support for this should not be too hard.
In order to mark a function as an event handler like so:
_on_foo needs to point to a method of ‘self’. Not a function, not a lambda.
So at the moment that is also not supported. Unless of course you go and tweak the template.
I plan on adding some support for this in the near future.