0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-30 02:31:10 +00:00

chore(api): refactor nodes/vms/vms_types.go: split into multiple files (#1368)

Split all `Custom*` structs and marshaling code into separate files from `vms_types.go`

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
Pavel Boldyrev 2024-06-09 00:11:16 -04:00 committed by GitHub
parent 5d4193b6be
commit 580381f892
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 2224 additions and 1738 deletions

View File

@ -0,0 +1,87 @@
/*
* 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"
"net/url"
"strings"
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
)
// CustomAgent handles QEMU agent parameters.
type CustomAgent struct {
Enabled *types.CustomBool `json:"enabled,omitempty" url:"enabled,int"`
TrimClonedDisks *types.CustomBool `json:"fstrim_cloned_disks" url:"fstrim_cloned_disks,int"`
Type *string `json:"type" url:"type"`
}
// EncodeValues converts a CustomAgent struct to a URL value.
func (r *CustomAgent) EncodeValues(key string, v *url.Values) error {
var values []string
if r.Enabled != nil {
if *r.Enabled {
values = append(values, "enabled=1")
} else {
values = append(values, "enabled=0")
}
}
if r.TrimClonedDisks != nil {
if *r.TrimClonedDisks {
values = append(values, "fstrim_cloned_disks=1")
} else {
values = append(values, "fstrim_cloned_disks=0")
}
}
if r.Type != nil {
values = append(values, fmt.Sprintf("type=%s", *r.Type))
}
if len(values) > 0 {
v.Add(key, strings.Join(values, ","))
}
return nil
}
// UnmarshalJSON converts a CustomAgent string to an object.
func (r *CustomAgent) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("error unmarshalling CustomAgent: %w", err)
}
pairs := strings.Split(s, ",")
for _, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 1 {
enabled := types.CustomBool(v[0] == "1")
r.Enabled = &enabled
} else if len(v) == 2 {
switch v[0] {
case "enabled":
enabled := types.CustomBool(v[1] == "1")
r.Enabled = &enabled
case "fstrim_cloned_disks":
fstrim := types.CustomBool(v[1] == "1")
r.TrimClonedDisks = &fstrim
case "type":
r.Type = &v[1]
}
}
}
return nil
}

View File

@ -0,0 +1,76 @@
/*
* 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"
"net/url"
"strings"
)
// CustomAudioDevice handles QEMU audio parameters.
type CustomAudioDevice struct {
Device string `json:"device" url:"device"`
Driver *string `json:"driver" url:"driver"`
Enabled bool `json:"-" url:"-"`
}
// CustomAudioDevices handles QEMU audio device parameters.
type CustomAudioDevices []CustomAudioDevice
// EncodeValues converts a CustomAudioDevice struct to a URL value.
func (r *CustomAudioDevice) EncodeValues(key string, v *url.Values) error {
values := []string{fmt.Sprintf("device=%s", r.Device)}
if r.Driver != nil {
values = append(values, fmt.Sprintf("driver=%s", *r.Driver))
}
v.Add(key, strings.Join(values, ","))
return nil
}
// UnmarshalJSON converts a CustomAudioDevice string to an object.
func (r *CustomAudioDevice) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("error unmarshalling CustomAudioDevice: %w", err)
}
pairs := strings.Split(s, ",")
for _, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 2 {
switch v[0] {
case "device":
r.Device = v[1]
case "driver":
r.Driver = &v[1]
}
}
}
return nil
}
// EncodeValues converts a CustomAudioDevices array to multiple URL values.
func (r CustomAudioDevices) EncodeValues(key string, v *url.Values) error {
for i, d := range r {
if d.Enabled {
if err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v); err != nil {
return fmt.Errorf("unable to encode audio device %d: %w", i, err)
}
}
}
return nil
}

View File

@ -0,0 +1,52 @@
/*
* 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"
"net/url"
"strings"
)
// CustomBoot handles QEMU boot parameters.
type CustomBoot struct {
Order *[]string `json:"order,omitempty" url:"order,omitempty,semicolon"`
}
// EncodeValues converts a CustomBoot struct to multiple URL values.
func (r *CustomBoot) EncodeValues(key string, v *url.Values) error {
if r.Order != nil && len(*r.Order) > 0 {
v.Add(key, fmt.Sprintf("order=%s", strings.Join(*r.Order, ";")))
}
return nil
}
// UnmarshalJSON converts a CustomBoot string to an object.
func (r *CustomBoot) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("error unmarshalling CustomBoot: %w", err)
}
pairs := strings.Split(s, ",")
for _, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 2 {
if v[0] == "order" {
o := strings.Split(strings.TrimSpace(v[1]), ";")
r.Order = &o
}
}
}
return nil
}

View File

@ -0,0 +1,210 @@
/*
* 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"
"net/url"
"strings"
)
// CustomCloudInitConfig handles QEMU cloud-init parameters.
type CustomCloudInitConfig struct {
Files *CustomCloudInitFiles `json:"cicustom,omitempty" url:"cicustom,omitempty"`
IPConfig []CustomCloudInitIPConfig `json:"ipconfig,omitempty" url:"ipconfig,omitempty,numbered"`
Nameserver *string `json:"nameserver,omitempty" url:"nameserver,omitempty"`
Password *string `json:"cipassword,omitempty" url:"cipassword,omitempty"`
SearchDomain *string `json:"searchdomain,omitempty" url:"searchdomain,omitempty"`
SSHKeys *CustomCloudInitSSHKeys `json:"sshkeys,omitempty" url:"sshkeys,omitempty"`
Type *string `json:"citype,omitempty" url:"citype,omitempty"`
// Can't be reliably set, it is TRUE by default in PVE
// Upgrade *types.CustomBool `json:"ciupgrade,omitempty" url:"ciupgrade,omitempty,int"`
Username *string `json:"ciuser,omitempty" url:"ciuser,omitempty"`
}
// CustomCloudInitFiles handles QEMU cloud-init custom files parameters.
type CustomCloudInitFiles struct {
MetaVolume *string `json:"meta,omitempty" url:"meta,omitempty"`
NetworkVolume *string `json:"network,omitempty" url:"network,omitempty"`
UserVolume *string `json:"user,omitempty" url:"user,omitempty"`
VendorVolume *string `json:"vendor,omitempty" url:"vendor,omitempty"`
}
// CustomCloudInitIPConfig handles QEMU cloud-init IP configuration parameters.
type CustomCloudInitIPConfig struct {
GatewayIPv4 *string `json:"gw,omitempty" url:"gw,omitempty"`
GatewayIPv6 *string `json:"gw6,omitempty" url:"gw6,omitempty"`
IPv4 *string `json:"ip,omitempty" url:"ip,omitempty"`
IPv6 *string `json:"ip6,omitempty" url:"ip6,omitempty"`
}
// CustomCloudInitSSHKeys handles QEMU cloud-init SSH keys parameters.
type CustomCloudInitSSHKeys []string
// EncodeValues converts a CustomCloudInitConfig struct to multiple URL values.
func (r CustomCloudInitConfig) EncodeValues(_ string, v *url.Values) error {
//nolint:nestif
if r.Files != nil {
var volumes []string
if r.Files.MetaVolume != nil {
volumes = append(volumes, fmt.Sprintf("meta=%s", *r.Files.MetaVolume))
}
if r.Files.NetworkVolume != nil {
volumes = append(volumes, fmt.Sprintf("network=%s", *r.Files.NetworkVolume))
}
if r.Files.UserVolume != nil {
volumes = append(volumes, fmt.Sprintf("user=%s", *r.Files.UserVolume))
}
if r.Files.VendorVolume != nil {
volumes = append(volumes, fmt.Sprintf("vendor=%s", *r.Files.VendorVolume))
}
if len(volumes) > 0 {
v.Add("cicustom", strings.Join(volumes, ","))
}
}
for i, c := range r.IPConfig {
var config []string
if c.GatewayIPv4 != nil {
config = append(config, fmt.Sprintf("gw=%s", *c.GatewayIPv4))
}
if c.GatewayIPv6 != nil {
config = append(config, fmt.Sprintf("gw6=%s", *c.GatewayIPv6))
}
if c.IPv4 != nil {
config = append(config, fmt.Sprintf("ip=%s", *c.IPv4))
}
if c.IPv6 != nil {
config = append(config, fmt.Sprintf("ip6=%s", *c.IPv6))
}
if len(config) > 0 {
v.Add(fmt.Sprintf("ipconfig%d", i), strings.Join(config, ","))
}
}
if r.Nameserver != nil {
v.Add("nameserver", *r.Nameserver)
}
if r.Password != nil {
v.Add("cipassword", *r.Password)
}
if r.SearchDomain != nil {
v.Add("searchdomain", *r.SearchDomain)
}
if r.SSHKeys != nil {
v.Add(
"sshkeys",
strings.ReplaceAll(url.QueryEscape(strings.Join(*r.SSHKeys, "\n")), "+", "%20"),
)
}
if r.Type != nil {
v.Add("citype", *r.Type)
}
if r.Username != nil {
v.Add("ciuser", *r.Username)
}
return nil
}
// UnmarshalJSON converts a CustomCloudInitFiles string to an object.
func (r *CustomCloudInitFiles) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("error unmarshalling CustomCloudInitFiles: %w", err)
}
pairs := strings.Split(s, ",")
for _, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 2 {
switch v[0] {
case "meta":
r.MetaVolume = &v[1]
case "network":
r.NetworkVolume = &v[1]
case "user":
r.UserVolume = &v[1]
case "vendor":
r.VendorVolume = &v[1]
}
}
}
return nil
}
// UnmarshalJSON converts a CustomCloudInitIPConfig string to an object.
func (r *CustomCloudInitIPConfig) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("error unmarshalling CustomCloudInitIPConfig: %w", err)
}
pairs := strings.Split(s, ",")
for _, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 2 {
switch v[0] {
case "gw":
r.GatewayIPv4 = &v[1]
case "gw6":
r.GatewayIPv6 = &v[1]
case "ip":
r.IPv4 = &v[1]
case "ip6":
r.IPv6 = &v[1]
}
}
}
return nil
}
// UnmarshalJSON converts a CustomCloudInitFiles string to an object.
func (r *CustomCloudInitSSHKeys) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("error unmarshalling CustomCloudInitSSHKeys: %w", err)
}
s, err := url.QueryUnescape(s)
if err != nil {
return fmt.Errorf("error unescaping CustomCloudInitSSHKeys: %w", err)
}
if s != "" {
*r = strings.Split(strings.TrimSpace(s), "\n")
} else {
*r = []string{}
}
return nil
}

View File

@ -0,0 +1,95 @@
/*
* 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"
"errors"
"fmt"
"net/url"
"strings"
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
)
// CustomCPUEmulation handles QEMU CPU emulation parameters.
type CustomCPUEmulation struct {
Flags *[]string `json:"flags,omitempty" url:"flags,omitempty,semicolon"`
Hidden *types.CustomBool `json:"hidden,omitempty" url:"hidden,omitempty,int"`
HVVendorID *string `json:"hv-vendor-id,omitempty" url:"hv-vendor-id,omitempty"`
Type string `json:"cputype,omitempty" url:"cputype,omitempty"`
}
// EncodeValues converts a CustomCPUEmulation struct to a URL value.
func (r *CustomCPUEmulation) EncodeValues(key string, v *url.Values) error {
values := []string{
fmt.Sprintf("cputype=%s", r.Type),
}
if r.Flags != nil && len(*r.Flags) > 0 {
values = append(values, fmt.Sprintf("flags=%s", strings.Join(*r.Flags, ";")))
}
if r.Hidden != nil {
if *r.Hidden {
values = append(values, "hidden=1")
} else {
values = append(values, "hidden=0")
}
}
if r.HVVendorID != nil {
values = append(values, fmt.Sprintf("hv-vendor-id=%s", *r.HVVendorID))
}
v.Add(key, strings.Join(values, ","))
return nil
}
// UnmarshalJSON converts a CustomCPUEmulation string to an object.
func (r *CustomCPUEmulation) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("error unmarshalling CustomCPUEmulation: %w", err)
}
if s == "" {
return errors.New("unexpected empty string")
}
pairs := strings.Split(s, ",")
for _, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 1 {
r.Type = v[0]
} else if len(v) == 2 {
switch v[0] {
case "cputype":
r.Type = v[1]
case "flags":
if v[1] != "" {
f := strings.Split(v[1], ";")
r.Flags = &f
} else {
var f []string
r.Flags = &f
}
case "hidden":
bv := types.CustomBool(v[1] == "1")
r.Hidden = &bv
case "hv-vendor-id":
r.HVVendorID = &v[1]
}
}
}
return nil
}

View File

@ -0,0 +1,87 @@
/*
* 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"
"net/url"
"strings"
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
)
// CustomEFIDisk handles QEMU EFI disk parameters.
type CustomEFIDisk struct {
FileVolume string `json:"file" url:"file"`
Format *string `json:"format,omitempty" url:"format,omitempty"`
Type *string `json:"efitype,omitempty" url:"efitype,omitempty"`
PreEnrolledKeys *types.CustomBool `json:"pre-enrolled-keys,omitempty" url:"pre-enrolled-keys,omitempty,int"`
}
// EncodeValues converts a CustomEFIDisk struct to a URL value.
func (r *CustomEFIDisk) EncodeValues(key string, v *url.Values) error {
values := []string{
fmt.Sprintf("file=%s", r.FileVolume),
}
if r.Format != nil {
values = append(values, fmt.Sprintf("format=%s", *r.Format))
}
if r.Type != nil {
values = append(values, fmt.Sprintf("efitype=%s", *r.Type))
}
if r.PreEnrolledKeys != nil {
if *r.PreEnrolledKeys {
values = append(values, "pre-enrolled-keys=1")
} else {
values = append(values, "pre-enrolled-keys=0")
}
}
v.Add(key, strings.Join(values, ","))
return nil
}
// UnmarshalJSON converts a CustomEFIDisk string to an object.
func (r *CustomEFIDisk) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("failed to unmarshal CustomEFIDisk: %w", err)
}
pairs := strings.Split(s, ",")
for i, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 1 && i == 0 {
r.FileVolume = v[0]
}
if len(v) == 2 {
switch v[0] {
case "file":
r.FileVolume = v[1]
case "format":
r.Format = &v[1]
case "efitype":
t := strings.ToLower(v[1])
r.Type = &t
case "pre-enrolled-keys":
bv := types.CustomBool(v[1] == "1")
r.PreEnrolledKeys = &bv
}
}
}
return nil
}

View File

@ -0,0 +1,191 @@
/*
* 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"
"net/url"
"strconv"
"strings"
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
)
// CustomNetworkDevice handles QEMU network device parameters.
type CustomNetworkDevice struct {
Enabled bool `json:"-" url:"-"`
Bridge *string `json:"bridge,omitempty" url:"bridge,omitempty"`
Firewall *types.CustomBool `json:"firewall,omitempty" url:"firewall,omitempty,int"`
LinkDown *types.CustomBool `json:"link_down,omitempty" url:"link_down,omitempty,int"`
MACAddress *string `json:"macaddr,omitempty" url:"macaddr,omitempty"`
MTU *int `json:"mtu,omitempty" url:"mtu,omitempty"`
Model string `json:"model" url:"model"`
Queues *int `json:"queues,omitempty" url:"queues,omitempty"`
RateLimit *float64 `json:"rate,omitempty" url:"rate,omitempty"`
Tag *int `json:"tag,omitempty" url:"tag,omitempty"`
Trunks []int `json:"trunks,omitempty" url:"trunks,omitempty"`
}
// CustomNetworkDevices handles QEMU network device parameters.
type CustomNetworkDevices []CustomNetworkDevice
// EncodeValues converts a CustomNetworkDevice struct to a URL value.
func (r *CustomNetworkDevice) EncodeValues(key string, v *url.Values) error {
values := []string{
fmt.Sprintf("model=%s", r.Model),
}
if r.Bridge != nil {
values = append(values, fmt.Sprintf("bridge=%s", *r.Bridge))
}
if r.Firewall != nil {
if *r.Firewall {
values = append(values, "firewall=1")
} else {
values = append(values, "firewall=0")
}
}
if r.LinkDown != nil {
if *r.LinkDown {
values = append(values, "link_down=1")
} else {
values = append(values, "link_down=0")
}
}
if r.MACAddress != nil {
values = append(values, fmt.Sprintf("macaddr=%s", *r.MACAddress))
}
if r.Queues != nil {
values = append(values, fmt.Sprintf("queues=%d", *r.Queues))
}
if r.RateLimit != nil {
values = append(values, fmt.Sprintf("rate=%f", *r.RateLimit))
}
if r.Tag != nil {
values = append(values, fmt.Sprintf("tag=%d", *r.Tag))
}
if r.MTU != nil {
values = append(values, fmt.Sprintf("mtu=%d", *r.MTU))
}
if len(r.Trunks) > 0 {
trunks := make([]string, len(r.Trunks))
for i, v := range r.Trunks {
trunks[i] = strconv.Itoa(v)
}
values = append(values, fmt.Sprintf("trunks=%s", strings.Join(trunks, ";")))
}
v.Add(key, strings.Join(values, ","))
return nil
}
// EncodeValues converts a CustomNetworkDevices array to multiple URL values.
func (r CustomNetworkDevices) EncodeValues(key string, v *url.Values) error {
for i, d := range r {
if d.Enabled {
if err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v); err != nil {
return fmt.Errorf("failed to encode network device %d: %w", i, err)
}
}
}
return nil
}
// UnmarshalJSON converts a CustomNetworkDevice string to an object.
func (r *CustomNetworkDevice) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("failed to unmarshal CustomNetworkDevice: %w", err)
}
pairs := strings.Split(s, ",")
for _, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
//nolint:nestif
if len(v) == 2 {
switch v[0] {
case "bridge":
r.Bridge = &v[1]
case "firewall":
bv := types.CustomBool(v[1] == "1")
r.Firewall = &bv
case "link_down":
bv := types.CustomBool(v[1] == "1")
r.LinkDown = &bv
case "macaddr":
r.MACAddress = &v[1]
case "model":
r.Model = v[1]
case "queues":
iv, err := strconv.Atoi(v[1])
if err != nil {
return fmt.Errorf("failed to parse queues: %w", err)
}
r.Queues = &iv
case "rate":
fv, err := strconv.ParseFloat(v[1], 64)
if err != nil {
return fmt.Errorf("failed to parse rate: %w", err)
}
r.RateLimit = &fv
case "mtu":
iv, err := strconv.Atoi(v[1])
if err != nil {
return fmt.Errorf("failed to parse mtu: %w", err)
}
r.MTU = &iv
case "tag":
iv, err := strconv.Atoi(v[1])
if err != nil {
return fmt.Errorf("failed to parse tag: %w", err)
}
r.Tag = &iv
case "trunks":
trunks := strings.Split(v[1], ";")
r.Trunks = make([]int, len(trunks))
for i, trunk := range trunks {
iv, err := strconv.Atoi(trunk)
if err != nil {
return fmt.Errorf("failed to parse trunk %d: %w", i, err)
}
r.Trunks[i] = iv
}
default:
r.MACAddress = &v[1]
r.Model = v[0]
}
}
}
r.Enabled = true
return nil
}

View File

@ -0,0 +1,95 @@
/*
* 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"
"net/url"
"strconv"
"strings"
)
// CustomNUMADevice handles QEMU NUMA device parameters.
type CustomNUMADevice struct {
CPUIDs []string `json:"cpus" url:"cpus,semicolon"`
HostNodeNames *[]string `json:"hostnodes,omitempty" url:"hostnodes,omitempty,semicolon"`
Memory *int `json:"memory,omitempty" url:"memory,omitempty"`
Policy *string `json:"policy,omitempty" url:"policy,omitempty"`
}
// CustomNUMADevices handles QEMU NUMA device parameters.
type CustomNUMADevices []CustomNUMADevice
// EncodeValues converts a CustomNUMADevice struct to a URL value.
func (r *CustomNUMADevice) EncodeValues(key string, v *url.Values) error {
values := []string{
fmt.Sprintf("cpus=%s", strings.Join(r.CPUIDs, ";")),
}
if r.HostNodeNames != nil {
values = append(values, fmt.Sprintf("hostnodes=%s", strings.Join(*r.HostNodeNames, ";")))
}
if r.Memory != nil {
values = append(values, fmt.Sprintf("memory=%d", *r.Memory))
}
if r.Policy != nil {
values = append(values, fmt.Sprintf("policy=%s", *r.Policy))
}
v.Add(key, strings.Join(values, ","))
return nil
}
// EncodeValues converts a CustomNUMADevices array to multiple URL values.
func (r CustomNUMADevices) EncodeValues(key string, v *url.Values) error {
for i, d := range r {
if err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v); err != nil {
return fmt.Errorf("failed to encode NUMA device %d: %w", i, err)
}
}
return nil
}
// UnmarshalJSON converts a CustomNUMADevice string to an object.
func (r *CustomNUMADevice) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("failed to unmarshal CustomNUMADevice: %w", err)
}
pairs := strings.Split(s, ",")
for _, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 2 {
switch v[0] {
case "cpus":
r.CPUIDs = strings.Split(v[1], ";")
case "hostnodes":
hostnodes := strings.Split(v[1], ";")
r.HostNodeNames = &hostnodes
case "memory":
memory, err := strconv.Atoi(v[1])
if err != nil {
return fmt.Errorf("failed to parse memory size: %w", err)
}
r.Memory = &memory
case "policy":
r.Policy = &v[1]
}
}
}
return nil
}

View File

@ -0,0 +1,54 @@
/*
* 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 (
"testing"
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
)
func TestCustomNUMADevice_UnmarshalJSON(t *testing.T) {
t.Parallel()
tests := []struct {
name string
line string
want *CustomNUMADevice
wantErr bool
}{
{
name: "numa device all options",
line: `"cpus=1-2;3-4,hostnodes=1-2,memory=1024,policy=preferred"`,
want: &CustomNUMADevice{
CPUIDs: []string{"1-2", "3-4"},
HostNodeNames: &[]string{"1-2"},
Memory: ptr.Ptr(1024),
Policy: ptr.Ptr("preferred"),
},
},
{
name: "numa device cpus/memory only",
line: `"cpus=1-2,memory=1024"`,
want: &CustomNUMADevice{
CPUIDs: []string{"1-2"},
Memory: ptr.Ptr(1024),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &CustomNUMADevice{}
if err := r.UnmarshalJSON([]byte(tt.line)); (err != nil) != tt.wantErr {
t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@ -0,0 +1,136 @@
/*
* 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"
"net/url"
"strings"
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
)
// CustomPCIDevice handles QEMU host PCI device mapping parameters.
type CustomPCIDevice struct {
DeviceIDs *[]string `json:"host,omitempty" url:"host,omitempty,semicolon"`
Mapping *string `json:"mapping,omitempty" url:"mapping,omitempty"`
MDev *string `json:"mdev,omitempty" url:"mdev,omitempty"`
PCIExpress *types.CustomBool `json:"pcie,omitempty" url:"pcie,omitempty,int"`
ROMBAR *types.CustomBool `json:"rombar,omitempty" url:"rombar,omitempty,int"`
ROMFile *string `json:"romfile,omitempty" url:"romfile,omitempty"`
XVGA *types.CustomBool `json:"x-vga,omitempty" url:"x-vga,omitempty,int"`
}
// CustomPCIDevices handles QEMU host PCI device mapping parameters.
type CustomPCIDevices []CustomPCIDevice
// EncodeValues converts a CustomPCIDevice struct to a URL value.
func (r *CustomPCIDevice) EncodeValues(key string, v *url.Values) error {
var values []string
if r.DeviceIDs == nil && r.Mapping == nil {
return fmt.Errorf("either device ID or resource mapping must be set")
}
if r.DeviceIDs != nil {
values = append(values, fmt.Sprintf("host=%s", strings.Join(*r.DeviceIDs, ";")))
}
if r.Mapping != nil {
values = append(values, fmt.Sprintf("mapping=%s", *r.Mapping))
}
if r.MDev != nil {
values = append(values, fmt.Sprintf("mdev=%s", *r.MDev))
}
if r.PCIExpress != nil {
if *r.PCIExpress {
values = append(values, "pcie=1")
} else {
values = append(values, "pcie=0")
}
}
if r.ROMBAR != nil {
if *r.ROMBAR {
values = append(values, "rombar=1")
} else {
values = append(values, "rombar=0")
}
}
if r.ROMFile != nil {
values = append(values, fmt.Sprintf("romfile=%s", *r.ROMFile))
}
if r.XVGA != nil {
if *r.XVGA {
values = append(values, "x-vga=1")
} else {
values = append(values, "x-vga=0")
}
}
v.Add(key, strings.Join(values, ","))
return nil
}
// EncodeValues converts a CustomPCIDevices array to multiple URL values.
func (r CustomPCIDevices) EncodeValues(key string, v *url.Values) error {
for i, d := range r {
if err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v); err != nil {
return fmt.Errorf("failed to encode PCI device %d: %w", i, err)
}
}
return nil
}
// UnmarshalJSON converts a CustomPCIDevice string to an object.
func (r *CustomPCIDevice) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("failed to unmarshal CustomPCIDevice: %w", err)
}
pairs := strings.Split(s, ",")
for _, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 1 {
dIDs := strings.Split(v[0], ";")
r.DeviceIDs = &dIDs
} else if len(v) == 2 {
switch v[0] {
case "host":
dIDs := strings.Split(v[1], ";")
r.DeviceIDs = &dIDs
case "mapping":
r.Mapping = &v[1]
case "mdev":
r.MDev = &v[1]
case "pcie":
bv := types.CustomBool(v[1] == "1")
r.PCIExpress = &bv
case "rombar":
bv := types.CustomBool(v[1] == "1")
r.ROMBAR = &bv
case "romfile":
r.ROMFile = &v[1]
case "x-vga":
bv := types.CustomBool(v[1] == "1")
r.XVGA = &bv
}
}
}
return nil
}

View File

@ -0,0 +1,73 @@
/*
* 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 (
"testing"
"github.com/stretchr/testify/require"
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
)
func TestCustomPCIDevice_UnmarshalJSON(t *testing.T) {
t.Parallel()
tests := []struct {
name string
line string
want *CustomPCIDevice
wantErr bool
}{
{
name: "id only pci device",
line: `"0000:81:00.2"`,
want: &CustomPCIDevice{
DeviceIDs: &[]string{"0000:81:00.2"},
},
},
{
name: "pci device with more details",
line: `"host=81:00.4,pcie=0,rombar=1,x-vga=0"`,
want: &CustomPCIDevice{
DeviceIDs: &[]string{"81:00.4"},
MDev: nil,
PCIExpress: types.CustomBool(false).Pointer(),
ROMBAR: types.CustomBool(true).Pointer(),
ROMFile: nil,
XVGA: types.CustomBool(false).Pointer(),
},
},
{
name: "pci device with mapping",
line: `"mapping=mappeddevice,pcie=0,rombar=1,x-vga=0"`,
want: &CustomPCIDevice{
DeviceIDs: nil,
Mapping: ptr.Ptr("mappeddevice"),
MDev: nil,
PCIExpress: types.CustomBool(false).Pointer(),
ROMBAR: types.CustomBool(true).Pointer(),
ROMFile: nil,
XVGA: types.CustomBool(false).Pointer(),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &CustomPCIDevice{}
if err := r.UnmarshalJSON([]byte(tt.line)); (err != nil) != tt.wantErr {
t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
}
require.Equal(t, tt.want, r)
})
}
}

View File

@ -0,0 +1,24 @@
/*
* 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 (
"fmt"
"net/url"
)
// CustomSerialDevices handles QEMU serial device parameters.
type CustomSerialDevices []string
// EncodeValues converts a CustomSerialDevices array to multiple URL values.
func (r CustomSerialDevices) EncodeValues(key string, v *url.Values) error {
for i, d := range r {
v.Add(fmt.Sprintf("%s%d", key, i), d)
}
return nil
}

View File

@ -0,0 +1,67 @@
/*
* 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"
"net/url"
"strconv"
"strings"
)
// CustomSharedMemory handles QEMU Inter-VM shared memory parameters.
type CustomSharedMemory struct {
Name *string `json:"name,omitempty" url:"name,omitempty"`
Size int `json:"size" url:"size"`
}
// EncodeValues converts a CustomSharedMemory struct to a URL value.
func (r *CustomSharedMemory) EncodeValues(key string, v *url.Values) error {
values := []string{
fmt.Sprintf("size=%d", r.Size),
}
if r.Name != nil {
values = append(values, fmt.Sprintf("name=%s", *r.Name))
}
v.Add(key, strings.Join(values, ","))
return nil
}
// UnmarshalJSON converts a CustomSharedMemory string to an object.
func (r *CustomSharedMemory) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("failed to unmarshal CustomSharedMemory: %w", err)
}
pairs := strings.Split(s, ",")
for _, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 2 {
switch v[0] {
case "name":
r.Name = &v[1]
case "size":
var err error
r.Size, err = strconv.Atoi(v[1])
if err != nil {
return fmt.Errorf("failed to parse shared memory size: %w", err)
}
}
}
}
return nil
}

View File

@ -0,0 +1,114 @@
/*
* 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"
"net/url"
"strings"
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
)
// CustomSMBIOS handles QEMU SMBIOS parameters.
type CustomSMBIOS struct {
Base64 *types.CustomBool `json:"base64,omitempty" url:"base64,omitempty,int"`
Family *string `json:"family,omitempty" url:"family,omitempty"`
Manufacturer *string `json:"manufacturer,omitempty" url:"manufacturer,omitempty"`
Product *string `json:"product,omitempty" url:"product,omitempty"`
Serial *string `json:"serial,omitempty" url:"serial,omitempty"`
SKU *string `json:"sku,omitempty" url:"sku,omitempty"`
UUID *string `json:"uuid,omitempty" url:"uuid,omitempty"`
Version *string `json:"version,omitempty" url:"version,omitempty"`
}
// EncodeValues converts a CustomSMBIOS struct to a URL value.
func (r *CustomSMBIOS) EncodeValues(key string, v *url.Values) error {
var values []string
if r.Base64 != nil {
if *r.Base64 {
values = append(values, "base64=1")
} else {
values = append(values, "base64=0")
}
}
if r.Family != nil {
values = append(values, fmt.Sprintf("family=%s", *r.Family))
}
if r.Manufacturer != nil {
values = append(values, fmt.Sprintf("manufacturer=%s", *r.Manufacturer))
}
if r.Product != nil {
values = append(values, fmt.Sprintf("product=%s", *r.Product))
}
if r.Serial != nil {
values = append(values, fmt.Sprintf("serial=%s", *r.Serial))
}
if r.SKU != nil {
values = append(values, fmt.Sprintf("sku=%s", *r.SKU))
}
if r.UUID != nil {
values = append(values, fmt.Sprintf("uuid=%s", *r.UUID))
}
if r.Version != nil {
values = append(values, fmt.Sprintf("version=%s", *r.Version))
}
if len(values) > 0 {
v.Add(key, strings.Join(values, ","))
}
return nil
}
// UnmarshalJSON converts a CustomSMBIOS string to an object.
func (r *CustomSMBIOS) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("failed to unmarshal CustomSMBIOS: %w", err)
}
pairs := strings.Split(s, ",")
for _, p := range pairs {
v := strings.SplitN(strings.TrimSpace(p), "=", 2)
if len(v) == 2 {
switch v[0] {
case "base64":
base64 := types.CustomBool(v[1] == "1")
r.Base64 = &base64
case "family":
r.Family = &v[1]
case "manufacturer":
r.Manufacturer = &v[1]
case "product":
r.Product = &v[1]
case "serial":
r.Serial = &v[1]
case "sku":
r.SKU = &v[1]
case "uuid":
r.UUID = &v[1]
case "version":
r.Version = &v[1]
}
}
}
return nil
}

View File

@ -0,0 +1,72 @@
/*
* 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"
"net/url"
"strings"
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
)
// CustomSpiceEnhancements handles QEMU spice enhancement parameters.
type CustomSpiceEnhancements struct {
FolderSharing *types.CustomBool `json:"foldersharing,omitempty" url:"foldersharing,omitempty"`
VideoStreaming *string `json:"videostreaming,omitempty" url:"videostreaming,omitempty"`
}
// EncodeValues converts a CustomSpiceEnhancements struct to a URL value.
func (r *CustomSpiceEnhancements) EncodeValues(key string, v *url.Values) error {
var values []string
if r.FolderSharing != nil {
if *r.FolderSharing {
values = append(values, "foldersharing=1")
} else {
values = append(values, "foldersharing=0")
}
}
if r.VideoStreaming != nil {
values = append(values, fmt.Sprintf("videostreaming=%s", *r.VideoStreaming))
}
if len(values) > 0 {
v.Add(key, strings.Join(values, ","))
}
return nil
}
// UnmarshalJSON converts JSON to a CustomSpiceEnhancements struct.
func (r *CustomSpiceEnhancements) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("failed to unmarshal CustomSpiceEnhancements: %w", err)
}
pairs := strings.Split(s, ",")
for _, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 2 {
switch v[0] {
case "foldersharing":
v := types.CustomBool(v[1] == "1")
r.FolderSharing = &v
case "videostreaming":
r.VideoStreaming = &v[1]
}
}
}
return nil
}

View File

@ -0,0 +1,88 @@
/*
* 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"
"net/url"
"strconv"
"strings"
)
// CustomStartupOrder handles QEMU startup order parameters.
type CustomStartupOrder struct {
Down *int `json:"down,omitempty" url:"down,omitempty"`
Order *int `json:"order,omitempty" url:"order,omitempty"`
Up *int `json:"up,omitempty" url:"up,omitempty"`
}
// EncodeValues converts a CustomStartupOrder struct to a URL value.
func (r *CustomStartupOrder) EncodeValues(key string, v *url.Values) error {
var values []string
if r.Order != nil {
values = append(values, fmt.Sprintf("order=%d", *r.Order))
}
if r.Up != nil {
values = append(values, fmt.Sprintf("up=%d", *r.Up))
}
if r.Down != nil {
values = append(values, fmt.Sprintf("down=%d", *r.Down))
}
if len(values) > 0 {
v.Add(key, strings.Join(values, ","))
}
return nil
}
// UnmarshalJSON converts a CustomStartupOrder string to an object.
func (r *CustomStartupOrder) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("failed to unmarshal CustomStartupOrder: %w", err)
}
pairs := strings.Split(s, ",")
for _, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 2 {
switch v[0] {
case "order":
order, err := strconv.Atoi(v[1])
if err != nil {
return fmt.Errorf("failed to parse int: %w", err)
}
r.Order = &order
case "up":
up, err := strconv.Atoi(v[1])
if err != nil {
return fmt.Errorf("failed to parse int: %w", err)
}
r.Up = &up
case "down":
down, err := strconv.Atoi(v[1])
if err != nil {
return fmt.Errorf("failed to parse int: %w", err)
}
r.Down = &down
}
}
}
return nil
}

View File

@ -1,8 +1,18 @@
/*
* 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"
"net/url"
"path/filepath"
"reflect"
"strconv"
"strings"
"unicode"
@ -36,8 +46,11 @@ type CustomStorageDevice struct {
Interface *string `json:"-" url:"-"`
}
// CustomStorageDevices handles map of QEMU storage device per disk interface.
type CustomStorageDevices map[string]*CustomStorageDevice
// PathInDatastore returns path part of FileVolume or nil if it is not yet allocated.
func (d CustomStorageDevice) PathInDatastore() *string {
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',
@ -67,7 +80,7 @@ func (d CustomStorageDevice) PathInDatastore() *string {
// 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 {
func (d *CustomStorageDevice) IsOwnedBy(vmID int) bool {
pathInDatastore := d.PathInDatastore()
if pathInDatastore == nil {
// not yet allocated volume, consider disk not owned by any VM
@ -89,14 +102,14 @@ func (d CustomStorageDevice) IsOwnedBy(vmID int) bool {
}
// IsCloudInitDrive returns true, if CustomStorageDevice is a cloud-init drive.
func (d CustomStorageDevice) IsCloudInitDrive(vmID int) bool {
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 {
func (d *CustomStorageDevice) StorageInterface() string {
for i, r := range *d.Interface {
if unicode.IsDigit(r) {
return (*d.Interface)[:i]
@ -108,7 +121,7 @@ func (d CustomStorageDevice) StorageInterface() string {
}
// EncodeOptions converts a CustomStorageDevice's common options a URL value.
func (d CustomStorageDevice) EncodeOptions() string {
func (d *CustomStorageDevice) EncodeOptions() string {
values := []string{}
if d.AIO != nil {
@ -191,7 +204,7 @@ func (d CustomStorageDevice) EncodeOptions() string {
}
// EncodeValues converts a CustomStorageDevice struct to a URL value.
func (d CustomStorageDevice) EncodeValues(key string, v *url.Values) error {
func (d *CustomStorageDevice) EncodeValues(key string, v *url.Values) error {
values := []string{
fmt.Sprintf("file=%s", d.FileVolume),
}
@ -215,15 +228,157 @@ func (d CustomStorageDevice) EncodeValues(key string, v *url.Values) error {
return nil
}
// CustomStorageDevices handles map of QEMU storage device per disk interface.
type CustomStorageDevices map[string]*CustomStorageDevice
// UnmarshalJSON converts a CustomStorageDevice string to an object.
func (d *CustomStorageDevice) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("failed to unmarshal CustomStorageDevice: %w", err)
}
pairs := strings.Split(s, ",")
for _, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
//nolint:nestif
if len(v) == 1 {
d.FileVolume = v[0]
ext := filepath.Ext(v[0])
if ext != "" {
format := string([]byte(ext)[1:])
d.Format = &format
}
} else if len(v) == 2 {
switch v[0] {
case "aio":
d.AIO = &v[1]
case "backup":
bv := types.CustomBool(v[1] == "1")
d.Backup = &bv
case "cache":
d.Cache = &v[1]
case "discard":
d.Discard = &v[1]
case "file":
d.FileVolume = v[1]
case "format":
d.Format = &v[1]
case "iops_rd":
iv, err := strconv.Atoi(v[1])
if err != nil {
return fmt.Errorf("failed to convert iops_rd to int: %w", err)
}
d.IopsRead = &iv
case "iops_rd_max":
iv, err := strconv.Atoi(v[1])
if err != nil {
return fmt.Errorf("failed to convert iops_rd_max to int: %w", err)
}
d.MaxIopsRead = &iv
case "iops_wr":
iv, err := strconv.Atoi(v[1])
if err != nil {
return fmt.Errorf("failed to convert iops_wr to int: %w", err)
}
d.IopsWrite = &iv
case "iops_wr_max":
iv, err := strconv.Atoi(v[1])
if err != nil {
return fmt.Errorf("failed to convert iops_wr_max to int: %w", err)
}
d.MaxIopsWrite = &iv
case "iothread":
bv := types.CustomBool(v[1] == "1")
d.IOThread = &bv
case "mbps_rd":
iv, err := strconv.Atoi(v[1])
if err != nil {
return fmt.Errorf("failed to convert mbps_rd to int: %w", err)
}
d.MaxReadSpeedMbps = &iv
case "mbps_rd_max":
iv, err := strconv.Atoi(v[1])
if err != nil {
return fmt.Errorf("failed to convert mbps_rd_max to int: %w", err)
}
d.BurstableReadSpeedMbps = &iv
case "mbps_wr":
iv, err := strconv.Atoi(v[1])
if err != nil {
return fmt.Errorf("failed to convert mbps_wr to int: %w", err)
}
d.MaxWriteSpeedMbps = &iv
case "mbps_wr_max":
iv, err := strconv.Atoi(v[1])
if err != nil {
return fmt.Errorf("failed to convert mbps_wr_max to int: %w", err)
}
d.BurstableWriteSpeedMbps = &iv
case "media":
d.Media = &v[1]
case "replicate":
bv := types.CustomBool(v[1] == "1")
d.Replicate = &bv
case "size":
d.Size = new(types.DiskSize)
err := d.Size.UnmarshalJSON([]byte(v[1]))
if err != nil {
return fmt.Errorf("failed to unmarshal disk size: %w", err)
}
case "ssd":
bv := types.CustomBool(v[1] == "1")
d.SSD = &bv
}
}
}
d.Enabled = true
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)
for k, v := range d {
if v.StorageInterface() == storageInterface {
if fn(v) {
result[k] = v
}
}
@ -243,3 +398,27 @@ func (d CustomStorageDevices) EncodeValues(_ string, v *url.Values) error {
return nil
}
// MapCustomStorageDevices maps the custom storage devices from the API response.
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(csd 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 {
csd[keyPrefix+strconv.Itoa(i)] = val.(*CustomStorageDevice)
}
}
}
}

View File

@ -171,7 +171,7 @@ func TestCustomStorageDevices_ByStorageInterface(t *testing.T) {
want: CustomStorageDevices{},
},
{
name: "not in the list",
name: "nothing matches",
iface: "sata",
devices: CustomStorageDevices{
"virtio0": &CustomStorageDevice{
@ -184,7 +184,7 @@ func TestCustomStorageDevices_ByStorageInterface(t *testing.T) {
want: CustomStorageDevices{},
},
{
name: "not in the list",
name: "partially matches",
iface: "virtio",
devices: CustomStorageDevices{
"virtio0": &CustomStorageDevice{
@ -218,45 +218,47 @@ func TestCustomStorageDevices_ByStorageInterface(t *testing.T) {
}
}
func TestCustomPCIDevice_UnmarshalJSON(t *testing.T) {
func TestMapCustomStorageDevices(t *testing.T) {
t.Parallel()
type args struct {
resp GetResponseData
}
tests := []struct {
name string
line string
want *CustomPCIDevice
wantErr bool
name string
args args
want CustomStorageDevices
}{
{"no storage devices", args{GetResponseData{}}, CustomStorageDevices{}},
{
name: "id only pci device",
line: `"0000:81:00.2"`,
want: &CustomPCIDevice{
DeviceIDs: &[]string{"0000:81:00.2"},
},
"ide0 storage devices",
args{GetResponseData{IDEDevice0: &CustomStorageDevice{}}},
map[string]*CustomStorageDevice{"ide0": {}},
},
{
name: "pci device with more details",
line: `"host=81:00.4,pcie=0,rombar=1,x-vga=0"`,
want: &CustomPCIDevice{
DeviceIDs: &[]string{"81:00.4"},
MDev: nil,
PCIExpress: types.CustomBool(false).Pointer(),
ROMBAR: types.CustomBool(true).Pointer(),
ROMFile: nil,
XVGA: types.CustomBool(false).Pointer(),
},
"multiple ide storage devices",
args{GetResponseData{
IDEDevice1: &CustomStorageDevice{},
IDEDevice3: &CustomStorageDevice{},
}},
map[string]*CustomStorageDevice{"ide1": {}, "ide3": {}},
},
{
name: "pci device with mapping",
line: `"mapping=mappeddevice,pcie=0,rombar=1,x-vga=0"`,
want: &CustomPCIDevice{
DeviceIDs: nil,
Mapping: ptr.Ptr("mappeddevice"),
MDev: nil,
PCIExpress: types.CustomBool(false).Pointer(),
ROMBAR: types.CustomBool(true).Pointer(),
ROMFile: nil,
XVGA: types.CustomBool(false).Pointer(),
"mixed storage devices",
args{GetResponseData{
IDEDevice1: &CustomStorageDevice{},
VirtualIODevice5: &CustomStorageDevice{},
SATADevice0: &CustomStorageDevice{},
IDEDevice3: &CustomStorageDevice{},
SCSIDevice10: &CustomStorageDevice{},
}},
map[string]*CustomStorageDevice{
"ide1": {},
"virtio5": {},
"sata0": {},
"ide3": {},
"scsi10": {},
},
},
}
@ -265,100 +267,7 @@ func TestCustomPCIDevice_UnmarshalJSON(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &CustomPCIDevice{}
if err := r.UnmarshalJSON([]byte(tt.line)); (err != nil) != tt.wantErr {
t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
}
require.Equal(t, tt.want, r)
})
}
}
func TestCustomNUMADevice_UnmarshalJSON(t *testing.T) {
t.Parallel()
tests := []struct {
name string
line string
want *CustomNUMADevice
wantErr bool
}{
{
name: "numa device all options",
line: `"cpus=1-2;3-4,hostnodes=1-2,memory=1024,policy=preferred"`,
want: &CustomNUMADevice{
CPUIDs: []string{"1-2", "3-4"},
HostNodeNames: &[]string{"1-2"},
Memory: ptr.Ptr(1024),
Policy: ptr.Ptr("preferred"),
},
},
{
name: "numa device cpus/memory only",
line: `"cpus=1-2,memory=1024"`,
want: &CustomNUMADevice{
CPUIDs: []string{"1-2"},
Memory: ptr.Ptr(1024),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &CustomNUMADevice{}
if err := r.UnmarshalJSON([]byte(tt.line)); (err != nil) != tt.wantErr {
t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestCustomUSBDevice_UnmarshalJSON(t *testing.T) {
t.Parallel()
tests := []struct {
name string
line string
want *CustomUSBDevice
wantErr bool
}{
{
name: "id only usb device",
line: `"host=0000:81"`,
want: &CustomUSBDevice{
HostDevice: ptr.Ptr("0000:81"),
},
},
{
name: "usb device with more details",
line: `"host=81:00,usb3=0"`,
want: &CustomUSBDevice{
HostDevice: ptr.Ptr("81:00"),
USB3: types.CustomBool(false).Pointer(),
},
},
{
name: "usb device with mapping",
line: `"mapping=mappeddevice,usb=0"`,
want: &CustomUSBDevice{
HostDevice: nil,
Mapping: ptr.Ptr("mappeddevice"),
USB3: types.CustomBool(false).Pointer(),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &CustomUSBDevice{}
if err := r.UnmarshalJSON([]byte(tt.line)); (err != nil) != tt.wantErr {
t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
}
assert.Equalf(t, tt.want, MapCustomStorageDevices(tt.args.resp), "MapCustomStorageDevices(%v)", tt.args.resp)
})
}
}

View File

@ -0,0 +1,62 @@
/*
* 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"
"net/url"
"strings"
)
// CustomTPMState handles QEMU TPM state parameters.
type CustomTPMState struct {
FileVolume string `json:"file" url:"file"`
Version *string `json:"version,omitempty" url:"version,omitempty"`
}
// EncodeValues converts a CustomTPMState struct to a URL value.
func (r *CustomTPMState) EncodeValues(key string, v *url.Values) error {
values := []string{
fmt.Sprintf("file=%s", r.FileVolume),
}
if r.Version != nil {
values = append(values, fmt.Sprintf("version=%s", *r.Version))
}
v.Add(key, strings.Join(values, ","))
return nil
}
// UnmarshalJSON converts a CustomTPMState string to an object.
func (r *CustomTPMState) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("failed to unmarshal CustomTPMState: %w", err)
}
pairs := strings.Split(s, ",")
for _, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 1 {
r.FileVolume = v[0]
} else if len(v) == 2 {
switch v[0] {
case "file":
r.FileVolume = v[1]
case "version":
r.Version = &v[1]
}
}
}
return nil
}

View File

@ -0,0 +1,95 @@
/*
* 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"
"net/url"
"strings"
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
)
// CustomUSBDevice handles QEMU USB device parameters.
type CustomUSBDevice struct {
HostDevice *string `json:"host" url:"host"`
Mapping *string `json:"mapping,omitempty" url:"mapping,omitempty"`
USB3 *types.CustomBool `json:"usb3,omitempty" url:"usb3,omitempty,int"`
}
// CustomUSBDevices handles QEMU USB device parameters.
type CustomUSBDevices []CustomUSBDevice
// EncodeValues converts a CustomUSBDevice struct to a URL value.
func (r *CustomUSBDevice) EncodeValues(key string, v *url.Values) error {
if r.HostDevice == nil && r.Mapping == nil {
return fmt.Errorf("either device ID or resource mapping must be set")
}
values := []string{}
if r.HostDevice != nil {
values = append(values, fmt.Sprintf("host=%s", *(r.HostDevice)))
}
if r.Mapping != nil {
values = append(values, fmt.Sprintf("mapping=%s", *r.Mapping))
}
if r.USB3 != nil {
if *r.USB3 {
values = append(values, "usb3=1")
} else {
values = append(values, "usb3=0")
}
}
v.Add(key, strings.Join(values, ","))
return nil
}
// EncodeValues converts a CustomUSBDevices array to multiple URL values.
func (r CustomUSBDevices) EncodeValues(key string, v *url.Values) error {
for i, d := range r {
if err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v); err != nil {
return fmt.Errorf("error encoding USB device %d: %w", i, err)
}
}
return nil
}
// UnmarshalJSON converts a CustomUSBDevice string to an object.
func (r *CustomUSBDevice) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("failed to unmarshal CustomUSBDevice: %w", err)
}
pairs := strings.Split(s, ",")
for _, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 1 {
r.HostDevice = &v[1]
} else if len(v) == 2 {
switch v[0] {
case "host":
r.HostDevice = &v[1]
case "mapping":
r.Mapping = &v[1]
case "usb3":
bv := types.CustomBool(v[1] == "1")
r.USB3 = &bv
}
}
}
return nil
}

View File

@ -0,0 +1,61 @@
/*
* 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 (
"testing"
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
)
func TestCustomUSBDevice_UnmarshalJSON(t *testing.T) {
t.Parallel()
tests := []struct {
name string
line string
want *CustomUSBDevice
wantErr bool
}{
{
name: "id only usb device",
line: `"host=0000:81"`,
want: &CustomUSBDevice{
HostDevice: ptr.Ptr("0000:81"),
},
},
{
name: "usb device with more details",
line: `"host=81:00,usb3=0"`,
want: &CustomUSBDevice{
HostDevice: ptr.Ptr("81:00"),
USB3: types.CustomBool(false).Pointer(),
},
},
{
name: "usb device with mapping",
line: `"mapping=mappeddevice,usb=0"`,
want: &CustomUSBDevice{
HostDevice: nil,
Mapping: ptr.Ptr("mappeddevice"),
USB3: types.CustomBool(false).Pointer(),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &CustomUSBDevice{}
if err := r.UnmarshalJSON([]byte(tt.line)); (err != nil) != tt.wantErr {
t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@ -0,0 +1,83 @@
/*
* 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"
"net/url"
"strconv"
"strings"
)
// CustomVGADevice handles QEMU VGA device parameters.
type CustomVGADevice struct {
Clipboard *string `json:"clipboard,omitempty" url:"memory,omitempty"`
Memory *int64 `json:"memory,omitempty" url:"memory,omitempty"`
Type *string `json:"type,omitempty" url:"type,omitempty"`
}
// EncodeValues converts a CustomVGADevice struct to a URL value.
func (r CustomVGADevice) EncodeValues(key string, v *url.Values) error {
var values []string
if r.Clipboard != nil {
values = append(values, fmt.Sprintf("clipboard=%s", *r.Clipboard))
}
if r.Memory != nil {
values = append(values, fmt.Sprintf("memory=%d", *r.Memory))
}
if r.Type != nil {
values = append(values, fmt.Sprintf("type=%s", *r.Type))
}
v.Add(key, strings.Join(values, ","))
return nil
}
// UnmarshalJSON converts a CustomVGADevice string to an object.
func (r *CustomVGADevice) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("failed to unmarshal CustomVGADevice: %w", err)
}
if s == "" {
return nil
}
pairs := strings.Split(s, ",")
for _, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 1 {
r.Type = &v[0]
} else if len(v) == 2 {
switch v[0] {
case "clipboard":
r.Clipboard = &v[1]
case "memory":
m, err := strconv.ParseInt(v[1], 10, 64)
if err != nil {
return fmt.Errorf("failed to convert memory to int: %w", err)
}
r.Memory = &m
case "type":
r.Type = &v[1]
}
}
}
return nil
}

View File

@ -0,0 +1,62 @@
/*
* 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 (
"fmt"
"net/url"
"strings"
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
)
// CustomVirtualIODevice handles QEMU VirtIO device parameters.
type CustomVirtualIODevice struct {
AIO *string `json:"aio,omitempty" url:"aio,omitempty"`
BackupEnabled *types.CustomBool `json:"backup,omitempty" url:"backup,omitempty,int"`
Enabled bool `json:"-" url:"-"`
FileVolume string `json:"file" url:"file"`
}
// CustomVirtualIODevices handles QEMU VirtIO device parameters.
type CustomVirtualIODevices []CustomVirtualIODevice
// EncodeValues converts a CustomVirtualIODevice struct to a URL value.
func (r CustomVirtualIODevice) EncodeValues(key string, v *url.Values) error {
values := []string{
fmt.Sprintf("file=%s", r.FileVolume),
}
if r.AIO != nil {
values = append(values, fmt.Sprintf("aio=%s", *r.AIO))
}
if r.BackupEnabled != nil {
if *r.BackupEnabled {
values = append(values, "backup=1")
} else {
values = append(values, "backup=0")
}
}
v.Add(key, strings.Join(values, ","))
return nil
}
// EncodeValues converts a CustomVirtualIODevices array to multiple URL values.
func (r CustomVirtualIODevices) EncodeValues(key string, v *url.Values) error {
for i, d := range r {
if d.Enabled {
if err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v); err != nil {
return fmt.Errorf("error encoding virtual IO device %d: %w", i, err)
}
}
}
return nil
}

View File

@ -0,0 +1,67 @@
/*
* 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"
"net/url"
"strings"
)
// CustomWatchdogDevice handles QEMU watchdog device parameters.
type CustomWatchdogDevice struct {
Action *string `json:"action,omitempty" url:"action,omitempty"`
Model *string `json:"model" url:"model"`
}
// EncodeValues converts a CustomWatchdogDevice struct to a URL value.
func (r *CustomWatchdogDevice) EncodeValues(key string, v *url.Values) error {
values := []string{
fmt.Sprintf("model=%+v", r.Model),
}
if r.Action != nil {
values = append(values, fmt.Sprintf("action=%s", *r.Action))
}
v.Add(key, strings.Join(values, ","))
return nil
}
// UnmarshalJSON converts a CustomWatchdogDevice string to an object.
func (r *CustomWatchdogDevice) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("failed to unmarshal CustomWatchdogDevice: %w", err)
}
if s == "" {
return nil
}
pairs := strings.Split(s, ",")
for _, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 1 {
r.Model = &v[0]
} else if len(v) == 2 {
switch v[0] {
case "action":
r.Action = &v[1]
case "model":
r.Model = &v[1]
}
}
}
return nil
}

File diff suppressed because it is too large Load Diff

View File

@ -2215,8 +2215,6 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
return diag.FromErr(e)
}
/////////////////
allDiskInfo := disk.GetInfo(vmConfig, d) // from the cloned VM
planDisks, e := disk.GetDiskDeviceObjects(d, VM(), nil) // from the resource config