Skip to content

The adoption ladder

provisio's wedge is no behaviour change: it runs the commands you already run, now idempotent, previewable, and audited — and you can adopt it without rewriting your script. There is no cliff, only a ladder.

L0 — rewrite nothing

Route your existing commands through a CommandRunner / CliTool. Instantly you get a redacted audit log of every command and its exit code, and consistent error handling — for essentially no code change.

from provisio import CliTool, SubprocessCommandRunner, configure_audit_log
configure_audit_log(destination="audit.jsonl", fmt="json")
az = CliTool("az", SubprocessCommandRunner())
az("group", "create", "--name", "demo", "--location", "eastus2")  # audited

L1 — wrap each command as a step (~1 line)

command_step turns a raw command into a step. Now you get dry-run preview, explicit ordering, --skip, and a RunResult — still no idempotency.

from provisio import Plan, command_step
Plan([
    command_step("rg", "Resource group", "az", "group", "create", "--name", "demo", "--location", "eastus2"),
]).execute(ctx)

L2 — add an existence probe

Wrap the create in ensure with an exists= probe and the step becomes idempotent — safe to re-run.

from provisio import step, ensure

@step("rg", "Resource group")
def rg(ctx):
    az = ctx.tool("az")
    ensure(ctx, describe="resource group 'demo'",
           exists=lambda: az.exists("group", "show", "--name", "demo"),
           create=lambda: az("group", "create", "--name", "demo", "--location", "eastus2"))

L3 — the full framework

Add requires/produces to thread typed outputs, a Setting schema to get a generated CLI, and state so diff gates re-provisioning in CI. See the case study for a complete app at this level.

Using provisio inside a Terraform/Pulumi project

provisio is library-firstPlan.execute(ctx) returns a RunResult, no CLI required — so it drops straight into the imperative glue a TF/Pulumi project is forced to write anyway (local-exec / null_resource / Command), plus bootstrap (pre-Terraform) resources and cross-tool orchestration (gh, kubectl). Pass Terraform outputs in via environment variables; read provisio's outputs back out of its state JSON. It does not touch Terraform's core (state/drift/updates) — it upgrades its worst part.