diff --git a/docs/resources/vm_qemu.md b/docs/resources/vm_qemu.md index 7d31f054..29cca7a9 100644 --- a/docs/resources/vm_qemu.md +++ b/docs/resources/vm_qemu.md @@ -163,6 +163,7 @@ details. | `rate` | `int` | `0` | Network device rate limit in mbps (megabytes per second) as floating point number. Set to `0` to disable rate limiting. | | `queues` | `int` | `1` | Number of packet queues to be used on the device. Requires `virtio` model to have an effect. | | `link_down` | `bool` | `false` | Whether this interface should be disconnected (like pulling the plug). | + ### Disks Block The `disks` block is used to configure the disk devices. It may be specified once. There are four types of disk `ide`,`sata`,`scsi` and `virtio`. Configuration for these sub types can be found in their respective chapters: @@ -367,7 +368,7 @@ See the [docs about disks](https://pve.proxmox.com/pve-docs/chapter-qm.html#qm_h | `readonly` | `bool` | `false` | `scsi`, `virtio` | Whether the drive should be readonly. | | `replicate` | `bool` | `false` | `all` | Whether the drive should considered for replication jobs. | | `serial` | `str` | | `all` | The serial number of the disk. | -| `size` | `int` | | `all` | **Required** The size of the created disk in Gigabytes. | +| `size` | `string`| | `all` | **Required** The size of the created disk. Accepts `K` for kibibytes, `M` for mebibytes, `G` for gibibytes, `T` for tibibytes. When only a number is provided gibibytes is assumed.| | `storage` | `str` | | `all` | **Required** The name of the storage pool on which to store the disk. | ### Disks.x.Passthrough Block @@ -396,7 +397,7 @@ See the [docs about disks](https://pve.proxmox.com/pve-docs/chapter-qm.html#qm_h | `readonly` | `bool` | `false` | `scsi`, `virtio` | Whether the drive should be readonly. | | `replicate` | `bool` | `false` | `all` | Whether the drive should considered for replication jobs. | | `serial` | `str` | | `all` | The serial number of the disk. | -| `size` | `int` | | `all` | **Computed** Size of the disk. | +| `size` | `string`| | `all` | **Computed** Size of the disk, `K` for kibibytes, `M` for mebibytes, `G` for gibibytes, `T` for tibibytes.| ### EFI Disk Block diff --git a/go.mod b/go.mod index 8903235c..72853afa 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Telmate/terraform-provider-proxmox/v2 go 1.20 require ( - github.com/Telmate/proxmox-api-go v0.0.0-20240131155550-58f6bb52981b + github.com/Telmate/proxmox-api-go v0.0.0-20240205124300-ede76bab601e github.com/google/uuid v1.6.0 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-sdk/v2 v2.32.0 diff --git a/go.sum b/go.sum index 92b3285a..fe8bb94f 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/Telmate/proxmox-api-go v0.0.0-20240131155550-58f6bb52981b h1:PdwcMpUQ1tLfUhW2f3QKi4ZxObZSZ2ShBfM1vjJ8+Zw= -github.com/Telmate/proxmox-api-go v0.0.0-20240131155550-58f6bb52981b/go.mod h1:xOwyTd8uC2IiYfmjwCVU2fTTVToFCm9yxJzn4cd7rPw= +github.com/Telmate/proxmox-api-go v0.0.0-20240205124300-ede76bab601e h1:ojWFe4idcU9W/0GzBjoZQBaTp0ugRfG4XC7mfWHV0Xk= +github.com/Telmate/proxmox-api-go v0.0.0-20240205124300-ede76bab601e/go.mod h1:xOwyTd8uC2IiYfmjwCVU2fTTVToFCm9yxJzn4cd7rPw= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= diff --git a/proxmox/converters.go b/proxmox/converters.go new file mode 100644 index 00000000..27666ff0 --- /dev/null +++ b/proxmox/converters.go @@ -0,0 +1,49 @@ +package proxmox + +import ( + "strconv" +) + +const ( + kibibyte int64 = 1 + mebibyte int64 = 1024 + gibibyte int64 = 1048576 + tebibyte int64 = 1073741824 +) + +func convert_KibibytesToString(kibibytes int64) string { + if kibibytes%tebibyte == 0 { + return strconv.FormatInt(kibibytes/tebibyte, 10) + "T" + } + if kibibytes%gibibyte == 0 { + return strconv.FormatInt(kibibytes/gibibyte, 10) + "G" + } + if kibibytes%mebibyte == 0 { + return strconv.FormatInt(kibibytes/mebibyte, 10) + "M" + } + return strconv.FormatInt(kibibytes, 10) + "K" +} + +// Relies on the input being validated +func convert_SizeStringToKibibytes_Unsafe(size string) int { + if len(size) > 1 { + switch size[len(size)-1:] { + case "T": + return parseSize_Unsafe(size, tebibyte) + case "G": + return parseSize_Unsafe(size, gibibyte) + case "M": + return parseSize_Unsafe(size, mebibyte) + case "K": + return parseSize_Unsafe(size, kibibyte) + } + } + tmpSize, _ := strconv.ParseInt(size, 10, 0) + return int(tmpSize * gibibyte) +} + +// Relies on the input being validated +func parseSize_Unsafe(size string, multiplier int64) int { + tmpSize, _ := strconv.ParseInt(size[:len(size)-1], 10, 0) + return int(tmpSize * multiplier) +} diff --git a/proxmox/resource_vm_qemu.go b/proxmox/resource_vm_qemu.go index 90f92793..bb241460 100755 --- a/proxmox/resource_vm_qemu.go +++ b/proxmox/resource_vm_qemu.go @@ -2121,7 +2121,7 @@ func mapFromStruct_QemuIdeStorage(config *pxapi.QemuIdeStorage, setting string) "linked_disk_id": mapFromStruct_LinkedCloneId(config.Disk.LinkedDiskId), "replicate": config.Disk.Replicate, "serial": string(config.Disk.Serial), - "size": int(config.Disk.Size), + "size": convert_KibibytesToString(int64(config.Disk.SizeInKibibytes)), "storage": string(config.Disk.Storage), } mapFormStruct_QemuDiskBandwidth(mapParams, config.Disk.Bandwidth) @@ -2141,7 +2141,7 @@ func mapFromStruct_QemuIdeStorage(config *pxapi.QemuIdeStorage, setting string) "file": config.Passthrough.File, "replicate": config.Passthrough.Replicate, "serial": string(config.Passthrough.Serial), - "size": int(config.Passthrough.Size), + "size": convert_KibibytesToString(int64(config.Passthrough.SizeInKibibytes)), } mapFormStruct_QemuDiskBandwidth(mapParams, config.Passthrough.Bandwidth) return []interface{}{ @@ -2185,7 +2185,7 @@ func mapFromStruct_QemuSataStorage(config *pxapi.QemuSataStorage, setting string "linked_disk_id": mapFromStruct_LinkedCloneId(config.Disk.LinkedDiskId), "replicate": config.Disk.Replicate, "serial": string(config.Disk.Serial), - "size": int(config.Disk.Size), + "size": convert_KibibytesToString(int64(config.Disk.SizeInKibibytes)), "storage": string(config.Disk.Storage), } mapFormStruct_QemuDiskBandwidth(mapParams, config.Disk.Bandwidth) @@ -2205,7 +2205,7 @@ func mapFromStruct_QemuSataStorage(config *pxapi.QemuSataStorage, setting string "file": config.Passthrough.File, "replicate": config.Passthrough.Replicate, "serial": string(config.Passthrough.Serial), - "size": int(config.Passthrough.Size), + "size": convert_KibibytesToString(int64(config.Disk.SizeInKibibytes)), } mapFormStruct_QemuDiskBandwidth(mapParams, config.Passthrough.Bandwidth) return []interface{}{ @@ -2276,7 +2276,7 @@ func mapFromStruct_QemuScsiStorage(config *pxapi.QemuScsiStorage, setting string "readonly": config.Disk.ReadOnly, "replicate": config.Disk.Replicate, "serial": string(config.Disk.Serial), - "size": int(config.Disk.Size), + "size": convert_KibibytesToString(int64(config.Disk.SizeInKibibytes)), "storage": string(config.Disk.Storage), } mapFormStruct_QemuDiskBandwidth(mapParams, config.Disk.Bandwidth) @@ -2298,7 +2298,7 @@ func mapFromStruct_QemuScsiStorage(config *pxapi.QemuScsiStorage, setting string "readonly": config.Passthrough.ReadOnly, "replicate": config.Passthrough.Replicate, "serial": string(config.Passthrough.Serial), - "size": int(config.Passthrough.Size), + "size": convert_KibibytesToString(int64(config.Passthrough.SizeInKibibytes)), } mapFormStruct_QemuDiskBandwidth(mapParams, config.Passthrough.Bandwidth) return []interface{}{ @@ -2354,7 +2354,7 @@ func mapFromStruct_QemuVirtIOStorage(config *pxapi.QemuVirtIOStorage, setting st "readonly": config.Disk.ReadOnly, "replicate": config.Disk.Replicate, "serial": string(config.Disk.Serial), - "size": int(config.Disk.Size), + "size": convert_KibibytesToString(int64(config.Disk.SizeInKibibytes)), "storage": string(config.Disk.Storage), } mapFormStruct_QemuDiskBandwidth(mapParams, config.Disk.Bandwidth) @@ -2375,7 +2375,7 @@ func mapFromStruct_QemuVirtIOStorage(config *pxapi.QemuVirtIOStorage, setting st "readonly": config.Passthrough.ReadOnly, "replicate": config.Passthrough.Replicate, "serial": string(config.Passthrough.Serial), - "size": int(config.Passthrough.Size), + "size": convert_KibibytesToString(int64(config.Passthrough.SizeInKibibytes)), } mapFormStruct_QemuDiskBandwidth(mapParams, config.Passthrough.Bandwidth) return []interface{}{ @@ -2465,14 +2465,14 @@ func mapToStruct_QemuIdeStorage(ide *pxapi.QemuIdeStorage, key string, schema ma if ok && len(tmpDisk) == 1 && tmpDisk[0] != nil { disk := tmpDisk[0].(map[string]interface{}) ide.Disk = &pxapi.QemuIdeDisk{ - Backup: disk["backup"].(bool), - Bandwidth: mapToStruct_QemuDiskBandwidth(disk), - Discard: disk["discard"].(bool), - EmulateSSD: disk["emulatessd"].(bool), - Format: pxapi.QemuDiskFormat(disk["format"].(string)), - Replicate: disk["replicate"].(bool), - Size: uint(disk["size"].(int)), - Storage: disk["storage"].(string), + Backup: disk["backup"].(bool), + Bandwidth: mapToStruct_QemuDiskBandwidth(disk), + Discard: disk["discard"].(bool), + EmulateSSD: disk["emulatessd"].(bool), + Format: pxapi.QemuDiskFormat(disk["format"].(string)), + Replicate: disk["replicate"].(bool), + SizeInKibibytes: pxapi.QemuDiskSize(convert_SizeStringToKibibytes_Unsafe(disk["size"].(string))), + Storage: disk["storage"].(string), } if asyncIO, ok := disk["asyncio"].(string); ok { ide.Disk.AsyncIO = pxapi.QemuDiskAsyncIO(asyncIO) @@ -2534,14 +2534,14 @@ func mapToStruct_QemuSataStorage(sata *pxapi.QemuSataStorage, key string, schema if ok && len(tmpDisk) == 1 && tmpDisk[0] != nil { disk := tmpDisk[0].(map[string]interface{}) sata.Disk = &pxapi.QemuSataDisk{ - Backup: disk["backup"].(bool), - Bandwidth: mapToStruct_QemuDiskBandwidth(disk), - Discard: disk["discard"].(bool), - EmulateSSD: disk["emulatessd"].(bool), - Format: pxapi.QemuDiskFormat(disk["format"].(string)), - Replicate: disk["replicate"].(bool), - Size: uint(disk["size"].(int)), - Storage: disk["storage"].(string), + Backup: disk["backup"].(bool), + Bandwidth: mapToStruct_QemuDiskBandwidth(disk), + Discard: disk["discard"].(bool), + EmulateSSD: disk["emulatessd"].(bool), + Format: pxapi.QemuDiskFormat(disk["format"].(string)), + Replicate: disk["replicate"].(bool), + SizeInKibibytes: pxapi.QemuDiskSize(convert_SizeStringToKibibytes_Unsafe(disk["size"].(string))), + Storage: disk["storage"].(string), } if asyncIO, ok := disk["asyncio"].(string); ok { sata.Disk.AsyncIO = pxapi.QemuDiskAsyncIO(asyncIO) @@ -2628,16 +2628,16 @@ func mapToStruct_QemuScsiStorage(scsi *pxapi.QemuScsiStorage, key string, schema if ok && len(tmpDisk) == 1 && tmpDisk[0] != nil { disk := tmpDisk[0].(map[string]interface{}) scsi.Disk = &pxapi.QemuScsiDisk{ - Backup: disk["backup"].(bool), - Bandwidth: mapToStruct_QemuDiskBandwidth(disk), - Discard: disk["discard"].(bool), - EmulateSSD: disk["emulatessd"].(bool), - Format: pxapi.QemuDiskFormat(disk["format"].(string)), - IOThread: disk["iothread"].(bool), - ReadOnly: disk["readonly"].(bool), - Replicate: disk["replicate"].(bool), - Size: uint(disk["size"].(int)), - Storage: disk["storage"].(string), + Backup: disk["backup"].(bool), + Bandwidth: mapToStruct_QemuDiskBandwidth(disk), + Discard: disk["discard"].(bool), + EmulateSSD: disk["emulatessd"].(bool), + Format: pxapi.QemuDiskFormat(disk["format"].(string)), + IOThread: disk["iothread"].(bool), + ReadOnly: disk["readonly"].(bool), + Replicate: disk["replicate"].(bool), + SizeInKibibytes: pxapi.QemuDiskSize(convert_SizeStringToKibibytes_Unsafe(disk["size"].(string))), + Storage: disk["storage"].(string), } if asyncIO, ok := disk["asyncio"].(string); ok { scsi.Disk.AsyncIO = pxapi.QemuDiskAsyncIO(asyncIO) @@ -2792,15 +2792,15 @@ func mapToStruct_VirtIOStorage(virtio *pxapi.QemuVirtIOStorage, key string, sche if ok && len(tmpDisk) == 1 && tmpDisk[0] != nil { disk := tmpDisk[0].(map[string]interface{}) virtio.Disk = &pxapi.QemuVirtIODisk{ - Backup: disk["backup"].(bool), - Bandwidth: mapToStruct_QemuDiskBandwidth(disk), - Discard: disk["discard"].(bool), - Format: pxapi.QemuDiskFormat(disk["format"].(string)), - IOThread: disk["iothread"].(bool), - ReadOnly: disk["readonly"].(bool), - Replicate: disk["replicate"].(bool), - Size: uint(disk["size"].(int)), - Storage: disk["storage"].(string), + Backup: disk["backup"].(bool), + Bandwidth: mapToStruct_QemuDiskBandwidth(disk), + Discard: disk["discard"].(bool), + Format: pxapi.QemuDiskFormat(disk["format"].(string)), + IOThread: disk["iothread"].(bool), + ReadOnly: disk["readonly"].(bool), + Replicate: disk["replicate"].(bool), + SizeInKibibytes: pxapi.QemuDiskSize(convert_SizeStringToKibibytes_Unsafe(disk["size"].(string))), + Storage: disk["storage"].(string), } if asyncIO, ok := disk["asyncio"].(string); ok { virtio.Disk.AsyncIO = pxapi.QemuDiskAsyncIO(asyncIO) @@ -3279,9 +3279,21 @@ func schema_DiskSerial() *schema.Schema { func schema_DiskSize() *schema.Schema { return &schema.Schema{ - Type: schema.TypeInt, - Required: true, - ValidateDiagFunc: uintValidator(), + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: func(i interface{}, k cty.Path) diag.Diagnostics { + v, ok := i.(string) + if !ok { + return diag.Errorf(errorString, k) + } + if !regexp.MustCompile(`^[123456789]\d*[KMGT]?$`).MatchString(v) { + return diag.Errorf("%s must match the following regex ^[123456789]\\d*[KMGT]?$", k) + } + return nil + }, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return convert_SizeStringToKibibytes_Unsafe(old) == convert_SizeStringToKibibytes_Unsafe(new) + }, } } @@ -3325,7 +3337,7 @@ func schema_PassthroughFile() *schema.Schema { func schema_PassthroughSize() *schema.Schema { return &schema.Schema{ - Type: schema.TypeInt, + Type: schema.TypeString, Computed: true, } } diff --git a/proxmox/validators.go b/proxmox/validators.go index 3e6ed5b4..b51280d3 100644 --- a/proxmox/validators.go +++ b/proxmox/validators.go @@ -93,13 +93,3 @@ func VMStateValidator() schema.SchemaValidateDiagFunc { "stopped", }, false)) } - -func uintValidator() schema.SchemaValidateDiagFunc { - return func(i interface{}, k cty.Path) diag.Diagnostics { - v, ok := i.(int) - if !ok || v < 0 { - return diag.Errorf(errorUint, k) - } - return nil - } -}