diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index be2166e..a7dda0f 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -189,16 +189,14 @@ - pre-reboot gate was 5/0/6 (recorded at `docs/dc0-deploy-readiness.md:59`, `docs/session-ledger.md:278`). -7/2/7 is the post-reboot SYMPTOM of the D-130 seed-volume defect (7 add = -5 committed DC0 creates + voffice1 cloudinit-disk re-create + seed-volume -replace-create; 2 change = the state-blind autostart updates; 7 destroy = -6 empty planes + seed-volume replace-destroy -- decomposition at -`docs/audit/env-snapshot-20260718.md:33-46`). 5/0/6 was the pre-reboot -gate; 5/2/6 was an intermediate recorded value (GA-F02). The post-fix -expected triple DOES NOT EXIST until D-130 is ruled, and this document -deliberately does not pick one. When D-130 is ruled, the new expected -triple and WHY get recorded HERE before any re-plan (charter Phase 6 -item 4). This document may not be used to justify an apply until then. +7/2/7 was the post-reboot SYMPTOM of the D-130 seed-volume defect +(decomposition at `docs/audit/env-snapshot-20260718.md:33-46`). D-130 is +now RULED and implemented (2026-07-19): the v8 capture measures the +post-fix plan at 6/2/6 (`docs/audit/outer-plan-20260719-v8-ignorechanges +.txt`; same-day baseline re-confirmed 7/2/7 pre-fix). The 2 changes are +the state-blind autostart updates, whose removal is gate G6's reconcile. +The FINAL expected triple gets recorded HERE with the G7 re-plan capture +after G6 closes -- until then this document may not justify an apply. ## 6. Open gates @@ -209,9 +207,9 @@ |---|------|----------------|-------|---------------------------| | G1 | Audit Phase 3: fresh-agent grounding test | 3 clean-context probes score the 7-question set against this doc; holes map made | session | CLOSED 2026-07-18: 3 probes, 21/21 PASS, holes H1 (amended into G9) + H2 (no action) -- `docs/audit/phase3-grounding-test-20260718.md` | | G2 | Audit Phase 4: GA-R1..R7 structural rulings + the stage-status vocabulary A/B | ruling-type gate (GA-R6 rule 6): closes when every item carries a GA-R5 Status block | operator | CLOSED 2026-07-18: all seven GA-R + vocabulary (Option A + H1) RATIFIED, utterances quoted (`docs/audit/ga-rulings.md`, through commit `fe4f1c4` + this one) | -| G3 | Audit Phase 5: repair sweep of GA-F01..F15 (incl. memory hygiene GA-F05..F08, skill sweep) | operator-gated fix batches, each commit naming its GA-F | operator + session | Batch 0 OPENED by operator 2026-07-19; items 0.1 (repo-lint L10, GA-R1/C1), 0.2 (SEC repoint, GA-R4/F3), 0.3 (counter hardening, GA-F15), 0.4 (extractor vocab scan, GA-F10/H1) landed; Batch 0 CLOSED (verification passed 2026-07-19); Batch 1 OPENED by operator 2026-07-19 (L10 flipped to FAIL at open per plan); Batches 2-6 await their gates; FREEZE holds for all un-gated surfaces | -| G4 | The two D-130 verifications (charter-exempt, read-only/throwaway): (v7) v0.9.8 volume-replace-under-running-domain on a throwaway domain; (v8) lifecycle ignore_changes suppression of the forced replacement | run them, capture output | session | TO RUN (`docs/audit/env-snapshot-20260718.md:47-49`) | -| G5 | D-130 mechanism ruling (seed-volume durable fix) | operator rules in Phase 5, quoting G4's captured output | operator | D-130 unassigned; next-free D confirmed 130 (`ledger-scan` run 2026-07-18; no `^## D-130` in design-decisions.md) | +| G3 | Audit Phase 5: repair sweep of GA-F01..F15 (incl. memory hygiene GA-F05..F08, skill sweep) | operator-gated fix batches, each commit naming its GA-F | operator + session | Batch 0 OPENED by operator 2026-07-19; items 0.1 (repo-lint L10, GA-R1/C1), 0.2 (SEC repoint, GA-R4/F3), 0.3 (counter hardening, GA-F15), 0.4 (extractor vocab scan, GA-F10/H1) landed; Batch 0 CLOSED (verification passed 2026-07-19); Batch 1 OPEN: v8+v7 run (G4 CLOSED), D-130 RULED+implemented (G5 CLOSED); next G6 reconcile ruling -> triple -> G7 re-plan; Batches 2-6 await gates; FREEZE holds for un-gated surfaces | +| G4 | The two D-130 verifications | run them, capture output | session | CLOSED 2026-07-19: v8 suppression CONFIRMED (7/2/7 -> 6/2/6, zero forces-replacement; `docs/audit/outer-plan-20260719-v8-ignorechanges.txt` + `-baseline.txt`); v7 no-bounce under running domain, zero residue (`docs/audit/throwaway-v7-20260719.txt`) | +| G5 | D-130 mechanism ruling (seed-volume durable fix) | operator rules in Phase 5, quoting G4's captured output | operator | CLOSED 2026-07-19: D-130 ADOPTED (a) ignore_changes (`docs/design-decisions.md` D-130, GA-R5 utterance quoted); implemented in `modules/cloudinit-vm` + `tests/cloudinit-vm` | | G6 | State reconcile of autostart + seed WITHOUT bouncing guests | gated mechanism per the finding: `tofu import` (reads autostart back) or state surgery, or an operator-scheduled maintenance-window apply; `-refresh-only` is PROVEN a no-op | operator | Operator earlier selected reconcile-first (finding doc:136); superseded into audit sequencing by `2b718f5`; nothing reconciled yet | | G7 | New captured plan == the post-D-130 expected triple recorded in section 5 | re-plan to a capture file after G5+G6 | session | Impossible today: expected triple UNRESOLVED (section 5) | | G8 | Same-session pre-apply re-verify: 6 planes still empty (0 leases / 0 attached domains) | run in the SAME session as the apply | session | Last verified in a prior session only (finding doc:259-265) | @@ -247,22 +245,8 @@ ## 8. Open decision queue (the exact questions the operator must answer) -1. D-130 (to be assigned; next-free D = 130, verified) -- seed-volume - durable mechanism. QUESTION: adopt (a) `lifecycle { ignore_changes = - [create] }` on `libvirt_volume.seed` in `modules/cloudinit-vm` -- the - operator's recorded lean, EXPLICITLY CONTINGENT on verification G4-v8 - confirming it suppresses the forced replacement; if v8 refutes it, the - lean is DEAD (do not rescue) and the fallback is (b) a persistent - `$TMPDIR` staging path plus a runbook-enforced env guard, with the - fragility concern stated and accepted (charter section 1a). Evidence - needed before ruling: the captured outputs of both G4 verifications; - the decision text must quote them. Full apply-scoping option set - (a)-(d) and the verified provider mechanics: - `docs/finding-20260718-voffice1-cloudinit-seed-replace.md:104-257`. - Blast radius (measured): voffice1 + vvr1-dc0 (+ vvr1-dc1 later) = 3 - VMs; node VMs and the edge are UNAFFECTED (finding doc:206-223). - Sequencing: ruled inside audit Phase 5, AFTER this document (charter - section 1a, last bullet). +1. D-130: RULED 2026-07-19, ADOPTED (a) ignore_changes -- rotated to + `docs/design-decisions.md` D-130 (question + utterance + captures). 2. D-100 netem parameter sub-item (gap #11). Already ruled: same-metro dark-fiber default lean (D-100 sub-items, `docs/design-decisions.md: 1962`) and placeholder-for-the-rehearsal (readiness doc:73-75, operator diff --git a/docs/audit/outer-plan-20260719-baseline.txt b/docs/audit/outer-plan-20260719-baseline.txt new file mode 100644 index 0000000..245d12b --- /dev/null +++ b/docs/audit/outer-plan-20260719-baseline.txt @@ -0,0 +1,433 @@ +module.vr1_dc1_storage.libvirt_pool.dc: Refreshing state... [id=4a1df114-ee04-4c80-9233-cc0c140c8556] +module.mesh_vr1_dc0_vr1_dc1.libvirt_network.link: Refreshing state... [id=9cbc8589-9f40-48e6-872e-ef3abfe29a93] +module.office1_storage.libvirt_pool.dc: Refreshing state... [id=5f94194c-69c1-4b04-a85f-c18d87303a03] +module.vr1_dc0_planes.libvirt_network.plane["replication"]: Refreshing state... [id=295dca85-52de-4e78-9894-16e48d969654] +module.vr1_dc0_planes.libvirt_network.plane["metal-internal"]: Refreshing state... [id=99768f9d-256a-4a89-90dd-38de04efea03] +module.vr1_dc0_planes.libvirt_network.plane["storage"]: Refreshing state... [id=e5558f0e-9601-48fc-8eff-6a0e6adad0d7] +module.vr1_dc0_planes.libvirt_network.plane["data-tenant"]: Refreshing state... [id=31782537-790a-4399-a3a9-64dce7232e7b] +module.vr1_dc0_planes.libvirt_network.plane["metal-admin"]: Refreshing state... [id=29eca094-9b40-4e8c-9750-d1c7857f0a3c] +module.vr1_dc0_planes.libvirt_network.plane["provider-public"]: Refreshing state... [id=3bb1e17c-53ce-4c1a-a4af-c9d6ad1f1c1b] +module.office1_network.libvirt_network.office1_local: Refreshing state... [id=8fdd2a97-417c-44d4-89e4-ae8d65594135] +module.voffice1.libvirt_cloudinit_disk.seed: Refreshing state... [id=775234004c2669d9] +module.vr1_dc0_storage.libvirt_pool.dc: Refreshing state... [id=7ce1101c-a89e-40ca-9263-5f572bee40a9] +module.mesh_vr1_dc0_office1.libvirt_network.link: Refreshing state... [id=8318548f-c3d6-4e06-bef4-fe3f11d68125] +module.mesh_vr1_dc1_office1.libvirt_network.link: Refreshing state... [id=38a20d2d-cd91-4604-a5f4-8e2a6609633c] +module.voffice1.libvirt_volume.seed: Refreshing state... [id=/var/lib/libvirt/vr1/office1/voffice1-cloudinit.iso] +module.ubuntu_noble_base.libvirt_volume.base: Refreshing state... [id=/var/lib/libvirt/vr1/office1/ubuntu-24.04-base.qcow2] +module.office1_opnsense.libvirt_volume.disk: Refreshing state... [id=/var/lib/libvirt/vr1/office1/office1-opnsense-disk.qcow2] +module.voffice1.libvirt_volume.disk: Refreshing state... [id=/var/lib/libvirt/vr1/office1/voffice1-disk.qcow2] +module.office1_opnsense.libvirt_domain.vm: Refreshing state... [name=office1-opnsense] +module.voffice1.libvirt_domain.vm: Refreshing state... [name=voffice1] + +Note: Objects have changed outside of OpenTofu + +OpenTofu detected the following changes made outside of OpenTofu since the +last "tofu apply" which may have affected this plan: + + # module.office1_opnsense.libvirt_domain.vm has changed + ~ resource "libvirt_domain" "vm" { + ~ id = 13 -> 2 + name = "office1-opnsense" + # (10 unchanged attributes hidden) + } + + # module.voffice1.libvirt_cloudinit_disk.seed has been deleted + - resource "libvirt_cloudinit_disk" "seed" { + id = "775234004c2669d9" + name = "voffice1-cloudinit" + - path = "/tmp/terraform-provider-libvirt-cloudinit/cloudinit-775234004c2669d9.iso" -> null + # (4 unchanged attributes hidden) + } + + # module.voffice1.libvirt_domain.vm has changed + ~ resource "libvirt_domain" "vm" { + ~ id = 14 -> 1 + name = "voffice1" + # (10 unchanged attributes hidden) + } + + +Unless you have made equivalent changes to your configuration, or ignored the +relevant attributes using ignore_changes, the following plan may include +actions to undo or respond to these changes. + +───────────────────────────────────────────────────────────────────────────── + +OpenTofu used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + + create + ~ update in-place (current -> planned) + - destroy +-/+ destroy and then create replacement + +OpenTofu will perform the following actions: + + # module.office1_opnsense.libvirt_domain.vm will be updated in-place + ~ resource "libvirt_domain" "vm" { + + autostart = true + id = 2 + name = "office1-opnsense" + # (10 unchanged attributes hidden) + } + + # module.voffice1.libvirt_cloudinit_disk.seed will be created + + resource "libvirt_cloudinit_disk" "seed" { + + id = (known after apply) + + meta_data = <<-EOT + instance-id: voffice1-d114 + local-hostname: voffice1 + EOT + + name = "voffice1-cloudinit" + + network_config = <<-EOT + version: 2 + ethernets: + lan: + match: + name: "en*" + dhcp4: true + EOT + + path = (known after apply) + + size = (known after apply) + + user_data = <<-EOT + #cloud-config + hostname: voffice1 + fqdn: voffice1.cloud.neumatrix.local + manage_etc_hosts: true + users: + - name: jessea123 + groups: [adm, sudo] + shell: /bin/bash + sudo: "ALL=(ALL) NOPASSWD:ALL" + ssh_authorized_keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINOgHIEyfMecOJ/G2Tbw5kKMd4ofUfxdyhVb00cjcpUX vr1-office1-svc + package_update: true + packages: + - qemu-guest-agent + runcmd: + - [systemctl, enable, --now, qemu-guest-agent] + EOT + } + + # module.voffice1.libvirt_domain.vm will be updated in-place + ~ resource "libvirt_domain" "vm" { + + autostart = true + id = 1 + name = "voffice1" + # (10 unchanged attributes hidden) + } + + # module.voffice1.libvirt_volume.seed must be replaced +-/+ resource "libvirt_volume" "seed" { + ~ allocation = 45056 -> (known after apply) + ~ capacity = 45056 -> (known after apply) + ~ create = { # forces replacement + ~ content = { + ~ url = "/tmp/terraform-provider-libvirt-cloudinit/cloudinit-775234004c2669d9.iso" -> (known after apply) + } + } + ~ id = "/var/lib/libvirt/vr1/office1/voffice1-cloudinit.iso" -> (known after apply) + ~ key = "/var/lib/libvirt/vr1/office1/voffice1-cloudinit.iso" -> (known after apply) + name = "voffice1-cloudinit.iso" + ~ path = "/var/lib/libvirt/vr1/office1/voffice1-cloudinit.iso" -> (known after apply) + ~ physical = 45056 -> (known after apply) + # (1 unchanged attribute hidden) + } + + # module.vr1_dc0_planes.libvirt_network.plane["data-tenant"] will be destroyed + # (because libvirt_network.plane is not in configuration) + - resource "libvirt_network" "plane" { + - autostart = true -> null + - domain = { + - name = "data-tenant.vr1-dc0.cloud.neumatrix.local" -> null + } -> null + - id = "31782537-790a-4399-a3a9-64dce7232e7b" -> null + - mtu = { + - size = 9000 -> null + } -> null + - name = "vr1-dc0-data-tenant" -> null + - uuid = "31782537-790a-4399-a3a9-64dce7232e7b" -> null + } + + # module.vr1_dc0_planes.libvirt_network.plane["metal-admin"] will be destroyed + # (because libvirt_network.plane is not in configuration) + - resource "libvirt_network" "plane" { + - autostart = true -> null + - domain = { + - name = "metal-admin.vr1-dc0.cloud.neumatrix.local" -> null + } -> null + - id = "29eca094-9b40-4e8c-9750-d1c7857f0a3c" -> null + - mtu = { + - size = 9000 -> null + } -> null + - name = "vr1-dc0-metal-admin" -> null + - uuid = "29eca094-9b40-4e8c-9750-d1c7857f0a3c" -> null + } + + # module.vr1_dc0_planes.libvirt_network.plane["metal-internal"] will be destroyed + # (because libvirt_network.plane is not in configuration) + - resource "libvirt_network" "plane" { + - autostart = true -> null + - domain = { + - name = "metal-internal.vr1-dc0.cloud.neumatrix.local" -> null + } -> null + - id = "99768f9d-256a-4a89-90dd-38de04efea03" -> null + - mtu = { + - size = 9000 -> null + } -> null + - name = "vr1-dc0-metal-internal" -> null + - uuid = "99768f9d-256a-4a89-90dd-38de04efea03" -> null + } + + # module.vr1_dc0_planes.libvirt_network.plane["provider-public"] will be destroyed + # (because libvirt_network.plane is not in configuration) + - resource "libvirt_network" "plane" { + - autostart = true -> null + - domain = { + - name = "provider-public.vr1-dc0.cloud.neumatrix.local" -> null + } -> null + - id = "3bb1e17c-53ce-4c1a-a4af-c9d6ad1f1c1b" -> null + - mtu = { + - size = 9000 -> null + } -> null + - name = "vr1-dc0-provider-public" -> null + - uuid = "3bb1e17c-53ce-4c1a-a4af-c9d6ad1f1c1b" -> null + } + + # module.vr1_dc0_planes.libvirt_network.plane["replication"] will be destroyed + # (because libvirt_network.plane is not in configuration) + - resource "libvirt_network" "plane" { + - autostart = true -> null + - domain = { + - name = "replication.vr1-dc0.cloud.neumatrix.local" -> null + } -> null + - id = "295dca85-52de-4e78-9894-16e48d969654" -> null + - mtu = { + - size = 9000 -> null + } -> null + - name = "vr1-dc0-replication" -> null + - uuid = "295dca85-52de-4e78-9894-16e48d969654" -> null + } + + # module.vr1_dc0_planes.libvirt_network.plane["storage"] will be destroyed + # (because libvirt_network.plane is not in configuration) + - resource "libvirt_network" "plane" { + - autostart = true -> null + - domain = { + - name = "storage.vr1-dc0.cloud.neumatrix.local" -> null + } -> null + - id = "e5558f0e-9601-48fc-8eff-6a0e6adad0d7" -> null + - mtu = { + - size = 9000 -> null + } -> null + - name = "vr1-dc0-storage" -> null + - uuid = "e5558f0e-9601-48fc-8eff-6a0e6adad0d7" -> null + } + + # module.vr1_dc0_uplink.libvirt_network.site_wan will be created + + resource "libvirt_network" "site_wan" { + + autostart = true + + domain = { + + name = "vr1-dc0-uplink" + } + + forward = { + + mode = "nat" + } + + id = (known after apply) + + ips = [ + + { + + address = "172.30.2.1" + + prefix = 24 + }, + ] + + mtu = { + + size = 1500 + } + + name = "vr1-dc0-uplink" + + uuid = (known after apply) + } + + # module.vvr1_dc0.libvirt_cloudinit_disk.seed will be created + + resource "libvirt_cloudinit_disk" "seed" { + + id = (known after apply) + + meta_data = <<-EOT + instance-id: vvr1-dc0-d123 + local-hostname: vvr1-dc0 + EOT + + name = "vvr1-dc0-cloudinit" + + network_config = <<-EOT + version: 2 + ethernets: + mgmt: + match: + name: "enp1s0" + set-name: mgmt + addresses: ["172.31.0.2/30"] + routes: + - to: "10.10.0.0/22" + via: "172.31.0.1" + uplink: + match: + name: "enp2s0" + set-name: uplink + dhcp4: false + dhcp6: false + bridges: + br-vr1-dc0-wan: + interfaces: [uplink] + dhcp4: false + dhcp6: false + parameters: + stp: false + forward-delay: 0 + EOT + + path = (known after apply) + + size = (known after apply) + + user_data = <<-EOT + #cloud-config + hostname: vvr1-dc0 + fqdn: vvr1-dc0.cloud.neumatrix.local + manage_etc_hosts: true + users: + - name: jessea123 + groups: [adm, sudo] + shell: /bin/bash + sudo: "ALL=(ALL) NOPASSWD:ALL" + ssh_authorized_keys: + # D-126 per-env-key (ruling 2026-07-16, option a): vvr1-dc0 authorizes the DEDICATED + # dc0 key, NOT office1's -- per-env blast-radius isolation. Inner root's qemu+ssh matches. + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID54oMqwY4majxq4oWpBviXb6DlVyj99TUPxDkhszcZG vr1-dc0_svc (D-126 per-env key) + package_update: true + packages: + - qemu-guest-agent + runcmd: + - [systemctl, enable, --now, qemu-guest-agent] + EOT + } + + # module.vvr1_dc0.libvirt_domain.vm will be created + + resource "libvirt_domain" "vm" { + + autostart = false + + cpu = { + + features = [] + + mode = "host-passthrough" + } + + devices = { + + disks = [ + + { + + driver = { + + type = "qcow2" + } + + source = { + + volume = { + + pool = "vr1-dc0-pool" + + volume = "vvr1-dc0-disk.qcow2" + } + } + + target = { + + bus = "virtio" + + dev = "vda" + } + }, + + { + + device = "cdrom" + + source = { + + volume = { + + pool = "vr1-dc0-pool" + + volume = "vvr1-dc0-cloudinit.iso" + } + } + + target = { + + bus = "sata" + + dev = "sda" + } + }, + ] + + interfaces = [ + + { + + model = { + + type = "virtio" + } + + source = { + + network = { + + network = "mesh-vr1-dc0-office1" + } + } + }, + + { + + model = { + + type = "virtio" + } + + source = { + + network = { + + network = "vr1-dc0-uplink" + } + } + }, + ] + } + + features = { + + acpi = true + + apic = {} + } + + id = (known after apply) + + memory = 425984 + + memory_unit = "MiB" + + name = "vvr1-dc0" + + os = { + + type = "hvm" + + type_arch = "x86_64" + + type_machine = "q35" + } + + running = true + + type = "kvm" + + uuid = (known after apply) + + vcpu = 108 + } + + # module.vvr1_dc0.libvirt_volume.disk will be created + + resource "libvirt_volume" "disk" { + + allocation = (known after apply) + + backing_store = { + + format = { + + type = "qcow2" + } + + path = "/var/lib/libvirt/vr1/office1/ubuntu-24.04-base.qcow2" + } + + capacity = 3221225472000 + + id = (known after apply) + + key = (known after apply) + + name = "vvr1-dc0-disk.qcow2" + + path = (known after apply) + + physical = (known after apply) + + pool = "vr1-dc0-pool" + + target = { + + format = { + + type = "qcow2" + } + + path = (known after apply) + } + } + + # module.vvr1_dc0.libvirt_volume.seed will be created + + resource "libvirt_volume" "seed" { + + allocation = (known after apply) + + capacity = (known after apply) + + create = { + + content = { + + url = (known after apply) + } + } + + id = (known after apply) + + key = (known after apply) + + name = "vvr1-dc0-cloudinit.iso" + + path = (known after apply) + + physical = (known after apply) + + pool = "vr1-dc0-pool" + } + +Plan: 7 to add, 2 to change, 7 to destroy. + +───────────────────────────────────────────────────────────────────────────── + +Note: You didn't use the -out option to save this plan, so OpenTofu can't +guarantee to take exactly these actions if you run "tofu apply" now. diff --git a/docs/audit/outer-plan-20260719-v8-ignorechanges.txt b/docs/audit/outer-plan-20260719-v8-ignorechanges.txt new file mode 100644 index 0000000..1774bdc --- /dev/null +++ b/docs/audit/outer-plan-20260719-v8-ignorechanges.txt @@ -0,0 +1,407 @@ +module.vr1_dc1_storage.libvirt_pool.dc: Refreshing state... [id=4a1df114-ee04-4c80-9233-cc0c140c8556] +module.mesh_vr1_dc1_office1.libvirt_network.link: Refreshing state... [id=38a20d2d-cd91-4604-a5f4-8e2a6609633c] +module.vr1_dc0_planes.libvirt_network.plane["storage"]: Refreshing state... [id=e5558f0e-9601-48fc-8eff-6a0e6adad0d7] +module.vr1_dc0_planes.libvirt_network.plane["metal-admin"]: Refreshing state... [id=29eca094-9b40-4e8c-9750-d1c7857f0a3c] +module.vr1_dc0_planes.libvirt_network.plane["data-tenant"]: Refreshing state... [id=31782537-790a-4399-a3a9-64dce7232e7b] +module.mesh_vr1_dc0_office1.libvirt_network.link: Refreshing state... [id=8318548f-c3d6-4e06-bef4-fe3f11d68125] +module.vr1_dc0_planes.libvirt_network.plane["provider-public"]: Refreshing state... [id=3bb1e17c-53ce-4c1a-a4af-c9d6ad1f1c1b] +module.vr1_dc0_planes.libvirt_network.plane["metal-internal"]: Refreshing state... [id=99768f9d-256a-4a89-90dd-38de04efea03] +module.vr1_dc0_planes.libvirt_network.plane["replication"]: Refreshing state... [id=295dca85-52de-4e78-9894-16e48d969654] +module.office1_storage.libvirt_pool.dc: Refreshing state... [id=5f94194c-69c1-4b04-a85f-c18d87303a03] +module.voffice1.libvirt_cloudinit_disk.seed: Refreshing state... [id=775234004c2669d9] +module.vr1_dc0_storage.libvirt_pool.dc: Refreshing state... [id=7ce1101c-a89e-40ca-9263-5f572bee40a9] +module.mesh_vr1_dc0_vr1_dc1.libvirt_network.link: Refreshing state... [id=9cbc8589-9f40-48e6-872e-ef3abfe29a93] +module.office1_network.libvirt_network.office1_local: Refreshing state... [id=8fdd2a97-417c-44d4-89e4-ae8d65594135] +module.voffice1.libvirt_volume.seed: Refreshing state... [id=/var/lib/libvirt/vr1/office1/voffice1-cloudinit.iso] +module.ubuntu_noble_base.libvirt_volume.base: Refreshing state... [id=/var/lib/libvirt/vr1/office1/ubuntu-24.04-base.qcow2] +module.office1_opnsense.libvirt_volume.disk: Refreshing state... [id=/var/lib/libvirt/vr1/office1/office1-opnsense-disk.qcow2] +module.voffice1.libvirt_volume.disk: Refreshing state... [id=/var/lib/libvirt/vr1/office1/voffice1-disk.qcow2] +module.office1_opnsense.libvirt_domain.vm: Refreshing state... [name=office1-opnsense] +module.voffice1.libvirt_domain.vm: Refreshing state... [name=voffice1] + +Note: Objects have changed outside of OpenTofu + +OpenTofu detected the following changes made outside of OpenTofu since the +last "tofu apply" which may have affected this plan: + + # module.office1_opnsense.libvirt_domain.vm has changed + ~ resource "libvirt_domain" "vm" { + ~ id = 13 -> 2 + name = "office1-opnsense" + # (10 unchanged attributes hidden) + } + + # module.voffice1.libvirt_domain.vm has changed + ~ resource "libvirt_domain" "vm" { + ~ id = 14 -> 1 + name = "voffice1" + # (10 unchanged attributes hidden) + } + + +Unless you have made equivalent changes to your configuration, or ignored the +relevant attributes using ignore_changes, the following plan may include +actions to undo or respond to these changes. + +───────────────────────────────────────────────────────────────────────────── + +OpenTofu used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + + create + ~ update in-place (current -> planned) + - destroy + +OpenTofu will perform the following actions: + + # module.office1_opnsense.libvirt_domain.vm will be updated in-place + ~ resource "libvirt_domain" "vm" { + + autostart = true + id = 2 + name = "office1-opnsense" + # (10 unchanged attributes hidden) + } + + # module.voffice1.libvirt_cloudinit_disk.seed will be created + + resource "libvirt_cloudinit_disk" "seed" { + + id = (known after apply) + + meta_data = <<-EOT + instance-id: voffice1-d114 + local-hostname: voffice1 + EOT + + name = "voffice1-cloudinit" + + network_config = <<-EOT + version: 2 + ethernets: + lan: + match: + name: "en*" + dhcp4: true + EOT + + path = (known after apply) + + size = (known after apply) + + user_data = <<-EOT + #cloud-config + hostname: voffice1 + fqdn: voffice1.cloud.neumatrix.local + manage_etc_hosts: true + users: + - name: jessea123 + groups: [adm, sudo] + shell: /bin/bash + sudo: "ALL=(ALL) NOPASSWD:ALL" + ssh_authorized_keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINOgHIEyfMecOJ/G2Tbw5kKMd4ofUfxdyhVb00cjcpUX vr1-office1-svc + package_update: true + packages: + - qemu-guest-agent + runcmd: + - [systemctl, enable, --now, qemu-guest-agent] + EOT + } + + # module.voffice1.libvirt_domain.vm will be updated in-place + ~ resource "libvirt_domain" "vm" { + + autostart = true + id = 1 + name = "voffice1" + # (10 unchanged attributes hidden) + } + + # module.vr1_dc0_planes.libvirt_network.plane["data-tenant"] will be destroyed + # (because libvirt_network.plane is not in configuration) + - resource "libvirt_network" "plane" { + - autostart = true -> null + - domain = { + - name = "data-tenant.vr1-dc0.cloud.neumatrix.local" -> null + } -> null + - id = "31782537-790a-4399-a3a9-64dce7232e7b" -> null + - mtu = { + - size = 9000 -> null + } -> null + - name = "vr1-dc0-data-tenant" -> null + - uuid = "31782537-790a-4399-a3a9-64dce7232e7b" -> null + } + + # module.vr1_dc0_planes.libvirt_network.plane["metal-admin"] will be destroyed + # (because libvirt_network.plane is not in configuration) + - resource "libvirt_network" "plane" { + - autostart = true -> null + - domain = { + - name = "metal-admin.vr1-dc0.cloud.neumatrix.local" -> null + } -> null + - id = "29eca094-9b40-4e8c-9750-d1c7857f0a3c" -> null + - mtu = { + - size = 9000 -> null + } -> null + - name = "vr1-dc0-metal-admin" -> null + - uuid = "29eca094-9b40-4e8c-9750-d1c7857f0a3c" -> null + } + + # module.vr1_dc0_planes.libvirt_network.plane["metal-internal"] will be destroyed + # (because libvirt_network.plane is not in configuration) + - resource "libvirt_network" "plane" { + - autostart = true -> null + - domain = { + - name = "metal-internal.vr1-dc0.cloud.neumatrix.local" -> null + } -> null + - id = "99768f9d-256a-4a89-90dd-38de04efea03" -> null + - mtu = { + - size = 9000 -> null + } -> null + - name = "vr1-dc0-metal-internal" -> null + - uuid = "99768f9d-256a-4a89-90dd-38de04efea03" -> null + } + + # module.vr1_dc0_planes.libvirt_network.plane["provider-public"] will be destroyed + # (because libvirt_network.plane is not in configuration) + - resource "libvirt_network" "plane" { + - autostart = true -> null + - domain = { + - name = "provider-public.vr1-dc0.cloud.neumatrix.local" -> null + } -> null + - id = "3bb1e17c-53ce-4c1a-a4af-c9d6ad1f1c1b" -> null + - mtu = { + - size = 9000 -> null + } -> null + - name = "vr1-dc0-provider-public" -> null + - uuid = "3bb1e17c-53ce-4c1a-a4af-c9d6ad1f1c1b" -> null + } + + # module.vr1_dc0_planes.libvirt_network.plane["replication"] will be destroyed + # (because libvirt_network.plane is not in configuration) + - resource "libvirt_network" "plane" { + - autostart = true -> null + - domain = { + - name = "replication.vr1-dc0.cloud.neumatrix.local" -> null + } -> null + - id = "295dca85-52de-4e78-9894-16e48d969654" -> null + - mtu = { + - size = 9000 -> null + } -> null + - name = "vr1-dc0-replication" -> null + - uuid = "295dca85-52de-4e78-9894-16e48d969654" -> null + } + + # module.vr1_dc0_planes.libvirt_network.plane["storage"] will be destroyed + # (because libvirt_network.plane is not in configuration) + - resource "libvirt_network" "plane" { + - autostart = true -> null + - domain = { + - name = "storage.vr1-dc0.cloud.neumatrix.local" -> null + } -> null + - id = "e5558f0e-9601-48fc-8eff-6a0e6adad0d7" -> null + - mtu = { + - size = 9000 -> null + } -> null + - name = "vr1-dc0-storage" -> null + - uuid = "e5558f0e-9601-48fc-8eff-6a0e6adad0d7" -> null + } + + # module.vr1_dc0_uplink.libvirt_network.site_wan will be created + + resource "libvirt_network" "site_wan" { + + autostart = true + + domain = { + + name = "vr1-dc0-uplink" + } + + forward = { + + mode = "nat" + } + + id = (known after apply) + + ips = [ + + { + + address = "172.30.2.1" + + prefix = 24 + }, + ] + + mtu = { + + size = 1500 + } + + name = "vr1-dc0-uplink" + + uuid = (known after apply) + } + + # module.vvr1_dc0.libvirt_cloudinit_disk.seed will be created + + resource "libvirt_cloudinit_disk" "seed" { + + id = (known after apply) + + meta_data = <<-EOT + instance-id: vvr1-dc0-d123 + local-hostname: vvr1-dc0 + EOT + + name = "vvr1-dc0-cloudinit" + + network_config = <<-EOT + version: 2 + ethernets: + mgmt: + match: + name: "enp1s0" + set-name: mgmt + addresses: ["172.31.0.2/30"] + routes: + - to: "10.10.0.0/22" + via: "172.31.0.1" + uplink: + match: + name: "enp2s0" + set-name: uplink + dhcp4: false + dhcp6: false + bridges: + br-vr1-dc0-wan: + interfaces: [uplink] + dhcp4: false + dhcp6: false + parameters: + stp: false + forward-delay: 0 + EOT + + path = (known after apply) + + size = (known after apply) + + user_data = <<-EOT + #cloud-config + hostname: vvr1-dc0 + fqdn: vvr1-dc0.cloud.neumatrix.local + manage_etc_hosts: true + users: + - name: jessea123 + groups: [adm, sudo] + shell: /bin/bash + sudo: "ALL=(ALL) NOPASSWD:ALL" + ssh_authorized_keys: + # D-126 per-env-key (ruling 2026-07-16, option a): vvr1-dc0 authorizes the DEDICATED + # dc0 key, NOT office1's -- per-env blast-radius isolation. Inner root's qemu+ssh matches. + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID54oMqwY4majxq4oWpBviXb6DlVyj99TUPxDkhszcZG vr1-dc0_svc (D-126 per-env key) + package_update: true + packages: + - qemu-guest-agent + runcmd: + - [systemctl, enable, --now, qemu-guest-agent] + EOT + } + + # module.vvr1_dc0.libvirt_domain.vm will be created + + resource "libvirt_domain" "vm" { + + autostart = false + + cpu = { + + features = [] + + mode = "host-passthrough" + } + + devices = { + + disks = [ + + { + + driver = { + + type = "qcow2" + } + + source = { + + volume = { + + pool = "vr1-dc0-pool" + + volume = "vvr1-dc0-disk.qcow2" + } + } + + target = { + + bus = "virtio" + + dev = "vda" + } + }, + + { + + device = "cdrom" + + source = { + + volume = { + + pool = "vr1-dc0-pool" + + volume = "vvr1-dc0-cloudinit.iso" + } + } + + target = { + + bus = "sata" + + dev = "sda" + } + }, + ] + + interfaces = [ + + { + + model = { + + type = "virtio" + } + + source = { + + network = { + + network = "mesh-vr1-dc0-office1" + } + } + }, + + { + + model = { + + type = "virtio" + } + + source = { + + network = { + + network = "vr1-dc0-uplink" + } + } + }, + ] + } + + features = { + + acpi = true + + apic = {} + } + + id = (known after apply) + + memory = 425984 + + memory_unit = "MiB" + + name = "vvr1-dc0" + + os = { + + type = "hvm" + + type_arch = "x86_64" + + type_machine = "q35" + } + + running = true + + type = "kvm" + + uuid = (known after apply) + + vcpu = 108 + } + + # module.vvr1_dc0.libvirt_volume.disk will be created + + resource "libvirt_volume" "disk" { + + allocation = (known after apply) + + backing_store = { + + format = { + + type = "qcow2" + } + + path = "/var/lib/libvirt/vr1/office1/ubuntu-24.04-base.qcow2" + } + + capacity = 3221225472000 + + id = (known after apply) + + key = (known after apply) + + name = "vvr1-dc0-disk.qcow2" + + path = (known after apply) + + physical = (known after apply) + + pool = "vr1-dc0-pool" + + target = { + + format = { + + type = "qcow2" + } + + path = (known after apply) + } + } + + # module.vvr1_dc0.libvirt_volume.seed will be created + + resource "libvirt_volume" "seed" { + + allocation = (known after apply) + + capacity = (known after apply) + + create = { + + content = { + + url = (known after apply) + } + } + + id = (known after apply) + + key = (known after apply) + + name = "vvr1-dc0-cloudinit.iso" + + path = (known after apply) + + physical = (known after apply) + + pool = "vr1-dc0-pool" + } + +Plan: 6 to add, 2 to change, 6 to destroy. + +───────────────────────────────────────────────────────────────────────────── + +Note: You didn't use the -out option to save this plan, so OpenTofu can't +guarantee to take exactly these actions if you run "tofu apply" now. diff --git a/docs/audit/throwaway-v7-20260719.txt b/docs/audit/throwaway-v7-20260719.txt new file mode 100644 index 0000000..f5e2622 --- /dev/null +++ b/docs/audit/throwaway-v7-20260719.txt @@ -0,0 +1,331 @@ +=== G4-v7 throwaway exercise, 2026-07-19 === +=== STEP 1: create (tofu apply -auto-approve, throwaway root) === + +OpenTofu used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + + create + +OpenTofu will perform the following actions: + + # libvirt_cloudinit_disk.seed will be created + + resource "libvirt_cloudinit_disk" "seed" { + + id = (known after apply) + + meta_data = <<-EOT + instance-id: throwaway-v7 + local-hostname: throwaway-v7 + EOT + + name = "throwaway-v7-cloudinit" + + path = (known after apply) + + size = (known after apply) + + user_data = <<-EOT + #cloud-config + hostname: throwaway-v7 + EOT + } + + # libvirt_domain.vm will be created + + resource "libvirt_domain" "vm" { + + devices = { + + disks = [ + + { + + driver = { + + type = "qcow2" + } + + source = { + + volume = { + + pool = "throwaway-v7-pool" + + volume = "throwaway-v7-disk.qcow2" + } + } + + target = { + + bus = "virtio" + + dev = "vda" + } + }, + + { + + device = "cdrom" + + source = { + + volume = { + + pool = "throwaway-v7-pool" + + volume = "throwaway-v7-cloudinit.iso" + } + } + + target = { + + bus = "sata" + + dev = "sda" + } + }, + ] + } + + features = { + + acpi = true + + apic = {} + } + + id = (known after apply) + + memory = 256 + + memory_unit = "MiB" + + name = "throwaway-v7-domain" + + os = { + + type = "hvm" + + type_arch = "x86_64" + + type_machine = "q35" + } + + running = true + + type = "kvm" + + uuid = (known after apply) + + vcpu = 1 + } + + # libvirt_pool.scratch will be created + + resource "libvirt_pool" "scratch" { + + allocation = (known after apply) + + available = (known after apply) + + capacity = (known after apply) + + id = (known after apply) + + name = "throwaway-v7-pool" + + target = { + + path = "/var/lib/libvirt/vr1/throwaway-v7" + } + + type = "dir" + + uuid = (known after apply) + } + + # libvirt_volume.disk will be created + + resource "libvirt_volume" "disk" { + + allocation = (known after apply) + + capacity = 104857600 + + id = (known after apply) + + key = (known after apply) + + name = "throwaway-v7-disk.qcow2" + + path = (known after apply) + + physical = (known after apply) + + pool = "throwaway-v7-pool" + + target = { + + format = { + + type = "qcow2" + } + + path = (known after apply) + } + } + + # libvirt_volume.seed will be created + + resource "libvirt_volume" "seed" { + + allocation = (known after apply) + + capacity = (known after apply) + + create = { + + content = { + + url = (known after apply) + } + } + + id = (known after apply) + + key = (known after apply) + + name = "throwaway-v7-cloudinit.iso" + + path = (known after apply) + + physical = (known after apply) + + pool = "throwaway-v7-pool" + } + +Plan: 5 to add, 0 to change, 0 to destroy. +libvirt_cloudinit_disk.seed: Creating... +libvirt_cloudinit_disk.seed: Creation complete after 0s [id=c78933fb4b72fe38] +libvirt_pool.scratch: Creating... +libvirt_pool.scratch: Creation complete after 0s [id=7e9fef28-f7dc-4922-8631-5f6d2c31df9b] +libvirt_volume.seed: Creating... +libvirt_volume.disk: Creating... +libvirt_volume.seed: Creation complete after 0s [id=/var/lib/libvirt/vr1/throwaway-v7/throwaway-v7-cloudinit.iso] +libvirt_volume.disk: Creation complete after 0s [id=/var/lib/libvirt/vr1/throwaway-v7/throwaway-v7-disk.qcow2] +libvirt_domain.vm: Creating... +libvirt_domain.vm: Creation complete after 1s [name=throwaway-v7-domain] + +Apply complete! Resources: 5 added, 0 changed, 0 destroyed. + +=== STEP 2: pre-replace live state (virsh) === +running + +3 + + Name Path +-------------------------------------------------------------------------------------------- + throwaway-v7-cloudinit.iso /var/lib/libvirt/vr1/throwaway-v7/throwaway-v7-cloudinit.iso + throwaway-v7-disk.qcow2 /var/lib/libvirt/vr1/throwaway-v7/throwaway-v7-disk.qcow2 + +staging ISO(s): +total 116 +drwxr-xr-x 2 jessea123 jessea123 4096 Jul 19 08:04 . +drwxrwxrwt 41 root root 65536 Jul 19 08:04 .. +-rw-r--r-- 1 jessea123 jessea123 43008 Jul 19 08:04 cloudinit-c78933fb4b72fe38.iso + +=== STEP 3: simulate host-reboot loss of the staging ISO === +$ rm /tmp/terraform-provider-libvirt-cloudinit/cloudinit-c78933fb4b72fe38.iso +removed + +=== STEP 4: re-plan after ISO loss (expect forced replacement) === +libvirt_cloudinit_disk.seed: Refreshing state... [id=c78933fb4b72fe38] +libvirt_pool.scratch: Refreshing state... [id=7e9fef28-f7dc-4922-8631-5f6d2c31df9b] +libvirt_volume.seed: Refreshing state... [id=/var/lib/libvirt/vr1/throwaway-v7/throwaway-v7-cloudinit.iso] +libvirt_volume.disk: Refreshing state... [id=/var/lib/libvirt/vr1/throwaway-v7/throwaway-v7-disk.qcow2] +libvirt_domain.vm: Refreshing state... [name=throwaway-v7-domain] + +Note: Objects have changed outside of OpenTofu + +OpenTofu detected the following changes made outside of OpenTofu since the +last "tofu apply" which may have affected this plan: + + # libvirt_cloudinit_disk.seed has been deleted + - resource "libvirt_cloudinit_disk" "seed" { + id = "c78933fb4b72fe38" + name = "throwaway-v7-cloudinit" + - path = "/tmp/terraform-provider-libvirt-cloudinit/cloudinit-c78933fb4b72fe38.iso" -> null + # (3 unchanged attributes hidden) + } + + +Unless you have made equivalent changes to your configuration, or ignored the +relevant attributes using ignore_changes, the following plan may include +actions to undo or respond to these changes. + +───────────────────────────────────────────────────────────────────────────── + +OpenTofu used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + + create +-/+ destroy and then create replacement + +OpenTofu will perform the following actions: + + # libvirt_cloudinit_disk.seed will be created + + resource "libvirt_cloudinit_disk" "seed" { + + id = (known after apply) + + meta_data = <<-EOT + instance-id: throwaway-v7 + local-hostname: throwaway-v7 + EOT + + name = "throwaway-v7-cloudinit" + + path = (known after apply) + + size = (known after apply) + + user_data = <<-EOT + #cloud-config + hostname: throwaway-v7 + EOT + } + + # libvirt_volume.seed must be replaced +-/+ resource "libvirt_volume" "seed" { + ~ allocation = 45056 -> (known after apply) + ~ capacity = 43008 -> (known after apply) + ~ create = { # forces replacement + ~ content = { + ~ url = "/tmp/terraform-provider-libvirt-cloudinit/cloudinit-c78933fb4b72fe38.iso" -> (known after apply) + } + } + ~ id = "/var/lib/libvirt/vr1/throwaway-v7/throwaway-v7-cloudinit.iso" -> (known after apply) + ~ key = "/var/lib/libvirt/vr1/throwaway-v7/throwaway-v7-cloudinit.iso" -> (known after apply) + name = "throwaway-v7-cloudinit.iso" + ~ path = "/var/lib/libvirt/vr1/throwaway-v7/throwaway-v7-cloudinit.iso" -> (known after apply) + ~ physical = 43008 -> (known after apply) + # (1 unchanged attribute hidden) + } + +Plan: 2 to add, 0 to change, 1 to destroy. + +───────────────────────────────────────────────────────────────────────────── + +Note: You didn't use the -out option to save this plan, so OpenTofu can't +guarantee to take exactly these actions if you run "tofu apply" now. + +=== STEP 5: apply the replacement UNDER the running domain === +domid before: 3 +libvirt_cloudinit_disk.seed: Refreshing state... [id=c78933fb4b72fe38] +libvirt_pool.scratch: Refreshing state... [id=7e9fef28-f7dc-4922-8631-5f6d2c31df9b] +libvirt_volume.seed: Refreshing state... [id=/var/lib/libvirt/vr1/throwaway-v7/throwaway-v7-cloudinit.iso] +libvirt_volume.disk: Refreshing state... [id=/var/lib/libvirt/vr1/throwaway-v7/throwaway-v7-disk.qcow2] +libvirt_domain.vm: Refreshing state... [name=throwaway-v7-domain] + +Note: Objects have changed outside of OpenTofu + +OpenTofu detected the following changes made outside of OpenTofu since the +last "tofu apply" which may have affected this plan: + + # libvirt_cloudinit_disk.seed has been deleted + - resource "libvirt_cloudinit_disk" "seed" { + id = "c78933fb4b72fe38" + name = "throwaway-v7-cloudinit" + - path = "/tmp/terraform-provider-libvirt-cloudinit/cloudinit-c78933fb4b72fe38.iso" -> null + # (3 unchanged attributes hidden) + } + + +Unless you have made equivalent changes to your configuration, or ignored the +relevant attributes using ignore_changes, the following plan may include +actions to undo or respond to these changes. + +───────────────────────────────────────────────────────────────────────────── + +OpenTofu used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + + create +-/+ destroy and then create replacement + +OpenTofu will perform the following actions: + + # libvirt_cloudinit_disk.seed will be created + + resource "libvirt_cloudinit_disk" "seed" { + + id = (known after apply) + + meta_data = <<-EOT + instance-id: throwaway-v7 + local-hostname: throwaway-v7 + EOT + + name = "throwaway-v7-cloudinit" + + path = (known after apply) + + size = (known after apply) + + user_data = <<-EOT + #cloud-config + hostname: throwaway-v7 + EOT + } + + # libvirt_volume.seed must be replaced +-/+ resource "libvirt_volume" "seed" { + ~ allocation = 45056 -> (known after apply) + ~ capacity = 43008 -> (known after apply) + ~ create = { # forces replacement + ~ content = { + ~ url = "/tmp/terraform-provider-libvirt-cloudinit/cloudinit-c78933fb4b72fe38.iso" -> (known after apply) + } + } + ~ id = "/var/lib/libvirt/vr1/throwaway-v7/throwaway-v7-cloudinit.iso" -> (known after apply) + ~ key = "/var/lib/libvirt/vr1/throwaway-v7/throwaway-v7-cloudinit.iso" -> (known after apply) + name = "throwaway-v7-cloudinit.iso" + ~ path = "/var/lib/libvirt/vr1/throwaway-v7/throwaway-v7-cloudinit.iso" -> (known after apply) + ~ physical = 43008 -> (known after apply) + # (1 unchanged attribute hidden) + } + +Plan: 2 to add, 0 to change, 1 to destroy. +libvirt_volume.seed: Destroying... [id=/var/lib/libvirt/vr1/throwaway-v7/throwaway-v7-cloudinit.iso] +libvirt_volume.seed: Destruction complete after 0s +libvirt_cloudinit_disk.seed: Creating... +libvirt_cloudinit_disk.seed: Creation complete after 0s [id=c78933fb4b72fe38] +libvirt_volume.seed: Creating... +libvirt_volume.seed: Creation complete after 0s [id=/var/lib/libvirt/vr1/throwaway-v7/throwaway-v7-cloudinit.iso] + +Apply complete! Resources: 2 added, 0 changed, 1 destroyed. +rc=0 +domid after: 3 +domstate after: running + +=== STEP 6: teardown (tofu destroy) + zero-residue verification === +libvirt_volume.seed: Destruction complete after 0s +libvirt_volume.disk: Destruction complete after 0s +libvirt_cloudinit_disk.seed: Destroying... [id=c78933fb4b72fe38] +libvirt_cloudinit_disk.seed: Destruction complete after 0s +libvirt_pool.scratch: Destroying... [id=7e9fef28-f7dc-4922-8631-5f6d2c31df9b] +libvirt_pool.scratch: Destruction complete after 0s + +Destroy complete! Resources: 5 destroyed. +rc=0 +--- residue checks --- +domains: 0 +pools: 0 +dir: ls: cannot access '/var/lib/libvirt/vr1/throwaway-v7': No such file or directory +staging: diff --git a/docs/design-decisions.md b/docs/design-decisions.md index fe3b650..f546332 100644 --- a/docs/design-decisions.md +++ b/docs/design-decisions.md @@ -4106,3 +4106,43 @@ **Related:** D-100/D-122 (edge = boundary role), D-113 (REST-API config), D-105 (COS scope), D-106 (Designate owns DNS), D-107 (Tailscale subnet-router VM), D-124/D-125 (static transit), D-127 (autostart), D-126 (base-leg). **Revert:** delete this entry + the review doc; nothing was installed. + +## D-130: cloudinit seed-volume durable fix -- ignore_changes on the staging-derived create + +**Status:** ADOPTED 2026-07-19 (operator; GA-R5 -- question + utterance below; sweep Batch 1 item 3). + +**Question as presented (2026-07-19):** "D-130 -- durable mechanism for the cloudinit seed-volume +forced replacement (recurs after every host reboot; affects voffice1 + vvr1-dc0 + vvr1-dc1). v8 +CONFIRMED your contingent lean (a): ignore_changes fully suppresses the replacement (7/2/7 -> 6/2/6, +zero forces-replacement). v7 additionally showed the replace itself would not bounce a running guest. +Which mechanism do you adopt? (a) ignore_changes / (b) persistent TMPDIR staging / neither-defer." +**Operator answer (2026-07-19), exact utterance:** "(a) ignore_changes (Recommended)". + +**Ruling:** `modules/cloudinit-vm`'s `libvirt_volume.seed` carries +`lifecycle { ignore_changes = [create] }`. The provider's staging ISO path (Go os.TempDir-derived, +no provider knob -- verified via `tofu providers schema -json`) is re-minted after every host +reboot; without the lifecycle guard that config-derived `create.content.url` change forces +replacement of a correct, in-use volume on every post-reboot plan (GA-F01; mechanism verified +against provider v0.9.8 source in docs/finding-20260718-voffice1-cloudinit-seed-replace.md). + +**Evidence (captured, quoted per charter 1a):** +- G4-v8 `docs/audit/outer-plan-20260719-v8-ignorechanges.txt`: with the edit, + "Plan: 6 to add, 2 to change, 6 to destroy." and ZERO forces-replacement lines + (baseline same day, `outer-plan-20260719-baseline.txt`: "Plan: 7 to add, 2 to change, + 7 to destroy." -- identical to the 2026-07-18 capture, no drift). DC0's own seed volume + still plans as a clean create -- the build path is unaffected. +- G4-v7 `docs/audit/throwaway-v7-20260719.txt`: defect reproduced on an isolated + `throwaway-v7-*` domain (own scratch pool, zero live contact); the replacement applied + UNDER the running domain completed rc=0 with NO bounce ("domid before: 3" / "domid after: 3", + domstate running); teardown verified zero residue. Corollary recorded: the source-verified + bounce path is the autostart/domain Update path, NOT the volume replace -- the churn's real + hazard is plan noise plus an unscoped apply carrying the autostart diffs (which DO bounce). + +**Rejected:** (b) persistent `$TMPDIR` staging + env guard -- environment-dependent, fragile +across operators/hosts; not needed on the v8 evidence. Defer/no-fix -- leaves every post-reboot +plan carrying a replace pair and keeps G7's plan-matches-evidence gate noisy. + +**Scope:** all consumers of `modules/cloudinit-vm` (voffice1, vvr1-dc0, later vvr1-dc1). +**Delivery:** module edit + `tests/cloudinit-vm/run-tests.sh` harness (static lifecycle-guard +assertions; `tofu validate` when the binary is present). **Revert:** remove the lifecycle block +and the harness; the replace pair returns on the next post-reboot plan. diff --git a/opentofu/modules/cloudinit-vm/main.tf b/opentofu/modules/cloudinit-vm/main.tf index 43fed3a..6f6d907 100644 --- a/opentofu/modules/cloudinit-vm/main.tf +++ b/opentofu/modules/cloudinit-vm/main.tf @@ -63,6 +63,16 @@ url = libvirt_cloudinit_disk.seed.path } } + + # D-130 (ADOPTED 2026-07-19): the provider's staging ISO path (os.TempDir- + # derived, no provider knob) is re-minted after every host reboot, so the + # config-derived create.content.url would force replacement of a correct, + # in-use volume on every post-reboot plan. Verified: suppression by v8 + # (outer plan 7/2/7 -> 6/2/6, zero forces-replacement), mechanism by v7 + # (docs/audit/throwaway-v7-20260719.txt). Guarded by tests/cloudinit-vm. + lifecycle { + ignore_changes = [create] + } } resource "libvirt_domain" "vm" { diff --git a/tests/cloudinit-vm/run-tests.sh b/tests/cloudinit-vm/run-tests.sh new file mode 100644 index 0000000..b4ab4f1 --- /dev/null +++ b/tests/cloudinit-vm/run-tests.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# tests/cloudinit-vm/run-tests.sh -- guard for modules/cloudinit-vm (D-130). +# Static assertions that the D-130 lifecycle guard is present and correctly +# placed (regression guard: removing it silently reintroduces the post-reboot +# seed-volume forced replacement, GA-F01). Runs `tofu validate` on the module +# when the binary is available; skips gracefully when it is not. +# Exit: 0 all pass | 1 any case failed. ASCII + LF. +set -uo pipefail +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MOD="$(cd "$HERE/../.." && pwd)/opentofu/modules/cloudinit-vm" +PASS=0; FAIL=0 +ok(){ echo " PASS $1"; PASS=$((PASS+1)); } +no(){ echo " FAIL $1"; FAIL=$((FAIL+1)); } + +[ -f "$MOD/main.tf" ] && ok "T1 module main.tf present" || no "T1 module main.tf present" + +# T2: the D-130 lifecycle guard lives INSIDE the seed-volume resource block -- +# extract the block by brace counting from awk, then assert its content. +SEED_BLOCK="$(awk '/^resource "libvirt_volume" "seed" \{/{f=1} f{print; n+=gsub(/\{/,"{"); n-=gsub(/\}/,"}"); if(n==0 && f)exit}' "$MOD/main.tf")" +grep -q 'ignore_changes' <<<"$SEED_BLOCK" \ + && ok "T2 seed volume carries lifecycle ignore_changes (D-130)" \ + || no "T2 seed volume carries lifecycle ignore_changes (D-130)" +grep -q 'ignore_changes = \[create\]' <<<"$SEED_BLOCK" \ + && ok "T3 guard targets the create attribute exactly" \ + || no "T3 guard targets the create attribute exactly" +grep -q 'D-130' <<<"$SEED_BLOCK" \ + && ok "T4 guard cites its governing decision" \ + || no "T4 guard cites its governing decision" + +# T5: the guard must NOT leak onto the data disk (only the seed volume's +# create is staging-derived; ignoring the disk's attributes would mask drift) +DISK_BLOCK="$(awk '/^resource "libvirt_volume" "disk" \{/{f=1} f{print; n+=gsub(/\{/,"{"); n-=gsub(/\}/,"}"); if(n==0 && f)exit}' "$MOD/main.tf")" +grep -q 'ignore_changes' <<<"$DISK_BLOCK" \ + && no "T5 data-disk volume has NO ignore_changes" \ + || ok "T5 data-disk volume has NO ignore_changes" + +# T6: module still validates when tofu is available (init -backend=false is +# offline once the provider is in the plugin cache; skip cleanly otherwise) +if command -v tofu >/dev/null 2>&1; then + TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT + cp "$MOD"/*.tf "$TMP"/ + if (cd "$TMP" && tofu init -backend=false -input=false >/dev/null 2>&1 \ + && tofu validate >/dev/null 2>&1); then + ok "T6 module validates (tofu validate)" + else + no "T6 module validates (tofu validate)" + fi +else + echo " SKIP T6 tofu binary not available (static checks above still bind)" +fi + +echo; echo "cloudinit-vm: $PASS passed, $FAIL failed" +[ "$FAIL" -eq 0 ]