diff --git a/docs/resources/virtual_environment_vm.md b/docs/resources/virtual_environment_vm.md index 5272d07b..c2e91d62 100644 --- a/docs/resources/virtual_environment_vm.md +++ b/docs/resources/virtual_environment_vm.md @@ -120,6 +120,8 @@ output "ubuntu_vm_public_key" { - `bios` - (Optional) The BIOS implementation (defaults to `seabios`). - `ovmf` - OVMF (UEFI). - `seabios` - SeaBIOS. +- `boot_order` - (Optional) Specify a list of devices to boot from in the order + they appear in the list (defaults to `[]`). - `cdrom` - (Optional) The CDROM configuration. - `enabled` - (Optional) Whether to enable the CDROM drive (defaults to `false`). @@ -361,7 +363,8 @@ output "ubuntu_vm_public_key" { - `device` - (Optional) The device (defaults to `socket`). - `/dev/*` - A host serial device. - `socket` - A unix socket. -- `scsi_hardware` - (Optional) The SCSI hardware type (defaults to `virtio-scsi-pci`). +- `scsi_hardware` - (Optional) The SCSI hardware type (defaults + to `virtio-scsi-pci`). - `lsi` - LSI Logic SAS1068E. - `lsi53c810` - LSI Logic 53C810. - `virtio-scsi-pci` - VirtIO SCSI. diff --git a/proxmox/virtual_environment_vm_types.go b/proxmox/virtual_environment_vm_types.go index 1575a3c9..24825906 100644 --- a/proxmox/virtual_environment_vm_types.go +++ b/proxmox/virtual_environment_vm_types.go @@ -33,6 +33,10 @@ type CustomAudioDevice struct { // CustomAudioDevices handles QEMU audio device parameters. type CustomAudioDevices []CustomAudioDevice +type CustomBoot struct { + Order *[]string `json:"order,omitempty" url:"order,omitempty,semicolon"` +} + // CustomCloudInitConfig handles QEMU cloud-init parameters. type CustomCloudInitConfig struct { Files *CustomCloudInitFiles `json:"cicustom,omitempty" url:"cicustom,omitempty"` @@ -236,8 +240,7 @@ type VirtualEnvironmentVMCreateRequestBody struct { BackupFile *string `json:"archive,omitempty" url:"archive,omitempty"` BandwidthLimit *int `json:"bwlimit,omitempty" url:"bwlimit,omitempty"` BIOS *string `json:"bios,omitempty" url:"bios,omitempty"` - BootDisk *string `json:"bootdisk,omitempty" url:"bootdisk,omitempty"` - BootOrder *string `json:"boot,omitempty" url:"boot,omitempty"` + Boot *CustomBoot `json:"boot,omitempty" url:"boot,omitempty"` CDROM *string `json:"cdrom,omitempty" url:"cdrom,omitempty"` CloudInitConfig *CustomCloudInitConfig `json:"cloudinit,omitempty" url:"cloudinit,omitempty"` CPUArchitecture *string `json:"arch,omitempty" url:"arch,omitempty"` @@ -646,6 +649,14 @@ func (r CustomAudioDevices) EncodeValues(key string, v *url.Values) error { return nil } +func (r CustomBoot) EncodeValues(key string, v *url.Values) error { + if r.Order != nil && len(*r.Order) > 0 { + v.Add(key, fmt.Sprintf("order=%s", strings.Join(*r.Order, ";"))) + } + + return nil +} + // EncodeValues converts a CustomCloudInitConfig struct to multiple URL values. func (r CustomCloudInitConfig) EncodeValues(_ string, v *url.Values) error { if r.Files != nil { @@ -1299,6 +1310,31 @@ func (r *CustomAudioDevice) UnmarshalJSON(b []byte) error { return nil } +// UnmarshalJSON converts a CustomBoot string to an object. +func (r *CustomBoot) UnmarshalJSON(b []byte) error { + var s string + + err := json.Unmarshal(b, &s) + if err != nil { + return fmt.Errorf("error unmarshalling CustomBoot: %w", err) + } + + pairs := strings.Split(s, ",") + + for _, p := range pairs { + v := strings.Split(strings.TrimSpace(p), "=") + + if len(v) == 2 { + if v[0] == "order" { + v := strings.Split(strings.TrimSpace(v[1]), ";") + r.Order = &v + } + } + } + + return nil +} + // UnmarshalJSON converts a CustomCloudInitFiles string to an object. func (r *CustomCloudInitFiles) UnmarshalJSON(b []byte) error { var s string diff --git a/proxmoxtf/resource/vm.go b/proxmoxtf/resource/vm.go index a51179ba..ef0aad25 100644 --- a/proxmoxtf/resource/vm.go +++ b/proxmoxtf/resource/vm.go @@ -112,6 +112,7 @@ const ( mkResourceVirtualEnvironmentVMRebootAfterCreation = "reboot" mkResourceVirtualEnvironmentVMOnBoot = "on_boot" + mkResourceVirtualEnvironmentVMBootOrder = "boot_order" mkResourceVirtualEnvironmentVMACPI = "acpi" mkResourceVirtualEnvironmentVMAgent = "agent" mkResourceVirtualEnvironmentVMAgentEnabled = "enabled" @@ -242,6 +243,15 @@ func VM() *schema.Resource { Optional: true, Default: dvResourceVirtualEnvironmentVMOnBoot, }, + mkResourceVirtualEnvironmentVMBootOrder: { + Type: schema.TypeList, + Description: "The guest will attempt to boot from devices in the order they appear here", + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + DefaultFunc: func() (interface{}, error) { + return []interface{}{}, nil + }, + }, mkResourceVirtualEnvironmentVMACPI: { Type: schema.TypeBool, Description: "Whether to enable ACPI", @@ -1915,15 +1925,33 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) var memorySharedObject *proxmox.CustomSharedMemory - bootDisk := "scsi0" - bootOrder := "c" - + var bootOrderConverted []string if cdromEnabled { - bootOrder = "cd" + bootOrderConverted = []string{"ide3"} + } + bootOrder := d.Get(mkResourceVirtualEnvironmentVMBootOrder).([]interface{}) + //nolint:nestif + if len(bootOrder) == 0 { + if sataDeviceObjects != nil { + bootOrderConverted = append(bootOrderConverted, "sata0") + } + if scsiDeviceObjects != nil { + bootOrderConverted = append(bootOrderConverted, "scsi0") + } + if virtioDeviceObjects != nil { + bootOrderConverted = append(bootOrderConverted, "virtio0") + } + if networkDeviceObjects != nil { + bootOrderConverted = append(bootOrderConverted, "net0") + } + } else { + bootOrderConverted = make([]string, len(bootOrder)) + for i, device := range bootOrder { + bootOrderConverted[i] = device.(string) + } } cpuFlagsConverted := make([]string, len(cpuFlags)) - for fi, flag := range cpuFlags { cpuFlagsConverted[fi] = flag.(string) } @@ -1959,10 +1987,11 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) TrimClonedDisks: &agentTrim, Type: &agentType, }, - AudioDevices: audioDevices, - BIOS: &bios, - BootDisk: &bootDisk, - BootOrder: &bootOrder, + AudioDevices: audioDevices, + BIOS: &bios, + Boot: &proxmox.CustomBoot{ + Order: &bootOrderConverted, + }, CloudInitConfig: initializationConfig, CPUCores: &cpuCores, CPUEmulation: &proxmox.CustomCPUEmulation{ @@ -4036,6 +4065,19 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D rebootRequired = true } + // Prepare the new boot configuration. + if d.HasChange(mkResourceVirtualEnvironmentVMBootOrder) { + bootOrder := d.Get(mkResourceVirtualEnvironmentVMBootOrder).([]interface{}) + bootOrderConverted := make([]string, len(bootOrder)) + for i, device := range bootOrder { + bootOrderConverted[i] = device.(string) + } + updateBody.Boot = &proxmox.CustomBoot{ + Order: &bootOrderConverted, + } + rebootRequired = true + } + // Prepare the new CDROM configuration. if d.HasChange(mkResourceVirtualEnvironmentVMCDROM) { cdromBlock, err := getSchemaBlock( diff --git a/proxmoxtf/resource/vm_test.go b/proxmoxtf/resource/vm_test.go index 12561f67..649c5493 100644 --- a/proxmoxtf/resource/vm_test.go +++ b/proxmoxtf/resource/vm_test.go @@ -38,6 +38,7 @@ func TestVMSchema(t *testing.T) { mkResourceVirtualEnvironmentVMAgent, mkResourceVirtualEnvironmentVMAudioDevice, mkResourceVirtualEnvironmentVMBIOS, + mkResourceVirtualEnvironmentVMBootOrder, mkResourceVirtualEnvironmentVMCDROM, mkResourceVirtualEnvironmentVMClone, mkResourceVirtualEnvironmentVMCPU, @@ -73,6 +74,7 @@ func TestVMSchema(t *testing.T) { mkResourceVirtualEnvironmentVMAgent: schema.TypeList, mkResourceVirtualEnvironmentVMAudioDevice: schema.TypeList, mkResourceVirtualEnvironmentVMBIOS: schema.TypeString, + mkResourceVirtualEnvironmentVMBootOrder: schema.TypeList, mkResourceVirtualEnvironmentVMCDROM: schema.TypeList, mkResourceVirtualEnvironmentVMCPU: schema.TypeList, mkResourceVirtualEnvironmentVMDescription: schema.TypeString,