How to add an action to a charm

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

Contents:

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:

pause:
  description: Pause the database.
resume:
  description: Resume a paused database.
snapshot:
  description: Take a snapshot of the database.
  params:
    outfile:
      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:

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

An administrator would invoke this action like so:

$ juju run <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. For more information on working with actions as an administrator see Juju OLM | How to manage actions.

When you run an action via juju run, you either select a specific unit to run the action, or all units in your Juju application. Inside the unit(s) that will run the action, how the action is run depends on the type of the charm. 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:

grant-admin-role:
  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.
  params:
    user:
      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):
        super().__init__(*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
            event.fail(
                f"Failed to run '{' '.join(cmd)}'. Output was:\n{granted.stderr.decode('utf-8')}"
            )
        else:
            # Set the results of the action
            msg = f"Ran grant-admin-role for user '{user}'"
            event.set_results({"result": msg})

Last updated 2 months ago.