weave
State management

Reality is the source of truth.
State files are a mirror you can edit.

Terraform's killer feature is version control over config — version-pin a known-good state, diff what's changed, roll back when something breaks. weave gets the same property without the baggage of a desired-state file you have to keep in sync.

snapshot
Pull live config to YAML.
diff
Show pending changes between file and live.
apply
Push edits after diff review; confirms or --yes.

The model, in two diagrams

Terraform
files → reality (force)
  file.tf  ──apply──▶  cloud
                       │
                       └── drift means error

The file is the law. Anything different is "drift." Great for greenfield IaC. Painful when reality legitimately changes outside Terraform (someone fixes a port in the dashboard at 3am during an outage).

weave
reality ↔ files (mirror)
  cloud  ──snapshot──▶  ports.yaml
                                │
  cloud  ◀───apply──── (your edits)
                                │
                          (git tracks it)

Live system is the law. The YAML is a mirror you can version, edit, and push back. Someone fixed a port at 3am? Just resnapshot — the file catches up.

End-to-end: Meraki switch ports

Meraki ports is the canonical example below. The same shape works across modules: every kind supports snapshot and diff; apply only where the module wires live writes for that kind (see each module page). YAML headers and the Action diff model are shared. See "Kinds shipping today" below for the full matrix.

Safe workflow: snapshot → edit YAML → diff (read-only, anytime) → review the panel → apply --yes in CI or confirm interactively. Destructive do commands (remove, wipe, suspend, …) require --yes as well.

  1. 1 / snapshot

    Capture the truth

    $ weave meraki snapshot ports --network "Main Office"
    Saved .weave-state/meraki/acme-hq/main-office/ports.yaml  (1 switch, 24 ports)

    The file lands at .weave-state/<module>/<org-slug>/<network-slug>/<kind>.yaml. Override the root with WEAVE_STATE_DIR. Commit the folder to git — that's the whole versioning story.

  2. 2 / edit

    Open the file, change what you need

    # weave state file — captured live, edit freely, then `weave apply`.
    schema: weave.state/v1
    module: meraki
    kind: ports
    scope:
      org_name: Acme HQ
      network_id: L_12345
      network_name: Main Office
    captured_at: '2026-05-15T15:02:13Z'
    
    data:
      switches:
        - serial: Q2XX-AAA-BBB
          name: sw-01
          model: MS120-24
          ports:
            - port_id: "1"
              name: Reception
              enabled: true
              type: access
              vlan: 10    # <-- bump to 20
              poeEnabled: true
            - port_id: "2"
              name: Conf-Room    # <-- rename + disable
              enabled: true
              type: access
              vlan: 10
              poeEnabled: true
            ...

    Use cases that fall out naturally: bulk port changes (find/replace), swap a switch serial for hardware migration, review a coworker's pending change as a git diff, restore last week's known-good state with git checkout.

  3. 3 / diff

    See exactly what will change

    $ weave meraki diff ports --network "Main Office"
    Comparing .weave-state/meraki/acme-hq/main-office/ports.yaml against live …
    Pending changes:  +0  ~2  -0  (0 no-op)
    ~ switch-port    Q2XX-AAA-BBB port 1
        vlan:    10 → 20
    ~ switch-port    Q2XX-AAA-BBB port 2
        name:    'Conf-Room' → 'Conf-Room-Renamed'
        enabled: True → False

    Diffs are computed field-by-field — you see exactly the fields that will be touched, never a wall of unchanged YAML. diff is read-only and safe to run anytime. Run it after every snapshot to confirm nothing changed underneath you.

  4. 4 / apply

    Push the changes

    $ weave meraki apply ports --network "Main Office"
    Comparing .weave-state/meraki/acme-hq/main-office/ports.yaml against live …
    ╭─ Pending changes ─────────────────────────────────────╮
    │ meraki apply ports   +0 add  ~2 change  -0 remove     │
    ╰───────────────────────────────────────────────────────╯
    ~ switch-port    Q2XX-AAA-BBB port 1
        vlan: 10 → 20
    ~ switch-port    Q2XX-AAA-BBB port 2
        name: 'Conf-Room' → 'Conf-Room-Renamed'
        enabled: True → False
    Apply these changes? [y/N]: y
    Applied meraki ports: +0 ~2 -0

    apply always runs diff first and shows a summary panel before anything hits the API. With no pending changes it prints No changes and exits. Pass --dry-run (same as diff) to preview only, or --yes / -y in CI after you have reviewed the diff. Deletes and whole-document kinds (ACL, policies) get an extra high-impact warning.

  5. 5 / rollback

    Something broke? git is the rollback story.

    $ git checkout HEAD~1 .weave-state/meraki/acme-hq/main-office/ports.yaml
    $ weave meraki apply ports --network "Main Office" --yes
    Pushed 2 update(s) to Meraki.
    $ git checkout main .weave-state/    # go back to current state file

    No state engine. No terraform state ceremony. The file is the snapshot; git is the history; apply is the time machine.

Kinds shipping today

Twenty-four state kinds across twenty-three live modules — networking gear, DNS, identity, IAM, MDM, collaboration, observability, dev/ops, secrets, and EDR. More land every week.

Coming next on the roadmap (PRs welcome): meraki firewall-rules · cloudflare page-rules · okta apps · jamf policies · intune compliance-policies · vault auth-methods / mounts · aws_iam roles.

File layout

.weave-state/
├── meraki/
│   ├── acme-hq/
│   │   ├── main-office/
│   │   │   └── ports.yaml
│   │   └── branch/
│   │       └── ports.yaml
│   └── sandbox/
│       └── lab/
│           └── ports.yaml
├── cloudflare/
│   └── weavewhatever-com/
│       └── dns.yaml
├── fortinet/
│   └── policies.yaml
├── paloalto/
│   └── security-rules.yaml
├── unifi/
│   └── default/
│       └── wlans.yaml
├── mikrotik/
│   └── firewall-filter.yaml
├── tailscale/
│   └── acl.yaml
├── okta/
│   └── groups.yaml
├── entra/
│   └── groups.yaml
├── googleworkspace/
│   └── my-customer/
│       └── groups.yaml
├── activedirectory/
│   └── it-admins/
│       └── group-memberships.yaml
├── aws_iam/
│   └── policies.yaml
├── intune/
│   └── configuration-profiles.yaml
├── jamf/
│   └── configuration-profiles.yaml
├── mosyle/
│   └── profiles.yaml
├── teams/
│   └── teams.yaml
├── github/
│   └── org-repo/
│       └── branch-protection.yaml
├── slack/
│   └── channels.yaml
├── pagerduty/
│   └── services.yaml
├── sentry/
│   └── my-org/
│       └── projects.yaml
├── datadog/
│   └── monitors.yaml
├── vault/
│   └── policies.yaml
└── crowdstrike/
    └── prevention-policies.yaml

Adding state kinds to a module

Each module can opt-in to as many state kinds as makes sense — 24 kinds across 23 modules ship today (see "Kinds shipping today" above). Add a new kind by writing three small functions:

# src/weave/modules/<name>/state.py
def snapshot_ports(network: str) -> tuple[dict, dict]:
    """Pull live state. Return (scope, data) for the YAML header + body."""
    ...

def diff_ports(desired: dict, live: dict) -> list[Action]:
    """Compare file vs live. Return one Action per change."""
    ...

def apply_ports(actions: list[Action]) -> None:
    """Execute the Actions against the live API."""
    ...

Then declare it on the manifest and wire three Typer commands — full example in CONTRIBUTING.md.

What this is not

Ready to round-trip something?

Meraki ports is the canonical first example. Wire up the next kind on a module you care about — open a PR or file an issue with the kind you want.

CLI reference Add a state kind