mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-06-30 02:31:10 +00:00
* feat: add ACL resource Signed-off-by: hrmny <8845940+ForsakenHarmony@users.noreply.github.com> * chore: move code under /access, cleanup acc tests Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> --------- Signed-off-by: hrmny <8845940+ForsakenHarmony@users.noreply.github.com> Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Co-authored-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
246 lines
7.7 KiB
Go
246 lines
7.7 KiB
Go
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"`
|
|
Backup *types.CustomBool `json:"backup,omitempty" url:"backup,omitempty,int"`
|
|
BurstableReadSpeedMbps *int `json:"mbps_rd_max,omitempty" url:"mbps_rd_max,omitempty"`
|
|
BurstableWriteSpeedMbps *int `json:"mbps_wr_max,omitempty" url:"mbps_wr_max,omitempty"`
|
|
Cache *string `json:"cache,omitempty" url:"cache,omitempty"`
|
|
Discard *string `json:"discard,omitempty" url:"discard,omitempty"`
|
|
FileVolume string `json:"file" url:"file"`
|
|
Format *string `json:"format,omitempty" url:"format,omitempty"`
|
|
IopsRead *int `json:"iops_rd,omitempty" url:"iops_rd,omitempty"`
|
|
IopsWrite *int `json:"iops_wr,omitempty" url:"iops_wr,omitempty"`
|
|
IOThread *types.CustomBool `json:"iothread,omitempty" url:"iothread,omitempty,int"`
|
|
MaxIopsRead *int `json:"iops_rd_max,omitempty" url:"iops_rd_max,omitempty"`
|
|
MaxIopsWrite *int `json:"iops_wr_max,omitempty" url:"iops_wr_max,omitempty"`
|
|
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"`
|
|
Replicate *types.CustomBool `json:"replicate,omitempty" url:"replicate,omitempty,int"`
|
|
Size *types.DiskSize `json:"size,omitempty" url:"size,omitempty"`
|
|
SSD *types.CustomBool `json:"ssd,omitempty" url:"ssd,omitempty,int"`
|
|
DatastoreID *string `json:"-" url:"-"`
|
|
Enabled bool `json:"-" url:"-"`
|
|
FileID *string `json:"-" url:"-"`
|
|
Interface *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.Backup != nil {
|
|
if *d.Backup {
|
|
values = append(values, "backup=1")
|
|
} else {
|
|
values = append(values, "backup=0")
|
|
}
|
|
}
|
|
|
|
if d.IopsRead != nil {
|
|
values = append(values, fmt.Sprintf("iops_rd=%d", *d.IopsRead))
|
|
}
|
|
|
|
if d.IopsWrite != nil {
|
|
values = append(values, fmt.Sprintf("iops_wr=%d", *d.IopsWrite))
|
|
}
|
|
|
|
if d.MaxIopsRead != nil {
|
|
values = append(values, fmt.Sprintf("iops_rd_max=%d", *d.MaxIopsRead))
|
|
}
|
|
|
|
if d.MaxIopsWrite != nil {
|
|
values = append(values, fmt.Sprintf("iops_wr_max=%d", *d.MaxIopsWrite))
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
if d.Replicate != nil {
|
|
if *d.Replicate {
|
|
values = append(values, "replicate=1")
|
|
} else {
|
|
values = append(values, "replicate=0")
|
|
}
|
|
}
|
|
|
|
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=%d", *d.Size))
|
|
}
|
|
|
|
values = append(values, d.EncodeOptions())
|
|
|
|
v.Add(key, strings.Join(values, ","))
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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
|
|
}
|