Skip to content

Plugins

Griffen Fargo edited this page Apr 21, 2026 · 1 revision

Plugins

Strut ships a lightweight plugin system so operators can extend the CLI with project-specific commands without forking the engine. Drop a cmd_<name>.sh file in .strut/plugins/ under your project root and strut will discover it at startup and dispatch strut <name> or strut <stack> <name> to it.

Why plugins

Strut's core commands are deliberately generic. Teams often have workflows on top of them — a blessed deploy-and-announce pipeline, a custom metrics pull, a bespoke bulk rotation — that shouldn't live in the engine. Plugins give you the familiar strut … namespace for those workflows while keeping strut itself upgradeable.

Directory layout

my-project/
├── strut.conf
├── stacks/
│   └── api/
└── .strut/
    └── plugins/
        ├── cmd_ship.sh          # strut <stack> ship
        ├── cmd_announce.sh      # strut announce "..."
        └── cmd_metrics.sh       # strut <stack> metrics

Plugins live under ${PROJECT_ROOT}/.strut/plugins/. The filename must match cmd_<name>.sh; everything else is ignored. The <name> portion is the command name operators will type.

Plugin contract

Every plugin file must define plugin_main. plugin_help is optional but strongly recommended — it's what strut list plugins and strut help <name> show.

#!/usr/bin/env bash
# .strut/plugins/cmd_ship.sh
set -euo pipefail

plugin_help() {
  echo "Validate, deploy, then announce to #releases"
}

plugin_main() {
  local stack="$1"; local env_name="$2"; shift 2
  strut "$stack" validate --env "$env_name"
  strut "$stack" deploy   --env "$env_name"
  strut notify test slack
}

Argument conventions

  • Top-level pluginsstrut <plugin> [args…]. plugin_main receives the raw args.
  • Stack-level pluginsstrut <stack> <plugin> [--env <name>] [args…]. Strut parses the universal flags (--env, --services, --json, --dry-run) before calling you, then invokes plugin_main "$stack" "$env_name" "<remaining-args>". The first two positional args are always stack name and env name.

What your plugin can use

Plugins are sourced in a subshell after all of strut's libraries are loaded, so you have direct access to strut's helpers:

Helper Purpose
log, ok, warn, fail, error logging primitives
run_cmd, run_cmd_eval dry-run aware command execution
build_ssh_opts consistent SSH option builder
resolve_compose_cmd stack-scoped docker-compose project naming
out_table_header, out_table_row, out_table_render table output
out_json_object, out_json_field, … streamed JSON output

Calling strut itself is fully supported — plugins are re-entrant.

Discovery & dispatch

  1. After strut loads its core libraries, it scans ${PROJECT_ROOT}/.strut/plugins/ for cmd_*.sh files.
  2. The plugin registry records each name and file path — no sourcing yet.
  3. When a command name comes in:
    • Core commands always win. strut deploy, strut list, strut doctor etc. never get routed to a plugin of the same name.
    • Stacks always win. At the top level, if $1 is a stack directory, strut dispatches to the stack regardless of plugin names. You can't shadow a stack.
    • Otherwise, if a plugin matches, strut sources the file in a subshell and invokes plugin_main.
  4. strut list plugins renders the registry; strut help <plugin> runs the plugin's plugin_help.

Listing and help

strut list plugins             # table view
strut list plugins --json      # machine-readable
strut help ship                # runs plugin_help from cmd_ship.sh

Safety model

  • Subshell isolation. Plugin code runs in a (...) subshell. A plugin that calls exit 1, hits set -e, or gets killed does not crash strut.
  • Operator privileges. Plugins run with the same permissions as the shell invoking strut — no sandbox. Only ship plugins you trust.
  • Core precedence. A plugin named cmd_deploy.sh will never run as strut <stack> deploy. Plugin names must not collide with core commands (use cmd_ship.sh etc.).

Writing a plugin — end to end

$ mkdir -p .strut/plugins
$ cat > .strut/plugins/cmd_ship.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

plugin_help() {
  echo "Validate + deploy + post-deploy hook"
}

plugin_main() {
  local stack="$1" env="$2"
  log "shipping $stack to $env"
  strut "$stack" validate --env "$env"
  strut "$stack" deploy   --env "$env"
  ok "shipped $stack → $env"
}
EOF
$ strut list plugins
Plugins:

Name  Description                        File
----  ---------------------------------  --------------------------------
ship  Validate + deploy + post-deploy …  /path/.strut/plugins/cmd_ship.sh

$ strut api ship --env prod

Testing plugins

The lib/plugins.sh module is itself covered by tests/test_plugins.bats, which doubles as a working reference for plugin authors — it shows how to structure a plugin and assert its behavior end-to-end through the strut entrypoint.

Clone this wiki locally