Handling Actions

Actions are methods defined by a charm developer designed to be invoked by an administrator against a deployed charm. They are commonly used to expose maintenance or operations tasks, such as creating a snapshot of a database, adding a user to a system or dumping debug information.

Actions are defined in the actions.yaml file. As with configuration, each action is defined as a top-level key of a YAML map. The name of the key is the name of the action, and corresponds to a map of fields that define the action.

Action names are validated to ensure they do not collide with Python keywords, and that they are valid identifiers:

  • Identifiers contain only alphanumeric characters or underscores
  • First character must not be a digit
  • Action names may also include hyphens

Each action should define a description, and can optionally define some parameters in a YAML map named params, which is a JSON Schema transformed into a YAML map. Parameters can contain nested JSON schema.

Some notes on the use of JSON Schema in actions.yaml:

- The $schema and $ref keys from JSON schema are not currently supported
- The additionalProperties and required keys from JSON Schema can be used at the top-level of an action (adjacent to description and params), but also used anywhere within a nested schema

Example action definitions

The following illustrate some example action definitions:

Simple action example

The following shows a simple example of an actions.yaml file, defining three actions named pause, resume, and snapshot. The snapshot action takes a single string parameter named outfile:

  description: Pause the database.
  description: Resume a paused database.
  description: Take a snapshot of the database.
      type: string
      description: The filename to write to.

Complex action example

The following example showcases a more complex configuration file that uses a nested schema to define detailed options. It also mandates that the action should not run if extra parameters are provided (additionalProperties: false) and makes the filename field mandatory:

  description: Pause the database.
  description: Resume a paused database.
  description: Take a snapshot of the database.
      type: string
      description: The name of the snapshot file.
      type: object
      description: The type of compression to use.
          type: string
          enum: [gzip, bzip2, xz]
          description: Compression quality
          type: integer
          minimum: 0
          maximum: 9
  required: [filename]
  additionalProperties: false

An administrator would invoke this action like so:

$ juju run-action <unit> snapshot filename=out.tar.gz compression.kind=gzip

You can opt-in to a nicer actions user experience at the command line by using a feature flag with Juju. Read more on Discourse. This will become the default in future releases of Juju.

Action handling

Actions are surfaced as events that a charm can observe, similarly to lifecycle events. Action names are parsed by the Charmed Operator Framework and surfaced as events named <action_name>_action.

If hyphens are used in action names, they are replaced with underscores in the corresponding event names. For example, an action named snapshot-database would result in an event named snapshot_database_action being triggered when the action is invoked.

Action handlers are passed an ActionEvent as their first parameter, and thus have access to the action parameter values through the <event>.params construct. In addition to the params dict, the ActionEvent class provides three convenience methods to the developer:

  • <event>.fail(message=""): Report to the administrator that the action has failed, optionally providing a message indicating why the failure occured.
  • <event>.log(message): Log a message for the administrator (available while the action is running).
  • event.set_results(results): Report the result of the action, where results is a dictionary.

Messages are displayed to administrators in real time when they run juju run-action with the --wait flag. Otherwise, results and log messages can be queried retrospectively with juju show-action-results. There is more information about working with actions as an administrator in the Juju docs.

It is important to consider where your action will be run. If your charm is a machine charm, actions are executed on the same machine as the application. If your charm is a Kubernetes charm implementing the sidecar pattern, the charm action is run in the charm container.

Action handling example

For the following actions.yaml:

  description: >
    Grant the "system_admin" role to a user. The user will need to log out and
    log back in to realise their permissions upgrade.
      type: string
      description: The user to grant "system_admin" role to.
  required: [user]

A charm developer could handle the action invocation like so:

class ActionsCharm(CharmBase):
    def __init__(self, *args):
        # ...
        self.framework.observe(self.on.grant_admin_role_action, self._on_grant_admin_role_action)
        # ...

    def _on_grant_admin_role_action(self, event):
        """Handle the grant-admin-role action."""
        # Fetch the user parameter from the ActionEvent params dict
        user = event.params["user"]
        # Do something useful with it
        cmd = ["/usr/bin/myapp", "roles", "system_admin", user]
        # Set a log message for the action
        event.log(f"Running this command: {' '.join(cmd)}")
        granted = subprocess.run(cmd, capture_output=True)
        if granted.returncode != 0:
            # Fail the action if there is an error
                f"Failed to run '{' '.join(cmd)}'. Output was:\n{granted.stderr.decode('utf-8')}"
            # Set the results of the action
            msg = f"Ran grant-admin-role for user '{user}'"
            event.set_results({"result": msg})