diff --git a/CHANGELOG.md b/CHANGELOG.md index cf9b7e11..c1c96c1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,13 @@ ENHANCEMENTS: +* resource/virtual_environment_vm: Add `audio_device` argument * resource/virtual_environment_vm: Add `serial_device` argument +BUG FIXES: + +* resource/virtual_environment_vm: Fix `network_device` deletion issue + ## 0.2.0 BREAKING CHANGES: diff --git a/README.md b/README.md index 0ee76dea..5d37c09e 100644 --- a/README.md +++ b/README.md @@ -475,6 +475,14 @@ This resource doesn't expose any additional attributes. * `type` - (Optional) The QEMU agent interface type (defaults to `virtio`) * `isa` - ISA Serial Port * `virtio` - VirtIO (paravirtualized) +* `audio_device` - (Optional) An audio device + * `device` - (Optional) The device (defaults to `intel-hda`) + * `AC97` - Intel 82801AA AC97 Audio + * `ich9-intel-hda` - Intel HD Audio Controller (ich9) + * `intel-hda` - Intel HD Audio + * `driver` - (Optional) The driver (defaults to `spice`) + * `spice` - Spice + * `enabled` - (Optional) Whether to enable the audio device (defaults to `true`) * `bios` - (Optional) The BIOS implementation (defaults to `seabios`) * `ovmf` - OVMF (UEFI) * `seabios` - SeaBIOS diff --git a/proxmox/virtual_environment_vm_types.go b/proxmox/virtual_environment_vm_types.go index 9c9f407c..de208278 100644 --- a/proxmox/virtual_environment_vm_types.go +++ b/proxmox/virtual_environment_vm_types.go @@ -22,10 +22,14 @@ type CustomAgent struct { // CustomAudioDevice handles QEMU audio parameters. type CustomAudioDevice struct { - Device string `json:"device" url:"device"` - Driver string `json:"driver" url:"driver"` + Device string `json:"device" url:"device"` + Driver *string `json:"driver" url:"driver"` + Enabled bool `json:"-" url:"-"` } +// CustomAudioDevices handles QEMU audio device parameters. +type CustomAudioDevices []CustomAudioDevice + // CustomCloudInitConfig handles QEMU cloud-init parameters. type CustomCloudInitConfig struct { Files *CustomCloudInitFiles `json:"cicustom,omitempty" url:"cicustom,omitempty"` @@ -200,7 +204,7 @@ type VirtualEnvironmentVMCreateRequestBody struct { ACPI *CustomBool `json:"acpi,omitempty" url:"acpi,omitempty,int"` Agent *CustomAgent `json:"agent,omitempty" url:"agent,omitempty"` AllowReboot *CustomBool `json:"reboot,omitempty" url:"reboot,omitempty,int"` - AudioDevice *CustomAudioDevice `json:"audio0,omitempty" url:"audio0,omitempty"` + AudioDevices CustomAudioDevices `json:"audio,omitempty" url:"audio,omitempty"` Autostart *CustomBool `json:"autostart,omitempty" url:"autostart,omitempty,int"` BackupFile *string `json:"archive,omitempty" url:"archive,omitempty"` BandwidthLimit *int `json:"bwlimit,omitempty" url:"bwlimit,omitempty"` @@ -508,7 +512,24 @@ func (r CustomAgent) EncodeValues(key string, v *url.Values) error { // EncodeValues converts a CustomAudioDevice struct to a URL vlaue. func (r CustomAudioDevice) EncodeValues(key string, v *url.Values) error { - v.Add(key, fmt.Sprintf("device=%s,driver=%s", r.Device, r.Driver)) + values := []string{fmt.Sprintf("device=%s", r.Device)} + + if r.Driver != nil { + values = append(values, fmt.Sprintf("driver=%s", *r.Driver)) + } + + v.Add(key, strings.Join(values, ",")) + + return nil +} + +// EncodeValues converts a CustomAudioDevices array to multiple URL values. +func (r CustomAudioDevices) EncodeValues(key string, v *url.Values) error { + for i, d := range r { + if d.Enabled { + d.EncodeValues(fmt.Sprintf("%s%d", key, i), v) + } + } return nil } @@ -1088,6 +1109,34 @@ func (r *CustomAgent) UnmarshalJSON(b []byte) error { return nil } +// UnmarshalJSON converts a CustomAgent string to an object. +func (r *CustomAudioDevice) UnmarshalJSON(b []byte) error { + var s string + + err := json.Unmarshal(b, &s) + + if err != nil { + return err + } + + pairs := strings.Split(s, ",") + + for _, p := range pairs { + v := strings.Split(strings.TrimSpace(p), "=") + + if len(v) == 2 { + switch v[0] { + case "device": + r.Device = v[1] + case "driver": + r.Driver = &v[1] + } + } + } + + 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_virtual_environment_container.go b/proxmoxtf/resource_virtual_environment_container.go index dea0aeec..5fd51572 100644 --- a/proxmoxtf/resource_virtual_environment_container.go +++ b/proxmoxtf/resource_virtual_environment_container.go @@ -102,16 +102,13 @@ func resourceVirtualEnvironmentContainer() *schema.Resource { Description: "The console configuration", Optional: true, DefaultFunc: func() (interface{}, error) { - defaultList := make([]interface{}, 1) - defaultMap := map[string]interface{}{} - - defaultMap[mkResourceVirtualEnvironmentContainerConsoleEnabled] = dvResourceVirtualEnvironmentContainerConsoleEnabled - defaultMap[mkResourceVirtualEnvironmentContainerConsoleMode] = dvResourceVirtualEnvironmentContainerConsoleMode - defaultMap[mkResourceVirtualEnvironmentContainerConsoleTTYCount] = dvResourceVirtualEnvironmentContainerConsoleTTYCount - - defaultList[0] = defaultMap - - return defaultList, nil + return []interface{}{ + map[string]interface{}{ + mkResourceVirtualEnvironmentContainerConsoleEnabled: dvResourceVirtualEnvironmentContainerConsoleEnabled, + mkResourceVirtualEnvironmentContainerConsoleMode: dvResourceVirtualEnvironmentContainerConsoleMode, + mkResourceVirtualEnvironmentContainerConsoleTTYCount: dvResourceVirtualEnvironmentContainerConsoleTTYCount, + }, + }, nil }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -145,16 +142,13 @@ func resourceVirtualEnvironmentContainer() *schema.Resource { Description: "The CPU allocation", Optional: true, DefaultFunc: func() (interface{}, error) { - defaultList := make([]interface{}, 1) - defaultMap := map[string]interface{}{} - - defaultMap[mkResourceVirtualEnvironmentContainerCPUArchitecture] = dvResourceVirtualEnvironmentContainerCPUArchitecture - defaultMap[mkResourceVirtualEnvironmentContainerCPUCores] = dvResourceVirtualEnvironmentContainerCPUCores - defaultMap[mkResourceVirtualEnvironmentContainerCPUUnits] = dvResourceVirtualEnvironmentContainerCPUUnits - - defaultList[0] = defaultMap - - return defaultList, nil + return []interface{}{ + map[string]interface{}{ + mkResourceVirtualEnvironmentContainerCPUArchitecture: dvResourceVirtualEnvironmentContainerCPUArchitecture, + mkResourceVirtualEnvironmentContainerCPUCores: dvResourceVirtualEnvironmentContainerCPUCores, + mkResourceVirtualEnvironmentContainerCPUUnits: dvResourceVirtualEnvironmentContainerCPUUnits, + }, + }, nil }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -343,15 +337,12 @@ func resourceVirtualEnvironmentContainer() *schema.Resource { Description: "The memory allocation", Optional: true, DefaultFunc: func() (interface{}, error) { - defaultList := make([]interface{}, 1) - defaultMap := map[string]interface{}{} - - defaultMap[mkResourceVirtualEnvironmentContainerMemoryDedicated] = dvResourceVirtualEnvironmentContainerMemoryDedicated - defaultMap[mkResourceVirtualEnvironmentContainerMemorySwap] = dvResourceVirtualEnvironmentContainerMemorySwap - - defaultList[0] = defaultMap - - return defaultList, nil + return []interface{}{ + map[string]interface{}{ + mkResourceVirtualEnvironmentContainerMemoryDedicated: dvResourceVirtualEnvironmentContainerMemoryDedicated, + mkResourceVirtualEnvironmentContainerMemorySwap: dvResourceVirtualEnvironmentContainerMemorySwap, + }, + }, nil }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ diff --git a/proxmoxtf/resource_virtual_environment_vm.go b/proxmoxtf/resource_virtual_environment_vm.go index a40b7248..9ae1b924 100644 --- a/proxmoxtf/resource_virtual_environment_vm.go +++ b/proxmoxtf/resource_virtual_environment_vm.go @@ -16,10 +16,17 @@ import ( ) const ( + maxAudioDevices = 1 + maxNetworkDevices = 8 + maxSerialDevices = 4 + dvResourceVirtualEnvironmentVMACPI = true dvResourceVirtualEnvironmentVMAgentEnabled = false dvResourceVirtualEnvironmentVMAgentTrim = false dvResourceVirtualEnvironmentVMAgentType = "virtio" + dvResourceVirtualEnvironmentVMAudioDeviceDevice = "intel-hda" + dvResourceVirtualEnvironmentVMAudioDeviceDriver = "spice" + dvResourceVirtualEnvironmentVMAudioDeviceEnabled = true dvResourceVirtualEnvironmentVMBIOS = "seabios" dvResourceVirtualEnvironmentVMCDROMEnabled = false dvResourceVirtualEnvironmentVMCDROMFileID = "" @@ -72,6 +79,10 @@ const ( mkResourceVirtualEnvironmentVMAgentEnabled = "enabled" mkResourceVirtualEnvironmentVMAgentTrim = "trim" mkResourceVirtualEnvironmentVMAgentType = "type" + mkResourceVirtualEnvironmentVMAudioDevice = "audio_device" + mkResourceVirtualEnvironmentVMAudioDeviceDevice = "device" + mkResourceVirtualEnvironmentVMAudioDeviceDriver = "driver" + mkResourceVirtualEnvironmentVMAudioDeviceEnabled = "enabled" mkResourceVirtualEnvironmentVMBIOS = "bios" mkResourceVirtualEnvironmentVMCDROM = "cdrom" mkResourceVirtualEnvironmentVMCDROMEnabled = "enabled" @@ -157,16 +168,13 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Description: "The QEMU agent configuration", Optional: true, DefaultFunc: func() (interface{}, error) { - defaultList := make([]interface{}, 1) - defaultMap := map[string]interface{}{} - - defaultMap[mkResourceVirtualEnvironmentVMAgentEnabled] = dvResourceVirtualEnvironmentVMAgentEnabled - defaultMap[mkResourceVirtualEnvironmentVMAgentTrim] = dvResourceVirtualEnvironmentVMAgentTrim - defaultMap[mkResourceVirtualEnvironmentVMAgentType] = dvResourceVirtualEnvironmentVMAgentType - - defaultList[0] = defaultMap - - return defaultList, nil + return []interface{}{ + map[string]interface{}{ + mkResourceVirtualEnvironmentVMAgentEnabled: dvResourceVirtualEnvironmentVMAgentEnabled, + mkResourceVirtualEnvironmentVMAgentTrim: dvResourceVirtualEnvironmentVMAgentEnabled, + mkResourceVirtualEnvironmentVMAgentType: dvResourceVirtualEnvironmentVMAgentType, + }, + }, nil }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -194,6 +202,40 @@ func resourceVirtualEnvironmentVM() *schema.Resource { MaxItems: 1, MinItems: 0, }, + mkResourceVirtualEnvironmentVMAudioDevice: &schema.Schema{ + Type: schema.TypeList, + Description: "The audio devices", + Optional: true, + DefaultFunc: func() (interface{}, error) { + return []interface{}{}, nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentVMAudioDeviceDevice: { + Type: schema.TypeString, + Description: "The device", + Optional: true, + Default: dvResourceVirtualEnvironmentVMAudioDeviceDevice, + ValidateFunc: resourceVirtualEnvironmentVMGetAudioDeviceValidator(), + }, + mkResourceVirtualEnvironmentVMAudioDeviceDriver: { + Type: schema.TypeString, + Description: "The driver", + Optional: true, + Default: dvResourceVirtualEnvironmentVMAudioDeviceDriver, + ValidateFunc: resourceVirtualEnvironmentVMGetAudioDriverValidator(), + }, + mkResourceVirtualEnvironmentVMAudioDeviceEnabled: { + Type: schema.TypeBool, + Description: "Whether to enable the audio device", + Optional: true, + Default: dvResourceVirtualEnvironmentVMAudioDeviceEnabled, + }, + }, + }, + MaxItems: maxAudioDevices, + MinItems: 0, + }, mkResourceVirtualEnvironmentVMBIOS: { Type: schema.TypeString, Description: "The BIOS implementation", @@ -206,15 +248,12 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Description: "The CDROM drive", Optional: true, DefaultFunc: func() (interface{}, error) { - defaultList := make([]interface{}, 1) - defaultMap := map[string]interface{}{} - - defaultMap[mkResourceVirtualEnvironmentVMCDROMEnabled] = dvResourceVirtualEnvironmentVMCDROMEnabled - defaultMap[mkResourceVirtualEnvironmentVMCDROMFileID] = dvResourceVirtualEnvironmentVMCDROMFileID - - defaultList[0] = defaultMap - - return defaultList, nil + return []interface{}{ + map[string]interface{}{ + mkResourceVirtualEnvironmentVMCDROMEnabled: dvResourceVirtualEnvironmentVMCDROMEnabled, + mkResourceVirtualEnvironmentVMCDROMFileID: dvResourceVirtualEnvironmentVMCDROMFileID, + }, + }, nil }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -241,20 +280,17 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Description: "The CPU allocation", Optional: true, DefaultFunc: func() (interface{}, error) { - defaultList := make([]interface{}, 1) - defaultMap := map[string]interface{}{} - - defaultMap[mkResourceVirtualEnvironmentVMCPUArchitecture] = dvResourceVirtualEnvironmentVMCPUArchitecture - defaultMap[mkResourceVirtualEnvironmentVMCPUCores] = dvResourceVirtualEnvironmentVMCPUCores - defaultMap[mkResourceVirtualEnvironmentVMCPUFlags] = []interface{}{} - defaultMap[mkResourceVirtualEnvironmentVMCPUHotplugged] = dvResourceVirtualEnvironmentVMCPUHotplugged - defaultMap[mkResourceVirtualEnvironmentVMCPUSockets] = dvResourceVirtualEnvironmentVMCPUSockets - defaultMap[mkResourceVirtualEnvironmentVMCPUType] = dvResourceVirtualEnvironmentVMCPUType - defaultMap[mkResourceVirtualEnvironmentVMCPUUnits] = dvResourceVirtualEnvironmentVMCPUUnits - - defaultList[0] = defaultMap - - return defaultList, nil + return []interface{}{ + map[string]interface{}{ + mkResourceVirtualEnvironmentVMCPUArchitecture: dvResourceVirtualEnvironmentVMCPUArchitecture, + mkResourceVirtualEnvironmentVMCPUCores: dvResourceVirtualEnvironmentVMCPUCores, + mkResourceVirtualEnvironmentVMCPUFlags: []interface{}{}, + mkResourceVirtualEnvironmentVMCPUHotplugged: dvResourceVirtualEnvironmentVMCPUHotplugged, + mkResourceVirtualEnvironmentVMCPUSockets: dvResourceVirtualEnvironmentVMCPUSockets, + mkResourceVirtualEnvironmentVMCPUType: dvResourceVirtualEnvironmentVMCPUType, + mkResourceVirtualEnvironmentVMCPUUnits: dvResourceVirtualEnvironmentVMCPUUnits, + }, + }, nil }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -326,17 +362,14 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Optional: true, ForceNew: true, DefaultFunc: func() (interface{}, error) { - defaultList := make([]interface{}, 1) - defaultMap := map[string]interface{}{} - - defaultMap[mkResourceVirtualEnvironmentVMDiskDatastoreID] = dvResourceVirtualEnvironmentVMDiskDatastoreID - defaultMap[mkResourceVirtualEnvironmentVMDiskFileFormat] = dvResourceVirtualEnvironmentVMDiskFileFormat - defaultMap[mkResourceVirtualEnvironmentVMDiskFileID] = dvResourceVirtualEnvironmentVMDiskFileID - defaultMap[mkResourceVirtualEnvironmentVMDiskSize] = dvResourceVirtualEnvironmentVMDiskSize - - defaultList[0] = defaultMap - - return defaultList, nil + return []interface{}{ + map[string]interface{}{ + mkResourceVirtualEnvironmentVMDiskDatastoreID: dvResourceVirtualEnvironmentVMDiskDatastoreID, + mkResourceVirtualEnvironmentVMDiskFileFormat: dvResourceVirtualEnvironmentVMDiskFileFormat, + mkResourceVirtualEnvironmentVMDiskFileID: dvResourceVirtualEnvironmentVMDiskFileID, + mkResourceVirtualEnvironmentVMDiskSize: dvResourceVirtualEnvironmentVMDiskSize, + }, + }, nil }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -376,17 +409,14 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Description: "The speed limits", Optional: true, DefaultFunc: func() (interface{}, error) { - defaultList := make([]interface{}, 1) - defaultMap := map[string]interface{}{} - - defaultMap[mkResourceVirtualEnvironmentVMDiskSpeedRead] = dvResourceVirtualEnvironmentVMDiskSpeedRead - defaultMap[mkResourceVirtualEnvironmentVMDiskSpeedReadBurstable] = dvResourceVirtualEnvironmentVMDiskSpeedReadBurstable - defaultMap[mkResourceVirtualEnvironmentVMDiskSpeedWrite] = dvResourceVirtualEnvironmentVMDiskSpeedWrite - defaultMap[mkResourceVirtualEnvironmentVMDiskSpeedWriteBurstable] = dvResourceVirtualEnvironmentVMDiskSpeedWriteBurstable - - defaultList[0] = defaultMap - - return defaultList, nil + return []interface{}{ + map[string]interface{}{ + mkResourceVirtualEnvironmentVMDiskSpeedRead: dvResourceVirtualEnvironmentVMDiskSpeedRead, + mkResourceVirtualEnvironmentVMDiskSpeedReadBurstable: dvResourceVirtualEnvironmentVMDiskSpeedReadBurstable, + mkResourceVirtualEnvironmentVMDiskSpeedWrite: dvResourceVirtualEnvironmentVMDiskSpeedWrite, + mkResourceVirtualEnvironmentVMDiskSpeedWriteBurstable: dvResourceVirtualEnvironmentVMDiskSpeedWriteBurstable, + }, + }, nil }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -613,16 +643,13 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Description: "The memory allocation", Optional: true, DefaultFunc: func() (interface{}, error) { - defaultList := make([]interface{}, 1) - defaultMap := map[string]interface{}{} - - defaultMap[mkResourceVirtualEnvironmentVMMemoryDedicated] = dvResourceVirtualEnvironmentVMMemoryDedicated - defaultMap[mkResourceVirtualEnvironmentVMMemoryFloating] = dvResourceVirtualEnvironmentVMMemoryFloating - defaultMap[mkResourceVirtualEnvironmentVMMemoryShared] = dvResourceVirtualEnvironmentVMMemoryShared - - defaultList[0] = defaultMap - - return defaultList, nil + return []interface{}{ + map[string]interface{}{ + mkResourceVirtualEnvironmentVMMemoryDedicated: dvResourceVirtualEnvironmentVMMemoryDedicated, + mkResourceVirtualEnvironmentVMMemoryFloating: dvResourceVirtualEnvironmentVMMemoryFloating, + mkResourceVirtualEnvironmentVMMemoryShared: dvResourceVirtualEnvironmentVMMemoryShared, + }, + }, nil }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -710,7 +737,7 @@ func resourceVirtualEnvironmentVM() *schema.Resource { }, }, }, - MaxItems: 8, + MaxItems: maxNetworkDevices, MinItems: 0, }, mkResourceVirtualEnvironmentVMNetworkInterfaceNames: { @@ -730,14 +757,11 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Description: "The operating system configuration", Optional: true, DefaultFunc: func() (interface{}, error) { - defaultList := make([]interface{}, 1) - defaultMap := map[string]interface{}{} - - defaultMap[mkResourceVirtualEnvironmentVMOperatingSystemType] = dvResourceVirtualEnvironmentVMOperatingSystemType - - defaultList[0] = defaultMap - - return defaultList, nil + return []interface{}{ + map[string]interface{}{ + mkResourceVirtualEnvironmentVMOperatingSystemType: dvResourceVirtualEnvironmentVMOperatingSystemType, + }, + }, nil }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -782,7 +806,7 @@ func resourceVirtualEnvironmentVM() *schema.Resource { }, }, }, - MaxItems: 4, + MaxItems: maxSerialDevices, MinItems: 0, }, mkResourceVirtualEnvironmentVMStarted: { @@ -802,16 +826,13 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Description: "The VGA configuration", Optional: true, DefaultFunc: func() (interface{}, error) { - defaultList := make([]interface{}, 1) - defaultMap := map[string]interface{}{} - - defaultMap[mkResourceVirtualEnvironmentVMVGAEnabled] = dvResourceVirtualEnvironmentVMVGAEnabled - defaultMap[mkResourceVirtualEnvironmentVMVGAMemory] = dvResourceVirtualEnvironmentVMVGAMemory - defaultMap[mkResourceVirtualEnvironmentVMVGAType] = dvResourceVirtualEnvironmentVMVGAType - - defaultList[0] = defaultMap - - return defaultList, nil + return []interface{}{ + map[string]interface{}{ + mkResourceVirtualEnvironmentVMVGAEnabled: dvResourceVirtualEnvironmentVMVGAEnabled, + mkResourceVirtualEnvironmentVMVGAMemory: dvResourceVirtualEnvironmentVMVGAMemory, + mkResourceVirtualEnvironmentVMVGAType: dvResourceVirtualEnvironmentVMVGAType, + }, + }, nil }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -878,6 +899,12 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e agentTrim := proxmox.CustomBool(agentBlock[mkResourceVirtualEnvironmentVMAgentTrim].(bool)) agentType := agentBlock[mkResourceVirtualEnvironmentVMAgentType].(string) + audioDevices, err := resourceVirtualEnvironmentVMGetAudioDeviceList(d, m) + + if err != nil { + return err + } + bios := d.Get(mkResourceVirtualEnvironmentVMBIOS).(string) cdromBlock, err := getSchemaBlock(resource, d, m, []string{mkResourceVirtualEnvironmentVMCDROM}, 0, true) @@ -1030,6 +1057,7 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e TrimClonedDisks: &agentTrim, Type: &agentType, }, + AudioDevices: audioDevices, BIOS: &bios, BootDisk: &bootDisk, BootOrder: &bootOrder, @@ -1347,6 +1375,39 @@ func resourceVirtualEnvironmentVMGetCloudInitConfig(d *schema.ResourceData, m in return initializationConfig, nil } +func resourceVirtualEnvironmentVMGetAudioDeviceList(d *schema.ResourceData, m interface{}) (proxmox.CustomAudioDevices, error) { + devices := d.Get(mkResourceVirtualEnvironmentVMAudioDevice).([]interface{}) + list := make(proxmox.CustomAudioDevices, len(devices)) + + for i, v := range devices { + block := v.(map[string]interface{}) + + device, _ := block[mkResourceVirtualEnvironmentVMAudioDeviceDevice].(string) + driver, _ := block[mkResourceVirtualEnvironmentVMAudioDeviceDriver].(string) + enabled, _ := block[mkResourceVirtualEnvironmentVMAudioDeviceEnabled].(bool) + + list[i].Device = device + list[i].Driver = &driver + list[i].Enabled = enabled + } + + return list, nil +} + +func resourceVirtualEnvironmentVMGetAudioDeviceValidator() schema.SchemaValidateFunc { + return validation.StringInSlice([]string{ + "AC97", + "ich9-intel-hda", + "intel-hda", + }, false) +} + +func resourceVirtualEnvironmentVMGetAudioDriverValidator() schema.SchemaValidateFunc { + return validation.StringInSlice([]string{ + "spice", + }, false) +} + func resourceVirtualEnvironmentVMGetCPUArchitectureValidator() schema.SchemaValidateFunc { return validation.StringInSlice([]string{ "aarch64", @@ -1630,6 +1691,39 @@ func resourceVirtualEnvironmentVMRead(d *schema.ResourceData, m interface{}) err d.Set(mkResourceVirtualEnvironmentVMAgent, []interface{}{}) } + // Compare the audio devices to those stored in the state. + audioDevices := make([]interface{}, 1) + audioDevicesArray := []*proxmox.CustomAudioDevice{ + vmConfig.AudioDevice, + } + audioDevicesCount := 0 + + for adi, ad := range audioDevicesArray { + m := map[string]interface{}{} + + if ad != nil { + m[mkResourceVirtualEnvironmentVMAudioDeviceDevice] = ad.Device + + if ad.Driver != nil { + m[mkResourceVirtualEnvironmentVMAudioDeviceDriver] = *ad.Driver + } else { + m[mkResourceVirtualEnvironmentVMAudioDeviceDriver] = "" + } + + m[mkResourceVirtualEnvironmentVMAudioDeviceEnabled] = true + + audioDevicesCount = adi + 1 + } else { + m[mkResourceVirtualEnvironmentVMAudioDeviceDevice] = "" + m[mkResourceVirtualEnvironmentVMAudioDeviceDriver] = "" + m[mkResourceVirtualEnvironmentVMAudioDeviceEnabled] = false + } + + audioDevices[adi] = m + } + + d.Set(mkResourceVirtualEnvironmentVMAudioDevice, audioDevices[:audioDevicesCount]) + // Compare the IDE devices to the CDROM and cloud-init configurations stored in the state. if vmConfig.IDEDevice2 != nil { if *vmConfig.IDEDevice2.Media == "cdrom" { @@ -2307,6 +2401,27 @@ func resourceVirtualEnvironmentVMUpdate(d *schema.ResourceData, m interface{}) e rebootRequired = true } + // Prepare the new audio devices. + if d.HasChange(mkResourceVirtualEnvironmentVMAudioDevice) { + body.AudioDevices, err = resourceVirtualEnvironmentVMGetAudioDeviceList(d, m) + + if err != nil { + return err + } + + for i := 0; i < len(body.AudioDevices); i++ { + if !body.AudioDevices[i].Enabled { + delete = append(delete, fmt.Sprintf("audio%d", i)) + } + } + + for i := len(body.AudioDevices); i < maxAudioDevices; i++ { + delete = append(delete, fmt.Sprintf("audio%d", i)) + } + + rebootRequired = true + } + // Prepare the new CDROM configuration. if d.HasChange(mkResourceVirtualEnvironmentVMCDROM) { cdromBlock, err := getSchemaBlock(resource, d, m, []string{mkResourceVirtualEnvironmentVMCDROM}, 0, true) @@ -2478,6 +2593,16 @@ func resourceVirtualEnvironmentVMUpdate(d *schema.ResourceData, m interface{}) e return err } + for i := 0; i < len(body.NetworkDevices); i++ { + if !body.NetworkDevices[i].Enabled { + delete = append(delete, fmt.Sprintf("net%d", i)) + } + } + + for i := len(body.NetworkDevices); i < maxNetworkDevices; i++ { + delete = append(delete, fmt.Sprintf("net%d", i)) + } + rebootRequired = true } @@ -2504,6 +2629,10 @@ func resourceVirtualEnvironmentVMUpdate(d *schema.ResourceData, m interface{}) e return err } + for i := len(body.SerialDevices); i < maxSerialDevices; i++ { + delete = append(delete, fmt.Sprintf("serial%d", i)) + } + rebootRequired = true } diff --git a/proxmoxtf/resource_virtual_environment_vm_test.go b/proxmoxtf/resource_virtual_environment_vm_test.go index c4510fc4..adf5fb17 100644 --- a/proxmoxtf/resource_virtual_environment_vm_test.go +++ b/proxmoxtf/resource_virtual_environment_vm_test.go @@ -29,6 +29,8 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) { testOptionalArguments(t, s, []string{ mkResourceVirtualEnvironmentVMACPI, + mkResourceVirtualEnvironmentVMAgent, + mkResourceVirtualEnvironmentVMAudioDevice, mkResourceVirtualEnvironmentVMBIOS, mkResourceVirtualEnvironmentVMCDROM, mkResourceVirtualEnvironmentVMInitialization, @@ -57,6 +59,7 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) { testSchemaValueTypes(t, s, []string{ mkResourceVirtualEnvironmentVMACPI, mkResourceVirtualEnvironmentVMAgent, + mkResourceVirtualEnvironmentVMAudioDevice, mkResourceVirtualEnvironmentVMBIOS, mkResourceVirtualEnvironmentVMCDROM, mkResourceVirtualEnvironmentVMInitialization, @@ -80,6 +83,7 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) { }, []schema.ValueType{ schema.TypeBool, schema.TypeList, + schema.TypeList, schema.TypeString, schema.TypeList, schema.TypeList, @@ -120,6 +124,21 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) { schema.TypeString, }) + audioDeviceSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentVMAudioDevice) + + testOptionalArguments(t, audioDeviceSchema, []string{ + mkResourceVirtualEnvironmentVMAudioDeviceDevice, + mkResourceVirtualEnvironmentVMAudioDeviceDriver, + }) + + testSchemaValueTypes(t, audioDeviceSchema, []string{ + mkResourceVirtualEnvironmentVMAudioDeviceDevice, + mkResourceVirtualEnvironmentVMAudioDeviceDriver, + }, []schema.ValueType{ + schema.TypeString, + schema.TypeString, + }) + cdromSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentVMCDROM) testOptionalArguments(t, cdromSchema, []string{