Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/Skidbladnir

.nornir/unfold-design.md

unfold — design

Class: design · Function axis: f7 (AIRGAP7). States only the shape we ship; the why (pure builders, honest stubs, no virsh) is in unfold-reasoning.md.

The target abstraction

pub trait Unfold { fn unfold(&self) -> Result<()> } (unfold.rs:34) — provision/boot a target ready for install. Every backend implements it. pub enum Target (unfold.rs:22) names where the stack is unfolded: Redfish{bmc}, Terraform{workspace}, Kvm{name}, Libvirt{host,name}.

Each backend splits into pure builders (request bodies / config / argv — always testable without the external tool) and a drive (unfold(), which actually executes). Only the drive touches the outside world.

KVM — local test convenience (unfold.rs:39)

KvmTarget { name, disk, memory_mb, cpus } (unfold.rs:44; new, unfold.rs:53). qemu_argv() (unfold.rs:64) builds the real q35,accel=kvm:tcg argv (pure). kvm_available() (unfold.rs:86) checks /dev/kvm. impl Unfold (unfold.rs:91) spawns qemu if present, else an honest bail ("qemu not found … argv is built but cannot boot here").

libvirt — pure-Rust RPC (unfold.rs:133)

LibvirtTarget { host, name, disk, iso, memory_mib, vcpus } (unfold.rs:138; builders new/iso/ resources, unfold.rs:151–:165). connect_uri() (unfold.rs:172) and domain_xml() (unfold.rs:182) are pure. The client speaks libvirt's RPC directly over TCP:16509 — no virsh: mod rpc (unfold.rs:208) hand-rolls the XDR framing and procedure calls (connect_open, domain_define_xml, domain_create, domain_destroy, domain_undefine). rpc_connect() (unfold.rs:389), impl Unfold (unfold.rs:409) define+create; teardown() (unfold.rs:401) destroy+undefine.

Redfish — BMC provisioning (feature redfish, unfold.rs:426)

RedfishTarget { bmc_url, username, password, media_url, system_id } (unfold.rs:451). Pure request-body builders (reset_body, power_body, insert_media_body, boot_override_body, unfold.rs:479–:495) and path builders (unfold.rs:505–:514). With redfish, impl Unfold (unfold.rs:518) does the real drive (insert media → boot Cd → ForceRestart) via RedfishClient (unfold.rs:544, blocking reqwest, accepts self-signed BMC certs). Without the feature, impl Unfold (unfold.rs:530) is an honest stub that bails "needs the redfish cargo feature". PowerAction (unfold.rs:431) → as_reset_type (unfold.rs:439).

Terraform — cloud/VM (feature terraform, unfold.rs:633)

TerraformTarget { workspace, product, count } (unfold.rs:638). Pure renderers render_resource (unfold.rs:663) / render_config (unfold.rs:673) emit nornir_component HCL; write_config (unfold.rs:681) writes main.tf. With terraform, run (unfold.rs:693) drives terraform -chdir=… <sub>, impl Unfold (unfold.rs:719) does init+apply, destroy (unfold.rs:714) tears down. Without the feature, impl Unfold (unfold.rs:729) bails honestly.

The "plan" is the pure rendered artifact per backend (Terraform main.tf, libvirt domain_xml, KVM qemu_argv, Redfish bodies/paths) — usable without the tool; --run (the unfold() drive) is what executes it.

Contract (inject-assert tests)

kvm_qemu_argv_is_built_like_tunnr (unfold.rs:856), libvirt_domain_xml_carries_injected_fields (unfold.rs:759), libvirt_unfold_requires_tcp_host_no_shell (unfold.rs:819), terraform_config_renders_nornir_component (unfold.rs:876), redfish_request_shapes_are_well_formed (unfold.rs:903), redfish_without_feature_errors (unfold.rs:933), terraform_without_feature_errors (unfold.rs:940).