diff --git a/fwprovider/vm/cdrom/model.go b/fwprovider/vm/cdrom/model.go index b6c3db88..2f1a932d 100644 --- a/fwprovider/vm/cdrom/model.go +++ b/fwprovider/vm/cdrom/model.go @@ -25,10 +25,9 @@ func attributeTypes() map[string]attr.Type { } } -func (m *Model) exportToCustomStorageDevice(iface string) vms.CustomStorageDevice { +func (m *Model) exportToCustomStorageDevice() vms.CustomStorageDevice { return vms.CustomStorageDevice{ FileVolume: m.FileID.ValueString(), - Interface: &iface, Media: ptr.Ptr("cdrom"), } } diff --git a/fwprovider/vm/cdrom/resource.go b/fwprovider/vm/cdrom/resource.go index e1eed7c5..cf00a9e3 100644 --- a/fwprovider/vm/cdrom/resource.go +++ b/fwprovider/vm/cdrom/resource.go @@ -22,7 +22,7 @@ type Value = types.Map // NewValue returns a new Value with the given CD-ROM settings from the PVE API. func NewValue(ctx context.Context, config *vms.GetResponseData, diags *diag.Diagnostics) Value { // find storage devices with media=cdrom - cdroms := vms.MapCustomStorageDevices(*config).Filter(func(device *vms.CustomStorageDevice) bool { + cdroms := config.CustomStorageDevices.Filter(func(device *vms.CustomStorageDevice) bool { return device.Media != nil && *device.Media == "cdrom" }) @@ -57,10 +57,7 @@ func FillCreateBody(ctx context.Context, planValue Value, body *vms.CreateReques } for iface, cdrom := range plan { - err := body.AddCustomStorageDevice(cdrom.exportToCustomStorageDevice(iface)) - if err != nil { - diags.AddError(err.Error(), "") - } + body.AddCustomStorageDevice(iface, cdrom.exportToCustomStorageDevice()) } } @@ -91,18 +88,12 @@ func FillUpdateBody( toCreate, toUpdate, toDelete := utils.MapDiff(plan, state) for iface, dev := range toCreate { - err := updateBody.AddCustomStorageDevice(dev.exportToCustomStorageDevice(iface)) - if err != nil { - diags.AddError(err.Error(), "") - } + updateBody.AddCustomStorageDevice(iface, dev.exportToCustomStorageDevice()) } for iface, dev := range toUpdate { // for CD-ROMs, the update fully override the existing device, we don't do per-attribute check - err := updateBody.AddCustomStorageDevice(dev.exportToCustomStorageDevice(iface)) - if err != nil { - diags.AddError(err.Error(), "") - } + updateBody.AddCustomStorageDevice(iface, dev.exportToCustomStorageDevice()) } for iface := range toDelete { diff --git a/proxmox/nodes/vms/custom_storage_device.go b/proxmox/nodes/vms/custom_storage_device.go index 6244109d..316e4ada 100644 --- a/proxmox/nodes/vms/custom_storage_device.go +++ b/proxmox/nodes/vms/custom_storage_device.go @@ -11,14 +11,17 @@ import ( "fmt" "net/url" "path/filepath" - "reflect" "strconv" "strings" - "unicode" "github.com/bpg/terraform-provider-proxmox/proxmox/types" ) +// StorageInterfaces is a list of supported storage interfaces. +// +//nolint:gochecknoglobals +var StorageInterfaces = []string{"ide", "sata", "scsi", "virtio"} + // CustomStorageDevice handles QEMU SATA device parameters. type CustomStorageDevice struct { AIO *string `json:"aio,omitempty" url:"aio,omitempty"` @@ -44,7 +47,6 @@ type CustomStorageDevice struct { DatastoreID *string `json:"-" url:"-"` Enabled bool `json:"-" url:"-"` FileID *string `json:"-" url:"-"` - Interface *string `json:"-" url:"-"` } // CustomStorageDevices handles map of QEMU storage device per disk interface. @@ -108,19 +110,6 @@ func (d *CustomStorageDevice) IsCloudInitDrive(vmID int) bool { strings.Contains(d.FileVolume, fmt.Sprintf("vm-%d-cloudinit", vmID)) } -// StorageInterface returns the storage interface of the CustomStorageDevice, -// e.g. "virtio" or "scsi" for "virtio0" or "scsi2". -func (d *CustomStorageDevice) StorageInterface() string { - for i, r := range *d.Interface { - if unicode.IsDigit(r) { - return (*d.Interface)[:i] - } - } - - // panic(fmt.Sprintf("cannot determine storage interface for disk interface '%s'", *d.Interface)) - return "" -} - // EncodeOptions converts a CustomStorageDevice's common options a URL value. func (d *CustomStorageDevice) EncodeOptions() string { values := []string{} @@ -374,13 +363,6 @@ func (d *CustomStorageDevice) UnmarshalJSON(b []byte) error { return nil } -// ByStorageInterface returns a map of CustomStorageDevices filtered by the given storage interface. -func (d CustomStorageDevices) ByStorageInterface(storageInterface string) CustomStorageDevices { - return d.Filter(func(d *CustomStorageDevice) bool { - return d.StorageInterface() == storageInterface - }) -} - // Filter returns a map of CustomStorageDevices filtered by the given function. func (d CustomStorageDevices) Filter(fn func(*CustomStorageDevice) bool) CustomStorageDevices { result := make(CustomStorageDevices) @@ -406,29 +388,3 @@ func (d CustomStorageDevices) EncodeValues(_ string, v *url.Values) error { return nil } - -// MapCustomStorageDevices maps the custom storage devices from the API response. -// -// NOTE: CustomStorageDevice.FileID and CustomStorageDevice.DatastoreID are not set in this function. -func MapCustomStorageDevices(resp GetResponseData) CustomStorageDevices { - csd := CustomStorageDevices{} - - mapDevice(csd, resp, "ide", "IDE", 3) - mapDevice(csd, resp, "sata", "SATA", 5) - mapDevice(csd, resp, "scsi", "SCSI", 13) - mapDevice(csd, resp, "virtio", "VirtualIO", 15) - - return csd -} - -func mapDevice(csds CustomStorageDevices, resp GetResponseData, keyPrefix, fieldPrefix string, end int) { - for i := 0; i <= end; i++ { - field := reflect.ValueOf(resp).FieldByName(fieldPrefix + "Device" + strconv.Itoa(i)) - if !field.IsZero() { - val := field.Interface() - if val != nil { - csds[keyPrefix+strconv.Itoa(i)] = val.(*CustomStorageDevice) - } - } - } -} diff --git a/proxmox/nodes/vms/custom_storage_device_test.go b/proxmox/nodes/vms/custom_storage_device_test.go index 0301bfa7..a74bdd30 100644 --- a/proxmox/nodes/vms/custom_storage_device_test.go +++ b/proxmox/nodes/vms/custom_storage_device_test.go @@ -121,154 +121,3 @@ func TestCustomStorageDevice_IsCloudInitDrive(t *testing.T) { }) } } - -func TestCustomStorageDevice_StorageInterface(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - device CustomStorageDevice - want string - }{ - { - name: "virtio0", - device: CustomStorageDevice{ - Interface: ptr.Ptr("virtio0"), - }, - want: "virtio", - }, { - name: "scsi13", - device: CustomStorageDevice{ - Interface: ptr.Ptr("scsi13"), - }, - want: "scsi", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - got := tt.device.StorageInterface() - assert.Equal(t, tt.want, got) - }) - } -} - -func TestCustomStorageDevices_ByStorageInterface(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - iface string - devices CustomStorageDevices - want CustomStorageDevices - }{ - { - name: "empty", - iface: "virtio", - devices: CustomStorageDevices{}, - want: CustomStorageDevices{}, - }, - { - name: "nothing matches", - iface: "sata", - devices: CustomStorageDevices{ - "virtio0": &CustomStorageDevice{ - Interface: ptr.Ptr("virtio0"), - }, - "scsi13": &CustomStorageDevice{ - Interface: ptr.Ptr("scsi13"), - }, - }, - want: CustomStorageDevices{}, - }, - { - name: "partially matches", - iface: "virtio", - devices: CustomStorageDevices{ - "virtio0": &CustomStorageDevice{ - Interface: ptr.Ptr("virtio0"), - }, - "scsi13": &CustomStorageDevice{ - Interface: ptr.Ptr("scsi13"), - }, - "virtio1": &CustomStorageDevice{ - Interface: ptr.Ptr("virtio1"), - }, - }, - want: CustomStorageDevices{ - "virtio0": &CustomStorageDevice{ - Interface: ptr.Ptr("virtio0"), - }, - "virtio1": &CustomStorageDevice{ - Interface: ptr.Ptr("virtio1"), - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - got := tt.devices.ByStorageInterface(tt.iface) - assert.Equal(t, tt.want, got) - }) - } -} - -func TestMapCustomStorageDevices(t *testing.T) { - t.Parallel() - - type args struct { - resp GetResponseData - } - - tests := []struct { - name string - args args - want CustomStorageDevices - }{ - {"no storage devices", args{GetResponseData{}}, CustomStorageDevices{}}, - { - "ide0 storage devices", - args{GetResponseData{IDEDevice0: &CustomStorageDevice{}}}, - map[string]*CustomStorageDevice{"ide0": {}}, - }, - { - "multiple ide storage devices", - args{GetResponseData{ - IDEDevice1: &CustomStorageDevice{}, - IDEDevice3: &CustomStorageDevice{}, - }}, - map[string]*CustomStorageDevice{"ide1": {}, "ide3": {}}, - }, - { - "mixed storage devices", - args{GetResponseData{ - IDEDevice1: &CustomStorageDevice{}, - VirtualIODevice5: &CustomStorageDevice{}, - SATADevice0: &CustomStorageDevice{}, - IDEDevice3: &CustomStorageDevice{}, - SCSIDevice10: &CustomStorageDevice{}, - }}, - map[string]*CustomStorageDevice{ - "ide1": {}, - "virtio5": {}, - "sata0": {}, - "ide3": {}, - "scsi10": {}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - devices := MapCustomStorageDevices(tt.args.resp) - assert.Equalf(t, tt.want, devices, "MapCustomStorageDevices(%v)", tt.args.resp) - }) - } -} diff --git a/proxmox/nodes/vms/vms_types.go b/proxmox/nodes/vms/vms_types.go index 0bc3e2ba..ba4c5034 100644 --- a/proxmox/nodes/vms/vms_types.go +++ b/proxmox/nodes/vms/vms_types.go @@ -7,9 +7,11 @@ package vms import ( + "encoding/json" "errors" "fmt" "reflect" + "regexp" "strings" "github.com/bpg/terraform-provider-proxmox/proxmox/types" @@ -60,7 +62,6 @@ type CreateRequestBody struct { HookScript *string `json:"hookscript,omitempty" url:"hookscript,omitempty"` Hotplug types.CustomCommaSeparatedList `json:"hotplug,omitempty" url:"hotplug,omitempty,comma"` Hugepages *string `json:"hugepages,omitempty" url:"hugepages,omitempty"` - IDEDevices CustomStorageDevices `json:"ide,omitempty" url:",omitempty"` KeepHugepages *types.CustomBool `json:"keephugepages,omitempty" url:"keephugepages,omitempty,int"` KeyboardLayout *string `json:"keyboard,omitempty" url:"keyboard,omitempty"` KVMArguments *string `json:"args,omitempty" url:"args,omitempty,space"` @@ -79,8 +80,6 @@ type CreateRequestBody struct { PCIDevices CustomPCIDevices `json:"hostpci,omitempty" url:"hostpci,omitempty"` PoolID *string `json:"pool,omitempty" url:"pool,omitempty"` Revert *string `json:"revert,omitempty" url:"revert,omitempty"` - SATADevices CustomStorageDevices `json:"sata,omitempty" url:"sata,omitempty"` - SCSIDevices CustomStorageDevices `json:"scsi,omitempty" url:"scsi,omitempty"` SCSIHardware *string `json:"scsihw,omitempty" url:"scsihw,omitempty"` SerialDevices CustomSerialDevices `json:"serial,omitempty" url:"serial,omitempty"` SharedMemory *CustomSharedMemory `json:"ivshmem,omitempty" url:"ivshmem,omitempty"` @@ -98,51 +97,22 @@ type CreateRequestBody struct { USBDevices CustomUSBDevices `json:"usb,omitempty" url:"usb,omitempty"` VGADevice *CustomVGADevice `json:"vga,omitempty" url:"vga,omitempty"` VirtualCPUCount *int64 `json:"vcpus,omitempty" url:"vcpus,omitempty"` - VirtualIODevices CustomStorageDevices `json:"virtio,omitempty" url:"virtio,omitempty"` VMGenerationID *string `json:"vmgenid,omitempty" url:"vmgenid,omitempty"` VMID int `json:"vmid,omitempty" url:"vmid,omitempty"` VMStateDatastoreID *string `json:"vmstatestorage,omitempty" url:"vmstatestorage,omitempty"` WatchdogDevice *CustomWatchdogDevice `json:"watchdog,omitempty" url:"watchdog,omitempty"` + CustomStorageDevices CustomStorageDevices `json:"-"` } // AddCustomStorageDevice adds a custom storage device to the create request body. -func (b *CreateRequestBody) AddCustomStorageDevice(device CustomStorageDevice) error { +func (b *CreateRequestBody) AddCustomStorageDevice(iface string, device CustomStorageDevice) { device.Enabled = true - switch device.StorageInterface() { - case "ide": - if b.IDEDevices == nil { - b.IDEDevices = make(CustomStorageDevices, 1) - } - - b.IDEDevices[*device.Interface] = &device - - case "sata": - if b.SATADevices == nil { - b.SATADevices = make(CustomStorageDevices, 1) - } - - b.SATADevices[*device.Interface] = &device - - case "scsi": - if b.SCSIDevices == nil { - b.SCSIDevices = make(CustomStorageDevices, 1) - } - - b.SCSIDevices[*device.Interface] = &device - - case "virtio": - if b.VirtualIODevices == nil { - b.VirtualIODevices = make(CustomStorageDevices, 1) - } - - b.VirtualIODevices[*device.Interface] = &device - - default: - return fmt.Errorf("unsupported storage interface: %s", device.StorageInterface()) + if b.CustomStorageDevices == nil { + b.CustomStorageDevices = make(CustomStorageDevices, 1) } - return nil + b.CustomStorageDevices[iface] = &device } // CreateResponseBody contains the body from a create response. @@ -235,10 +205,6 @@ type GetResponseData struct { HookScript *string `json:"hookscript,omitempty"` Hotplug *types.CustomCommaSeparatedList `json:"hotplug,omitempty"` Hugepages *string `json:"hugepages,omitempty"` - IDEDevice0 *CustomStorageDevice `json:"ide0,omitempty"` - IDEDevice1 *CustomStorageDevice `json:"ide1,omitempty"` - IDEDevice2 *CustomStorageDevice `json:"ide2,omitempty"` - IDEDevice3 *CustomStorageDevice `json:"ide3,omitempty"` IPConfig0 *CustomCloudInitIPConfig `json:"ipconfig0,omitempty"` IPConfig1 *CustomCloudInitIPConfig `json:"ipconfig1,omitempty"` IPConfig2 *CustomCloudInitIPConfig `json:"ipconfig2,omitempty"` @@ -330,26 +296,6 @@ type GetResponseData struct { PCIDevice3 *CustomPCIDevice `json:"hostpci3,omitempty"` PoolID *string `json:"pool,omitempty"` Revert *string `json:"revert,omitempty"` - SATADevice0 *CustomStorageDevice `json:"sata0,omitempty"` - SATADevice1 *CustomStorageDevice `json:"sata1,omitempty"` - SATADevice2 *CustomStorageDevice `json:"sata2,omitempty"` - SATADevice3 *CustomStorageDevice `json:"sata3,omitempty"` - SATADevice4 *CustomStorageDevice `json:"sata4,omitempty"` - SATADevice5 *CustomStorageDevice `json:"sata5,omitempty"` - SCSIDevice0 *CustomStorageDevice `json:"scsi0,omitempty"` - SCSIDevice1 *CustomStorageDevice `json:"scsi1,omitempty"` - SCSIDevice2 *CustomStorageDevice `json:"scsi2,omitempty"` - SCSIDevice3 *CustomStorageDevice `json:"scsi3,omitempty"` - SCSIDevice4 *CustomStorageDevice `json:"scsi4,omitempty"` - SCSIDevice5 *CustomStorageDevice `json:"scsi5,omitempty"` - SCSIDevice6 *CustomStorageDevice `json:"scsi6,omitempty"` - SCSIDevice7 *CustomStorageDevice `json:"scsi7,omitempty"` - SCSIDevice8 *CustomStorageDevice `json:"scsi8,omitempty"` - SCSIDevice9 *CustomStorageDevice `json:"scsi9,omitempty"` - SCSIDevice10 *CustomStorageDevice `json:"scsi10,omitempty"` - SCSIDevice11 *CustomStorageDevice `json:"scsi11,omitempty"` - SCSIDevice12 *CustomStorageDevice `json:"scsi12,omitempty"` - SCSIDevice13 *CustomStorageDevice `json:"scsi13,omitempty"` SCSIHardware *string `json:"scsihw,omitempty"` SerialDevice0 *string `json:"serial0,omitempty"` SerialDevice1 *string `json:"serial1,omitempty"` @@ -373,25 +319,10 @@ type GetResponseData struct { USBDevice3 *CustomUSBDevice `json:"usb3,omitempty"` VGADevice *CustomVGADevice `json:"vga,omitempty"` VirtualCPUCount *int64 `json:"vcpus,omitempty"` - VirtualIODevice0 *CustomStorageDevice `json:"virtio0,omitempty"` - VirtualIODevice1 *CustomStorageDevice `json:"virtio1,omitempty"` - VirtualIODevice2 *CustomStorageDevice `json:"virtio2,omitempty"` - VirtualIODevice3 *CustomStorageDevice `json:"virtio3,omitempty"` - VirtualIODevice4 *CustomStorageDevice `json:"virtio4,omitempty"` - VirtualIODevice5 *CustomStorageDevice `json:"virtio5,omitempty"` - VirtualIODevice6 *CustomStorageDevice `json:"virtio6,omitempty"` - VirtualIODevice7 *CustomStorageDevice `json:"virtio7,omitempty"` - VirtualIODevice8 *CustomStorageDevice `json:"virtio8,omitempty"` - VirtualIODevice9 *CustomStorageDevice `json:"virtio9,omitempty"` - VirtualIODevice10 *CustomStorageDevice `json:"virtio10,omitempty"` - VirtualIODevice11 *CustomStorageDevice `json:"virtio11,omitempty"` - VirtualIODevice12 *CustomStorageDevice `json:"virtio12,omitempty"` - VirtualIODevice13 *CustomStorageDevice `json:"virtio13,omitempty"` - VirtualIODevice14 *CustomStorageDevice `json:"virtio14,omitempty"` - VirtualIODevice15 *CustomStorageDevice `json:"virtio15,omitempty"` VMGenerationID *string `json:"vmgenid,omitempty"` VMStateDatastoreID *string `json:"vmstatestorage,omitempty"` WatchdogDevice *CustomWatchdogDevice `json:"watchdog,omitempty"` + CustomStorageDevices CustomStorageDevices `json:"-"` } // GetStatusResponseBody contains the body from a VM get status response. @@ -518,6 +449,48 @@ type UpdateAsyncResponseBody struct { // UpdateRequestBody contains the data for an virtual machine update request. type UpdateRequestBody = CreateRequestBody +// UnmarshalJSON unmarshals the custom storage devices from the JSON data. +func (d *GetResponseData) UnmarshalJSON(b []byte) error { + type Alias GetResponseData + + var data Alias + + // get original struct + if err := json.Unmarshal(b, &data); err != nil { + return fmt.Errorf("failed to unmarshal data: %w", err) + } + + var byAttr map[string]interface{} + + // now get map by attribute name + err := json.Unmarshal(b, &byAttr) + if err != nil { + return fmt.Errorf("failed to unmarshal data: %w", err) + } + + allDevices := make(CustomStorageDevices) + + for key, value := range byAttr { + for _, prefix := range StorageInterfaces { + r := regexp.MustCompile(`^` + prefix + `\d+$`) + if r.MatchString(key) { + var device CustomStorageDevice + if err := json.Unmarshal([]byte(`"`+value.(string)+`"`), &device); err != nil { + return fmt.Errorf("failed to unmarshal %s: %w", key, err) + } + + allDevices[key] = &device + } + } + } + + data.CustomStorageDevices = allDevices + + *d = GetResponseData(data) + + return nil +} + // ToDelete adds a field to the delete list. The field name should be the **actual** field name in the struct. func (b *UpdateRequestBody) ToDelete(fieldName string) error { if b == nil { diff --git a/proxmox/nodes/vms/vms_types_test.go b/proxmox/nodes/vms/vms_types_test.go new file mode 100644 index 00000000..bef72e63 --- /dev/null +++ b/proxmox/nodes/vms/vms_types_test.go @@ -0,0 +1,64 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package vms + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUnmarshalGetResponseData(t *testing.T) { + t.Parallel() + + jsonData := fmt.Sprintf(`{ + "archive": "test", + "ide0": "%[1]s", + "ide1": "%[1]s", + "ide2": "%[1]s", + "ide3": "%[1]s", + "virtio13": "%[1]s", + "scsi22": "%[1]s" + }`, "local-lvm:vm-100-disk-0,aio=io_uring,backup=1,cache=none,discard=ignore,replicate=1,size=8G,ssd=1") + + var data GetResponseData + err := json.Unmarshal([]byte(jsonData), &data) + require.NoError(t, err) + + assert.Equal(t, "test", *data.BackupFile) + + assert.NotNil(t, data.CustomStorageDevices) + assert.Len(t, data.CustomStorageDevices, 6) + assert.NotNil(t, data.CustomStorageDevices["ide0"]) + assertDevice(t, data.CustomStorageDevices["ide0"]) + assert.NotNil(t, data.CustomStorageDevices["ide1"]) + assertDevice(t, data.CustomStorageDevices["ide1"]) + assert.NotNil(t, data.CustomStorageDevices["ide2"]) + assertDevice(t, data.CustomStorageDevices["ide2"]) + assert.NotNil(t, data.CustomStorageDevices["ide3"]) + assertDevice(t, data.CustomStorageDevices["ide3"]) + assert.NotNil(t, data.CustomStorageDevices["virtio13"]) + assertDevice(t, data.CustomStorageDevices["virtio13"]) + assert.NotNil(t, data.CustomStorageDevices["scsi22"]) + assertDevice(t, data.CustomStorageDevices["scsi22"]) +} + +func assertDevice(t *testing.T, dev *CustomStorageDevice) { + t.Helper() + + assert.Equal(t, "io_uring", *dev.AIO) + assert.True(t, bool(*dev.Backup)) + assert.Equal(t, "none", *dev.Cache) + assert.Equal(t, "ignore", *dev.Discard) + assert.Equal(t, "local-lvm:vm-100-disk-0", dev.FileVolume) + assert.True(t, bool(*dev.Replicate)) + assert.Equal(t, "8G", dev.Size.String()) + assert.True(t, bool(*dev.SSD)) +} diff --git a/proxmoxtf/resource/vm/disk/disk.go b/proxmoxtf/resource/vm/disk/disk.go index ea27302e..7479ec3f 100644 --- a/proxmoxtf/resource/vm/disk/disk.go +++ b/proxmoxtf/resource/vm/disk/disk.go @@ -11,6 +11,7 @@ import ( "errors" "fmt" "regexp" + "slices" "strings" "unicode" @@ -26,56 +27,10 @@ import ( "github.com/bpg/terraform-provider-proxmox/utils" ) -const supportedDiskInterfaces = "virtio, sata, scsi, ide" - // GetInfo returns the disk information for a VM. // Deprecated: use vms.MapCustomStorageDevices from proxmox/nodes/vms instead. func GetInfo(resp *vms.GetResponseData, d *schema.ResourceData) vms.CustomStorageDevices { - storageDevices := vms.CustomStorageDevices{} - - storageDevices["ide0"] = resp.IDEDevice0 - storageDevices["ide1"] = resp.IDEDevice1 - storageDevices["ide2"] = resp.IDEDevice2 - storageDevices["ide3"] = resp.IDEDevice3 - - storageDevices["sata0"] = resp.SATADevice0 - storageDevices["sata1"] = resp.SATADevice1 - storageDevices["sata2"] = resp.SATADevice2 - storageDevices["sata3"] = resp.SATADevice3 - storageDevices["sata4"] = resp.SATADevice4 - storageDevices["sata5"] = resp.SATADevice5 - - storageDevices["scsi0"] = resp.SCSIDevice0 - storageDevices["scsi1"] = resp.SCSIDevice1 - storageDevices["scsi2"] = resp.SCSIDevice2 - storageDevices["scsi3"] = resp.SCSIDevice3 - storageDevices["scsi4"] = resp.SCSIDevice4 - storageDevices["scsi5"] = resp.SCSIDevice5 - storageDevices["scsi6"] = resp.SCSIDevice6 - storageDevices["scsi7"] = resp.SCSIDevice7 - storageDevices["scsi8"] = resp.SCSIDevice8 - storageDevices["scsi9"] = resp.SCSIDevice9 - storageDevices["scsi10"] = resp.SCSIDevice10 - storageDevices["scsi11"] = resp.SCSIDevice11 - storageDevices["scsi12"] = resp.SCSIDevice12 - storageDevices["scsi13"] = resp.SCSIDevice13 - - storageDevices["virtio0"] = resp.VirtualIODevice0 - storageDevices["virtio1"] = resp.VirtualIODevice1 - storageDevices["virtio2"] = resp.VirtualIODevice2 - storageDevices["virtio3"] = resp.VirtualIODevice3 - storageDevices["virtio4"] = resp.VirtualIODevice4 - storageDevices["virtio5"] = resp.VirtualIODevice5 - storageDevices["virtio6"] = resp.VirtualIODevice6 - storageDevices["virtio7"] = resp.VirtualIODevice7 - storageDevices["virtio8"] = resp.VirtualIODevice8 - storageDevices["virtio9"] = resp.VirtualIODevice9 - storageDevices["virtio10"] = resp.VirtualIODevice10 - storageDevices["virtio11"] = resp.VirtualIODevice11 - storageDevices["virtio12"] = resp.VirtualIODevice12 - storageDevices["virtio13"] = resp.VirtualIODevice13 - storageDevices["virtio14"] = resp.VirtualIODevice14 - storageDevices["virtio15"] = resp.VirtualIODevice15 + storageDevices := resp.CustomStorageDevices currentDisk := d.Get(MkDisk) @@ -92,10 +47,6 @@ func GetInfo(resp *vms.GetResponseData, d *schema.ResourceData) vms.CustomStorag if v.Size == nil { v.Size = new(types.DiskSize) } - - // defensive copy of the loop variable - iface := k - v.Interface = &iface } } @@ -106,7 +57,7 @@ func GetInfo(resp *vms.GetResponseData, d *schema.ResourceData) vms.CustomStorag func CreateClone( ctx context.Context, d *schema.ResourceData, - planDisks map[string]vms.CustomStorageDevices, + planDisks vms.CustomStorageDevices, allDiskInfo vms.CustomStorageDevices, vmAPI *vms.Client, ) error { @@ -116,45 +67,18 @@ func CreateClone( diskInterface := diskBlock[mkDiskInterface].(string) dataStoreID := diskBlock[mkDiskDatastoreID].(string) diskSize := int64(diskBlock[mkDiskSize].(int)) - prefix := DigitPrefix(diskInterface) currentDiskInfo := allDiskInfo[diskInterface] - configuredDiskInfo := planDisks[prefix][diskInterface] + configuredDiskInfo := planDisks[diskInterface] if currentDiskInfo == nil { diskUpdateBody := &vms.UpdateRequestBody{} - switch prefix { - case "virtio": - if diskUpdateBody.VirtualIODevices == nil { - diskUpdateBody.VirtualIODevices = vms.CustomStorageDevices{} - } - - diskUpdateBody.VirtualIODevices[diskInterface] = configuredDiskInfo - case "sata": - if diskUpdateBody.SATADevices == nil { - diskUpdateBody.SATADevices = vms.CustomStorageDevices{} - } - - diskUpdateBody.SATADevices[diskInterface] = configuredDiskInfo - case "scsi": - if diskUpdateBody.SCSIDevices == nil { - diskUpdateBody.SCSIDevices = vms.CustomStorageDevices{} - } - - diskUpdateBody.SCSIDevices[diskInterface] = configuredDiskInfo - - case "ide": - if diskUpdateBody.IDEDevices == nil { - diskUpdateBody.IDEDevices = vms.CustomStorageDevices{} - } - - diskUpdateBody.IDEDevices[diskInterface] = configuredDiskInfo - } + diskUpdateBody.AddCustomStorageDevice(diskInterface, *configuredDiskInfo) err := vmAPI.UpdateVM(ctx, diskUpdateBody) if err != nil { - return fmt.Errorf("disk create fails: %w", err) + return fmt.Errorf("disk update fails: %w", err) } continue @@ -225,7 +149,7 @@ func GetDiskDeviceObjects( d *schema.ResourceData, resource *schema.Resource, disks []interface{}, -) (map[string]vms.CustomStorageDevices, error) { +) (vms.CustomStorageDevices, error) { var diskDevices []interface{} if disks != nil { @@ -234,7 +158,7 @@ func GetDiskDeviceObjects( diskDevices = d.Get(MkDisk).([]interface{}) } - diskDeviceObjects := map[string]vms.CustomStorageDevices{} + diskDeviceObjects := vms.CustomStorageDevices{} for _, diskEntry := range diskDevices { diskDevice := &vms.CustomStorageDevice{ @@ -300,7 +224,6 @@ func GetDiskDeviceObjects( diskDevice.Discard = &discard diskDevice.FileID = &fileID diskDevice.Format = &fileFormat - diskDevice.Interface = &diskInterface diskDevice.Replicate = &replicate diskDevice.Serial = &serial diskDevice.Size = types.DiskSizeFromGigabytes(int64(size)) @@ -356,21 +279,16 @@ func GetDiskDeviceObjects( } } - baseDiskInterface := DigitPrefix(diskInterface) - if !strings.Contains(supportedDiskInterfaces, baseDiskInterface) { + if !slices.Contains(vms.StorageInterfaces, DigitPrefix(diskInterface)) { errorMsg := fmt.Sprintf( - "Defined disk interface not supported. Interface was %s, but only %s are supported", - diskInterface, supportedDiskInterfaces, + "Defined disk interface not supported. Interface was %s, but only %v are supported", + diskInterface, vms.StorageInterfaces, ) return diskDeviceObjects, errors.New(errorMsg) } - if _, present := diskDeviceObjects[baseDiskInterface]; !present { - diskDeviceObjects[baseDiskInterface] = vms.CustomStorageDevices{} - } - - diskDeviceObjects[baseDiskInterface][diskInterface] = diskDevice + diskDeviceObjects[diskInterface] = diskDevice } return diskDeviceObjects, nil @@ -382,16 +300,14 @@ func CreateCustomDisks( client proxmox.Client, nodeName string, vmID int, - storageDevices map[string]vms.CustomStorageDevices, + storageDevices vms.CustomStorageDevices, ) diag.Diagnostics { - for _, diskDevice := range storageDevices { - for _, disk := range diskDevice { - if disk != nil && disk.FileID != nil && *disk.FileID != "" { - // only custom disks with defined file ID - err := createCustomDisk(ctx, client, nodeName, vmID, *disk) - if err != nil { - return diag.FromErr(err) - } + for iface, disk := range storageDevices { + if disk != nil && disk.FileID != nil && *disk.FileID != "" { + // only custom disks with defined file ID + err := createCustomDisk(ctx, client, nodeName, vmID, iface, *disk) + if err != nil { + return diag.FromErr(err) } } } @@ -404,23 +320,24 @@ func createCustomDisk( client proxmox.Client, nodeName string, vmID int, - d vms.CustomStorageDevice, + iface string, + disk vms.CustomStorageDevice, ) error { fileFormat := dvDiskFileFormat - if d.Format != nil && *d.Format != "" { - fileFormat = *d.Format + if disk.Format != nil && *disk.Format != "" { + fileFormat = *disk.Format } //nolint:lll commands := []string{ `set -e`, ssh.TrySudo, - fmt.Sprintf(`file_id="%s"`, *d.FileID), + fmt.Sprintf(`file_id="%s"`, *disk.FileID), fmt.Sprintf(`file_format="%s"`, fileFormat), - fmt.Sprintf(`datastore_id_target="%s"`, *d.DatastoreID), + fmt.Sprintf(`datastore_id_target="%s"`, *disk.DatastoreID), fmt.Sprintf(`vm_id="%d"`, vmID), - fmt.Sprintf(`disk_options="%s"`, d.EncodeOptions()), - fmt.Sprintf(`disk_interface="%s"`, *d.Interface), + fmt.Sprintf(`disk_options="%s"`, disk.EncodeOptions()), + fmt.Sprintf(`disk_interface="%s"`, iface), `source_image=$(try_sudo "pvesm path $file_id")`, `imported_disk="$(try_sudo "qm importdisk $vm_id $source_image $datastore_id_target -format $file_format" | grep "unused0" | cut -d ":" -f 3 | cut -d "'" -f 1)"`, `disk_id="${datastore_id_target}:$imported_disk,${disk_options}"`, @@ -441,8 +358,8 @@ func createCustomDisk( }) err = client.Node(nodeName).VM(vmID).ResizeVMDisk(ctx, &vms.ResizeDiskRequestBody{ - Disk: *d.Interface, - Size: *d.Size, + Disk: iface, + Size: *disk.Size, }) if err != nil { return fmt.Errorf("resizing disk: %w", err) @@ -651,103 +568,62 @@ func Update( nodeName string, vmID int, d *schema.ResourceData, - planDisks map[string]vms.CustomStorageDevices, + planDisks vms.CustomStorageDevices, currentDisks vms.CustomStorageDevices, updateBody *vms.UpdateRequestBody, ) (bool, error) { rebootRequired := false if d.HasChange(MkDisk) { - for prefix, diskMap := range planDisks { - if diskMap == nil { + for iface, disk := range planDisks { + var tmp *vms.CustomStorageDevice + + switch { + case currentDisks[iface] == nil && disk != nil: + if disk.FileID != nil && *disk.FileID != "" { + // only disks with defined file ID are custom image disks that need to be created via import. + err := createCustomDisk(ctx, client, nodeName, vmID, iface, *disk) + if err != nil { + return false, fmt.Errorf("creating custom disk: %w", err) + } + } else { + // otherwise this is a blank disk that can be added directly via update API + tmp = disk + } + case currentDisks[iface] != nil: + // update existing disk + tmp = currentDisks[iface] + default: + // something went wrong + return false, fmt.Errorf("missing device %s", iface) + } + + if tmp == nil || disk == nil { continue } - for key, disk := range diskMap { - var tmp *vms.CustomStorageDevice - - switch { - case currentDisks[key] == nil && disk != nil: - if disk.FileID != nil && *disk.FileID != "" { - // only disks with defined file ID are custom image disks that need to be created via import. - err := createCustomDisk(ctx, client, nodeName, vmID, *disk) - if err != nil { - return false, fmt.Errorf("creating custom disk: %w", err) - } - } else { - // otherwise this is a blank disk that can be added directly via update API - tmp = disk - } - case currentDisks[key] != nil: - // update existing disk - tmp = currentDisks[key] - default: - // something went wrong - return false, fmt.Errorf("missing %s device %s", prefix, key) - } - - if tmp == nil || disk == nil { - continue - } - - if tmp.AIO != disk.AIO { - rebootRequired = true - tmp.AIO = disk.AIO - } - - tmp.Backup = disk.Backup - tmp.BurstableReadSpeedMbps = disk.BurstableReadSpeedMbps - tmp.BurstableWriteSpeedMbps = disk.BurstableWriteSpeedMbps - tmp.Cache = disk.Cache - tmp.Discard = disk.Discard - tmp.IOThread = disk.IOThread - tmp.IopsRead = disk.IopsRead - tmp.IopsWrite = disk.IopsWrite - tmp.MaxIopsRead = disk.MaxIopsRead - tmp.MaxIopsWrite = disk.MaxIopsWrite - tmp.MaxReadSpeedMbps = disk.MaxReadSpeedMbps - tmp.MaxWriteSpeedMbps = disk.MaxWriteSpeedMbps - tmp.Replicate = disk.Replicate - tmp.Serial = disk.Serial - tmp.SSD = disk.SSD - - switch prefix { - case "virtio": - { - if updateBody.VirtualIODevices == nil { - updateBody.VirtualIODevices = vms.CustomStorageDevices{} - } - - updateBody.VirtualIODevices[key] = tmp - } - case "sata": - { - if updateBody.SATADevices == nil { - updateBody.SATADevices = vms.CustomStorageDevices{} - } - - updateBody.SATADevices[key] = tmp - } - case "scsi": - { - if updateBody.SCSIDevices == nil { - updateBody.SCSIDevices = vms.CustomStorageDevices{} - } - - updateBody.SCSIDevices[key] = tmp - } - case "ide": - { - if updateBody.IDEDevices == nil { - updateBody.IDEDevices = vms.CustomStorageDevices{} - } - - updateBody.IDEDevices[key] = tmp - } - default: - return false, fmt.Errorf("device prefix %s not supported", prefix) - } + if tmp.AIO != disk.AIO { + rebootRequired = true + tmp.AIO = disk.AIO } + + tmp.Backup = disk.Backup + tmp.BurstableReadSpeedMbps = disk.BurstableReadSpeedMbps + tmp.BurstableWriteSpeedMbps = disk.BurstableWriteSpeedMbps + tmp.Cache = disk.Cache + tmp.Discard = disk.Discard + tmp.IOThread = disk.IOThread + tmp.IopsRead = disk.IopsRead + tmp.IopsWrite = disk.IopsWrite + tmp.MaxIopsRead = disk.MaxIopsRead + tmp.MaxIopsWrite = disk.MaxIopsWrite + tmp.MaxReadSpeedMbps = disk.MaxReadSpeedMbps + tmp.MaxWriteSpeedMbps = disk.MaxWriteSpeedMbps + tmp.Replicate = disk.Replicate + tmp.Serial = disk.Serial + tmp.SSD = disk.SSD + + updateBody.AddCustomStorageDevice(iface, *tmp) } } diff --git a/proxmoxtf/resource/vm/vm.go b/proxmoxtf/resource/vm/vm.go index e813e172..e11cd5b6 100644 --- a/proxmoxtf/resource/vm/vm.go +++ b/proxmoxtf/resource/vm/vm.go @@ -1524,52 +1524,12 @@ func vmCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D // Check for an existing CloudInit IDE drive. If no such drive is found, return the specified `defaultValue`. func findExistingCloudInitDrive(vmConfig *vms.GetResponseData, vmID int, defaultValue string) string { - ideDevices := []*vms.CustomStorageDevice{ - vmConfig.IDEDevice0, - vmConfig.IDEDevice1, - vmConfig.IDEDevice2, - vmConfig.IDEDevice3, - } - for i, device := range ideDevices { - if device != nil && device.Enabled && device.IsCloudInitDrive(vmID) { - return fmt.Sprintf("ide%d", i) - } - } + devs := vmConfig.CustomStorageDevices.Filter(func(device *vms.CustomStorageDevice) bool { + return device.IsCloudInitDrive(vmID) + }) - sataDevices := []*vms.CustomStorageDevice{ - vmConfig.SATADevice0, - vmConfig.SATADevice1, - vmConfig.SATADevice2, - vmConfig.SATADevice3, - vmConfig.SATADevice4, - vmConfig.SATADevice5, - } - for i, device := range sataDevices { - if device != nil && device.Enabled && device.IsCloudInitDrive(vmID) { - return fmt.Sprintf("sata%d", i) - } - } - - scsiDevices := []*vms.CustomStorageDevice{ - vmConfig.SCSIDevice0, - vmConfig.SCSIDevice1, - vmConfig.SCSIDevice2, - vmConfig.SCSIDevice3, - vmConfig.SCSIDevice4, - vmConfig.SCSIDevice5, - vmConfig.SCSIDevice6, - vmConfig.SCSIDevice7, - vmConfig.SCSIDevice8, - vmConfig.SCSIDevice9, - vmConfig.SCSIDevice10, - vmConfig.SCSIDevice11, - vmConfig.SCSIDevice12, - vmConfig.SCSIDevice13, - } - for i, device := range scsiDevices { - if device != nil && device.Enabled && device.IsCloudInitDrive(vmID) { - return fmt.Sprintf("scsi%d", i) - } + for iface := range devs { + return iface } return defaultValue @@ -1578,61 +1538,11 @@ func findExistingCloudInitDrive(vmConfig *vms.GetResponseData, vmID int, default // Return a pointer to the storage device configuration based on a name. The device name is assumed to be a // valid ide, sata, or scsi interface name. func getStorageDevice(vmConfig *vms.GetResponseData, deviceName string) *vms.CustomStorageDevice { - switch deviceName { - case "ide0": - return vmConfig.IDEDevice0 - case "ide1": - return vmConfig.IDEDevice1 - case "ide2": - return vmConfig.IDEDevice2 - case "ide3": - return vmConfig.IDEDevice3 - - case "sata0": - return vmConfig.SATADevice0 - case "sata1": - return vmConfig.SATADevice1 - case "sata2": - return vmConfig.SATADevice2 - case "sata3": - return vmConfig.SATADevice3 - case "sata4": - return vmConfig.SATADevice4 - case "sata5": - return vmConfig.SATADevice5 - - case "scsi0": - return vmConfig.SCSIDevice0 - case "scsi1": - return vmConfig.SCSIDevice1 - case "scsi2": - return vmConfig.SCSIDevice2 - case "scsi3": - return vmConfig.SCSIDevice3 - case "scsi4": - return vmConfig.SCSIDevice4 - case "scsi5": - return vmConfig.SCSIDevice5 - case "scsi6": - return vmConfig.SCSIDevice6 - case "scsi7": - return vmConfig.SCSIDevice7 - case "scsi8": - return vmConfig.SCSIDevice8 - case "scsi9": - return vmConfig.SCSIDevice9 - case "scsi10": - return vmConfig.SCSIDevice10 - case "scsi11": - return vmConfig.SCSIDevice11 - case "scsi12": - return vmConfig.SCSIDevice12 - case "scsi13": - return vmConfig.SCSIDevice13 - - default: - return nil + if dev, ok := vmConfig.CustomStorageDevices[deviceName]; ok { + return dev } + + return nil } // Delete IDE interfaces that can then be used for CloudInit. The first interface will always @@ -2073,7 +1983,9 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d } if len(cdrom) > 0 || len(initialization) > 0 { - updateBody.IDEDevices = ideDevices + for iface, dev := range ideDevices { + updateBody.AddCustomStorageDevice(iface, *dev) + } } if keyboardLayout != dvKeyboardLayout { @@ -2564,11 +2476,6 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) return diag.FromErr(err) } - virtioDeviceObjects := diskDeviceObjects["virtio"] - scsiDeviceObjects := diskDeviceObjects["scsi"] - ideDeviceObjects := diskDeviceObjects["ide"] - sataDeviceObjects := diskDeviceObjects["sata"] - var bootOrderConverted []string if cdromEnabled { bootOrderConverted = []string{cdromInterface} @@ -2577,19 +2484,19 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) bootOrder := d.Get(mkBootOrder).([]interface{}) if len(bootOrder) == 0 { - if ideDeviceObjects != nil { + if _, ok := diskDeviceObjects["ide0"]; ok { bootOrderConverted = append(bootOrderConverted, "ide0") } - if sataDeviceObjects != nil { + if _, ok := diskDeviceObjects["sata0"]; ok { bootOrderConverted = append(bootOrderConverted, "sata0") } - if scsiDeviceObjects != nil { + if _, ok := diskDeviceObjects["scsi0"]; ok { bootOrderConverted = append(bootOrderConverted, "scsi0") } - if virtioDeviceObjects != nil { + if _, ok := diskDeviceObjects["virtio0"]; ok { bootOrderConverted = append(bootOrderConverted, "virtio0") } @@ -2609,10 +2516,9 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) } ideDevice2Media := "cdrom" - ideDevices := vms.CustomStorageDevices{} if cdromCloudInitInterface != "" { - ideDevices[cdromCloudInitInterface] = &vms.CustomStorageDevice{ + diskDeviceObjects[cdromCloudInitInterface] = &vms.CustomStorageDevice{ Enabled: cdromCloudInitEnabled, FileVolume: cdromCloudInitFileID, Media: &ideDevice2Media, @@ -2620,7 +2526,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) } if cdromInterface != "" { - ideDevices[cdromInterface] = &vms.CustomStorageDevice{ + diskDeviceObjects[cdromInterface] = &vms.CustomStorageDevice{ Enabled: cdromEnabled, FileVolume: cdromFileID, Media: &ideDevice2Media, @@ -2657,47 +2563,31 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) Flags: &cpuFlagsConverted, Type: cpuType, }, - CPUSockets: ptr.Ptr(int64(cpuSockets)), - CPUUnits: ptr.Ptr(int64(cpuUnits)), - DedicatedMemory: &memoryDedicated, - DeletionProtection: &protection, - EFIDisk: efiDisk, - TPMState: tpmState, - FloatingMemory: &memoryFloating, - IDEDevices: ideDevices, - KeyboardLayout: &keyboardLayout, - NetworkDevices: networkDeviceObjects, - NUMAEnabled: &cpuNUMA, - NUMADevices: numaDeviceObjects, - OSType: &operatingSystemType, - PCIDevices: pciDeviceObjects, - SCSIHardware: &scsiHardware, - SerialDevices: serialDevices, - SharedMemory: memorySharedObject, - StartOnBoot: &onBoot, - SMBIOS: smbios, - StartupOrder: startupOrder, - TabletDeviceEnabled: &tabletDevice, - Template: &template, - USBDevices: usbDeviceObjects, - VGADevice: vgaDevice, - VMID: vmID, - } - - if ideDeviceObjects != nil { - createBody.IDEDevices = ideDeviceObjects - } - - if sataDeviceObjects != nil { - createBody.SATADevices = sataDeviceObjects - } - - if scsiDeviceObjects != nil { - createBody.SCSIDevices = scsiDeviceObjects - } - - if virtioDeviceObjects != nil { - createBody.VirtualIODevices = virtioDeviceObjects + CPUSockets: ptr.Ptr(int64(cpuSockets)), + CPUUnits: ptr.Ptr(int64(cpuUnits)), + DedicatedMemory: &memoryDedicated, + DeletionProtection: &protection, + EFIDisk: efiDisk, + TPMState: tpmState, + FloatingMemory: &memoryFloating, + KeyboardLayout: &keyboardLayout, + NetworkDevices: networkDeviceObjects, + NUMAEnabled: &cpuNUMA, + NUMADevices: numaDeviceObjects, + OSType: &operatingSystemType, + PCIDevices: pciDeviceObjects, + SCSIHardware: &scsiHardware, + SerialDevices: serialDevices, + SharedMemory: memorySharedObject, + StartOnBoot: &onBoot, + SMBIOS: smbios, + StartupOrder: startupOrder, + TabletDeviceEnabled: &tabletDevice, + Template: &template, + USBDevices: usbDeviceObjects, + VGADevice: vgaDevice, + VMID: vmID, + CustomStorageDevices: diskDeviceObjects, } // Only the root account is allowed to change the CPU architecture, which makes this check necessary. @@ -3017,14 +2907,11 @@ func vmGetEfiDiskAsStorageDevice(d *schema.ResourceData, disk []interface{}) (*v if efiDisk != nil { id := "0" - baseDiskInterface := "efidisk" - diskInterface := fmt.Sprint(baseDiskInterface, id) storageDevice = &vms.CustomStorageDevice{ Enabled: true, FileVolume: efiDisk.FileVolume, Format: efiDisk.Format, - Interface: &diskInterface, DatastoreID: &id, } @@ -3075,13 +2962,9 @@ func vmGetTPMStateAsStorageDevice(d *schema.ResourceData, disk []interface{}) *v if tpmState != nil { id := "0" - baseDiskInterface := "tpmstate" - diskInterface := fmt.Sprint(baseDiskInterface, id) - storageDevice = &vms.CustomStorageDevice{ Enabled: true, FileVolume: tpmState.FileVolume, - Interface: &diskInterface, DatastoreID: &id, } } @@ -4757,22 +4640,7 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D vmAPI := client.Node(nodeName).VM(vmID) - updateBody := &vms.UpdateRequestBody{ - IDEDevices: vms.CustomStorageDevices{ - "ide0": &vms.CustomStorageDevice{ - Enabled: false, - }, - "ide1": &vms.CustomStorageDevice{ - Enabled: false, - }, - "ide2": &vms.CustomStorageDevice{ - Enabled: false, - }, - "ide3": &vms.CustomStorageDevice{ - Enabled: false, - }, - }, - } + updateBody := &vms.UpdateRequestBody{} var del []string @@ -4961,11 +4829,11 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D cdromMedia := "cdrom" - updateBody.IDEDevices[cdromInterface] = &vms.CustomStorageDevice{ + updateBody.AddCustomStorageDevice(cdromInterface, vms.CustomStorageDevice{ Enabled: cdromEnabled, FileVolume: cdromFileID, Media: &cdromMedia, - } + }) } // Prepare the new CPU configuration. @@ -5132,11 +5000,11 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D fileVolume = ideDevice.FileVolume } - updateBody.IDEDevices[initializationInterface] = &vms.CustomStorageDevice{ + updateBody.AddCustomStorageDevice(initializationInterface, vms.CustomStorageDevice{ Enabled: true, FileVolume: fileVolume, Media: &cdromMedia, - } + }) } rebootRequired = true @@ -5416,13 +5284,11 @@ func vmUpdateDiskLocationAndSize( } if oldEfiDisk != nil { - baseDiskInterface := disk.DigitPrefix(*oldEfiDisk.Interface) - diskOldEntries[baseDiskInterface][*oldEfiDisk.Interface] = oldEfiDisk + diskOldEntries["efidisk0"] = oldEfiDisk } if newEfiDisk != nil { - baseDiskInterface := disk.DigitPrefix(*newEfiDisk.Interface) - diskNewEntries[baseDiskInterface][*newEfiDisk.Interface] = newEfiDisk + diskNewEntries["efidisk0"] = newEfiDisk } if oldEfiDisk != nil && newEfiDisk != nil && oldEfiDisk.Size != newEfiDisk.Size { @@ -5440,13 +5306,11 @@ func vmUpdateDiskLocationAndSize( newTPMState := vmGetTPMStateAsStorageDevice(d, diskNew.([]interface{})) if oldTPMState != nil { - baseDiskInterface := disk.DigitPrefix(*oldTPMState.Interface) - diskOldEntries[baseDiskInterface][*oldTPMState.Interface] = oldTPMState + diskOldEntries["tpmstate0"] = oldTPMState } if newTPMState != nil { - baseDiskInterface := disk.DigitPrefix(*newTPMState.Interface) - diskNewEntries[baseDiskInterface][*newTPMState.Interface] = newTPMState + diskNewEntries["tpmstate0"] = newTPMState } if oldTPMState != nil && newTPMState != nil && oldTPMState.Size != newTPMState.Size { @@ -5462,58 +5326,56 @@ func vmUpdateDiskLocationAndSize( shutdownForDisksRequired := false - for prefix, diskMap := range diskOldEntries { - for oldKey, oldDisk := range diskMap { - if _, present := diskNewEntries[prefix][oldKey]; !present { + for oldIface, oldDisk := range diskOldEntries { + if _, present := diskNewEntries[oldIface]; !present { + return diag.Errorf( + "deletion of disks not supported. Please delete disk by hand. Old interface was %q", + oldIface, + ) + } + + if *oldDisk.DatastoreID != *diskNewEntries[oldIface].DatastoreID { + if oldDisk.IsOwnedBy(vmID) { + deleteOriginalDisk := types.CustomBool(true) + + diskMoveBodies = append( + diskMoveBodies, + &vms.MoveDiskRequestBody{ + DeleteOriginalDisk: &deleteOriginalDisk, + Disk: oldIface, + TargetStorage: *diskNewEntries[oldIface].DatastoreID, + }, + ) + + // Cannot be done while VM is running. + shutdownForDisksRequired = true + } else { return diag.Errorf( - "deletion of disks not supported. Please delete disk by hand. Old interface was %q", - *oldDisk.Interface, + "Cannot move %s:%s to datastore %s in VM %d configuration, it is not owned by this VM!", + *oldDisk.DatastoreID, + *oldDisk.PathInDatastore(), + *diskNewEntries[oldIface].DatastoreID, + vmID, ) } + } - if *oldDisk.DatastoreID != *diskNewEntries[prefix][oldKey].DatastoreID { - if oldDisk.IsOwnedBy(vmID) { - deleteOriginalDisk := types.CustomBool(true) - - diskMoveBodies = append( - diskMoveBodies, - &vms.MoveDiskRequestBody{ - DeleteOriginalDisk: &deleteOriginalDisk, - Disk: *oldDisk.Interface, - TargetStorage: *diskNewEntries[prefix][oldKey].DatastoreID, - }, - ) - - // Cannot be done while VM is running. - shutdownForDisksRequired = true - } else { - return diag.Errorf( - "Cannot move %s:%s to datastore %s in VM %d configuration, it is not owned by this VM!", - *oldDisk.DatastoreID, - *oldDisk.PathInDatastore(), - *diskNewEntries[prefix][oldKey].DatastoreID, - vmID, - ) - } - } - - if *oldDisk.Size < *diskNewEntries[prefix][oldKey].Size { - if oldDisk.IsOwnedBy(vmID) { - diskResizeBodies = append( - diskResizeBodies, - &vms.ResizeDiskRequestBody{ - Disk: *oldDisk.Interface, - Size: *diskNewEntries[prefix][oldKey].Size, - }, - ) - } else { - return diag.Errorf( - "Cannot resize %s:%s in VM %d configuration, it is not owned by this VM!", - *oldDisk.DatastoreID, - *oldDisk.PathInDatastore(), - vmID, - ) - } + if *oldDisk.Size < *diskNewEntries[oldIface].Size { + if oldDisk.IsOwnedBy(vmID) { + diskResizeBodies = append( + diskResizeBodies, + &vms.ResizeDiskRequestBody{ + Disk: oldIface, + Size: *diskNewEntries[oldIface].Size, + }, + ) + } else { + return diag.Errorf( + "Cannot resize %s:%s in VM %d configuration, it is not owned by this VM!", + *oldDisk.DatastoreID, + *oldDisk.PathInDatastore(), + vmID, + ) } } } diff --git a/testacc b/testacc index ca8f0de5..59229aec 100755 --- a/testacc +++ b/testacc @@ -1,3 +1,10 @@ #!/bin/sh -TF_ACC=1 env $(cat testacc.env | xargs) go test -v -timeout 360s -run "$1" github.com/bpg/terraform-provider-proxmox/fwprovider/tests $2 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. +# + +# shellcheck disable=SC2046 +TF_ACC=1 env $(xargs < testacc.env) go test -v -count 1 -timeout 360s -run "$1" github.com/bpg/terraform-provider-proxmox/fwprovider/... $2