mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-06-29 18:21:10 +00:00
current state
Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
parent
7d2554db7d
commit
bd1e937cb0
@ -104,13 +104,14 @@ func TestAccResourceVMDisks(t *testing.T) {
|
||||
}`,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testResourceAttributes("proxmox_virtual_environment_vm.test_disk1", map[string]string{
|
||||
"disk.0.cache": "none",
|
||||
"disk.0.datastore_id": "local-lvm",
|
||||
"disk.0.discard": "ignore",
|
||||
"disk.0.file_format": "raw",
|
||||
// "disk.0.file_id": "", // is empty by default, but we can't check for that
|
||||
// those are empty by default, but we can't check for that
|
||||
// "disk.0.cache": "",
|
||||
// "disk.0.discard": "",
|
||||
// "disk.0.file_id": "",
|
||||
"disk.0.datastore_id": "local-lvm",
|
||||
"disk.0.file_format": "raw",
|
||||
"disk.0.interface": "virtio0",
|
||||
"disk.0.iothread": "false",
|
||||
"disk.0.iothread": "true",
|
||||
"disk.0.path_in_datastore": `vm-\d+-disk-\d+`,
|
||||
"disk.0.size": "8",
|
||||
"disk.0.ssd": "false",
|
||||
@ -140,11 +141,10 @@ func TestAccResourceVMDisks(t *testing.T) {
|
||||
}`,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testResourceAttributes("proxmox_virtual_environment_vm.test_disk2", map[string]string{
|
||||
"disk.0.cache": "none",
|
||||
"disk.0.datastore_id": "local-lvm",
|
||||
"disk.0.discard": "on",
|
||||
"disk.0.file_format": "raw",
|
||||
// "disk.0.file_id": "", // is empty by default, but we can't check for that
|
||||
"disk.0.cache": "none",
|
||||
"disk.0.datastore_id": "local-lvm",
|
||||
"disk.0.discard": "on",
|
||||
"disk.0.file_format": "raw",
|
||||
"disk.0.interface": "virtio0",
|
||||
"disk.0.iothread": "true",
|
||||
"disk.0.path_in_datastore": `vm-\d+-disk-\d+`,
|
||||
@ -153,7 +153,7 @@ func TestAccResourceVMDisks(t *testing.T) {
|
||||
}),
|
||||
),
|
||||
}}},
|
||||
{"clone default disk", []resource.TestStep{
|
||||
{"clone default disk without overrides", []resource.TestStep{
|
||||
{
|
||||
Config: `
|
||||
resource "proxmox_virtual_environment_vm" "test_disk3_template" {
|
||||
@ -188,6 +188,58 @@ func TestAccResourceVMDisks(t *testing.T) {
|
||||
RefreshState: true,
|
||||
},
|
||||
}},
|
||||
{"clone disk with new size", []resource.TestStep{
|
||||
{
|
||||
Config: `
|
||||
resource "proxmox_virtual_environment_vm" "test_disk3_template" {
|
||||
node_name = "pve"
|
||||
started = false
|
||||
name = "test-disk3-template"
|
||||
template = "true"
|
||||
|
||||
disk {
|
||||
file_format = "raw"
|
||||
datastore_id = "local-lvm"
|
||||
interface = "scsi0"
|
||||
size = 8
|
||||
discard = "on"
|
||||
iothread = true
|
||||
}
|
||||
}
|
||||
resource "proxmox_virtual_environment_vm" "test_disk3" {
|
||||
node_name = "pve"
|
||||
started = false
|
||||
name = "test-disk3"
|
||||
|
||||
clone {
|
||||
vm_id = proxmox_virtual_environment_vm.test_disk3_template.id
|
||||
}
|
||||
|
||||
disk {
|
||||
interface = "scsi0"
|
||||
size = 10
|
||||
ssd = true
|
||||
}
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testResourceAttributes("proxmox_virtual_environment_vm.test_disk3", map[string]string{
|
||||
"disk.0.datastore_id": "local-lvm",
|
||||
"disk.0.discard": "on",
|
||||
"disk.0.file_format": "raw",
|
||||
"disk.0.interface": "virtio0",
|
||||
"disk.0.iothread": "true",
|
||||
"disk.0.path_in_datastore": `vm-\d+-disk-\d+`,
|
||||
"disk.0.size": "10",
|
||||
"disk.0.ssd": "true",
|
||||
}),
|
||||
),
|
||||
},
|
||||
//{
|
||||
// RefreshState: true,
|
||||
// Destroy: false,
|
||||
//},
|
||||
}},
|
||||
//{"default disk parameters", resource.TestStep{}},
|
||||
//{"default disk parameters", resource.TestStep{}},
|
||||
}
|
||||
@ -213,10 +265,10 @@ func testResourceAttributes(res string, attrs map[string]string) resource.TestCh
|
||||
if err := resource.TestCheckResourceAttrWith(res, k, func(got string) error {
|
||||
match, err := regexp.Match(v, []byte(got)) //nolint:mirror
|
||||
if err != nil {
|
||||
return fmt.Errorf("error matching %s: %w", v, err)
|
||||
return fmt.Errorf("error matching '%s': %w", v, err)
|
||||
}
|
||||
if !match {
|
||||
return fmt.Errorf("expected %s to match %s", got, v)
|
||||
return fmt.Errorf("expected '%s' to match '%s'", got, v)
|
||||
}
|
||||
return nil
|
||||
})(s); err != nil {
|
||||
|
239
proxmox/nodes/vms/customstoragedevice.go
Normal file
239
proxmox/nodes/vms/customstoragedevice.go
Normal file
@ -0,0 +1,239 @@
|
||||
package vms
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
)
|
||||
|
||||
// CustomStorageDevice handles QEMU SATA device parameters.
|
||||
type CustomStorageDevice struct {
|
||||
AIO *string `json:"aio,omitempty" url:"aio,omitempty"`
|
||||
BackupEnabled *types.CustomBool `json:"backup,omitempty" url:"backup,omitempty,int"`
|
||||
BurstableReadSpeedMbps *int `json:"mbps_rd_max,omitempty" url:"mbps_rd_max,omitempty"`
|
||||
Cache *string `json:"cache,omitempty" url:"cache,omitempty"`
|
||||
BurstableWriteSpeedMbps *int `json:"mbps_wr_max,omitempty" url:"mbps_wr_max,omitempty"`
|
||||
Discard *string `json:"discard,omitempty" url:"discard,omitempty"`
|
||||
Enabled bool `json:"-" url:"-"`
|
||||
FileVolume string `json:"file" url:"file"`
|
||||
Format *string `json:"format,omitempty" url:"format,omitempty"`
|
||||
IOThread *types.CustomBool `json:"iothread,omitempty" url:"iothread,omitempty,int"`
|
||||
SSD *types.CustomBool `json:"ssd,omitempty" url:"ssd,omitempty,int"`
|
||||
MaxReadSpeedMbps *int `json:"mbps_rd,omitempty" url:"mbps_rd,omitempty"`
|
||||
MaxWriteSpeedMbps *int `json:"mbps_wr,omitempty" url:"mbps_wr,omitempty"`
|
||||
Media *string `json:"media,omitempty" url:"media,omitempty"`
|
||||
Size *types.DiskSize `json:"size,omitempty" url:"size,omitempty"`
|
||||
Interface *string `json:"-" url:"-"`
|
||||
DatastoreID *string `json:"-" url:"-"`
|
||||
FileID *string `json:"-" url:"-"`
|
||||
}
|
||||
|
||||
// PathInDatastore returns path part of FileVolume or nil if it is not yet allocated.
|
||||
func (d CustomStorageDevice) PathInDatastore() *string {
|
||||
probablyDatastoreID, pathInDatastore, hasDatastoreID := strings.Cut(d.FileVolume, ":")
|
||||
if !hasDatastoreID {
|
||||
// when no ':' separator is found, 'Cut' places the whole string to 'probablyDatastoreID',
|
||||
// we want it in 'pathInDatastore' (as it is absolute filesystem path)
|
||||
pathInDatastore = probablyDatastoreID
|
||||
|
||||
return &pathInDatastore
|
||||
}
|
||||
|
||||
pathInDatastoreWithoutDigits := strings.Map(
|
||||
func(c rune) rune {
|
||||
if c < '0' || c > '9' {
|
||||
return -1
|
||||
}
|
||||
return c
|
||||
},
|
||||
pathInDatastore)
|
||||
|
||||
if pathInDatastoreWithoutDigits == "" {
|
||||
// FileVolume is not yet allocated, it is in the "STORAGE_ID:SIZE_IN_GiB" format
|
||||
return nil
|
||||
}
|
||||
|
||||
return &pathInDatastore
|
||||
}
|
||||
|
||||
// IsOwnedBy returns true, if CustomStorageDevice is owned by given VM.
|
||||
// Not yet allocated volumes are not owned by any VM.
|
||||
func (d CustomStorageDevice) IsOwnedBy(vmID int) bool {
|
||||
pathInDatastore := d.PathInDatastore()
|
||||
if pathInDatastore == nil {
|
||||
// not yet allocated volume, consider disk not owned by any VM
|
||||
// NOTE: if needed, create IsOwnedByOtherThan(vmId) instead of changing this return value.
|
||||
return false
|
||||
}
|
||||
|
||||
// ZFS uses "local-zfs:vm-123-disk-0"
|
||||
if strings.HasPrefix(*pathInDatastore, fmt.Sprintf("vm-%d-", vmID)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// directory uses "local:123/vm-123-disk-0"
|
||||
if strings.HasPrefix(*pathInDatastore, fmt.Sprintf("%d/vm-%d-", vmID, vmID)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsCloudInitDrive returns true, if CustomStorageDevice is a cloud-init drive.
|
||||
func (d CustomStorageDevice) IsCloudInitDrive(vmID int) bool {
|
||||
return d.Media != nil && *d.Media == "cdrom" &&
|
||||
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{}
|
||||
|
||||
if d.AIO != nil {
|
||||
values = append(values, fmt.Sprintf("aio=%s", *d.AIO))
|
||||
}
|
||||
|
||||
if d.BackupEnabled != nil {
|
||||
if *d.BackupEnabled {
|
||||
values = append(values, "backup=1")
|
||||
} else {
|
||||
values = append(values, "backup=0")
|
||||
}
|
||||
}
|
||||
|
||||
if d.IOThread != nil {
|
||||
if *d.IOThread {
|
||||
values = append(values, "iothread=1")
|
||||
} else {
|
||||
values = append(values, "iothread=0")
|
||||
}
|
||||
}
|
||||
|
||||
if d.SSD != nil {
|
||||
if *d.SSD {
|
||||
values = append(values, "ssd=1")
|
||||
} else {
|
||||
values = append(values, "ssd=0")
|
||||
}
|
||||
}
|
||||
|
||||
if d.Discard != nil && *d.Discard != "" {
|
||||
values = append(values, fmt.Sprintf("discard=%s", *d.Discard))
|
||||
}
|
||||
|
||||
if d.Cache != nil && *d.Cache != "" {
|
||||
values = append(values, fmt.Sprintf("cache=%s", *d.Cache))
|
||||
}
|
||||
|
||||
if d.BurstableReadSpeedMbps != nil {
|
||||
values = append(values, fmt.Sprintf("mbps_rd_max=%d", *d.BurstableReadSpeedMbps))
|
||||
}
|
||||
|
||||
if d.BurstableWriteSpeedMbps != nil {
|
||||
values = append(values, fmt.Sprintf("mbps_wr_max=%d", *d.BurstableWriteSpeedMbps))
|
||||
}
|
||||
|
||||
if d.MaxReadSpeedMbps != nil {
|
||||
values = append(values, fmt.Sprintf("mbps_rd=%d", *d.MaxReadSpeedMbps))
|
||||
}
|
||||
|
||||
if d.MaxWriteSpeedMbps != nil {
|
||||
values = append(values, fmt.Sprintf("mbps_wr=%d", *d.MaxWriteSpeedMbps))
|
||||
}
|
||||
|
||||
return strings.Join(values, ",")
|
||||
}
|
||||
|
||||
// EncodeValues converts a CustomStorageDevice struct to a URL value.
|
||||
func (d CustomStorageDevice) EncodeValues(key string, v *url.Values) error {
|
||||
values := []string{
|
||||
fmt.Sprintf("file=%s", d.FileVolume),
|
||||
}
|
||||
|
||||
if d.Format != nil {
|
||||
values = append(values, fmt.Sprintf("format=%s", *d.Format))
|
||||
}
|
||||
|
||||
if d.Media != nil {
|
||||
values = append(values, fmt.Sprintf("media=%s", *d.Media))
|
||||
}
|
||||
|
||||
if d.Size != nil {
|
||||
values = append(values, fmt.Sprintf("size=%s", *d.Size))
|
||||
}
|
||||
|
||||
values = append(values, d.EncodeOptions())
|
||||
|
||||
v.Add(key, strings.Join(values, ","))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy returns a deep copy of the CustomStorageDevice.
|
||||
func (d CustomStorageDevice) Copy() *CustomStorageDevice {
|
||||
return &CustomStorageDevice{
|
||||
AIO: types.CopyString(d.AIO),
|
||||
BackupEnabled: d.BackupEnabled.Copy(),
|
||||
BurstableReadSpeedMbps: types.CopyInt(d.BurstableReadSpeedMbps),
|
||||
Cache: types.CopyString(d.Cache),
|
||||
BurstableWriteSpeedMbps: types.CopyInt(d.BurstableWriteSpeedMbps),
|
||||
Discard: types.CopyString(d.Discard),
|
||||
Enabled: d.Enabled,
|
||||
FileVolume: d.FileVolume,
|
||||
Format: types.CopyString(d.Format),
|
||||
IOThread: d.IOThread.Copy(),
|
||||
SSD: d.SSD.Copy(),
|
||||
MaxReadSpeedMbps: types.CopyInt(d.MaxReadSpeedMbps),
|
||||
MaxWriteSpeedMbps: types.CopyInt(d.MaxWriteSpeedMbps),
|
||||
Media: types.CopyString(d.Media),
|
||||
Size: d.Size.Copy(),
|
||||
Interface: types.CopyString(d.Interface),
|
||||
DatastoreID: types.CopyString(d.DatastoreID),
|
||||
FileID: types.CopyString(d.FileID),
|
||||
}
|
||||
}
|
||||
|
||||
// CustomStorageDevices handles map of QEMU storage device per disk interface.
|
||||
type CustomStorageDevices map[string]*CustomStorageDevice
|
||||
|
||||
// ByStorageInterface returns a map of CustomStorageDevices filtered by the given storage interface.
|
||||
func (d CustomStorageDevices) ByStorageInterface(storageInterface string) CustomStorageDevices {
|
||||
result := make(CustomStorageDevices)
|
||||
|
||||
for k, v := range d {
|
||||
if v.StorageInterface() == storageInterface {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// EncodeValues converts a CustomStorageDevices array to multiple URL values.
|
||||
func (d CustomStorageDevices) EncodeValues(_ string, v *url.Values) error {
|
||||
for s, d := range d {
|
||||
if d.Enabled {
|
||||
if err := d.EncodeValues(s, v); err != nil {
|
||||
return fmt.Errorf("error encoding storage device %s: %w", s, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -14,7 +14,6 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
)
|
||||
@ -165,112 +164,6 @@ type CustomStartupOrder struct {
|
||||
Up *int `json:"up,omitempty" url:"up,omitempty"`
|
||||
}
|
||||
|
||||
// CustomStorageDevice handles QEMU SATA device parameters.
|
||||
type CustomStorageDevice struct {
|
||||
AIO *string `json:"aio,omitempty" url:"aio,omitempty"`
|
||||
BackupEnabled *types.CustomBool `json:"backup,omitempty" url:"backup,omitempty,int"`
|
||||
BurstableReadSpeedMbps *int `json:"mbps_rd_max,omitempty" url:"mbps_rd_max,omitempty"`
|
||||
Cache *string `json:"cache,omitempty" url:"cache,omitempty"`
|
||||
BurstableWriteSpeedMbps *int `json:"mbps_wr_max,omitempty" url:"mbps_wr_max,omitempty"`
|
||||
Discard *string `json:"discard,omitempty" url:"discard,omitempty"`
|
||||
Enabled bool `json:"-" url:"-"`
|
||||
FileVolume string `json:"file" url:"file"`
|
||||
Format *string `json:"format,omitempty" url:"format,omitempty"`
|
||||
IOThread *types.CustomBool `json:"iothread,omitempty" url:"iothread,omitempty,int"`
|
||||
SSD *types.CustomBool `json:"ssd,omitempty" url:"ssd,omitempty,int"`
|
||||
MaxReadSpeedMbps *int `json:"mbps_rd,omitempty" url:"mbps_rd,omitempty"`
|
||||
MaxWriteSpeedMbps *int `json:"mbps_wr,omitempty" url:"mbps_wr,omitempty"`
|
||||
Media *string `json:"media,omitempty" url:"media,omitempty"`
|
||||
Size *types.DiskSize `json:"size,omitempty" url:"size,omitempty"`
|
||||
Interface *string
|
||||
ID *string
|
||||
FileID *string
|
||||
}
|
||||
|
||||
// PathInDatastore returns path part of FileVolume or nil if it is not yet allocated.
|
||||
func (d CustomStorageDevice) PathInDatastore() *string {
|
||||
probablyDatastoreID, pathInDatastore, hasDatastoreID := strings.Cut(d.FileVolume, ":")
|
||||
if !hasDatastoreID {
|
||||
// when no ':' separator is found, 'Cut' places the whole string to 'probablyDatastoreID',
|
||||
// we want it in 'pathInDatastore' (as it is absolute filesystem path)
|
||||
pathInDatastore = probablyDatastoreID
|
||||
|
||||
return &pathInDatastore
|
||||
}
|
||||
|
||||
pathInDatastoreWithoutDigits := strings.Map(
|
||||
func(c rune) rune {
|
||||
if c < '0' || c > '9' {
|
||||
return -1
|
||||
}
|
||||
return c
|
||||
},
|
||||
pathInDatastore)
|
||||
|
||||
if pathInDatastoreWithoutDigits == "" {
|
||||
// FileVolume is not yet allocated, it is in the "STORAGE_ID:SIZE_IN_GiB" format
|
||||
return nil
|
||||
}
|
||||
|
||||
return &pathInDatastore
|
||||
}
|
||||
|
||||
// IsOwnedBy returns true, if CustomStorageDevice is owned by given VM. Not yet allocated volumes are not owned by any VM.
|
||||
func (d CustomStorageDevice) IsOwnedBy(vmID int) bool {
|
||||
pathInDatastore := d.PathInDatastore()
|
||||
if pathInDatastore == nil {
|
||||
// not yet allocated volume, consider disk not owned by any VM
|
||||
// NOTE: if needed, create IsOwnedByOtherThan(vmId) instead of changing this return value.
|
||||
return false
|
||||
}
|
||||
|
||||
// ZFS uses "local-zfs:vm-123-disk-0"
|
||||
if strings.HasPrefix(*pathInDatastore, fmt.Sprintf("vm-%d-", vmID)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// directory uses "local:123/vm-123-disk-0"
|
||||
if strings.HasPrefix(*pathInDatastore, fmt.Sprintf("%d/vm-%d-", vmID, vmID)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsCloudInitDrive returns true, if CustomStorageDevice is a cloud-init drive.
|
||||
func (d CustomStorageDevice) IsCloudInitDrive(vmID int) bool {
|
||||
return d.Media != nil && *d.Media == "cdrom" &&
|
||||
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 ""
|
||||
}
|
||||
|
||||
// CustomStorageDevices handles map of QEMU storage device per disk interface.
|
||||
type CustomStorageDevices map[string]*CustomStorageDevice
|
||||
|
||||
// ByStorageInterface returns a map of CustomStorageDevices filtered by the given storage interface.
|
||||
func (d CustomStorageDevices) ByStorageInterface(storageInterface string) CustomStorageDevices {
|
||||
result := make(CustomStorageDevices)
|
||||
|
||||
for k, v := range d {
|
||||
if v.StorageInterface() == storageInterface {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// CustomTPMState handles QEMU TPM state parameters.
|
||||
type CustomTPMState struct {
|
||||
FileVolume string `json:"file" url:"file"`
|
||||
@ -1246,103 +1139,6 @@ func (r CustomStartupOrder) EncodeValues(key string, v *url.Values) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeOptions converts a CustomStorageDevice's common options a URL vlaue.
|
||||
func (d CustomStorageDevice) EncodeOptions() string {
|
||||
values := []string{}
|
||||
|
||||
if d.AIO != nil {
|
||||
values = append(values, fmt.Sprintf("aio=%s", *d.AIO))
|
||||
}
|
||||
|
||||
if d.BackupEnabled != nil {
|
||||
if *d.BackupEnabled {
|
||||
values = append(values, "backup=1")
|
||||
} else {
|
||||
values = append(values, "backup=0")
|
||||
}
|
||||
}
|
||||
|
||||
if d.IOThread != nil {
|
||||
if *d.IOThread {
|
||||
values = append(values, "iothread=1")
|
||||
} else {
|
||||
values = append(values, "iothread=0")
|
||||
}
|
||||
}
|
||||
|
||||
if d.SSD != nil {
|
||||
if *d.SSD {
|
||||
values = append(values, "ssd=1")
|
||||
} else {
|
||||
values = append(values, "ssd=0")
|
||||
}
|
||||
}
|
||||
|
||||
if d.Discard != nil && *d.Discard != "" {
|
||||
values = append(values, fmt.Sprintf("discard=%s", *d.Discard))
|
||||
}
|
||||
|
||||
if d.Cache != nil && *d.Cache != "" {
|
||||
values = append(values, fmt.Sprintf("cache=%s", *d.Cache))
|
||||
}
|
||||
|
||||
if d.BurstableReadSpeedMbps != nil {
|
||||
values = append(values, fmt.Sprintf("mbps_rd_max=%d", *d.BurstableReadSpeedMbps))
|
||||
}
|
||||
|
||||
if d.BurstableWriteSpeedMbps != nil {
|
||||
values = append(values, fmt.Sprintf("mbps_wr_max=%d", *d.BurstableWriteSpeedMbps))
|
||||
}
|
||||
|
||||
if d.MaxReadSpeedMbps != nil {
|
||||
values = append(values, fmt.Sprintf("mbps_rd=%d", *d.MaxReadSpeedMbps))
|
||||
}
|
||||
|
||||
if d.MaxWriteSpeedMbps != nil {
|
||||
values = append(values, fmt.Sprintf("mbps_wr=%d", *d.MaxWriteSpeedMbps))
|
||||
}
|
||||
|
||||
return strings.Join(values, ",")
|
||||
}
|
||||
|
||||
// EncodeValues converts a CustomStorageDevice struct to a URL vlaue.
|
||||
func (d CustomStorageDevice) EncodeValues(key string, v *url.Values) error {
|
||||
values := []string{
|
||||
fmt.Sprintf("file=%s", d.FileVolume),
|
||||
}
|
||||
|
||||
if d.Format != nil {
|
||||
values = append(values, fmt.Sprintf("format=%s", *d.Format))
|
||||
}
|
||||
|
||||
if d.Media != nil {
|
||||
values = append(values, fmt.Sprintf("media=%s", *d.Media))
|
||||
}
|
||||
|
||||
if d.Size != nil {
|
||||
values = append(values, fmt.Sprintf("size=%s", *d.Size))
|
||||
}
|
||||
|
||||
values = append(values, d.EncodeOptions())
|
||||
|
||||
v.Add(key, strings.Join(values, ","))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeValues converts a CustomStorageDevices array to multiple URL values.
|
||||
func (d CustomStorageDevices) EncodeValues(_ string, v *url.Values) error {
|
||||
for s, d := range d {
|
||||
if d.Enabled {
|
||||
if err := d.EncodeValues(s, v); err != nil {
|
||||
return fmt.Errorf("error encoding storage device %s: %w", s, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeValues converts a CustomTPMState struct to a URL vlaue.
|
||||
func (r CustomTPMState) EncodeValues(key string, v *url.Values) error {
|
||||
values := []string{
|
||||
|
@ -79,6 +79,15 @@ func (r *CustomBool) FromValue(tfValue types.Bool) {
|
||||
*r = CustomBool(tfValue.ValueBool())
|
||||
}
|
||||
|
||||
// Copy returns a copy of the boolean.
|
||||
func (r *CustomBool) Copy() *CustomBool {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return BoolPtr(bool(*r))
|
||||
}
|
||||
|
||||
// MarshalJSON converts a boolean to a JSON value.
|
||||
func (r *CustomCommaSeparatedList) MarshalJSON() ([]byte, error) {
|
||||
s := strings.Join(*r, ",")
|
||||
|
@ -67,6 +67,14 @@ func (r *DiskSize) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *DiskSize) Copy() *DiskSize {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &(*r)
|
||||
}
|
||||
|
||||
// ParseDiskSize parses a disk size string into a number of bytes.
|
||||
func ParseDiskSize(size string) (DiskSize, error) {
|
||||
matches := sizeRegex.FindStringSubmatch(size)
|
||||
|
@ -11,8 +11,30 @@ func StrPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
// IntPtr returns a pointer to an int.
|
||||
func IntPtr(i int) *int {
|
||||
return &i
|
||||
}
|
||||
|
||||
// BoolPtr returns a pointer to a bool.
|
||||
func BoolPtr(s bool) *CustomBool {
|
||||
customBool := CustomBool(s)
|
||||
return &customBool
|
||||
}
|
||||
|
||||
// CopyString copies content of a string pointer.
|
||||
func CopyString(s *string) *string {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return StrPtr(*s)
|
||||
}
|
||||
|
||||
func CopyInt(i *int) *int {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return IntPtr(*i)
|
||||
}
|
||||
|
@ -205,6 +205,7 @@ func createDisks(
|
||||
ctx context.Context, vmConfig *vms.GetResponseData, d *schema.ResourceData, vmAPI *vms.Client,
|
||||
) (vms.CustomStorageDevices, error) {
|
||||
// this is what VM has at the moment: map of interface name (virtio1) -> disk object
|
||||
// the disks have already been cloned, they have all original properties from the template
|
||||
currentDisks := populateFileIDs(mapStorageDevices(vmConfig), d)
|
||||
|
||||
// map of interface name (virtio1) -> disk object
|
||||
@ -277,16 +278,16 @@ func moveDiskIfRequired(
|
||||
) error {
|
||||
needToMove := false
|
||||
|
||||
if *planDisk.ID != "" {
|
||||
if *planDisk.DatastoreID != "" {
|
||||
fileIDParts := strings.Split(currentDisk.FileVolume, ":")
|
||||
needToMove = *planDisk.ID != fileIDParts[0]
|
||||
needToMove = *planDisk.DatastoreID != fileIDParts[0]
|
||||
}
|
||||
|
||||
if needToMove {
|
||||
diskMoveBody := &vms.MoveDiskRequestBody{
|
||||
DeleteOriginalDisk: types.CustomBool(true).Pointer(),
|
||||
Disk: *planDisk.Interface,
|
||||
TargetStorage: *planDisk.ID,
|
||||
TargetStorage: *planDisk.DatastoreID,
|
||||
}
|
||||
|
||||
err := vmAPI.MoveVMDisk(ctx, diskMoveBody, timeoutSec)
|
||||
@ -375,7 +376,7 @@ func vmImportCustomDisks(ctx context.Context, d *schema.ResourceData, m interfac
|
||||
ssh.TrySudo,
|
||||
fmt.Sprintf(`file_id="%s"`, *d.FileID),
|
||||
fmt.Sprintf(`file_format="%s"`, *d.Format),
|
||||
fmt.Sprintf(`datastore_id_target="%s"`, *d.ID),
|
||||
fmt.Sprintf(`datastore_id_target="%s"`, *d.DatastoreID),
|
||||
fmt.Sprintf(`disk_options="%s"`, diskOptions),
|
||||
fmt.Sprintf(`disk_size="%d"`, d.Size.InGigabytes()),
|
||||
fmt.Sprintf(`disk_interface="%s"`, *d.Interface),
|
||||
@ -476,10 +477,11 @@ func getDiskDeviceObjects1(d *schema.ResourceData, disks []interface{}) (vms.Cus
|
||||
diskDevice.FileVolume = pathInDatastore
|
||||
}
|
||||
} else {
|
||||
// a new disk, not yet allocated
|
||||
diskDevice.FileVolume = fmt.Sprintf("%s:%d", datastoreID, size)
|
||||
}
|
||||
|
||||
diskDevice.ID = &datastoreID
|
||||
diskDevice.DatastoreID = &datastoreID
|
||||
diskDevice.Interface = &diskInterface
|
||||
diskDevice.Format = &fileFormat
|
||||
diskDevice.FileID = &fileID
|
||||
|
@ -3,13 +3,16 @@ package vm
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
)
|
||||
|
||||
func TestMapStorageDevices(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
devices := &vms.GetResponseData{
|
||||
VirtualIODevice0: &vms.CustomStorageDevice{
|
||||
Interface: types.StrPtr("virtio0"),
|
||||
@ -37,6 +40,8 @@ func TestMapStorageDevices(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPopulateFileID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
devicesMap := map[string]*vms.CustomStorageDevice{
|
||||
"virtio0": {},
|
||||
"virtio1": {},
|
||||
|
@ -182,6 +182,7 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
|
||||
}
|
||||
|
||||
vmID = *vmIDNew
|
||||
|
||||
err = d.Set(mkVMID, vmID)
|
||||
if err != nil {
|
||||
return diag.FromErr(err)
|
||||
@ -304,6 +305,8 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
|
||||
return diag.FromErr(e)
|
||||
}
|
||||
|
||||
//// UPDATE AFTER CLONE, can we just call update?
|
||||
|
||||
// Now that the virtual machine has been cloned, we need to perform some modifications.
|
||||
acpi := types.CustomBool(d.Get(mkACPI).(bool))
|
||||
audioDevices := vmGetAudioDeviceList(d)
|
||||
@ -1378,11 +1381,11 @@ func vmGetEfiDiskAsStorageDevice(d *schema.ResourceData, disk []interface{}) (*v
|
||||
diskInterface := fmt.Sprint(baseDiskInterface, id)
|
||||
|
||||
storageDevice = &vms.CustomStorageDevice{
|
||||
Enabled: true,
|
||||
FileVolume: efiDisk.FileVolume,
|
||||
Format: efiDisk.Format,
|
||||
Interface: &diskInterface,
|
||||
ID: &id,
|
||||
Enabled: true,
|
||||
FileVolume: efiDisk.FileVolume,
|
||||
Format: efiDisk.Format,
|
||||
Interface: &diskInterface,
|
||||
DatastoreID: &id,
|
||||
}
|
||||
|
||||
if efiDisk.Type != nil {
|
||||
@ -1436,10 +1439,10 @@ func vmGetTPMStateAsStorageDevice(d *schema.ResourceData, disk []interface{}) *v
|
||||
diskInterface := fmt.Sprint(baseDiskInterface, id)
|
||||
|
||||
storageDevice = &vms.CustomStorageDevice{
|
||||
Enabled: true,
|
||||
FileVolume: tpmState.FileVolume,
|
||||
Interface: &diskInterface,
|
||||
ID: &id,
|
||||
Enabled: true,
|
||||
FileVolume: tpmState.FileVolume,
|
||||
Interface: &diskInterface,
|
||||
DatastoreID: &id,
|
||||
}
|
||||
}
|
||||
|
||||
@ -3819,18 +3822,12 @@ func vmUpdateDiskLocationAndSize(
|
||||
if d.HasChange(mkDisk) {
|
||||
diskOld, diskNew := d.GetChange(mkDisk)
|
||||
|
||||
diskOldEntries, err := getDiskDeviceObjects1(
|
||||
d,
|
||||
diskOld.([]interface{}),
|
||||
)
|
||||
diskOldEntries, err := getDiskDeviceObjects1(d, diskOld.([]interface{}))
|
||||
if err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
diskNewEntries, err := getDiskDeviceObjects1(
|
||||
d,
|
||||
diskNew.([]interface{}),
|
||||
)
|
||||
diskNewEntries, err := getDiskDeviceObjects1(d, diskNew.([]interface{}))
|
||||
if err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
@ -3902,7 +3899,7 @@ func vmUpdateDiskLocationAndSize(
|
||||
)
|
||||
}
|
||||
|
||||
if *oldDisk.ID != *diskNewEntries[oldKey].ID {
|
||||
if *oldDisk.DatastoreID != *diskNewEntries[oldKey].DatastoreID {
|
||||
if oldDisk.IsOwnedBy(vmID) {
|
||||
deleteOriginalDisk := types.CustomBool(true)
|
||||
|
||||
@ -3911,7 +3908,7 @@ func vmUpdateDiskLocationAndSize(
|
||||
&vms.MoveDiskRequestBody{
|
||||
DeleteOriginalDisk: &deleteOriginalDisk,
|
||||
Disk: *oldDisk.Interface,
|
||||
TargetStorage: *diskNewEntries[oldKey].ID,
|
||||
TargetStorage: *diskNewEntries[oldKey].DatastoreID,
|
||||
},
|
||||
)
|
||||
|
||||
@ -3920,9 +3917,9 @@ func vmUpdateDiskLocationAndSize(
|
||||
} else {
|
||||
return diag.Errorf(
|
||||
"Cannot move %s:%s to datastore %s in VM %d configuration, it is not owned by this VM!",
|
||||
*oldDisk.ID,
|
||||
*oldDisk.DatastoreID,
|
||||
*oldDisk.PathInDatastore(),
|
||||
*diskNewEntries[oldKey].ID,
|
||||
*diskNewEntries[oldKey].DatastoreID,
|
||||
vmID,
|
||||
)
|
||||
}
|
||||
@ -3940,7 +3937,7 @@ func vmUpdateDiskLocationAndSize(
|
||||
} else {
|
||||
return diag.Errorf(
|
||||
"Cannot resize %s:%s in VM %d configuration, it is not owned by this VM!",
|
||||
*oldDisk.ID,
|
||||
*oldDisk.DatastoreID,
|
||||
*oldDisk.PathInDatastore(),
|
||||
vmID,
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user