Skip to content

Writing state modules

A module is a YAML document describing a unit of desired state. You declare what a machine should look like; Ordo plans how to reach and maintain it. The full, machine-readable shape is published as a JSON Schema — associate it with *.ordo.yaml in your editor for autocompletion and validation.

A module has four top-level fields:

  • name — the module’s stable identity, referenced when you apply it.
  • supports — the attribute combinations the module is designed for. An agent matching no clause is rejected before any per-resource work.
  • templates — named, content-only fragments that resources can reference.
  • resources — an ordered list of resource declarations.
name: caddy-site
supports:
- { platform: linux, distro: debian, init_system: systemd }
- { platform: linux, distro: fedora, init_system: systemd }
templates:
- id: caddyfile
content: |
:80 {
root * /var/www/caddy-site
file_server
}
resources:
- id: caddyfile
resource:
file-unix:
path: /etc/caddy/Caddyfile
template: caddyfile
mode: "0644"

Each resource has an id and a resource body whose single key selects the resource kind. The first-class kinds are:

  • Packagespackage-apt, package-dnf, package-brew, package-choco, package-winget. Same shape (name, optional version); the manager is chosen by the key.
  • Files & directoriesfile-unix, file-windows, directory-unix, directory-windows. A file takes inline content or a template reference (the two are mutually exclusive), plus owner/group/mode on Unix.
  • Servicesservice-systemd, service-launchd, service-windows, with enabled and running state.
  • Scriptsscript, the escape hatch for work the first-class kinds do not model (see below).

Every resource is drift-checked and appears in the plan individually.

A resource can be universal (one body applies to every supported agent) or have variants gated by when clauses on agent attributes. The most specific matching variant wins — use this to swap the package manager per distro while keeping one logical resource:

resources:
- id: caddy-pkg
variants:
- when: { platform: linux }
resource:
package-apt: { name: caddy }
- when: { platform: linux, distro: fedora }
resource:
package-dnf: { name: caddy }

Resources apply in declaration order unless you override it with depends_on, which names the resource ids that must be applied first:

resources:
- id: caddy-pkg
resource:
package-apt: { name: caddy }
- id: caddyfile
depends_on: [caddy-pkg]
resource:
file-unix:
path: /etc/caddy/Caddyfile
template: caddyfile
mode: "0644"

Services and scripts can react when a resource they depend on actually changes, rather than on every apply:

  • reload_on (systemd only) — reload the service when a listed resource changes.
  • restart_on — restart the service (or re-run, for launchd/Windows which have no live reload) when a listed resource changes.
  • rerun_on (scripts) — re-run the script when a listed resource changes.

reload_on and restart_on must be disjoint within one service. For example, reload Caddy whenever its config file changes — with no dropped connections:

- id: caddy-service
depends_on: [caddyfile]
resource:
service-systemd:
name: caddy
enabled: true
running: true
reload_on: [caddyfile]

Triggered actions are predicted in the plan before they run, so you can see a reload or restart coming.

When no first-class kind fits, a script resource runs commands. Because a bare script has no way to diff its own state, give it a check command (its exit status decides whether apply needs to run), and declare manages/undo so removing the resource can tear down what it created. Pair it with rerun_on to re-run only when its inputs change.

ordo-state validates and inspects modules locally, before they reach the orchestrator:

Terminal window
ordo-state validate caddy-site.ordo.yaml # schema + semantic checks
ordo-state deps caddy-site.ordo.yaml # show the resolved apply order

Worked, validated examples ship in the repository — the caddy-site module models a static site entirely with first-class resources, and demo-compose-stack shows the script escape hatch driving Docker Compose.