Skip to main content

Custom Command Action

A Custom Command Action is an action with a type of "command-v4" (for executing on IPv4), or "command-v6" (for executing on IPv6). When this action is either started or stopped, the script at the path key is executed with additional data passed to it according to the format key.

The following runtime environments are already installed and available:

  • bash
  • expect
  • perl (Version 5)
  • python3 (Python 3.9)

In addition, any binary compiled for Enterprise Linux 9 can be executed.

info

It's recommended that most custom scripts intended to be executed by this action type are marked executable and placed in /usr/local/bin on Director's filesystem.

####################
# Custom Command Action
####################
# Run a command when this workflow starts and stops
# Command will be run in a clean environment.
[actions.CommandExample]
# This script will be sent IPv4 data from the triggering workflow
type = "command-v4"
# If you want IPv6 data instead, use this type
# type = "command-v6"

# Executable to run when trigger is met.
path = "/usr/local/bin/do_a_thing"

# Changes how data is passed to executable
# Valid formats:
# - text
# - json
# Optional, default "text"
format = "json"

# Cancel the command if it takes longer than this amount of time
# Optional, but recommended. If not specified, wait forever.
# See `Duration` below.
timeout = "1 minute"

# Just log, don't actually run command
# Optional, default is false
no-op = true

Keys​

The following sets of keys can be configured for a Custom Command Action:

  • path: This is a String, representing the path on Director's filesystem where the executable associated with this action exists.

  • format: This is an optional String value, defaulting to "text", that affects how data is passed over to the executable at path.

    • "text": When this value is used, the command at path is executed with the following positional arguments:
      1. Action ID: The UUID of this instance of the action as tracked in the Director CLI.
      2. Trigger ID: The UUID of the trigger that started this action, as tracked in the Director CLI.
      3. Status: The triggering condition, either "start" or "stop".
      4. Workflow: The name of the workflow that triggered this action, as defined in main configuration.
      5. Action: The name of the action being triggered, as defined in main configuration.
      6. Destination Type: A string, either "host" or "zone", indicating what information is in destination.
      7. Destination: If Destination Type is "host", this will be a string of the specific destination IP (IPv4 if the action type is "command-v4", and IPv6 if the action type is "command-v6"). If it is "zone", it will be a comma separated string of the subnets (IPv4 for "command-v4", and IPv6 for "command-v6") that comprise the selected Zone. If a zone only has subnets from one address family type, this means a Custom Command action from the opposite address family will never actually execute, as it will never have a valid destination value.
      8. Zone ID: The numeric Zone ID of the Zone that directly contains the destination.
      9. Zone Name: The textual representation of the Zone Name.
      10. Group ID: The numeric Group ID of the Group that directly contains the destination.
      11. Group Name: The textual representation of the Group Name.
      12. Reasons ...: For an action with a "start" status, this will be one or more text strings describing why the workflow has triggered for this entity.

    An example invocation of a "command-v4" in the recommended script location would thus look like this:

    /usr/local/bin/dummy_command.sh "36a75f2a-75de-4098-b645-ac3cbbeb6f81" "9b14436d-c5af-427e-baeb-0cfb2151a884" "stop" "Cmd" "LogCmd" "zone" "10.107.0.0/24,10.107.1.0/24,10.107.2.0/24" "10" "Test Zone" "7" "Test Group" "InPackets 20 >= 10"
    • "json": When this value is used, the executable at path is started with a JSON document passed to standard input. The JSON uses the same values documented above for the "text" format. The Reasons item will always be a list. Destination will also be a list when Destination Type is "zone", and a single IP string otherwise. An example would be:
    {
    "action": "LogJsonCmd",
    "action_id": "517b861b-06c2-4ccb-94c5-2a2d5ac65c4b",
    "destination": [
    "10.107.0.0/24",
    "10.107.1.0/24",
    "10.107.2.0/24"
    ],
    "destination_type": "zone",
    "group_id": 7,
    "group_name": "Test Group",
    "reasons": [
    "InPackets 20 >= 10"
    ],
    "status": "stop",
    "trigger_id": "9b14436d-c5af-427e-baeb-0cfb2151a884",
    "workflow": "Cmd",
    "zone_id": 10,
    "zone_name": "Test Zone"
    }
  • timeout: An optional duration value indicating how long Director will wait for the command to successfully execute. If not set, this will wait forever. This should be set defensively to ensure that any remote actions have a reasonable amount of time to execute, but not be waiting for excessive amounts of time.

  • no-op: An optional value, defaulting to false, that when configured logs how the command would have been executed, but it does not actually run the script. This can be useful for testing and debugging your custom scripts.

Caveats​

The only guarantee for a particular Custom Command action is that it will always perform the start iteration of the script before the stop iteration of the script. It is possible for multiple starts or stops to be executed in parallel, or overlapping starts and stops of different workflows to execute in parallel. This means the defined executable could have multiple overlapping invocations at any point in time.

If your script could modify a shared resource or something else that could be subject to the interceding update problem (e.g. if the script is modifying the config of a remote router), you may want to use a wrapper script with a file lock context of some kind, such as the flock utility (which is available on Director). A simple example:

#!/bin/bash

# Ideally put this script and long.sh in /usr/local/bin
# The `action`'s `path` would then point to `/usr/local/bin/this_script.sh`
LONG_RUNNING_SCRIPT=./long.sh

# Use the script as a lock file, and then run it.
# This means that only one instance of LONG_RUNNING_SCRIPT will
# be launched at a time.
flock $LONG_RUNNING_SCRIPT $LONG_RUNNING_SCRIPT "$@"