mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-07-04 21:14:05 +00:00
chore(vm): refactor storage devices handling from/to API (#1394)
Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
parent
b7d48f8716
commit
cc7fc63ec1
@ -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"),
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
64
proxmox/nodes/vms/vms_types_test.go
Normal file
64
proxmox/nodes/vms/vms_types_test.go
Normal file
@ -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))
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
9
testacc
9
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
|
||||
|
Loading…
Reference in New Issue
Block a user