diff --git a/README.md b/README.md index 51376d0e..67e29123 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Follow this [install guide](docs/guides/installation.md) to install the plugin. * `proxmox_vm_qemu` does not (yet) validate vm names, be sure to only use alphanumeric and dashes otherwise you may get an opaque 400 Parameter Verification failed (indicating a bad value was sent to proxmox). * When using the `proxmox_lxc` resource, the provider will crash unless `rootfs` is defined. +* When using the Network Boot mode (PXE), a valid NIC must be defined for the VM, and the boot order must specify network first. ## Contributing diff --git a/docs/resources/vm_qemu.md b/docs/resources/vm_qemu.md old mode 100755 new mode 100644 index 63c7d5f5..58fb7dd4 --- a/docs/resources/vm_qemu.md +++ b/docs/resources/vm_qemu.md @@ -4,19 +4,30 @@ This resource manages a Proxmox VM Qemu container. ## Create a Qemu VM resource -You can start from either an ISO or clone an existing VM. Optimally, you could create a VM resource you will use a clone -base with an ISO, and make the rest of the VM resources depend on that base "template" and clone it. +You can start from either an ISO, PXE boot the VM, or clone an existing VM. Optimally, you could +create a VM resource you will use a clone base with an ISO, and make the rest of the VM resources +depend on that base "template" and clone it. -When creating a VM Qemu resource, you create a `proxmox_vm_qemu` resource block. The name and target node of the VM are -the only required parameters. +When creating a VM Qemu resource, you create a `proxmox_vm_qemu` resource block. For ISO and clone +modes, the name and target node of the VM are the only required parameters. + +For the PXE mode, the `boot` directive must contain a *Network* boot order first. Generally, PXE +boot VMs should NOT contain the Agent config (`agent = 1`). PXE boot mode also requires external +infrastructure to support the Network PXE boot request by the VM. ```hcl resource "proxmox_vm_qemu" "resource-name" { name = "VM-name" target_node = "Node to create the VM on" iso = "ISO file name" - # or + + ### or for a Clone VM operation # clone = "template to clone" + + ### or for a PXE boot VM operation + # pxe = true + # boot = "net0;scsi0" + # agent = 0 } ``` @@ -80,6 +91,34 @@ variable `ciuser`, `cipassword`, `ipconfig0`, `ipconfig1`, `ipconfig2`, `ipconfi For more information, see the [Cloud-init guide](../guides/cloud_init.md). +## Provision through PXE Network Boot + +Specifying the `pxe = true` option will enable the Virtual Machine to perform a Network Boot (PXE). +In addition to enabling the PXE mode, a few other options should be specified to ensure successful +boot of the VM. A minimal Resource stanza for a PXE boot VM might look like this: + +``` +resource "proxmox_vm_qemu" "pxe-minimal-example" { + name = "pxe-minimal-example" + agent = 0 + boot = "order=net0;scsi0" + pxe = true + target_node = "test" + network { + bridge = "vmbr0" + firewall = false + link_down = false + model = "e1000" + } +} +``` + +The primary options that effect the correct operation of Network PXE boot mode are: + + * `boot`: a valid boot order must be specified with Network type first (eg `net0;scsi0` or `ncd`) + * a valid NIC attached to a network with a PXE boot server must be added to the VM + * generally speaking, disable the Agent (`agent = 0`) unless the installed OS contains the Agent in OS install configurations + ## Argument reference **Note: Except where explicitly stated in the description, all arguments are assumed to be optional.** @@ -104,8 +143,9 @@ The following arguments are supported in the top level resource block. | `boot` | `str` | `"cdn"` | The boot order for the VM. Ordered string of characters denoting boot order. Options: floppy (`a`), hard disk (`c`), CD-ROM (`d`), or network (`n`). | | `bootdisk` | `str` | | Enable booting from specified disk. You shouldn't need to change it under most circumstances. | | `agent` | `int` | `0` | Set to `1` to enable the QEMU Guest Agent. Note, you must run the [`qemu-guest-agent`](https://pve.proxmox.com/wiki/Qemu-guest-agent) daemon in the quest for this to have any effect. | -| `iso` | `str` | | The name of the ISO image to mount to the VM. Only applies when `clone` is not set. Either `clone` or `iso` needs to be set. | -| `clone` | `str` | | The base VM from which to clone to create the new VM. | +| `iso` | `str` | | The name of the ISO image to mount to the VM. Only applies when `clone` is not set. Either `clone` or `iso` needs to be set. Note that `iso` is mutually exclussive with `clone` and `pxe` modes. | +| `pxe` | `bool` | `false` | If set to `true`, enable PXE boot of the VM. Also requires a `boot` order be set with Network first (eg `boot = "net0;scsi0"`). Note that `pxe` is mutually exclussive with `iso` and `clone` modes. | +| `clone` | `str` | | The base VM from which to clone to create the new VM. Note that `clone` is mutually exclussive with `pxe` and `iso` modes. | | `full_clone` | `bool` | `true` | Set to `true` to create a full clone, or `false` to create a linked clone. See the [docs about cloning](https://pve.proxmox.com/pve-docs/chapter-qm.html#qm_copy_and_clone) for more info. Only applies when `clone` is set. | | `hastate` | `str` | | Requested HA state for the resource. One of "started", "stopped", "enabled", "disabled", or "ignored". See the [docs about HA](https://pve.proxmox.com/pve-docs/chapter-ha-manager.html#ha_manager_resource_config) for more info. | | `hagroup` | `str` | | The HA group identifier the resource belongs to (requires `hastate` to be set!). See the [docs about HA](https://pve.proxmox.com/pve-docs/chapter-ha-manager.html#ha_manager_resource_config) for more info. | diff --git a/examples/cloudinit_example.tf b/examples/cloudinit_example.tf old mode 100755 new mode 100644 diff --git a/examples/pxe_example.tf b/examples/pxe_example.tf new file mode 100644 index 00000000..0cfee5c7 --- /dev/null +++ b/examples/pxe_example.tf @@ -0,0 +1,73 @@ +terraform { + required_version = ">= 1.1.0" + required_providers { + proxmox = { + source = "telmate/proxmox" + version = ">= 2.9.5" + } + } +} + +provider "proxmox" { + pm_tls_insecure = true + pm_api_url = "https://proxmox01.example.com:8006/api2/json" + pm_password = "password" + pm_user = "root@pam" + pm_otp = "" +} + +resource "proxmox_vm_qemu" "pxe-example" { + name = "pxe-example" + desc = "A test VM for PXE boot mode." +# PXE option enables the network boot feature + pxe = true +# unless your PXE installed system includes the Agent in the installed +# OS, do not use this, especially for PXE boot VMs + agent = 0 + automatic_reboot = true + balloon = 0 + bios = "seabios" +# boot order MUST include network first, this is enforced in the Provider + boot = "order=net0;scsi0" + cores = 2 + cpu = "host" + define_connection_info = true + force_create = false + hotplug = "network,disk,usb" + kvm = true + memory = 2048 + numa = false + onboot = false + oncreate = true + os_type = "Linux 5.x - 2.6 Kernel" + qemu_os = "l26" + scsihw = "virtio-scsi-pci" + sockets = 1 + tablet = true + target_node = "test" + vcpus = 0 + + disk { + backup = 0 + cache = "none" + discard = "on" + iothread = 1 + mbps = 0 + mbps_rd = 0 + mbps_rd_max = 0 + mbps_wr = 0 + mbps_wr_max = 0 + replicate = 0 + size = "32G" + ssd = 1 + storage = "local-lvm" + type = "scsi" + } + + network { + bridge = "vmbr0" + firewall = false + link_down = false + model = "e1000" + } +} diff --git a/proxmox/resource_vm_qemu.go b/proxmox/resource_vm_qemu.go index fc34b15d..3c104b48 100755 --- a/proxmox/resource_vm_qemu.go +++ b/proxmox/resource_vm_qemu.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "path" + "regexp" "strconv" "strings" "time" @@ -102,15 +103,23 @@ func resourceVmQemu() *schema.Resource { Optional: true, Default: 0, }, + "pxe": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"clone"}, + }, "iso": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"clone"}, }, "clone": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"iso", "pxe"}, }, "cloudinit_cdrom_storage": { Type: schema.TypeString, @@ -811,7 +820,7 @@ func resourceVmQemuCreate(d *schema.ResourceData, meta interface{}) error { vmr.SetPool(pool) } - // check if ISO or clone + // check if ISO, clone, or PXE boot if d.Get("clone").(string) != "" { fullClone := 1 if !d.Get("full_clone").(bool) { @@ -880,12 +889,38 @@ func resourceVmQemuCreate(d *schema.ResourceData, meta interface{}) error { } else if d.Get("iso").(string) != "" { config.QemuIso = d.Get("iso").(string) + err := config.CreateVm(vmr, client) + if err != nil { + return err + } + } else if d.Get("pxe").(bool) { + var found bool + bs := d.Get("boot").(string) + regs := [...]string{"^n.*$", "^order=net.*$"} + + for _, reg := range regs { + re, err := regexp.Compile(reg) + if err != nil { + return err + } + + found = re.MatchString(bs) + + if found { + break + } + } + + if !found { + return fmt.Errorf("no network boot option matched in 'boot' config") + } + err := config.CreateVm(vmr, client) if err != nil { return err } } else { - return fmt.Errorf("either clone or iso must be set") + return fmt.Errorf("either 'clone', 'iso', or 'pxe' must be set") } } else { log.Printf("[DEBUG][QemuVmCreate] recycling VM vmId: %d", vmr.VmId()) diff --git a/proxmox/resource_vm_qemu_test.go b/proxmox/resource_vm_qemu_test.go index 73a823fe..8b762897 100644 --- a/proxmox/resource_vm_qemu_test.go +++ b/proxmox/resource_vm_qemu_test.go @@ -86,6 +86,27 @@ resource "proxmox_vm_qemu" "%s" { `, name, name, targetNode) } +// testAccExampleQemuPxe generates the most simplistic PXE boot VM +// we're able to make this confirms we can spin up a PXE boot VM +// using just default values, a valid Network must be specified +// for the VM to be able to Network boot +func testAccExampleQemuPxe(name string, targetNode string) string { + return fmt.Sprintf(` +resource "proxmox_vm_qemu" "%s" { + name = "%s" + target_node = "%s" + pxe = true + boot = "order=net0;scsi0" + network { + bridge = "vmbr0" + firewall = false + link_down = false + model = "e1000" + } +} +`, name, name, targetNode) +} + // testAccExampleResource generates a virtual machine and uses the disk // slot setting to assign a non-standard disk position (scsi5 vs scsi0) // func testAccExampleQemuWithDiskSlot(name string, diskSlot int, targetNode string) string { @@ -300,6 +321,27 @@ func TestAccProxmoxVmQemu_CreateCloneWithTwoDisks(t *testing.T) { }) } +// TestAccProxmoxVmQemu_PxeCreate tests a simple creation and destruction of the smallest, but +// but still viable, configuration for a PXE Network boot VM we can create. +func TestAccProxmoxVmQemu_PxeCreate(t *testing.T) { + resourceName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + resourcePath := fmt.Sprintf("proxmox_vm_qemu.%s", resourceName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProxmoxProviderFactory(), + //CheckDestroy: testAccCheckExampleResourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccExampleQemuPxe(resourceName, testAccProxmoxTargetNode), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourcePath, "name", resourceName), + ), + }, + }, + }) +} + // TestAccProxmoxVmQemu_StandardUpdateNoReboot tests a simple update of a vm_qemu resource, // and the modified parameters can be applied without reboot. func TestAccProxmoxVmQemu_UpdateNoReboot(t *testing.T) {