Automating labs

The four automation surfaces — provision scripts, event handlers, playbooks, and ad-hoc drives — and how to pick between them.

Everything in a lab can be automated, but there are four distinct surfaces — choose by *when* the automation runs and *what shape* it takes: | Surface | Runs | Shape | Reach for it when | | --- | --- | --- | --- | | Provision scripts (provision {}) | On vmlab up, in declaration order | Imperative wscript | Sequenced setup: install, configure, reboot, verify | | Event handlers (on "event" {}) | When a lifecycle event fires | Imperative wscript | Reactions: collect dumps on vm.crashed, alert on disk-low | | Playbooks (playbook {}) | On up (interleaved with provisions) and on demand | Declarative config-weave | Desired-state config: packages, files, services, domain joins — with drift check | | Ad-hoc | Whenever you run it | vmlab script x.ws, or one-shot exec / shell / cp | Experiments, debugging, one-off tasks | The imperative surfaces all land in the same place: a wscript main(lab) holding a Lab handle, from which The one Machine handle — for VMs and containers alike — exposes power, exec, file transfer, terminals, snapshots, keystrokes, screen matching and OCR. Multi-step guest work belongs in a script, not a chain of vmlab exec calls — scripts get send/expect terminals, retries and real control flow. Underneath, guest access rides the vmlab-agent channel (virtio-serial, no guest network needed). There is no fallback: a guest without the agent has no exec, no file copy and no readiness — which is why templates built before the agent must be rebuilt. Vision-based automation (screen matching, OCR) needs no agent at all — it reads the display, which is what makes even vintage guests scriptable. The surfaces compose: a typical lab declares playbooks for steady-state config, provisions for the sequenced glue playbooks can't express, and a crash handler or two — then you poke at the result with vmlab shell.