A plan is an ordered list of steps. Each step names the hosts it targets and the tools to run against them. Running a plan executes its steps in order, and for each step, its work on every target.
name: Patch nginx
steps:
- name: Back up the current config
targets: [production]
tool: file.backup
args:
remote_path: /etc/nginx/nginx.conf
backup_path: /etc/nginx/nginx.conf.bak
- name: Reload
targets: [production]
tool: nginx.reloadSteps and targets#
Every step carries its own target list, so a plan can touch different hosts at different stages: back up on one host, deploy on three, verify on a load balancer.
Each target is matched against the plan's own workspace, in this order:
- A server name: that server.
- An environment name or slug: every server in that environment. If two environments share a name, use the slug.
- The address of exactly one registered server: that server, run under its name. An address shared by several servers is refused; use a server name.
Anything else is refused before the run starts, with a message naming the target: a hostname that is not in your inventory, an environment with no servers, a name from another workspace. Opafra connects only to servers you registered, because it needs their credentials, their bastion and their environment, and an unmatched name would otherwise skip the approval gate of the environment it was meant to reach.
A target can also be {{ vars.x }}, or {{ targets }} for the targets run variable,
filled in from plan variables or run overrides when the run starts. Environment values,
secrets and facts cannot pick targets, because the environment is worked out from the
targets. The value can hold several names, as a list or separated by commas, and each
one is then matched as above.
A step runs against each of its servers in turn, and a server named twice runs once.
Variables#
Variables come from three places, and the later ones win:
- Environment values, belonging to the environment each host is in
- Plan variables, defined on the plan
- Run overrides, supplied when the run is started
Reference a variable as {{ vars.name }}. Three narrower namespaces exist when you need
to be specific about where a value came from:
| Reference | Resolves to |
|---|---|
{{ vars.x }} | The merged value, run override over plan over environment |
{{ env.x }} | This host's environment values only |
{{ inputs.x }} | Run overrides only |
{{ outputs.x }} | A value captured from an earlier step |
{{ facts.x }} | What Opafra observed about this host, such as the OS family |
A variable that never resolves is empty at run time. A dry run lists every unresolved reference it found, which is the quickest way to catch one.
Secrets in arguments#
A secret:// reference in a command or any other argument, including one reached through
a secret environment variable, is resolved when the step runs, on each host in turn: an
environment secret only for hosts in that environment. The value is masked as
[REDACTED] in the live console and the stored log, and the command is recorded with the
reference rather than the value. A reference that does not resolve fails the step before
anything is sent to the host. See secret references.
Typed inputs#
A plan can declare the inputs it expects, each with a type, whether it is required, a default, and optionally a fixed set of choices. Whoever starts a run is then prompted for them rather than having to know which variable names matter.
An input marked secret is treated as one: it is not echoed back and it is redacted in previews and logs.
Plans without declared inputs still accept free-form run variables, so this is an addition rather than a requirement.
Capturing output#
A step can store its output into a variable that later steps read:
- name: Read the running version
targets: [web-01]
tool: ssh.cmd
args:
command: nginx -v 2>&1
capture:
var: nginx_version
mode: regex
pattern: "nginx/([0-9.]+)"Four modes: raw keeps stdout as text, json parses it and can drill in with a dotted
path, lines splits into an array, and regex extracts the first capture group.
A capture that fails to parse is a warning, not a failure. The variable is left undefined and the run continues, because a plan should not stop because a version string was formatted unexpectedly.
Conditions#
Any step or item can carry a condition, and runs only when it evaluates true. An empty condition always runs.
condition: "{{ facts.os_family }} == 'debian' && {{ outputs.needs_restart }} == true"The supported grammar is deliberately small: &&, ||, the comparisons == != >
>= < <=, and contains for substring or array membership, over literals and
{{ }} references, with parentheses.
A malformed condition fails open: the step runs, and a warning is logged. That is the deliberate choice of the two available failures. A broken condition that silently skips a step means production automation quietly stops happening and nobody notices, which is worse than a step running when it should not have.
When something fails#
By default a run halts at the first failed step. Later steps do not execute, and the run is recorded as failed. The second half of a plan usually assumes the first half worked.
Three per-item settings change that, and they are properties of the plan rather than decisions made at run time:
| Setting | Effect |
|---|---|
continue_on_error | The item may fail without failing the run. It is still recorded as failed. |
retries | Extra attempts after the first. |
retry_delay_seconds | Pause between attempts. |
An item can also carry its own timeout_seconds, and a plan can carry a whole-run
wall-clock cap.
Revisions#
Every edit to a plan increments its revision. The number matters at the approval gate: the revision is pinned when approval is requested and compared at the moment of decision, so a plan edited between the two is surfaced rather than quietly approved.
Plans from git#
A plan can be owned by a git repository instead of the builder. A managed plan is read-only in the UI: edits go through the repository and arrive on the next sync, so the repository stays the single source of truth rather than one of two.
Next steps#
- Commands and scripts for dropping out of tools, and what it costs
- Tool catalog for what a step can call