Machine: lifecycle & state methods
| Method | Returns | Notes |
|---|---|---|
| m.name() | string | |
| m.kind() | string | "vm" or "container" — reported so a script can *say* what it has, never so it has to branch to drive it |
| m.start() / m.stop() / m.stop_force() / m.restart() | Result[unit, string] | stop = graceful ladder (agent → ACPI → kill) |
| m.poweroff() | Result[unit, string] | Clean QMP quit — exits QEMU flushing block caches. The only safe seal for guests with no ACPI (DOS, Win 3.x), where a force-kill can drop qcow2 writes |
| m.state() | string | one of "stopped" / "starting" / "running" / "stopping" |
| m.is_ready() | bool | The sticky ready flag — once set it stays set while QEMU runs, so it does not drop across a guest reboot |
| m.agent_answering() | bool | The live agent probe, ungated — goes false while the guest is down or mid-reboot. Use this to watch a reboot you requested from inside the guest |
| m.wait_ready(timeout_secs: int) | Result[unit, string] | Block until agent responds |
| m.wait_shutdown(timeout_secs: int) | Result[unit, string] | Block until powered off |
| m.ip() | Result[string, string] | Primary NIC IPv4 (DHCP lease / agent) |
| m.ip_nic(nic: int) | Result[string, string] | By NIC index (0-based) |
| m.is_healthy() | bool | Latest healthcheck verdict; a machine declaring none counts healthy once ready |
| m.logs(lines: int) | Result[string, string] | Tail of the console log — a container's captured stdout/stderr, a VM's serial log |
| m.logins() | List[Login] | The identities the machine declares with login {} (§19.2). Read from the lab file, so it answers on a stopped machine |
| m.as_login(selector: string) | Result[Machine, string] | A second handle onto the same machine, whose guest work runs as that login — a label, the account name, or the family floor (SYSTEM/root). Fails naming the machine on a selector nothing matches |
| m.as_account(user: string, password: string) | Result[Machine, string] | The same, for an account the lab file does not declare, or one whose declared secret has been rotated |
Login carries label, user, password: Option[string], elevated and default. Reading it is what lets a provision script create exactly the account the lab file declares, instead of the password living in two places that drift. The secret crosses as written — none where the author declared none, never an empty string. elevated and default cross *resolved*, so a lone login reports default == true without the lab file saying so.
as_login / as_account are the wscript rung of §19.2's precedence ladder, resolved exactly as vmlab exec --user/--password resolves it. They return a handle rather than switching this one, so an identity is said once and every call on the result — exec, copy_to, terminal — carries it. That is what lets a provision {} step address a login's home directory **before that account has ever logged on** (§19.8), which is the only route for anything that must land as the developer rather than as the machine. A playbook {} has no rung here at all.
Inside a machine's own first-boot script the readiness pair is special-cased: is_ready() / wait_ready() mean "does the agent answer right now", because the sticky flag is deliberately withheld until that script returns. Everywhere else they report full readiness, and agent_answering() is how you get the live signal.