A module is a reusable, parameterised sequence of items. A plan references it as a single item, and it expands into its steps when the run executes.
When the same four steps appear in six plans, that is a module. Fixing it once fixes all six.
Calling one#
A plan calls a module the way it calls a tool, with module: and the module's slug:
- name: Deploy the app
targets: [production]
tool: module:app-deploy
args:
repo: [email protected]:acme/api.git
release_dir: /srv/api
service: apiThose arguments are the module's declared parameters. Each one has a type, and can be required or carry a default, so calling a module wrongly is caught rather than producing a step with an empty path in it.
What a module declares#
Three things:
Items are the ordered work it expands into, each one an ordinary item with a tool, arguments, and optionally its own condition, timeout and capture.
Parameters are the interface. The calling plan fills them in, and they are available
inside the module as {{ params.x }}.
Outputs are what the module promises to produce, so a later step in the calling plan can consume them.
Two instances of the same module in one plan get their outputs namespaced separately, so
calling app-deploy twice for two services does not have the second overwrite the first.
Expansion happens at run time#
A module is not copied into the plan when you write it. It expands when the run executes, which means editing a module changes every plan that uses it, on their next run.
That is the point, and it is also the thing to be careful about. A fix to a module propagates everywhere without touching a single plan. So does a mistake.
The shipped library#
Opafra ships a curated library of modules under the opafra. prefix, visible to everyone
and read-only. They cover the sequences most teams write on their first week.
Your own module with the same slug shadows the shipped one. So the way to adapt a library module is to create your own with that slug, and every plan calling it picks yours up without being edited.
Shipped modules carry a version, so you can see which revision you are running.
When to extract one#
Extract when the same sequence appears in a third plan, not the second. Two copies are cheap to keep in step; three is where drift starts and where the copies begin to disagree about details nobody wrote down.
Extract also when a sequence encodes a decision you want made once: the order of a graceful restart, which paths get backed up, how long to wait before declaring a rollout failed. A module is where that decision lives so it stops being re-argued per plan.
Do not extract a single tool call. A module around one item adds a layer without adding a decision, and the plan reads better calling the tool directly.
Next steps#
- Plan structure for parameters, capture and conditions in full
- Tool catalogue for what a module's items can call