Skip to content

    Modules

    A module is a parameterised sequence of steps a plan calls as one item. Fixing the module fixes every plan that uses it.

    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:

    a plan step calling a module
    - name: Deploy the app
      targets: [production]
      tool: module:app-deploy
      args:
        repo: [email protected]:acme/api.git
        release_dir: /srv/api
        service: api

    Those 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#