mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-08-22 19:38:35 +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:
parent
5d4193b6be
commit
580381f892
87
proxmox/nodes/vms/custom_agent.go
Normal file
87
proxmox/nodes/vms/custom_agent.go
Normal 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
|
||||||
|
}
|
76
proxmox/nodes/vms/custom_audio_device.go
Normal file
76
proxmox/nodes/vms/custom_audio_device.go
Normal 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
|
||||||
|
}
|
52
proxmox/nodes/vms/custom_boot.go
Normal file
52
proxmox/nodes/vms/custom_boot.go
Normal 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
|
||||||
|
}
|
210
proxmox/nodes/vms/custom_cloud_init.go
Normal file
210
proxmox/nodes/vms/custom_cloud_init.go
Normal 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
|
||||||
|
}
|
95
proxmox/nodes/vms/custom_cpu_emulation.go
Normal file
95
proxmox/nodes/vms/custom_cpu_emulation.go
Normal 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
|
||||||
|
}
|
87
proxmox/nodes/vms/custom_efi_disk.go
Normal file
87
proxmox/nodes/vms/custom_efi_disk.go
Normal 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
|
||||||
|
}
|
191
proxmox/nodes/vms/custom_network_device.go
Normal file
191
proxmox/nodes/vms/custom_network_device.go
Normal 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
|
||||||
|
}
|
95
proxmox/nodes/vms/custom_numa_device.go
Normal file
95
proxmox/nodes/vms/custom_numa_device.go
Normal 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
|
||||||
|
}
|
54
proxmox/nodes/vms/custom_numa_device_test.go
Normal file
54
proxmox/nodes/vms/custom_numa_device_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
136
proxmox/nodes/vms/custom_pci_device.go
Normal file
136
proxmox/nodes/vms/custom_pci_device.go
Normal 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
|
||||||
|
}
|
73
proxmox/nodes/vms/custom_pci_device_test.go
Normal file
73
proxmox/nodes/vms/custom_pci_device_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
24
proxmox/nodes/vms/custom_serial_device.go
Normal file
24
proxmox/nodes/vms/custom_serial_device.go
Normal 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
|
||||||
|
}
|
67
proxmox/nodes/vms/custom_shared_memory.go
Normal file
67
proxmox/nodes/vms/custom_shared_memory.go
Normal 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
|
||||||
|
}
|
114
proxmox/nodes/vms/custom_smbios.go
Normal file
114
proxmox/nodes/vms/custom_smbios.go
Normal 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
|
||||||
|
}
|
72
proxmox/nodes/vms/custom_spice_enhancements.go
Normal file
72
proxmox/nodes/vms/custom_spice_enhancements.go
Normal 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
|
||||||
|
}
|
88
proxmox/nodes/vms/custom_startup_order.go
Normal file
88
proxmox/nodes/vms/custom_startup_order.go
Normal 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
|
||||||
|
}
|
@ -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
|
package vms
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
@ -36,8 +46,11 @@ type CustomStorageDevice struct {
|
|||||||
Interface *string `json:"-" url:"-"`
|
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.
|
// 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, ":")
|
probablyDatastoreID, pathInDatastore, hasDatastoreID := strings.Cut(d.FileVolume, ":")
|
||||||
if !hasDatastoreID {
|
if !hasDatastoreID {
|
||||||
// when no ':' separator is found, 'Cut' places the whole string to 'probablyDatastoreID',
|
// 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.
|
// IsOwnedBy returns true, if CustomStorageDevice is owned by given VM.
|
||||||
// Not yet allocated volumes are not owned by any 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()
|
pathInDatastore := d.PathInDatastore()
|
||||||
if pathInDatastore == nil {
|
if pathInDatastore == nil {
|
||||||
// not yet allocated volume, consider disk not owned by any VM
|
// 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.
|
// 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" &&
|
return d.Media != nil && *d.Media == "cdrom" &&
|
||||||
strings.Contains(d.FileVolume, fmt.Sprintf("vm-%d-cloudinit", vmID))
|
strings.Contains(d.FileVolume, fmt.Sprintf("vm-%d-cloudinit", vmID))
|
||||||
}
|
}
|
||||||
|
|
||||||
// StorageInterface returns the storage interface of the CustomStorageDevice,
|
// StorageInterface returns the storage interface of the CustomStorageDevice,
|
||||||
// e.g. "virtio" or "scsi" for "virtio0" or "scsi2".
|
// 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 {
|
for i, r := range *d.Interface {
|
||||||
if unicode.IsDigit(r) {
|
if unicode.IsDigit(r) {
|
||||||
return (*d.Interface)[:i]
|
return (*d.Interface)[:i]
|
||||||
@ -108,7 +121,7 @@ func (d CustomStorageDevice) StorageInterface() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EncodeOptions converts a CustomStorageDevice's common options a URL value.
|
// EncodeOptions converts a CustomStorageDevice's common options a URL value.
|
||||||
func (d CustomStorageDevice) EncodeOptions() string {
|
func (d *CustomStorageDevice) EncodeOptions() string {
|
||||||
values := []string{}
|
values := []string{}
|
||||||
|
|
||||||
if d.AIO != nil {
|
if d.AIO != nil {
|
||||||
@ -191,7 +204,7 @@ func (d CustomStorageDevice) EncodeOptions() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EncodeValues converts a CustomStorageDevice struct to a URL value.
|
// 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{
|
values := []string{
|
||||||
fmt.Sprintf("file=%s", d.FileVolume),
|
fmt.Sprintf("file=%s", d.FileVolume),
|
||||||
}
|
}
|
||||||
@ -215,15 +228,157 @@ func (d CustomStorageDevice) EncodeValues(key string, v *url.Values) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomStorageDevices handles map of QEMU storage device per disk interface.
|
// UnmarshalJSON converts a CustomStorageDevice string to an object.
|
||||||
type CustomStorageDevices map[string]*CustomStorageDevice
|
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.
|
// ByStorageInterface returns a map of CustomStorageDevices filtered by the given storage interface.
|
||||||
func (d CustomStorageDevices) ByStorageInterface(storageInterface string) CustomStorageDevices {
|
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)
|
result := make(CustomStorageDevices)
|
||||||
|
|
||||||
for k, v := range d {
|
for k, v := range d {
|
||||||
if v.StorageInterface() == storageInterface {
|
if fn(v) {
|
||||||
result[k] = v
|
result[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -243,3 +398,27 @@ func (d CustomStorageDevices) EncodeValues(_ string, v *url.Values) error {
|
|||||||
|
|
||||||
return nil
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -171,7 +171,7 @@ func TestCustomStorageDevices_ByStorageInterface(t *testing.T) {
|
|||||||
want: CustomStorageDevices{},
|
want: CustomStorageDevices{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "not in the list",
|
name: "nothing matches",
|
||||||
iface: "sata",
|
iface: "sata",
|
||||||
devices: CustomStorageDevices{
|
devices: CustomStorageDevices{
|
||||||
"virtio0": &CustomStorageDevice{
|
"virtio0": &CustomStorageDevice{
|
||||||
@ -184,7 +184,7 @@ func TestCustomStorageDevices_ByStorageInterface(t *testing.T) {
|
|||||||
want: CustomStorageDevices{},
|
want: CustomStorageDevices{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "not in the list",
|
name: "partially matches",
|
||||||
iface: "virtio",
|
iface: "virtio",
|
||||||
devices: CustomStorageDevices{
|
devices: CustomStorageDevices{
|
||||||
"virtio0": &CustomStorageDevice{
|
"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()
|
t.Parallel()
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
resp GetResponseData
|
||||||
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
line string
|
args args
|
||||||
want *CustomPCIDevice
|
want CustomStorageDevices
|
||||||
wantErr bool
|
|
||||||
}{
|
}{
|
||||||
|
{"no storage devices", args{GetResponseData{}}, CustomStorageDevices{}},
|
||||||
{
|
{
|
||||||
name: "id only pci device",
|
"ide0 storage devices",
|
||||||
line: `"0000:81:00.2"`,
|
args{GetResponseData{IDEDevice0: &CustomStorageDevice{}}},
|
||||||
want: &CustomPCIDevice{
|
map[string]*CustomStorageDevice{"ide0": {}},
|
||||||
DeviceIDs: &[]string{"0000:81:00.2"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "pci device with more details",
|
"multiple ide storage devices",
|
||||||
line: `"host=81:00.4,pcie=0,rombar=1,x-vga=0"`,
|
args{GetResponseData{
|
||||||
want: &CustomPCIDevice{
|
IDEDevice1: &CustomStorageDevice{},
|
||||||
DeviceIDs: &[]string{"81:00.4"},
|
IDEDevice3: &CustomStorageDevice{},
|
||||||
MDev: nil,
|
}},
|
||||||
PCIExpress: types.CustomBool(false).Pointer(),
|
map[string]*CustomStorageDevice{"ide1": {}, "ide3": {}},
|
||||||
ROMBAR: types.CustomBool(true).Pointer(),
|
|
||||||
ROMFile: nil,
|
|
||||||
XVGA: types.CustomBool(false).Pointer(),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "pci device with mapping",
|
"mixed storage devices",
|
||||||
line: `"mapping=mappeddevice,pcie=0,rombar=1,x-vga=0"`,
|
args{GetResponseData{
|
||||||
want: &CustomPCIDevice{
|
IDEDevice1: &CustomStorageDevice{},
|
||||||
DeviceIDs: nil,
|
VirtualIODevice5: &CustomStorageDevice{},
|
||||||
Mapping: ptr.Ptr("mappeddevice"),
|
SATADevice0: &CustomStorageDevice{},
|
||||||
MDev: nil,
|
IDEDevice3: &CustomStorageDevice{},
|
||||||
PCIExpress: types.CustomBool(false).Pointer(),
|
SCSIDevice10: &CustomStorageDevice{},
|
||||||
ROMBAR: types.CustomBool(true).Pointer(),
|
}},
|
||||||
ROMFile: nil,
|
map[string]*CustomStorageDevice{
|
||||||
XVGA: types.CustomBool(false).Pointer(),
|
"ide1": {},
|
||||||
|
"virtio5": {},
|
||||||
|
"sata0": {},
|
||||||
|
"ide3": {},
|
||||||
|
"scsi10": {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -265,100 +267,7 @@ func TestCustomPCIDevice_UnmarshalJSON(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
r := &CustomPCIDevice{}
|
assert.Equalf(t, tt.want, MapCustomStorageDevices(tt.args.resp), "MapCustomStorageDevices(%v)", tt.args.resp)
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
62
proxmox/nodes/vms/custom_tpm_state.go
Normal file
62
proxmox/nodes/vms/custom_tpm_state.go
Normal 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
|
||||||
|
}
|
95
proxmox/nodes/vms/custom_usb_device.go
Normal file
95
proxmox/nodes/vms/custom_usb_device.go
Normal 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
|
||||||
|
}
|
61
proxmox/nodes/vms/custom_usb_device_test.go
Normal file
61
proxmox/nodes/vms/custom_usb_device_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
83
proxmox/nodes/vms/custom_vga_device.go
Normal file
83
proxmox/nodes/vms/custom_vga_device.go
Normal 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
|
||||||
|
}
|
62
proxmox/nodes/vms/custom_virtualio_device.go
Normal file
62
proxmox/nodes/vms/custom_virtualio_device.go
Normal 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
|
||||||
|
}
|
67
proxmox/nodes/vms/custom_watchdog_device.go
Normal file
67
proxmox/nodes/vms/custom_watchdog_device.go
Normal 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
@ -2215,8 +2215,6 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
|
|||||||
return diag.FromErr(e)
|
return diag.FromErr(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////
|
|
||||||
|
|
||||||
allDiskInfo := disk.GetInfo(vmConfig, d) // from the cloned VM
|
allDiskInfo := disk.GetInfo(vmConfig, d) // from the cloned VM
|
||||||
|
|
||||||
planDisks, e := disk.GetDiskDeviceObjects(d, VM(), nil) // from the resource config
|
planDisks, e := disk.GetDiskDeviceObjects(d, VM(), nil) // from the resource config
|
||||||
|
Loading…
Reference in New Issue
Block a user