Skip to content

Declarative Does Not Mean Destructive

How Barn uses per-node hashes and explicit operations so missing configuration can never authorize deletion.
Note

This article describes the unreleased Barn 0.9.0 candidate. See Status for current validation and remaining release checks.

“Declarative” is often shortened to “make reality equal the file.” That is a useful slogan until the file is incomplete, the wrong branch is checked out, or one YAML group is temporarily removed. If absence is treated as deletion, an ordinary editing mistake becomes a destructive operation.

Barn uses a narrower rule:

Desired state may authorize creation. It can describe drift. It never authorizes destruction by omission.

The distinction is central to a VM runtime because roots, data disks, SSH keys, and local evidence are not stateless replicas. Recreating them may be correct, but it must be a decision the operator can see.

From Inventory to node identity

Barn does not hash the whole Pigsty Inventory. It first extracts the fields it owns, fills defaults, canonicalizes image selectors and architecture, and builds a canonical resolved spec. Exact Catalog artifact resolution remains separate, so a channel update does not itself change a node’s spec hash. Each node then receives a hash of:

  • the deployment envelope shared by every node, such as subnet, login user, architecture policy, and the deployment-level default image request; and
  • exactly that node’s resolved definition.

Adding a peer therefore does not change an existing node’s hash. Editing an unconsumed Pigsty field—PostgreSQL version, packages, or service policy—does not produce VM drift. The VM layer reacts only to the contract it actually understands.

This also avoids a dangerous half-promise: Barn does not pretend to implement Ansible’s entire variable system. Unknown vm_* keys and conflicting values inside the owned namespace fail. Everything outside the documented boundary is opaque rather than partially interpreted.

Note

Decision status: current. Barn converges additions automatically, but definition changes and removal require explicit commands. See Daily Operations for the command workflow.

The five plan outcomes

barn plan compares desired state, applied deployment state, and committed node state. The result is intentionally small:

Outcome Meaning Apply path
create desired node has no committed state barn up creates it
unchanged definition and runtime still match running peer stays untouched; stopped peer may start
recreate node definition changed explicit barn recreate --force <node>
missing applied node is absent or skipped in the Inventory explicit barn destroy <node> --force, or restore it to the file
envelope drift subnet, login identity, architecture, or runtime policy changed whole-deployment recreate

Plan is read-only. It reports the exact node sets and, in text mode, the command that applies the required explicit transition.

Why up stops at drift

Barn could decide that changing CPU or memory is harmless enough to apply, or that a new image should silently rebuild a root disk. Pre-1.0 intentionally does neither. A changed VM definition is classified as recreate and up returns a typed conflict.

That conservative boundary has two advantages:

  1. all changes that can invalidate Guest state share one visible operation;
  2. Barn can finish every prerequisite check before touching the current node.

The recreate path resolves the selected emulator, acceleration policy, firmware, image bytes, network backend, shares, and persistent-disk contract before destruction. If a foreign emulator is missing or a share is unsafe, the existing VM remains intact.

Why missing nodes block convergence

A node can disappear from desired state for many reasons that do not express deletion intent:

  • the operator opened a reduced Inventory while debugging;
  • a group was renamed or filtered;
  • vm_skip temporarily marks a real or external host;
  • a merge conflict dropped a YAML branch;
  • the configuration file itself is unavailable.

When applied state contains such a node, up stops and names it. The operator must either restore the definition or run the explicit destroy command. This is deliberately more friction than automatic garbage collection—and far less friction than recovering an unintended disk deletion.

Persistent data disks add another boundary. Normal destroy preserves them. Whole-deployment destroy --delete-persistent explicitly includes owned persistent disks, including retained disks, and accepts no node selectors; deleting deployment keys as well requires whole-deployment destroy --purge or purge. Retention is not a backup guarantee: guest bootstrap can reset unrecognized or confirmed damaged test filesystems, even on persistent disks. See Data disks. One confirmation cannot silently grow into broader authority.

Convergence is still incremental

Safety does not mean rebuilding everything. New nodes are created without stopping existing peers. Selected stopped nodes start without recreating running ones. A per-node recreate preserves peers and, when requested by the disk contract, persistent data.

The result is declarative where desired state is strong evidence—creation and comparison—and explicit where the cost is irreversible. Barn does not make the operator manually calculate drift, but it also does not confuse a diff with permission.

Read next: A PID is not a virtual machine.