# cloudinit-vm: one cloud-init-provisioned VM booting from a shared base
# image via copy-on-write. For Office1's own service VMs (MAAS-region,
# NetBox, GitBucket -- D-103) -- genuinely different from
# modules/node-vm's blank-disk PXE pattern, which is for MAAS-managed
# OpenStack nodes that MAAS itself images after enlistment. Call
# modules/base-image once per distinct image, this module once per VM
# instance.
#
# NOT CONFIRMED for OPNsense: this whole pattern (cloud-init/NoCloud via
# libvirt_cloudinit_disk) is standard for Linux cloud images; OPNsense is
# FreeBSD-based and its cloud-init/NoCloud support is not the same guarantee.
# Do not assume this module covers OPNsense without checking that
# specifically first -- see opentofu/README.md.
#
# Every resource/attribute shape below is used exactly as shown in
# examples/alpine_cloudinit.tf (fetched and read directly this session, tag
# v0.9.8) -- attribute-style nested objects throughout, same convention as
# modules/node-vm and the corrected modules/dc-planes etc. No `graphics`
# block: this is a headless server VM pattern (the real example included VNC
# graphics; omitted here as unnecessary for a service VM, not because it's
# confirmed invalid to include -- add it back if console-debug access is
# ever wanted).

resource "libvirt_volume" "disk" {
  name     = "${var.vm_name}-disk.qcow2"
  pool     = var.pool_name
  capacity = var.disk_size_bytes

  target = {
    format = {
      type = "qcow2"
    }
  }

  backing_store = {
    path = var.base_volume_path # the caller passes module.<base_image>.path
    # straight through -- a real attribute
    # reference, matching
    # examples/alpine_cloudinit.tf's
    # `libvirt_volume.alpine_base.path` exactly,
    # just threaded across a module boundary
    # instead of within one file.
    format = {
      type = "qcow2"
    }
  }
}

resource "libvirt_cloudinit_disk" "seed" {
  name = "${var.vm_name}-cloudinit"

  user_data      = var.user_data
  meta_data      = var.meta_data
  network_config = var.network_config
}

resource "libvirt_volume" "seed" {
  name = "${var.vm_name}-cloudinit.iso"
  pool = var.pool_name

  create = {
    content = {
      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" {
  name = var.vm_name
  # memory_unit is REQUIRED -- see the opnsense-edge module for the full write-up. On
  # dmacvicar/libvirt >=0.9, bare `memory` is interpreted in libvirt's default unit (KiB),
  # NOT MiB, so omitting this gives the guest 1024x too little RAM and it triple-faults
  # in its bootloader. (2026-07-12 incident.)
  memory      = var.memory_mib
  memory_unit = "MiB"
  vcpu        = var.vcpu
  type        = "kvm"
  running     = true
  autostart   = var.autostart # D-127 boot matrix -- set per instance in the root (never defaulted-by-accident)

  # ACPI/APIC: with no `features` block libvirt renders `acpi=off` -- see the
  # opnsense-edge module for the full write-up (a FreeBSD guest PANICS outright).
  # A Linux guest will usually still boot without ACPI, but degraded: no clean
  # ACPI shutdown/reboot signalling, and unreliable CPU/IRQ enumeration. Every
  # normal libvirt guest sets both; there is no reason for ours not to.
  features = {
    acpi = true
    apic = {}
  }

  # host-passthrough ALWAYS: with no cpu block at all libvirt renders a generic emulated
  # CPU model that exposes NO `svm` flag, so nested KVM is impossible in the guest no
  # matter what is asked for. (Measured 2026-07-13: the host is an EPYC 9965 while a
  # default-CPU guest is handed an "Opteron_G3".) `svm` itself is then passed through or
  # disabled per var.expose_nested_virt -- see modules/opnsense-edge, which disables it as
  # hardening for a router guest. D-114's site containment VMs REQUIRE it passed through:
  # they run LXD VMs composed by MAAS.
  cpu = {
    mode = "host-passthrough"
    features = var.expose_nested_virt ? [] : [
      {
        name   = "svm"
        policy = "disable"
      }
    ]
  }

  os = {
    type         = "hvm"
    type_arch    = "x86_64"
    type_machine = "q35"
  }

  devices = {
    disks = [
      {
        source = {
          volume = {
            pool   = libvirt_volume.disk.pool
            volume = libvirt_volume.disk.name
          }
        }
        target = {
          dev = "vda"
          bus = "virtio"
        }
        driver = {
          type = "qcow2"
        }
      },
      {
        device = "cdrom"
        source = {
          volume = {
            pool   = libvirt_volume.seed.pool
            volume = libvirt_volume.seed.name
          }
        }
        target = {
          dev = "sda"
          bus = "sata"
        }
      }
    ]

    interfaces = [
      for net_name in var.network_names : {
        model = {
          type = "virtio"
        }
        source = {
          network = {
            network = net_name
          }
        }
      }
    ]
  }
}
