Implementing actions in Juju charms
Actions are scripts, binaries, or other executables defined on a charm which may
be invoked or queued remotely by the user on demand. For example, the Charm
author might include a snapshot
Action on a database Charm.
See here for more on Actions and their use.
The user may give arguments when invoking the action. Complex, nested
arguments are possible. The charm uses a file named actions.yaml
to specify
the type and parameters of these arguments. On the
Juju GUI, the UI for an action invocation will be
automatically built based on actions.yaml
.
Action Tools may be used by the author to define how Actions interact with Juju. Actions can retrieve params passed by the user, set responses in a map, or set a failure status with a message.
Defining Actions
To define the Actions available on a Charm, executables (scripts, binaries,
etc.) for each Action must be included in the /actions
directory in the Charm,
and the name of each executable must be a top-level key in a YAML map in
/actions.yaml
in the Charm root:
Charm dir:
├── actions │ ├── pause │ ├── resume │ └── snapshot ├── actions.yaml ...
actions.yaml
defining three rudimentary Actions:
pause: description: Pause the database. resume: description: Resume a paused database. snapshot: description: Take a snapshot of the database.
Actions may be called with parameters. These parameters are specified as a
child YAML map under a params
key for each Action in actions.yaml
. Note
that Actions support JSON-Schema to enable nested
schemas, and many other features.
A simple Action schema showing the use of params
snapshot: description: Take a snapshot of the database. params: outfile: type: string description: The filename to write to. required: [outfile] additionalProperties: false
See below for a more detailed example.
Schema Requirements
actions.yaml
must be included in the charm root, and must conform to the
following requirements:
- Each Action is defined as a top-level key of a YAML map, with the same name as the script it defines.
- The value of each Action must be a map, which should include a
description
key and may include aparams
key to define a schema. If nodescription
is given, a default empty description will be used. - The value of
params
, if it is included, must be a YAML map. Each key underparams
must be a string, and must have valid JSON-Schema as its value, transformed to a YAML map. JSON-Schema may be nested to create complex schemas. - JSON-Schema defines special keys such as
required
andadditionalProperties
, which may be given for the whole action at the same level asdescription
andparams
, or within nested schemas at the usual level. - At this time, the
$schema
and$ref
keys are not supported by Juju, as they may trigger resolution of remote objects and other issues. additionalProperties: false
should be included at the same level as thedescription
key if additional params passed by the user should be rejected.
Example Schema
Charm dir:
├── actions │ ├── report │ └── snapshot ├── actions.yaml ...
actions.yaml example
# actions.yaml report: description: Make a report of the system status. 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
This schema would support a call such as:
juju action do mysql/0 snapshot filename=out.tar.gz compression.kind=gzip
Action Tools
Three tools are provided to the Action author for the Action to interact with Juju:
action-get
retrieves the params passed when invoking the Action.action-set
sets values in a map to be returned after the Action finishes.action-fail
sets the Action status tofailed
when it finishes, with a message to be returned to the user. The results set byaction-set
are preserved.
Use juju help-tool <tool name>
to see more detail on each tool.
action-get
action-get
prints the value of the parameter at the given key, serialized
according to the --format
option. If multiple keys are passed, action-get
will recurse into the param map as needed.
For example, if an action named snapshot
was defined on a mysql charm, and was
invoked by the user as follows:
juju action do mysql/0 snapshot outfile="foo"
then the snapshot
could use action-get
to retrieve the filename as follows:
#!/bin/bash # An Action named "snapshot" action-get outfile # "foo" will be printed
action-set
action-set
permits the Action to set results in a map to be returned at
completion of the Action.
Example:
#!/bin/bash # An Action named "report" action-set result-map.time-completed="$(date)" result-map.message="Hello world!" action-set outcome="success"
Results for this Action
juju action fetch <some action ID> # ... message: "" # No error message. results: result-map: time-completed: <some date> message: Hello world! outcome: success status: completed
Note that there are some restrictions on the names you can give to action keys: they must start and end with lowercase alphanumeric characters, and only contain lowercase alphanumeric characters, the hyphen "-" or full stop"." characters.
action-fail
action-fail
causes the Action to finish as failed
after completion. This
might be used to indicate a full disk if a database dump was attempted, or
perhaps to indicate that a remote service was unable to be resolved. The
results set by action-set
before or after failure are retained, and an Action
fail status cannot be unset.
Example:
# An Action named "sayhello" command=$(action-get command) action-set received.value="$command" if [ "$command" != "hello" ]; then action-fail "I only know one command." action-set received.known="no" else action-set received.known="yes" fi action-set timestamp="$(date)"
juju action do <unit> sayhello command="greetme" # ...
Results
message: I only know one command. results: received: known: no value: greetme timestamp: Thu Jan 15 13:28:25 EST 2015 status: failed
Params Transformation
This section specifies the transformation from params
to JSON-Schema. This is
meant as a clarifying aid to creating more complex schemas and should not be
necessary for most Actions. If that is what the reader seeks, read on!
The params
key of an action in actions.yaml
defines a YAML map which is
transformed to JSON-Schema as follows:
# actions.yaml <string A>: [description: <string AB>] [params: <string PAA>: <YAML map YAA> <string PAB>: <YAML map YAB>] [string C]: X [string D]: Y [string ...]: Z <string B>: ...
will define an Action for <A>
(and <B>
, <C>
, and so on) with JSON-Schema:
{ "description": "<AB>", "title": "<A>", "type": "object", "properties": { "<PAA>": {YAA as JSON}, "<PAB>": {YAB as JSON} }, "required": ["filename"], "<C>": X, "<D>": Y, ... }
which is valid JSON-Schema.
Simple YAML transform example
For example, a simple schema such as:
# actions.yaml snapshot: description: Take a snapshot of the database. params: outfile: type: string
will be transformed to an Action with JSON-Schema:
{ "description": "Take a snapshot of the database.", "title": "snapshot", "type": "object", "properties": { "outfile": { "type": "string" } } }
More complex YAML transform example
The first example given at the top of this document would transform as follows:
# actions.yaml report: 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
becomes two Actions, with respective JSON-Schema:
report:
{ "description": "No description", "title": "report", "type": "object", "properties": {} }
snapshot:
{ "description": "Take a snapshot of the database.", "title": "snapshot", "type": "object", "properties": { "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"] }, "description": { "type": "integer", "description": "Compression quality", "minimum": 0, "maximum": 9 } } } }, "required": ["filename"], "additionalProperties": "false" }