mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-06-29 18:21:10 +00:00
feat(lxc): increase number of supported mount points to 256 (#1939)
* feat(lxc): increase number of supported mount points to 256 * fix(container): correct condition for setting replicate value for rootfs Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
parent
64147cd24e
commit
a99220e9fb
@ -120,7 +120,10 @@ func TestAccResourceContainer(t *testing.T) {
|
|||||||
|
|
||||||
ctInfo, err := ct.GetContainer(ctx)
|
ctInfo, err := ct.GetContainer(ctx)
|
||||||
require.NoError(te.t, err, "failed to get container")
|
require.NoError(te.t, err, "failed to get container")
|
||||||
require.NotNil(te.t, ctInfo.DevicePassthrough0)
|
dev0, ok := ctInfo.PassthroughDevices["dev0"]
|
||||||
|
require.True(te.t, ok, `"dev0" passthrough device not found`)
|
||||||
|
require.NotNil(te.t, dev0, `"dev0" passthrough device is <nil>`)
|
||||||
|
require.Equal(te.t, "/dev/zero", dev0.Path)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@ -309,7 +312,10 @@ func TestAccResourceContainer(t *testing.T) {
|
|||||||
|
|
||||||
ctInfo, err := ct.GetContainer(ctx)
|
ctInfo, err := ct.GetContainer(ctx)
|
||||||
require.NoError(te.t, err, "failed to get container")
|
require.NoError(te.t, err, "failed to get container")
|
||||||
require.NotNil(te.t, ctInfo.DevicePassthrough0)
|
dev0, ok := ctInfo.PassthroughDevices["dev0"]
|
||||||
|
require.True(te.t, ok, `"dev0" passthrough device not found`)
|
||||||
|
require.NotNil(te.t, dev0, `"dev0" passthrough device is <nil>`)
|
||||||
|
require.Equal(te.t, "/dev/zero", dev0.Path)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
@ -10,12 +10,22 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// regexDeviceKey matches device keys like dev0, dev1, etc.
|
||||||
|
regexDeviceKey = regexp.MustCompile(`^dev\d+$`)
|
||||||
|
// regexNetworkKey matches network interface keys like net0, net1, etc.
|
||||||
|
regexNetworkKey = regexp.MustCompile(`^net\d+$`)
|
||||||
|
// regexMountPointKey matches mount point keys like mp0, mp1, etc.
|
||||||
|
regexMountPointKey = regexp.MustCompile(`^mp\d+$`)
|
||||||
|
)
|
||||||
|
|
||||||
// CloneRequestBody contains the data for an container clone request.
|
// CloneRequestBody contains the data for an container clone request.
|
||||||
type CloneRequestBody struct {
|
type CloneRequestBody struct {
|
||||||
BandwidthLimit *int `json:"bwlimit,omitempty" url:"bwlimit,omitempty"`
|
BandwidthLimit *int `json:"bwlimit,omitempty" url:"bwlimit,omitempty"`
|
||||||
@ -31,46 +41,46 @@ type CloneRequestBody struct {
|
|||||||
|
|
||||||
// CreateRequestBody contains the data for a user create request.
|
// CreateRequestBody contains the data for a user create request.
|
||||||
type CreateRequestBody struct {
|
type CreateRequestBody struct {
|
||||||
BandwidthLimit *float64 `json:"bwlimit,omitempty" url:"bwlimit,omitempty"`
|
BandwidthLimit *float64 `json:"bwlimit,omitempty" url:"bwlimit,omitempty"`
|
||||||
ConsoleEnabled *types.CustomBool `json:"console,omitempty" url:"console,omitempty,int"`
|
ConsoleEnabled *types.CustomBool `json:"console,omitempty" url:"console,omitempty,int"`
|
||||||
ConsoleMode *string `json:"cmode,omitempty" url:"cmode,omitempty"`
|
ConsoleMode *string `json:"cmode,omitempty" url:"cmode,omitempty"`
|
||||||
CPUArchitecture *string `json:"arch,omitempty" url:"arch,omitempty"`
|
CPUArchitecture *string `json:"arch,omitempty" url:"arch,omitempty"`
|
||||||
CPUCores *int `json:"cores,omitempty" url:"cores,omitempty"`
|
CPUCores *int `json:"cores,omitempty" url:"cores,omitempty"`
|
||||||
CPULimit *int `json:"cpulimit,omitempty" url:"cpulimit,omitempty"`
|
CPULimit *int `json:"cpulimit,omitempty" url:"cpulimit,omitempty"`
|
||||||
CPUUnits *int `json:"cpuunits,omitempty" url:"cpuunits,omitempty"`
|
CPUUnits *int `json:"cpuunits,omitempty" url:"cpuunits,omitempty"`
|
||||||
DatastoreID *string `json:"storage,omitempty" url:"storage,omitempty"`
|
DatastoreID *string `json:"storage,omitempty" url:"storage,omitempty"`
|
||||||
DedicatedMemory *int `json:"memory,omitempty" url:"memory,omitempty"`
|
DedicatedMemory *int `json:"memory,omitempty" url:"memory,omitempty"`
|
||||||
Delete []string `json:"delete,omitempty" url:"delete,omitempty"`
|
Delete []string `json:"delete,omitempty" url:"delete,omitempty"`
|
||||||
Description *string `json:"description,omitempty" url:"description,omitempty"`
|
Description *string `json:"description,omitempty" url:"description,omitempty"`
|
||||||
DNSDomain *string `json:"searchdomain,omitempty" url:"searchdomain,omitempty"`
|
DNSDomain *string `json:"searchdomain,omitempty" url:"searchdomain,omitempty"`
|
||||||
DNSServer *string `json:"nameserver,omitempty" url:"nameserver,omitempty"`
|
DNSServer *string `json:"nameserver,omitempty" url:"nameserver,omitempty"`
|
||||||
Features *CustomFeatures `json:"features,omitempty" url:"features,omitempty"`
|
Features *CustomFeatures `json:"features,omitempty" url:"features,omitempty"`
|
||||||
Force *types.CustomBool `json:"force,omitempty" url:"force,omitempty,int"`
|
Force *types.CustomBool `json:"force,omitempty" url:"force,omitempty,int"`
|
||||||
HookScript *string `json:"hookscript,omitempty" url:"hookscript,omitempty"`
|
HookScript *string `json:"hookscript,omitempty" url:"hookscript,omitempty"`
|
||||||
Hostname *string `json:"hostname,omitempty" url:"hostname,omitempty"`
|
Hostname *string `json:"hostname,omitempty" url:"hostname,omitempty"`
|
||||||
IgnoreUnpackErrors *types.CustomBool `json:"ignore-unpack-errors,omitempty" url:"force,omitempty,int"`
|
IgnoreUnpackErrors *types.CustomBool `json:"ignore-unpack-errors,omitempty" url:"force,omitempty,int"`
|
||||||
Lock *string `json:"lock,omitempty" url:"lock,omitempty,int"`
|
Lock *string `json:"lock,omitempty" url:"lock,omitempty,int"`
|
||||||
MountPoints CustomMountPointArray `json:"mp,omitempty" url:"mp,omitempty,numbered"`
|
MountPoints CustomMountPoints `json:"mp,omitempty" url:"mp,omitempty,numbered"`
|
||||||
DevicePassthrough CustomDevicePassthroughArray `json:"dev,omitempty" url:"dev,omitempty,numbered"`
|
PassthroughDevices CustomPassthroughDevices `json:"dev,omitempty" url:"dev,omitempty,numbered"`
|
||||||
NetworkInterfaces CustomNetworkInterfaceArray `json:"net,omitempty" url:"net,omitempty,numbered"`
|
NetworkInterfaces CustomNetworkInterfaces `json:"net,omitempty" url:"net,omitempty,numbered"`
|
||||||
OSTemplateFileVolume *string `json:"ostemplate,omitempty" url:"ostemplate,omitempty"`
|
OSTemplateFileVolume *string `json:"ostemplate,omitempty" url:"ostemplate,omitempty"`
|
||||||
OSType *string `json:"ostype,omitempty" url:"ostype,omitempty"`
|
OSType *string `json:"ostype,omitempty" url:"ostype,omitempty"`
|
||||||
Password *string `json:"password,omitempty" url:"password,omitempty"`
|
Password *string `json:"password,omitempty" url:"password,omitempty"`
|
||||||
PoolID *string `json:"pool,omitempty" url:"pool,omitempty"`
|
PoolID *string `json:"pool,omitempty" url:"pool,omitempty"`
|
||||||
Protection *types.CustomBool `json:"protection,omitempty" url:"protection,omitempty,int"`
|
Protection *types.CustomBool `json:"protection,omitempty" url:"protection,omitempty,int"`
|
||||||
Restore *types.CustomBool `json:"restore,omitempty" url:"restore,omitempty,int"`
|
Restore *types.CustomBool `json:"restore,omitempty" url:"restore,omitempty,int"`
|
||||||
RootFS *CustomRootFS `json:"rootfs,omitempty" url:"rootfs,omitempty"`
|
RootFS *CustomRootFS `json:"rootfs,omitempty" url:"rootfs,omitempty"`
|
||||||
SSHKeys *CustomSSHKeys `json:"ssh-public-keys,omitempty" url:"ssh-public-keys,omitempty"`
|
SSHKeys *CustomSSHKeys `json:"ssh-public-keys,omitempty" url:"ssh-public-keys,omitempty"`
|
||||||
Start *types.CustomBool `json:"start,omitempty" url:"start,omitempty,int"`
|
Start *types.CustomBool `json:"start,omitempty" url:"start,omitempty,int"`
|
||||||
StartOnBoot *types.CustomBool `json:"onboot,omitempty" url:"onboot,omitempty,int"`
|
StartOnBoot *types.CustomBool `json:"onboot,omitempty" url:"onboot,omitempty,int"`
|
||||||
StartupBehavior *CustomStartupBehavior `json:"startup,omitempty" url:"startup,omitempty"`
|
StartupBehavior *CustomStartupBehavior `json:"startup,omitempty" url:"startup,omitempty"`
|
||||||
Swap *int `json:"swap,omitempty" url:"swap,omitempty"`
|
Swap *int `json:"swap,omitempty" url:"swap,omitempty"`
|
||||||
Tags *string `json:"tags,omitempty" url:"tags,omitempty"`
|
Tags *string `json:"tags,omitempty" url:"tags,omitempty"`
|
||||||
Template *types.CustomBool `json:"template,omitempty" url:"template,omitempty,int"`
|
Template *types.CustomBool `json:"template,omitempty" url:"template,omitempty,int"`
|
||||||
TTY *int `json:"tty,omitempty" url:"tty,omitempty"`
|
TTY *int `json:"tty,omitempty" url:"tty,omitempty"`
|
||||||
Unique *types.CustomBool `json:"unique,omitempty" url:"unique,omitempty,int"`
|
Unique *types.CustomBool `json:"unique,omitempty" url:"unique,omitempty,int"`
|
||||||
Unprivileged *types.CustomBool `json:"unprivileged,omitempty" url:"unprivileged,omitempty,int"`
|
Unprivileged *types.CustomBool `json:"unprivileged,omitempty" url:"unprivileged,omitempty,int"`
|
||||||
VMID *int `json:"vmid,omitempty" url:"vmid,omitempty"`
|
VMID *int `json:"vmid,omitempty" url:"vmid,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomFeatures contains the values for the "features" property.
|
// CustomFeatures contains the values for the "features" property.
|
||||||
@ -96,8 +106,8 @@ type CustomMountPoint struct {
|
|||||||
Volume string `json:"volume" url:"volume"`
|
Volume string `json:"volume" url:"volume"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomMountPointArray is an array of CustomMountPoint.
|
// CustomMountPoints is a map of CustomMountPoint per mount point ID (i.e. `mp1`).
|
||||||
type CustomMountPointArray []CustomMountPoint
|
type CustomMountPoints map[string]*CustomMountPoint
|
||||||
|
|
||||||
// CustomNetworkInterface contains the values for the "net[n]" properties.
|
// CustomNetworkInterface contains the values for the "net[n]" properties.
|
||||||
type CustomNetworkInterface struct {
|
type CustomNetworkInterface struct {
|
||||||
@ -117,11 +127,11 @@ type CustomNetworkInterface struct {
|
|||||||
Type *string `json:"type,omitempty" url:"type,omitempty"`
|
Type *string `json:"type,omitempty" url:"type,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomDevicePassthroughArray is an array of CustomDevicePassthrough.
|
// CustomPassthroughDevices is a map of CustomPassthroughDevice per passthrough device ID (i.e. `dev0`).
|
||||||
type CustomDevicePassthroughArray []CustomDevicePassthrough
|
type CustomPassthroughDevices map[string]*CustomPassthroughDevice
|
||||||
|
|
||||||
// CustomDevicePassthrough contains the values for the "dev[n]" properties.
|
// CustomPassthroughDevice contains the values for the "dev[n]" properties.
|
||||||
type CustomDevicePassthrough struct {
|
type CustomPassthroughDevice struct {
|
||||||
DenyWrite *types.CustomBool `json:"deny-write,omitempty" url:"deny-write,omitempty,int"`
|
DenyWrite *types.CustomBool `json:"deny-write,omitempty" url:"deny-write,omitempty,int"`
|
||||||
Path string `json:"path" url:"path"`
|
Path string `json:"path" url:"path"`
|
||||||
UID *int `json:"uid,omitempty" url:"uid,omitempty"`
|
UID *int `json:"uid,omitempty" url:"uid,omitempty"`
|
||||||
@ -129,8 +139,8 @@ type CustomDevicePassthrough struct {
|
|||||||
Mode *string `json:"mode,omitempty" url:"mode,omitempty"`
|
Mode *string `json:"mode,omitempty" url:"mode,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomNetworkInterfaceArray is an array of CustomNetworkInterface.
|
// CustomNetworkInterfaces is a map of CustomNetworkInterface per network interface ID (i.e. `net0`).
|
||||||
type CustomNetworkInterfaceArray []CustomNetworkInterface
|
type CustomNetworkInterfaces map[string]*CustomNetworkInterface
|
||||||
|
|
||||||
// CustomRootFS contains the values for the "rootfs" property.
|
// CustomRootFS contains the values for the "rootfs" property.
|
||||||
type CustomRootFS struct {
|
type CustomRootFS struct {
|
||||||
@ -184,30 +194,9 @@ type GetResponseData struct {
|
|||||||
Hostname *string `json:"hostname,omitempty"`
|
Hostname *string `json:"hostname,omitempty"`
|
||||||
Lock *types.CustomBool `json:"lock,omitempty"`
|
Lock *types.CustomBool `json:"lock,omitempty"`
|
||||||
LXCConfiguration *[][2]string `json:"lxc,omitempty"`
|
LXCConfiguration *[][2]string `json:"lxc,omitempty"`
|
||||||
DevicePassthrough0 *CustomDevicePassthrough `json:"dev0,omitempty"`
|
MountPoints CustomMountPoints `json:"mp,omitempty"`
|
||||||
DevicePassthrough1 *CustomDevicePassthrough `json:"dev1,omitempty"`
|
PassthroughDevices CustomPassthroughDevices `json:"dev,omitempty"`
|
||||||
DevicePassthrough2 *CustomDevicePassthrough `json:"dev2,omitempty"`
|
NetworkInterfaces CustomNetworkInterfaces `json:"net,omitempty"`
|
||||||
DevicePassthrough3 *CustomDevicePassthrough `json:"dev3,omitempty"`
|
|
||||||
DevicePassthrough4 *CustomDevicePassthrough `json:"dev4,omitempty"`
|
|
||||||
DevicePassthrough5 *CustomDevicePassthrough `json:"dev5,omitempty"`
|
|
||||||
DevicePassthrough6 *CustomDevicePassthrough `json:"dev6,omitempty"`
|
|
||||||
DevicePassthrough7 *CustomDevicePassthrough `json:"dev7,omitempty"`
|
|
||||||
MountPoint0 *CustomMountPoint `json:"mp0,omitempty"`
|
|
||||||
MountPoint1 *CustomMountPoint `json:"mp1,omitempty"`
|
|
||||||
MountPoint2 *CustomMountPoint `json:"mp2,omitempty"`
|
|
||||||
MountPoint3 *CustomMountPoint `json:"mp3,omitempty"`
|
|
||||||
MountPoint4 *CustomMountPoint `json:"mp4,omitempty"`
|
|
||||||
MountPoint5 *CustomMountPoint `json:"mp5,omitempty"`
|
|
||||||
MountPoint6 *CustomMountPoint `json:"mp6,omitempty"`
|
|
||||||
MountPoint7 *CustomMountPoint `json:"mp7,omitempty"`
|
|
||||||
NetworkInterface0 *CustomNetworkInterface `json:"net0,omitempty"`
|
|
||||||
NetworkInterface1 *CustomNetworkInterface `json:"net1,omitempty"`
|
|
||||||
NetworkInterface2 *CustomNetworkInterface `json:"net2,omitempty"`
|
|
||||||
NetworkInterface3 *CustomNetworkInterface `json:"net3,omitempty"`
|
|
||||||
NetworkInterface4 *CustomNetworkInterface `json:"net4,omitempty"`
|
|
||||||
NetworkInterface5 *CustomNetworkInterface `json:"net5,omitempty"`
|
|
||||||
NetworkInterface6 *CustomNetworkInterface `json:"net6,omitempty"`
|
|
||||||
NetworkInterface7 *CustomNetworkInterface `json:"net7,omitempty"`
|
|
||||||
OSType *string `json:"ostype,omitempty"`
|
OSType *string `json:"ostype,omitempty"`
|
||||||
Protection *types.CustomBool `json:"protection,omitempty"`
|
Protection *types.CustomBool `json:"protection,omitempty"`
|
||||||
RootFS *CustomRootFS `json:"rootfs,omitempty"`
|
RootFS *CustomRootFS `json:"rootfs,omitempty"`
|
||||||
@ -299,8 +288,8 @@ func (r *CustomFeatures) EncodeValues(key string, v *url.Values) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeValues converts a CustomDevicePassthrough struct to a URL value.
|
// EncodeValues converts a CustomPassthroughDevice struct to a URL value.
|
||||||
func (r *CustomDevicePassthrough) EncodeValues(key string, v *url.Values) error {
|
func (r *CustomPassthroughDevice) EncodeValues(key string, v *url.Values) error {
|
||||||
var values []string
|
var values []string
|
||||||
|
|
||||||
if r.DenyWrite != nil {
|
if r.DenyWrite != nil {
|
||||||
@ -334,14 +323,14 @@ func (r *CustomDevicePassthrough) EncodeValues(key string, v *url.Values) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeValues converts a CustomDevicePassthroughArray array to multiple URL values.
|
// EncodeValues converts a CustomPassthroughDevices array to multiple URL values.
|
||||||
func (r CustomDevicePassthroughArray) EncodeValues(
|
func (r CustomPassthroughDevices) EncodeValues(
|
||||||
key string,
|
_ string,
|
||||||
v *url.Values,
|
v *url.Values,
|
||||||
) error {
|
) error {
|
||||||
for i, d := range r {
|
for key, d := range r {
|
||||||
if err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v); err != nil {
|
if err := d.EncodeValues(key, v); err != nil {
|
||||||
return fmt.Errorf("failed to encode CustomDevicePassthroughArray: %w", err)
|
return fmt.Errorf("failed to encode CustomPassthroughDevices: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,7 +343,7 @@ func (r *CustomMountPoint) EncodeValues(key string, v *url.Values) error {
|
|||||||
|
|
||||||
if r.ACL != nil {
|
if r.ACL != nil {
|
||||||
if *r.ACL {
|
if *r.ACL {
|
||||||
values = append(values, "acl=%d")
|
values = append(values, "acl=1")
|
||||||
} else {
|
} else {
|
||||||
values = append(values, "acl=0")
|
values = append(values, "acl=0")
|
||||||
}
|
}
|
||||||
@ -421,14 +410,14 @@ func (r *CustomMountPoint) EncodeValues(key string, v *url.Values) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeValues converts a CustomMountPointArray array to multiple URL values.
|
// EncodeValues converts a CustomMountPoints array to multiple URL values.
|
||||||
func (r CustomMountPointArray) EncodeValues(
|
func (r CustomMountPoints) EncodeValues(
|
||||||
key string,
|
_ string,
|
||||||
v *url.Values,
|
v *url.Values,
|
||||||
) error {
|
) error {
|
||||||
for i, d := range r {
|
for key, d := range r {
|
||||||
if err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v); err != nil {
|
if err := d.EncodeValues(key, v); err != nil {
|
||||||
return fmt.Errorf("failed to encode CustomMountPointArray: %w", err)
|
return fmt.Errorf("failed to encode CustomMountPoints: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,14 +498,14 @@ func (r *CustomNetworkInterface) EncodeValues(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeValues converts a CustomNetworkInterfaceArray array to multiple URL values.
|
// EncodeValues converts a CustomNetworkInterfaces array to multiple URL values.
|
||||||
func (r CustomNetworkInterfaceArray) EncodeValues(
|
func (r CustomNetworkInterfaces) EncodeValues(
|
||||||
key string,
|
_ string,
|
||||||
v *url.Values,
|
v *url.Values,
|
||||||
) error {
|
) error {
|
||||||
for i, d := range r {
|
for key, d := range r {
|
||||||
if err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v); err != nil {
|
if err := d.EncodeValues(key, v); err != nil {
|
||||||
return fmt.Errorf("failed to encode CustomNetworkInterfaceArray: %w", err)
|
return fmt.Errorf("failed to encode CustomNetworkInterfaces: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -529,7 +518,7 @@ func (r *CustomRootFS) EncodeValues(key string, v *url.Values) error {
|
|||||||
|
|
||||||
if r.ACL != nil {
|
if r.ACL != nil {
|
||||||
if *r.ACL {
|
if *r.ACL {
|
||||||
values = append(values, "acl=%d")
|
values = append(values, "acl=1")
|
||||||
} else {
|
} else {
|
||||||
values = append(values, "acl=0")
|
values = append(values, "acl=0")
|
||||||
}
|
}
|
||||||
@ -562,7 +551,7 @@ func (r *CustomRootFS) EncodeValues(key string, v *url.Values) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if r.Replicate != nil {
|
if r.Replicate != nil {
|
||||||
if *r.ReadOnly {
|
if *r.Replicate {
|
||||||
values = append(values, "replicate=1")
|
values = append(values, "replicate=1")
|
||||||
} else {
|
} else {
|
||||||
values = append(values, "replicate=0")
|
values = append(values, "replicate=0")
|
||||||
@ -659,13 +648,13 @@ func (r *CustomFeatures) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON converts a CustomDevicePassthrough string to an object.
|
// UnmarshalJSON converts a CustomPassthroughDevice string to an object.
|
||||||
func (r *CustomDevicePassthrough) UnmarshalJSON(b []byte) error {
|
func (r *CustomPassthroughDevice) UnmarshalJSON(b []byte) error {
|
||||||
var s string
|
var s string
|
||||||
|
|
||||||
err := json.Unmarshal(b, &s)
|
err := json.Unmarshal(b, &s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to unmarshal CustomDevicePassthrough: %w", err)
|
return fmt.Errorf("unable to unmarshal CustomPassthroughDevice: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pairs := strings.Split(s, ",")
|
pairs := strings.Split(s, ",")
|
||||||
@ -947,3 +936,66 @@ func (r *CustomStartupBehavior) UnmarshalJSON(b []byte) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals the data from the JSON response, populating the CustomStorageDevices field.
|
||||||
|
func (d *GetResponseData) UnmarshalJSON(b []byte) error {
|
||||||
|
type Alias GetResponseData
|
||||||
|
|
||||||
|
var data Alias
|
||||||
|
|
||||||
|
// get original struct
|
||||||
|
if err := json.Unmarshal(b, &data); err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var byAttr map[string]interface{}
|
||||||
|
|
||||||
|
// now get map by attribute name
|
||||||
|
err := json.Unmarshal(b, &byAttr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data.PassthroughDevices = make(CustomPassthroughDevices)
|
||||||
|
data.NetworkInterfaces = make(CustomNetworkInterfaces)
|
||||||
|
data.MountPoints = make(CustomMountPoints)
|
||||||
|
|
||||||
|
for key, value := range byAttr {
|
||||||
|
valueStr, ok := value.(string)
|
||||||
|
if !ok {
|
||||||
|
continue // Skip non-string values
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonValue := []byte(`"` + valueStr + `"`)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case regexDeviceKey.MatchString(key):
|
||||||
|
var device CustomPassthroughDevice
|
||||||
|
if e := json.Unmarshal(jsonValue, &device); e != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal %s with value %q: %w", key, valueStr, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
data.PassthroughDevices[key] = &device
|
||||||
|
|
||||||
|
case regexNetworkKey.MatchString(key):
|
||||||
|
var net CustomNetworkInterface
|
||||||
|
if e := json.Unmarshal(jsonValue, &net); e != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal %s with value %q: %w", key, valueStr, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
data.NetworkInterfaces[key] = &net
|
||||||
|
|
||||||
|
case regexMountPointKey.MatchString(key):
|
||||||
|
var mp CustomMountPoint
|
||||||
|
if e := json.Unmarshal(jsonValue, &mp); e != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal %s with value %q: %w", key, valueStr, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
data.MountPoints[key] = &mp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*d = GetResponseData(data)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -17,6 +17,18 @@ import (
|
|||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//nolint:gochecknoglobals
|
||||||
|
// regexStorageInterface is a regex pattern for matching storage interface names.
|
||||||
|
regexStorageInterface = func(prefix string) *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`^` + prefix + `\d+$`)
|
||||||
|
}
|
||||||
|
// regexPCIDevice is a regex pattern for matching PCI device names.
|
||||||
|
regexPCIDevice = regexp.MustCompile(`^hostpci\d+$`)
|
||||||
|
// regexVirtiofsShare is a regex pattern for matching virtiofs share names.
|
||||||
|
regexVirtiofsShare = regexp.MustCompile(`^virtiofs\d+$`)
|
||||||
|
)
|
||||||
|
|
||||||
// CloneRequestBody contains the data for an virtual machine clone request.
|
// CloneRequestBody contains the data for an virtual machine clone request.
|
||||||
type CloneRequestBody struct {
|
type CloneRequestBody struct {
|
||||||
BandwidthLimit *int `json:"bwlimit,omitempty" url:"bwlimit,omitempty"`
|
BandwidthLimit *int `json:"bwlimit,omitempty" url:"bwlimit,omitempty"`
|
||||||
@ -477,8 +489,7 @@ func (d *GetResponseData) UnmarshalJSON(b []byte) error {
|
|||||||
for _, prefix := range StorageInterfaces {
|
for _, prefix := range StorageInterfaces {
|
||||||
// the device names can overlap with other fields, for example`scsi0` and `scsihw`, so just checking
|
// the device names can overlap with other fields, for example`scsi0` and `scsihw`, so just checking
|
||||||
// the prefix is not enough
|
// the prefix is not enough
|
||||||
r := regexp.MustCompile(`^` + prefix + `\d+$`)
|
if regexStorageInterface(prefix).MatchString(key) {
|
||||||
if r.MatchString(key) {
|
|
||||||
var device CustomStorageDevice
|
var device CustomStorageDevice
|
||||||
if err := json.Unmarshal([]byte(`"`+value.(string)+`"`), &device); err != nil {
|
if err := json.Unmarshal([]byte(`"`+value.(string)+`"`), &device); err != nil {
|
||||||
return fmt.Errorf("failed to unmarshal %s: %w", key, err)
|
return fmt.Errorf("failed to unmarshal %s: %w", key, err)
|
||||||
@ -488,7 +499,7 @@ func (d *GetResponseData) UnmarshalJSON(b []byte) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r := regexp.MustCompile(`^hostpci\d+$`); r.MatchString(key) {
|
if regexPCIDevice.MatchString(key) {
|
||||||
var device CustomPCIDevice
|
var device CustomPCIDevice
|
||||||
if err := json.Unmarshal([]byte(`"`+value.(string)+`"`), &device); err != nil {
|
if err := json.Unmarshal([]byte(`"`+value.(string)+`"`), &device); err != nil {
|
||||||
return fmt.Errorf("failed to unmarshal %s: %w", key, err)
|
return fmt.Errorf("failed to unmarshal %s: %w", key, err)
|
||||||
@ -497,7 +508,7 @@ func (d *GetResponseData) UnmarshalJSON(b []byte) error {
|
|||||||
data.PCIDevices[key] = &device
|
data.PCIDevices[key] = &device
|
||||||
}
|
}
|
||||||
|
|
||||||
if r := regexp.MustCompile(`^virtiofs\d+$`); r.MatchString(key) {
|
if regexVirtiofsShare.MatchString(key) {
|
||||||
var share CustomVirtiofsShare
|
var share CustomVirtiofsShare
|
||||||
if err := json.Unmarshal([]byte(`"`+value.(string)+`"`), &share); err != nil {
|
if err := json.Unmarshal([]byte(`"`+value.(string)+`"`), &share); err != nil {
|
||||||
return fmt.Errorf("failed to unmarshal %s: %w", key, err)
|
return fmt.Errorf("failed to unmarshal %s: %w", key, err)
|
||||||
|
@ -89,7 +89,9 @@ const (
|
|||||||
dvTimeoutDelete = 60
|
dvTimeoutDelete = 60
|
||||||
dvUnprivileged = false
|
dvUnprivileged = false
|
||||||
|
|
||||||
maxResourceVirtualEnvironmentContainerNetworkInterfaces = 8
|
maxNetworkInterfaces = 10
|
||||||
|
maxPassthroughDevices = 8
|
||||||
|
maxMountPoints = 256
|
||||||
|
|
||||||
mkClone = "clone"
|
mkClone = "clone"
|
||||||
mkCloneDatastoreID = "datastore_id"
|
mkCloneDatastoreID = "datastore_id"
|
||||||
@ -686,7 +688,7 @@ func Container() *schema.Resource {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MaxItems: 8,
|
MaxItems: maxMountPoints,
|
||||||
MinItems: 0,
|
MinItems: 0,
|
||||||
},
|
},
|
||||||
mkDevicePassthrough: {
|
mkDevicePassthrough: {
|
||||||
@ -730,7 +732,7 @@ func Container() *schema.Resource {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MaxItems: 8,
|
MaxItems: maxPassthroughDevices,
|
||||||
MinItems: 0,
|
MinItems: 0,
|
||||||
},
|
},
|
||||||
mkNetworkInterface: {
|
mkNetworkInterface: {
|
||||||
@ -795,7 +797,7 @@ func Container() *schema.Resource {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MaxItems: maxResourceVirtualEnvironmentContainerNetworkInterfaces,
|
MaxItems: maxNetworkInterfaces,
|
||||||
MinItems: 0,
|
MinItems: 0,
|
||||||
},
|
},
|
||||||
mkNodeName: {
|
mkNodeName: {
|
||||||
@ -1264,14 +1266,14 @@ func containerCreateClone(ctx context.Context, d *schema.ResourceData, m interfa
|
|||||||
|
|
||||||
devicePassthrough := d.Get(mkDevicePassthrough).([]interface{})
|
devicePassthrough := d.Get(mkDevicePassthrough).([]interface{})
|
||||||
|
|
||||||
devicePassthroughArray := make(
|
passthroughDevices := make(
|
||||||
containers.CustomDevicePassthroughArray,
|
containers.CustomPassthroughDevices,
|
||||||
len(devicePassthrough),
|
len(devicePassthrough),
|
||||||
)
|
)
|
||||||
|
|
||||||
for di, dv := range devicePassthrough {
|
for di, dv := range devicePassthrough {
|
||||||
devicePassthroughMap := dv.(map[string]interface{})
|
devicePassthroughMap := dv.(map[string]interface{})
|
||||||
devicePassthroughObject := containers.CustomDevicePassthrough{}
|
devicePassthroughObject := containers.CustomPassthroughDevice{}
|
||||||
|
|
||||||
denyWrite := types.CustomBool(
|
denyWrite := types.CustomBool(
|
||||||
devicePassthroughMap[mkDevicePassthroughDenyWrite].(bool),
|
devicePassthroughMap[mkDevicePassthroughDenyWrite].(bool),
|
||||||
@ -1287,10 +1289,10 @@ func containerCreateClone(ctx context.Context, d *schema.ResourceData, m interfa
|
|||||||
devicePassthroughObject.Path = path
|
devicePassthroughObject.Path = path
|
||||||
devicePassthroughObject.UID = &uid
|
devicePassthroughObject.UID = &uid
|
||||||
|
|
||||||
devicePassthroughArray[di] = devicePassthroughObject
|
passthroughDevices[fmt.Sprintf("dev%d", di)] = &devicePassthroughObject
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBody.DevicePassthrough = devicePassthroughArray
|
updateBody.PassthroughDevices = passthroughDevices
|
||||||
|
|
||||||
networkInterface := d.Get(mkNetworkInterface).([]interface{})
|
networkInterface := d.Get(mkNetworkInterface).([]interface{})
|
||||||
|
|
||||||
@ -1301,8 +1303,8 @@ func containerCreateClone(ctx context.Context, d *schema.ResourceData, m interfa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
networkInterfaceArray := make(
|
networkInterfaces := make(
|
||||||
containers.CustomNetworkInterfaceArray,
|
containers.CustomNetworkInterfaces,
|
||||||
len(networkInterface),
|
len(networkInterface),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1364,18 +1366,18 @@ func containerCreateClone(ctx context.Context, d *schema.ResourceData, m interfa
|
|||||||
networkInterfaceObject.MTU = &mtu
|
networkInterfaceObject.MTU = &mtu
|
||||||
}
|
}
|
||||||
|
|
||||||
networkInterfaceArray[ni] = networkInterfaceObject
|
networkInterfaces[fmt.Sprintf("net%d", ni)] = &networkInterfaceObject
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBody.NetworkInterfaces = networkInterfaceArray
|
updateBody.NetworkInterfaces = networkInterfaces
|
||||||
|
|
||||||
for i, ni := range updateBody.NetworkInterfaces {
|
for key, ni := range updateBody.NetworkInterfaces {
|
||||||
if !ni.Enabled {
|
if !ni.Enabled {
|
||||||
updateBody.Delete = append(updateBody.Delete, fmt.Sprintf("net%d", i))
|
updateBody.Delete = append(updateBody.Delete, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := len(updateBody.NetworkInterfaces); i < maxResourceVirtualEnvironmentContainerNetworkInterfaces; i++ {
|
for i := len(updateBody.NetworkInterfaces); i < maxNetworkInterfaces; i++ {
|
||||||
updateBody.Delete = append(updateBody.Delete, fmt.Sprintf("net%d", i))
|
updateBody.Delete = append(updateBody.Delete, fmt.Sprintf("net%d", i))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1604,11 +1606,11 @@ func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interf
|
|||||||
memorySwap := memoryBlock[mkMemorySwap].(int)
|
memorySwap := memoryBlock[mkMemorySwap].(int)
|
||||||
|
|
||||||
mountPoint := d.Get(mkMountPoint).([]interface{})
|
mountPoint := d.Get(mkMountPoint).([]interface{})
|
||||||
mountPointArray := make(containers.CustomMountPointArray, 0, len(mountPoint))
|
mountPoints := make(containers.CustomMountPoints, len(mountPoint))
|
||||||
|
|
||||||
// because of default bool values:
|
// because of default bool values:
|
||||||
|
|
||||||
for _, mp := range mountPoint {
|
for mi, mp := range mountPoint {
|
||||||
mountPointMap := mp.(map[string]interface{})
|
mountPointMap := mp.(map[string]interface{})
|
||||||
mountPointObject := containers.CustomMountPoint{}
|
mountPointObject := containers.CustomMountPoint{}
|
||||||
|
|
||||||
@ -1675,13 +1677,13 @@ func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interf
|
|||||||
mountPointObject.MountOptions = &mountOptionsArray
|
mountPointObject.MountOptions = &mountOptionsArray
|
||||||
}
|
}
|
||||||
|
|
||||||
mountPointArray = append(mountPointArray, mountPointObject)
|
mountPoints[fmt.Sprintf("mp%d", mi)] = &mountPointObject
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootFS *containers.CustomRootFS
|
var rootFS *containers.CustomRootFS
|
||||||
|
|
||||||
diskSize := diskBlock[mkDiskSize].(int)
|
diskSize := diskBlock[mkDiskSize].(int)
|
||||||
if diskDatastoreID != "" && (diskSize != dvDiskSize || len(mountPointArray) > 0) {
|
if diskDatastoreID != "" && (diskSize != dvDiskSize || len(mountPoints) > 0) {
|
||||||
// This is a special case where the rootfs size is set to a non-default value at creation time.
|
// This is a special case where the rootfs size is set to a non-default value at creation time.
|
||||||
// see https://pve.proxmox.com/pve-docs/chapter-pct.html#_storage_backed_mount_points
|
// see https://pve.proxmox.com/pve-docs/chapter-pct.html#_storage_backed_mount_points
|
||||||
rootFS = &containers.CustomRootFS{
|
rootFS = &containers.CustomRootFS{
|
||||||
@ -1690,7 +1692,7 @@ func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interf
|
|||||||
}
|
}
|
||||||
|
|
||||||
networkInterface := d.Get(mkNetworkInterface).([]interface{})
|
networkInterface := d.Get(mkNetworkInterface).([]interface{})
|
||||||
networkInterfaceArray := make(containers.CustomNetworkInterfaceArray, len(networkInterface))
|
networkInterfaces := make(containers.CustomNetworkInterfaces, len(networkInterface))
|
||||||
|
|
||||||
for ni, nv := range networkInterface {
|
for ni, nv := range networkInterface {
|
||||||
networkInterfaceMap := nv.(map[string]interface{})
|
networkInterfaceMap := nv.(map[string]interface{})
|
||||||
@ -1750,19 +1752,19 @@ func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interf
|
|||||||
networkInterfaceObject.MTU = &mtu
|
networkInterfaceObject.MTU = &mtu
|
||||||
}
|
}
|
||||||
|
|
||||||
networkInterfaceArray[ni] = networkInterfaceObject
|
networkInterfaces[fmt.Sprintf("net%d", ni)] = &networkInterfaceObject
|
||||||
}
|
}
|
||||||
|
|
||||||
devicePassthrough := d.Get(mkDevicePassthrough).([]interface{})
|
devicePassthrough := d.Get(mkDevicePassthrough).([]interface{})
|
||||||
|
|
||||||
devicePassthroughArray := make(
|
passthroughDevices := make(
|
||||||
containers.CustomDevicePassthroughArray,
|
containers.CustomPassthroughDevices,
|
||||||
len(devicePassthrough),
|
len(devicePassthrough),
|
||||||
)
|
)
|
||||||
|
|
||||||
for di, dv := range devicePassthrough {
|
for di, dv := range devicePassthrough {
|
||||||
devicePassthroughMap := dv.(map[string]interface{})
|
devicePassthroughMap := dv.(map[string]interface{})
|
||||||
devicePassthroughObject := containers.CustomDevicePassthrough{}
|
devicePassthroughObject := containers.CustomPassthroughDevice{}
|
||||||
|
|
||||||
denyWrite := types.CustomBool(
|
denyWrite := types.CustomBool(
|
||||||
devicePassthroughMap[mkDevicePassthroughDenyWrite].(bool),
|
devicePassthroughMap[mkDevicePassthroughDenyWrite].(bool),
|
||||||
@ -1778,7 +1780,7 @@ func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interf
|
|||||||
devicePassthroughObject.Path = path
|
devicePassthroughObject.Path = path
|
||||||
devicePassthroughObject.UID = &uid
|
devicePassthroughObject.UID = &uid
|
||||||
|
|
||||||
devicePassthroughArray[di] = devicePassthroughObject
|
passthroughDevices[fmt.Sprintf("dev%d", di)] = &devicePassthroughObject
|
||||||
}
|
}
|
||||||
|
|
||||||
operatingSystem := d.Get(mkOperatingSystem).([]interface{})
|
operatingSystem := d.Get(mkOperatingSystem).([]interface{})
|
||||||
@ -1828,10 +1830,10 @@ func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interf
|
|||||||
CPUUnits: &cpuUnits,
|
CPUUnits: &cpuUnits,
|
||||||
DatastoreID: &diskDatastoreID,
|
DatastoreID: &diskDatastoreID,
|
||||||
DedicatedMemory: &memoryDedicated,
|
DedicatedMemory: &memoryDedicated,
|
||||||
DevicePassthrough: devicePassthroughArray,
|
PassthroughDevices: passthroughDevices,
|
||||||
Features: features,
|
Features: features,
|
||||||
MountPoints: mountPointArray,
|
MountPoints: mountPoints,
|
||||||
NetworkInterfaces: networkInterfaceArray,
|
NetworkInterfaces: networkInterfaces,
|
||||||
OSTemplateFileVolume: &operatingSystemTemplateFileID,
|
OSTemplateFileVolume: &operatingSystemTemplateFileID,
|
||||||
OSType: &operatingSystemType,
|
OSType: &operatingSystemType,
|
||||||
Protection: &protection,
|
Protection: &protection,
|
||||||
@ -1941,25 +1943,9 @@ func containerGetExistingNetworkInterface(
|
|||||||
return []interface{}{}, fmt.Errorf("error getting container information: %w", err)
|
return []interface{}{}, fmt.Errorf("error getting container information: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:prealloc
|
networkInterfaces := make([]interface{}, 0, len(containerInfo.NetworkInterfaces))
|
||||||
var networkInterfaces []interface{}
|
|
||||||
|
|
||||||
networkInterfaceArray := []*containers.CustomNetworkInterface{
|
|
||||||
containerInfo.NetworkInterface0,
|
|
||||||
containerInfo.NetworkInterface1,
|
|
||||||
containerInfo.NetworkInterface2,
|
|
||||||
containerInfo.NetworkInterface3,
|
|
||||||
containerInfo.NetworkInterface4,
|
|
||||||
containerInfo.NetworkInterface5,
|
|
||||||
containerInfo.NetworkInterface6,
|
|
||||||
containerInfo.NetworkInterface7,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, nv := range networkInterfaceArray {
|
|
||||||
if nv == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for _, nv := range containerInfo.NetworkInterfaces {
|
||||||
networkInterface := map[string]interface{}{}
|
networkInterface := map[string]interface{}{}
|
||||||
|
|
||||||
networkInterface[mkNetworkInterfaceEnabled] = true
|
networkInterface[mkNetworkInterfaceEnabled] = true
|
||||||
@ -2343,85 +2329,56 @@ func containerRead(ctx context.Context, d *schema.ResourceData, m interface{}) d
|
|||||||
initialization[mkInitializationHostname] = ""
|
initialization[mkInitializationHostname] = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
devicePassthroughArray := []*containers.CustomDevicePassthrough{
|
passthroughDevicesMap := make(map[string]interface{}, len(containerConfig.PassthroughDevices))
|
||||||
containerConfig.DevicePassthrough0,
|
|
||||||
containerConfig.DevicePassthrough1,
|
|
||||||
containerConfig.DevicePassthrough2,
|
|
||||||
containerConfig.DevicePassthrough3,
|
|
||||||
containerConfig.DevicePassthrough4,
|
|
||||||
containerConfig.DevicePassthrough5,
|
|
||||||
containerConfig.DevicePassthrough6,
|
|
||||||
containerConfig.DevicePassthrough7,
|
|
||||||
}
|
|
||||||
|
|
||||||
devicePassthroughList := make([]interface{}, 0, len(devicePassthroughArray))
|
for key, dp := range containerConfig.PassthroughDevices {
|
||||||
|
passthroughDevice := map[string]interface{}{}
|
||||||
for _, dp := range devicePassthroughArray {
|
|
||||||
if dp == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
devicePassthrough := map[string]interface{}{}
|
|
||||||
|
|
||||||
if dp.DenyWrite != nil {
|
if dp.DenyWrite != nil {
|
||||||
devicePassthrough[mkDevicePassthroughDenyWrite] = *dp.DenyWrite
|
passthroughDevice[mkDevicePassthroughDenyWrite] = *dp.DenyWrite
|
||||||
} else {
|
} else {
|
||||||
devicePassthrough[mkDevicePassthroughDenyWrite] = false
|
passthroughDevice[mkDevicePassthroughDenyWrite] = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if dp.GID != nil {
|
if dp.GID != nil {
|
||||||
devicePassthrough[mkDevicePassthroughGID] = *dp.GID
|
passthroughDevice[mkDevicePassthroughGID] = *dp.GID
|
||||||
} else {
|
} else {
|
||||||
devicePassthrough[mkDevicePassthroughGID] = 0
|
passthroughDevice[mkDevicePassthroughGID] = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if dp.Mode != nil {
|
if dp.Mode != nil {
|
||||||
devicePassthrough[mkDevicePassthroughMode] = *dp.Mode
|
passthroughDevice[mkDevicePassthroughMode] = *dp.Mode
|
||||||
} else {
|
} else {
|
||||||
devicePassthrough[mkDevicePassthroughMode] = dvDevicePassthroughMode
|
passthroughDevice[mkDevicePassthroughMode] = dvDevicePassthroughMode
|
||||||
}
|
}
|
||||||
|
|
||||||
devicePassthrough[mkDevicePassthroughPath] = dp.Path
|
passthroughDevice[mkDevicePassthroughPath] = dp.Path
|
||||||
|
|
||||||
if dp.UID != nil {
|
if dp.UID != nil {
|
||||||
devicePassthrough[mkDevicePassthroughUID] = *dp.UID
|
passthroughDevice[mkDevicePassthroughUID] = *dp.UID
|
||||||
} else {
|
} else {
|
||||||
devicePassthrough[mkDevicePassthroughUID] = 0
|
passthroughDevice[mkDevicePassthroughUID] = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
devicePassthroughList = append(devicePassthroughList, devicePassthrough)
|
passthroughDevicesMap[key] = passthroughDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
currentDevicePassthrough := d.Get(mkDevicePassthrough).([]interface{})
|
passthroughDevices := utils.OrderedListFromMap(passthroughDevicesMap)
|
||||||
|
currentPassthroughDevices := d.Get(mkDevicePassthrough).([]interface{})
|
||||||
|
|
||||||
if len(clone) > 0 {
|
if len(clone) > 0 {
|
||||||
if len(currentDevicePassthrough) > 0 {
|
if len(currentPassthroughDevices) > 0 {
|
||||||
err := d.Set(mkDevicePassthrough, devicePassthroughList)
|
err := d.Set(mkDevicePassthrough, passthroughDevices)
|
||||||
diags = append(diags, diag.FromErr(err)...)
|
diags = append(diags, diag.FromErr(err)...)
|
||||||
}
|
}
|
||||||
} else if len(devicePassthroughList) > 0 {
|
} else if len(passthroughDevicesMap) > 0 {
|
||||||
err := d.Set(mkDevicePassthrough, devicePassthroughList)
|
err := d.Set(mkDevicePassthrough, passthroughDevices)
|
||||||
diags = append(diags, diag.FromErr(err)...)
|
diags = append(diags, diag.FromErr(err)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
mountPointArray := []*containers.CustomMountPoint{
|
mountPointsMap := make(map[string]interface{}, len(containerConfig.MountPoints))
|
||||||
containerConfig.MountPoint0,
|
|
||||||
containerConfig.MountPoint1,
|
|
||||||
containerConfig.MountPoint2,
|
|
||||||
containerConfig.MountPoint3,
|
|
||||||
containerConfig.MountPoint4,
|
|
||||||
containerConfig.MountPoint5,
|
|
||||||
containerConfig.MountPoint6,
|
|
||||||
containerConfig.MountPoint7,
|
|
||||||
}
|
|
||||||
|
|
||||||
mountPointList := make([]interface{}, 0, len(mountPointArray))
|
|
||||||
|
|
||||||
for _, mp := range mountPointArray {
|
|
||||||
if mp == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for key, mp := range containerConfig.MountPoints {
|
||||||
mountPoint := map[string]interface{}{}
|
mountPoint := map[string]interface{}{}
|
||||||
|
|
||||||
if mp.ACL != nil {
|
if mp.ACL != nil {
|
||||||
@ -2476,42 +2433,27 @@ func containerRead(ctx context.Context, d *schema.ResourceData, m interface{}) d
|
|||||||
|
|
||||||
mountPoint[mkMountPointVolume] = mp.Volume
|
mountPoint[mkMountPointVolume] = mp.Volume
|
||||||
|
|
||||||
mountPointList = append(mountPointList, mountPoint)
|
mountPointsMap[key] = mountPoint
|
||||||
}
|
}
|
||||||
|
|
||||||
currentMountPoint := d.Get(mkMountPoint).([]interface{})
|
mountPoints := utils.OrderedListFromMap(mountPointsMap)
|
||||||
|
currentMountPoints := d.Get(mkMountPoint).([]interface{})
|
||||||
|
|
||||||
if len(clone) > 0 {
|
if len(clone) > 0 {
|
||||||
if len(currentMountPoint) > 0 {
|
if len(currentMountPoints) > 0 {
|
||||||
err := d.Set(mkMountPoint, mountPointList)
|
err := d.Set(mkMountPoint, mountPoints)
|
||||||
diags = append(diags, diag.FromErr(err)...)
|
diags = append(diags, diag.FromErr(err)...)
|
||||||
}
|
}
|
||||||
} else if len(mountPointList) > 0 {
|
} else if len(mountPointsMap) > 0 {
|
||||||
err := d.Set(mkMountPoint, mountPointList)
|
err := d.Set(mkMountPoint, mountPoints)
|
||||||
diags = append(diags, diag.FromErr(err)...)
|
diags = append(diags, diag.FromErr(err)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ipConfigList []interface{}
|
var ipConfigList []interface{}
|
||||||
|
|
||||||
networkInterfaceArray := []*containers.CustomNetworkInterface{
|
networkInterfacesMap := make(map[string]interface{}, len(containerConfig.NetworkInterfaces))
|
||||||
containerConfig.NetworkInterface0,
|
|
||||||
containerConfig.NetworkInterface1,
|
|
||||||
containerConfig.NetworkInterface2,
|
|
||||||
containerConfig.NetworkInterface3,
|
|
||||||
containerConfig.NetworkInterface4,
|
|
||||||
containerConfig.NetworkInterface5,
|
|
||||||
containerConfig.NetworkInterface6,
|
|
||||||
containerConfig.NetworkInterface7,
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:prealloc
|
|
||||||
var networkInterfaceList []interface{}
|
|
||||||
|
|
||||||
for _, nv := range networkInterfaceArray {
|
|
||||||
if nv == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for key, nv := range containerConfig.NetworkInterfaces {
|
||||||
if nv.IPv4Address != nil || nv.IPv4Gateway != nil || nv.IPv6Address != nil ||
|
if nv.IPv4Address != nil || nv.IPv4Gateway != nil || nv.IPv6Address != nil ||
|
||||||
nv.IPv6Gateway != nil {
|
nv.IPv6Gateway != nil {
|
||||||
ipConfig := map[string]interface{}{}
|
ipConfig := map[string]interface{}{}
|
||||||
@ -2604,9 +2546,10 @@ func containerRead(ctx context.Context, d *schema.ResourceData, m interface{}) d
|
|||||||
networkInterface[mkNetworkInterfaceMTU] = 0
|
networkInterface[mkNetworkInterfaceMTU] = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
networkInterfaceList = append(networkInterfaceList, networkInterface)
|
networkInterfacesMap[key] = networkInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
networkInterfaces := utils.OrderedListFromMap(networkInterfacesMap)
|
||||||
initialization[mkInitializationIPConfig] = ipConfigList
|
initialization[mkInitializationIPConfig] = ipConfigList
|
||||||
|
|
||||||
currentInitialization := d.Get(mkInitialization).([]interface{})
|
currentInitialization := d.Get(mkInitialization).([]interface{})
|
||||||
@ -2655,7 +2598,7 @@ func containerRead(ctx context.Context, d *schema.ResourceData, m interface{}) d
|
|||||||
if len(currentNetworkInterface) > 0 {
|
if len(currentNetworkInterface) > 0 {
|
||||||
err := d.Set(
|
err := d.Set(
|
||||||
mkNetworkInterface,
|
mkNetworkInterface,
|
||||||
networkInterfaceList,
|
networkInterfaces,
|
||||||
)
|
)
|
||||||
diags = append(diags, diag.FromErr(err)...)
|
diags = append(diags, diag.FromErr(err)...)
|
||||||
}
|
}
|
||||||
@ -2668,7 +2611,7 @@ func containerRead(ctx context.Context, d *schema.ResourceData, m interface{}) d
|
|||||||
|
|
||||||
diags = append(diags, diag.FromErr(e)...)
|
diags = append(diags, diag.FromErr(e)...)
|
||||||
|
|
||||||
err := d.Set(mkNetworkInterface, networkInterfaceList)
|
err := d.Set(mkNetworkInterface, networkInterfaces)
|
||||||
diags = append(diags, diag.FromErr(err)...)
|
diags = append(diags, diag.FromErr(err)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3051,14 +2994,14 @@ func containerUpdate(ctx context.Context, d *schema.ResourceData, m interface{})
|
|||||||
_, newDevicePassthrough := d.GetChange(mkDevicePassthrough)
|
_, newDevicePassthrough := d.GetChange(mkDevicePassthrough)
|
||||||
|
|
||||||
devicePassthrough := newDevicePassthrough.([]interface{})
|
devicePassthrough := newDevicePassthrough.([]interface{})
|
||||||
devicePassthroughArray := make(
|
passthroughDevices := make(
|
||||||
containers.CustomDevicePassthroughArray,
|
containers.CustomPassthroughDevices,
|
||||||
len(devicePassthrough),
|
len(devicePassthrough),
|
||||||
)
|
)
|
||||||
|
|
||||||
for i, dp := range devicePassthrough {
|
for i, dp := range devicePassthrough {
|
||||||
devicePassthroughMap := dp.(map[string]interface{})
|
devicePassthroughMap := dp.(map[string]interface{})
|
||||||
devicePassthroughObject := containers.CustomDevicePassthrough{}
|
devicePassthroughObject := containers.CustomPassthroughDevice{}
|
||||||
|
|
||||||
denyWrite := types.CustomBool(devicePassthroughMap[mkDevicePassthroughDenyWrite].(bool))
|
denyWrite := types.CustomBool(devicePassthroughMap[mkDevicePassthroughDenyWrite].(bool))
|
||||||
gid := devicePassthroughMap[mkDevicePassthroughGID].(int)
|
gid := devicePassthroughMap[mkDevicePassthroughGID].(int)
|
||||||
@ -3072,10 +3015,10 @@ func containerUpdate(ctx context.Context, d *schema.ResourceData, m interface{})
|
|||||||
devicePassthroughObject.Path = path
|
devicePassthroughObject.Path = path
|
||||||
devicePassthroughObject.UID = &uid
|
devicePassthroughObject.UID = &uid
|
||||||
|
|
||||||
devicePassthroughArray[i] = devicePassthroughObject
|
passthroughDevices[fmt.Sprintf("dev%d", i)] = &devicePassthroughObject
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBody.DevicePassthrough = devicePassthroughArray
|
updateBody.PassthroughDevices = passthroughDevices
|
||||||
|
|
||||||
rebootRequired = true
|
rebootRequired = true
|
||||||
}
|
}
|
||||||
@ -3085,8 +3028,8 @@ func containerUpdate(ctx context.Context, d *schema.ResourceData, m interface{})
|
|||||||
_, newMountPoints := d.GetChange(mkMountPoint)
|
_, newMountPoints := d.GetChange(mkMountPoint)
|
||||||
|
|
||||||
mountPoints := newMountPoints.([]interface{})
|
mountPoints := newMountPoints.([]interface{})
|
||||||
mountPointArray := make(
|
mountPointsMap := make(
|
||||||
containers.CustomMountPointArray,
|
containers.CustomMountPoints,
|
||||||
len(mountPoints),
|
len(mountPoints),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -3143,10 +3086,10 @@ func containerUpdate(ctx context.Context, d *schema.ResourceData, m interface{})
|
|||||||
mountPointObject.MountOptions = &mountOptionsArray
|
mountPointObject.MountOptions = &mountOptionsArray
|
||||||
}
|
}
|
||||||
|
|
||||||
mountPointArray[i] = mountPointObject
|
mountPointsMap[fmt.Sprintf("mp%d", i)] = &mountPointObject
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBody.MountPoints = mountPointArray
|
updateBody.MountPoints = mountPointsMap
|
||||||
|
|
||||||
rebootRequired = true
|
rebootRequired = true
|
||||||
}
|
}
|
||||||
@ -3163,8 +3106,8 @@ func containerUpdate(ctx context.Context, d *schema.ResourceData, m interface{})
|
|||||||
|
|
||||||
if d.HasChange(mkInitialization) ||
|
if d.HasChange(mkInitialization) ||
|
||||||
d.HasChange(mkNetworkInterface) {
|
d.HasChange(mkNetworkInterface) {
|
||||||
networkInterfaceArray := make(
|
networkInterfaces := make(
|
||||||
containers.CustomNetworkInterfaceArray,
|
containers.CustomNetworkInterfaces,
|
||||||
len(networkInterface),
|
len(networkInterface),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -3226,18 +3169,18 @@ func containerUpdate(ctx context.Context, d *schema.ResourceData, m interface{})
|
|||||||
networkInterfaceObject.MTU = &mtu
|
networkInterfaceObject.MTU = &mtu
|
||||||
}
|
}
|
||||||
|
|
||||||
networkInterfaceArray[ni] = networkInterfaceObject
|
networkInterfaces[fmt.Sprintf("net%d", ni)] = &networkInterfaceObject
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBody.NetworkInterfaces = networkInterfaceArray
|
updateBody.NetworkInterfaces = networkInterfaces
|
||||||
|
|
||||||
for i, ni := range updateBody.NetworkInterfaces {
|
for key, ni := range updateBody.NetworkInterfaces {
|
||||||
if !ni.Enabled {
|
if !ni.Enabled {
|
||||||
updateBody.Delete = append(updateBody.Delete, fmt.Sprintf("net%d", i))
|
updateBody.Delete = append(updateBody.Delete, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := len(updateBody.NetworkInterfaces); i < maxResourceVirtualEnvironmentContainerNetworkInterfaces; i++ {
|
for i := len(updateBody.NetworkInterfaces); i < maxNetworkInterfaces; i++ {
|
||||||
updateBody.Delete = append(updateBody.Delete, fmt.Sprintf("net%d", i))
|
updateBody.Delete = append(updateBody.Delete, fmt.Sprintf("net%d", i))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,10 +8,14 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OrderedListFromMap generates a list from a map's values. The values are sorted based on the map's keys.
|
// OrderedListFromMap generates a list from a map's values. The values are sorted based on the map's keys.
|
||||||
|
// The sorting is done using a custom comparison function that compares the keys with special rules, assuming they
|
||||||
|
// are strings representing device names or similar, i.e. "disk0", "net1", etc.
|
||||||
func OrderedListFromMap(inputMap map[string]interface{}) []interface{} {
|
func OrderedListFromMap(inputMap map[string]interface{}) []interface{} {
|
||||||
itemCount := len(inputMap)
|
itemCount := len(inputMap)
|
||||||
keyList := make([]string, itemCount)
|
keyList := make([]string, itemCount)
|
||||||
@ -22,11 +26,60 @@ func OrderedListFromMap(inputMap map[string]interface{}) []interface{} {
|
|||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(keyList)
|
slices.SortFunc(keyList, compareWithPrefix)
|
||||||
|
|
||||||
return OrderedListFromMapByKeyValues(inputMap, keyList)
|
return OrderedListFromMapByKeyValues(inputMap, keyList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CompareWithPrefix compares two string values with special rules:
|
||||||
|
// - If both start with the same prefix, trims the prefix and compares the rest as numbers if possible.
|
||||||
|
// - If numbers are equal, falls back to string comparison (preserving digit formatting).
|
||||||
|
// - If numeric parsing fails, falls back to string comparison.
|
||||||
|
// - If prefixes differ, compares the whole values as strings.
|
||||||
|
func compareWithPrefix(a, b string) int {
|
||||||
|
prefix := commonPrefix(a, b)
|
||||||
|
|
||||||
|
if prefix != "" {
|
||||||
|
aRest := strings.TrimPrefix(a, prefix)
|
||||||
|
bRest := strings.TrimPrefix(b, prefix)
|
||||||
|
|
||||||
|
aNum, aErr := strconv.Atoi(aRest)
|
||||||
|
bNum, bErr := strconv.Atoi(bRest)
|
||||||
|
|
||||||
|
if aErr == nil && bErr == nil {
|
||||||
|
if aNum != bNum {
|
||||||
|
if aNum < bNum {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
// numeric values equal, fallback to string comparison
|
||||||
|
return strings.Compare(aRest, bRest)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Compare(aRest, bRest)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Compare(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// commonPrefix returns the longest common prefix of two strings.
|
||||||
|
func commonPrefix(a, b string) string {
|
||||||
|
minLen := len(a)
|
||||||
|
if len(b) < minLen {
|
||||||
|
minLen = len(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range minLen {
|
||||||
|
if a[i] != b[i] {
|
||||||
|
return a[:i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return a[:minLen]
|
||||||
|
}
|
||||||
|
|
||||||
// ListResourcesAttributeValue generates a list of strings from a Terraform resource list (which is list of maps).
|
// ListResourcesAttributeValue generates a list of strings from a Terraform resource list (which is list of maps).
|
||||||
// The list is generated by extracting a specific key attribute from each resource. If the attribute is not found in a
|
// The list is generated by extracting a specific key attribute from each resource. If the attribute is not found in a
|
||||||
// resource, it is skipped.
|
// resource, it is skipped.
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* 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 utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOrderedListFromMap(t *testing.T) {
|
func TestOrderedListFromMap(t *testing.T) {
|
||||||
@ -72,3 +80,41 @@ func TestOrderedListFromMapByKeyValues(t *testing.T) {
|
|||||||
t.Errorf("OrderedListFromMapByKeyValues() = %v, want %v", result, expected)
|
t.Errorf("OrderedListFromMapByKeyValues() = %v, want %v", result, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCompareWithPrefix(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
a string
|
||||||
|
b string
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{"equal", args{"a", "a"}, 0},
|
||||||
|
{"a < b", args{"a", "b"}, -1},
|
||||||
|
{"b > a", args{"b", "a"}, 1},
|
||||||
|
{"a < b with prefix", args{"a1", "a2"}, -1},
|
||||||
|
{"b > a with prefix", args{"a2", "a1"}, 1},
|
||||||
|
{"a < b with different prefix", args{"a1", "b1"}, -1},
|
||||||
|
{"b > a with different prefix", args{"b1", "a1"}, 1},
|
||||||
|
{"a < b with different prefix and numbers", args{"a1", "a10"}, -1},
|
||||||
|
{"b > a with different prefix and numbers", args{"a10", "a1"}, 1},
|
||||||
|
{"a < b with different prefix and numbers", args{"a10", "b1"}, -1},
|
||||||
|
{"b > a with different prefix and numbers", args{"b1", "a10"}, 1},
|
||||||
|
{"a < b with different prefix and numbers", args{"a10", "b10"}, -1},
|
||||||
|
{"b > a with different prefix and numbers", args{"b10", "a10"}, 1},
|
||||||
|
{"b > a with different numbers leading zeros", args{"dev01", "dev001"}, 1},
|
||||||
|
{"a > b with different numbers leading zeros", args{"dev001", "dev01"}, -1},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
assert.Equalf(t, tt.want, compareWithPrefix(tt.args.a, tt.args.b), "compareWithPrefix(%v, %v)", tt.args.a, tt.args.b)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user