openstack-caracal-dc-dc / opentofu /
..
modules DOCFIX-142: opentofu/ network + storage-pool scaffold (VR1 IaC, gap #2) 2 hours ago
README.md Fold OPNsense's four separable sub-gaps into the tooling gap register 2 hours ago
main.tf DOCFIX-142: opentofu/ network + storage-pool scaffold (VR1 IaC, gap #2) 2 hours ago
variables.tf DOCFIX-142: opentofu/ network + storage-pool scaffold (VR1 IaC, gap #2) 2 hours ago
versions.tf DOCFIX-142: opentofu/ network + storage-pool scaffold (VR1 IaC, gap #2) 2 hours ago
README.md

opentofu/ -- VR1 (DC-DC) infrastructure-as-code

Owns create/destroy of the "physical" layer per D-103: libvirt domains (node VMs), all virtual networks (dark-fiber mesh, per-DC planes, ISP-edge), the OPNsense edge VMs, and the Office1 service VMs. Runs from the Office1 operator VM against the vcloud host libvirt. See docs/dc-dc-buildout-design.md and docs/dc-dc-deployment-workflow.md (Stage 1-2, tooling gap register item 2) for the surrounding plan.

Status: SCAFFOLD, UNVALIDATED. No tofu/terraform binary was available in the session that authored this -- nothing here has been run through tofu init/validate/plan, let alone apply. Before trusting any of it:

bash scripts/opentofu-validate.sh

on a machine with the binary and network access to the provider registry, and read the result before proceeding.

Scope of this delivery

Built:

  • modules/dc-planes -- the six per-DC plane segments (D-052/D-100) as isolated libvirt_network resources.
  • modules/mesh-link -- one inter-site L2 segment per leg of the D-100 dark-fiber triangle (DC1<->DC2, DC1<->Office1, DC2<->Office1).
  • modules/dc-storage-pool -- a directory-backed libvirt_pool per DC/Office1 for future node-VM disk images.
  • Root wiring for DC1 (planes + pool, using DC1's inherited DC0 CIDRs) and Office1 (pool only), plus all three mesh links.

Deliberately NOT built yet, and why:

  • libvirt_domain (the actual node VMs). The provider's current schema for this resource turned out to be a large (~40 top-level argument), deeply nested rewrite that closely mirrors raw libvirt domain XML (numa_tune, launch_security, memory_backing, a devices block containing disks, interfaces, filesystems, graphics, etc.) -- materially different from older, commonly-referenced examples of this provider. Doc-summarization tools available this session could not give high-confidence, verbatim field names for the nested devices.interfaces/devices.disks shape specifically. Authoring this blind risks exactly the "fabricated flag/value" failure mode this repo's operating discipline calls out as the cardinal sin. Next action: on a machine with the tofu binary, run tofu providers schema -json (after terraform init) and read the actual libvirt_domain schema directly, or start from the provider's own examples/ directory in its GitHub repo, before writing this module.
  • libvirt_volume (per-VM disk images). Follows directly from the domain module above; deferred with it.
  • DC2's plane networks. D-101 has not assigned DC2's supernet yet (it's an explicit open sub-item). main.tf has the DC2 module block written and commented out, ready to uncomment once NetBox assigns real CIDRs -- do not fill in a guessed default to make it "work" sooner.
  • tc netem WAN simulation. libvirt_network has no netem-equivalent argument; this is an OS-level tc qdisc setting applied to the bridge interface a mesh-link module creates, which needs either a null_resource provisioner or a small companion script -- not yet written. The exact parameters are themselves a Phase-0 open item (D-100), so this was going to need a second pass regardless.
  • OPNsense edges -- four separable pieces, not one, all currently blocked or unstarted: (a) the network substrate is done (modules/mesh-link, above); (b) the VM/image itself is blocked on the deferred domain module, and no image source (official OPNsense qcow2/ISO vs. a custom build) has been chosen; (c) OPNsense's own routing/firewall config is a config.xml the libvirt provider never touches -- pre-baked image vs. cloud-init first-boot vs. API/CLI-driven provisioner is an open choice, not started; (d) tc netem WAN simulation is a separate OS-level tc qdisc setting with no Terraform-native equivalent at all, needing its own provisioner/script, and whose parameters are still an unruled D-100 open item -- a decision, not just scripting, is needed first. Full breakdown in docs/dc-dc-deployment-workflow.md gap register item 4.
  • MAAS-region / NetBox / GitBucket service VMs. Depend on the deferred domain module (they're VMs too).

Schema notes (read before extending)

Verified this session against the provider's actual current docs (docs/resources/*.md in dmacvicar/terraform-provider-libvirt, tag v0.9.8, fetched 2026-07-09) -- NOT from training-data memory of older provider versions, which would have been wrong:

  • libvirt_network's isolation control lives at forward.mode (a NESTED block field), not a top-level mode argument as older examples show. This scaffold OMITS the forward block entirely for isolated planes/links (documented native-libvirt behavior for a private, unforwarded switch) rather than guess a mode = "none" value this session could not confirm is still valid. Re-check this specific choice first if tofu validate objects.
  • domain and mtu on libvirt_network are nested blocks (domain { name = ... }, mtu { size = ... }), not flat string/number arguments.
  • libvirt_pool's target is a nested block (target { path = ... }), types like dir are native libvirt storage-pool vocabulary (stable, long-standing, not provider-specific) -- reasonably high confidence.

Conventions carried from the rest of this repo

  • No hardcoded repo name (repo-lint L9 would need extending to cover .tf files if this becomes a recurring class of finding -- not yet done, flag if it comes up).
  • Every variable without a safe, already-ratified default requires explicit input (no invented fallbacks) -- same "never use an inferred value" discipline as everywhere else in this repo.
  • DC1's plane CIDRs are a second, hand-maintained copy of scripts/lib-net.sh's PLANE_CIDRS. Keep them in sync manually until a generator exists to derive one from the other (DOCFIX candidate, not actioned).