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

feat(lxc): add support for device_passthrough config option (#1706)

Signed-off-by: Tarasa24 <tarasa24@tarasa24.dev>
This commit is contained in:
Petr Gajdosik 2025-01-16 23:58:49 +01:00 committed by GitHub
parent c57dc78119
commit 7cbd1b46fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 427 additions and 98 deletions

View File

@ -170,6 +170,13 @@ output "ubuntu_container_public_key" {
Can be specified with a unit suffix (e.g. `10G`).
- `volume` (Required) Volume, device or directory to mount into the
container.
- `device_passthrough` - (Optional) Device to pass through to the container (multiple blocks supported).
- `deny_write` - (Optional) Deny the container to write to the device (defaults to `false`).
- `gid` - (Optional) Group ID to be assigned to the device node.
- `mode` - (Optional) Access mode to be set on the device node. Must be a
4-digit octal number.
- `path` - (Required) Device to pass through to the container (e.g. `/dev/sda`).
- `uid` - (Optional) User ID to be assigned to the device node.
- `network_interface` - (Optional) A network interface (multiple blocks
supported).
- `bridge` - (Optional) The name of the network bridge (defaults

View File

@ -75,6 +75,9 @@ func TestAccResourceContainer(t *testing.T) {
size = "4G"
path = "mnt/local"
}
device_passthrough {
path = "/dev/zero"
}
description = <<-EOT
my
description
@ -199,6 +202,9 @@ func TestAccResourceContainer(t *testing.T) {
size = "4G"
path = "mnt/local"
}
device_passthrough {
path = "/dev/zero"
}
initialization {
hostname = "test"
ip_config {

View File

@ -31,45 +31,46 @@ type CloneRequestBody struct {
// CreateRequestBody contains the data for a user create request.
type CreateRequestBody struct {
BandwidthLimit *float64 `json:"bwlimit,omitempty" url:"bwlimit,omitempty"`
ConsoleEnabled *types.CustomBool `json:"console,omitempty" url:"console,omitempty,int"`
ConsoleMode *string `json:"cmode,omitempty" url:"cmode,omitempty"`
CPUArchitecture *string `json:"arch,omitempty" url:"arch,omitempty"`
CPUCores *int `json:"cores,omitempty" url:"cores,omitempty"`
CPULimit *int `json:"cpulimit,omitempty" url:"cpulimit,omitempty"`
CPUUnits *int `json:"cpuunits,omitempty" url:"cpuunits,omitempty"`
DatastoreID *string `json:"storage,omitempty" url:"storage,omitempty"`
DedicatedMemory *int `json:"memory,omitempty" url:"memory,omitempty"`
Delete []string `json:"delete,omitempty" url:"delete,omitempty"`
Description *string `json:"description,omitempty" url:"description,omitempty"`
DNSDomain *string `json:"searchdomain,omitempty" url:"searchdomain,omitempty"`
DNSServer *string `json:"nameserver,omitempty" url:"nameserver,omitempty"`
Features *CustomFeatures `json:"features,omitempty" url:"features,omitempty"`
Force *types.CustomBool `json:"force,omitempty" url:"force,omitempty,int"`
HookScript *string `json:"hookscript,omitempty" url:"hookscript,omitempty"`
Hostname *string `json:"hostname,omitempty" url:"hostname,omitempty"`
IgnoreUnpackErrors *types.CustomBool `json:"ignore-unpack-errors,omitempty" url:"force,omitempty,int"`
Lock *string `json:"lock,omitempty" url:"lock,omitempty,int"`
MountPoints CustomMountPointArray `json:"mp,omitempty" url:"mp,omitempty,numbered"`
NetworkInterfaces CustomNetworkInterfaceArray `json:"net,omitempty" url:"net,omitempty,numbered"`
OSTemplateFileVolume *string `json:"ostemplate,omitempty" url:"ostemplate,omitempty"`
OSType *string `json:"ostype,omitempty" url:"ostype,omitempty"`
Password *string `json:"password,omitempty" url:"password,omitempty"`
PoolID *string `json:"pool,omitempty" url:"pool,omitempty"`
Protection *types.CustomBool `json:"protection,omitempty" url:"protection,omitempty,int"`
Restore *types.CustomBool `json:"restore,omitempty" url:"restore,omitempty,int"`
RootFS *CustomRootFS `json:"rootfs,omitempty" url:"rootfs,omitempty"`
SSHKeys *CustomSSHKeys `json:"ssh-public-keys,omitempty" url:"ssh-public-keys,omitempty"`
Start *types.CustomBool `json:"start,omitempty" url:"start,omitempty,int"`
StartOnBoot *types.CustomBool `json:"onboot,omitempty" url:"onboot,omitempty,int"`
StartupBehavior *CustomStartupBehavior `json:"startup,omitempty" url:"startup,omitempty"`
Swap *int `json:"swap,omitempty" url:"swap,omitempty"`
Tags *string `json:"tags,omitempty" url:"tags,omitempty"`
Template *types.CustomBool `json:"template,omitempty" url:"template,omitempty,int"`
TTY *int `json:"tty,omitempty" url:"tty,omitempty"`
Unique *types.CustomBool `json:"unique,omitempty" url:"unique,omitempty,int"`
Unprivileged *types.CustomBool `json:"unprivileged,omitempty" url:"unprivileged,omitempty,int"`
VMID *int `json:"vmid,omitempty" url:"vmid,omitempty"`
BandwidthLimit *float64 `json:"bwlimit,omitempty" url:"bwlimit,omitempty"`
ConsoleEnabled *types.CustomBool `json:"console,omitempty" url:"console,omitempty,int"`
ConsoleMode *string `json:"cmode,omitempty" url:"cmode,omitempty"`
CPUArchitecture *string `json:"arch,omitempty" url:"arch,omitempty"`
CPUCores *int `json:"cores,omitempty" url:"cores,omitempty"`
CPULimit *int `json:"cpulimit,omitempty" url:"cpulimit,omitempty"`
CPUUnits *int `json:"cpuunits,omitempty" url:"cpuunits,omitempty"`
DatastoreID *string `json:"storage,omitempty" url:"storage,omitempty"`
DedicatedMemory *int `json:"memory,omitempty" url:"memory,omitempty"`
Delete []string `json:"delete,omitempty" url:"delete,omitempty"`
Description *string `json:"description,omitempty" url:"description,omitempty"`
DNSDomain *string `json:"searchdomain,omitempty" url:"searchdomain,omitempty"`
DNSServer *string `json:"nameserver,omitempty" url:"nameserver,omitempty"`
Features *CustomFeatures `json:"features,omitempty" url:"features,omitempty"`
Force *types.CustomBool `json:"force,omitempty" url:"force,omitempty,int"`
HookScript *string `json:"hookscript,omitempty" url:"hookscript,omitempty"`
Hostname *string `json:"hostname,omitempty" url:"hostname,omitempty"`
IgnoreUnpackErrors *types.CustomBool `json:"ignore-unpack-errors,omitempty" url:"force,omitempty,int"`
Lock *string `json:"lock,omitempty" url:"lock,omitempty,int"`
MountPoints CustomMountPointArray `json:"mp,omitempty" url:"mp,omitempty,numbered"`
DevicePassthrough CustomDevicePassthroughArray `json:"dev,omitempty" url:"dev,omitempty,numbered"`
NetworkInterfaces CustomNetworkInterfaceArray `json:"net,omitempty" url:"net,omitempty,numbered"`
OSTemplateFileVolume *string `json:"ostemplate,omitempty" url:"ostemplate,omitempty"`
OSType *string `json:"ostype,omitempty" url:"ostype,omitempty"`
Password *string `json:"password,omitempty" url:"password,omitempty"`
PoolID *string `json:"pool,omitempty" url:"pool,omitempty"`
Protection *types.CustomBool `json:"protection,omitempty" url:"protection,omitempty,int"`
Restore *types.CustomBool `json:"restore,omitempty" url:"restore,omitempty,int"`
RootFS *CustomRootFS `json:"rootfs,omitempty" url:"rootfs,omitempty"`
SSHKeys *CustomSSHKeys `json:"ssh-public-keys,omitempty" url:"ssh-public-keys,omitempty"`
Start *types.CustomBool `json:"start,omitempty" url:"start,omitempty,int"`
StartOnBoot *types.CustomBool `json:"onboot,omitempty" url:"onboot,omitempty,int"`
StartupBehavior *CustomStartupBehavior `json:"startup,omitempty" url:"startup,omitempty"`
Swap *int `json:"swap,omitempty" url:"swap,omitempty"`
Tags *string `json:"tags,omitempty" url:"tags,omitempty"`
Template *types.CustomBool `json:"template,omitempty" url:"template,omitempty,int"`
TTY *int `json:"tty,omitempty" url:"tty,omitempty"`
Unique *types.CustomBool `json:"unique,omitempty" url:"unique,omitempty,int"`
Unprivileged *types.CustomBool `json:"unprivileged,omitempty" url:"unprivileged,omitempty,int"`
VMID *int `json:"vmid,omitempty" url:"vmid,omitempty"`
}
// CustomFeatures contains the values for the "features" property.
@ -116,6 +117,18 @@ type CustomNetworkInterface struct {
Type *string `json:"type,omitempty" url:"type,omitempty"`
}
// CustomDevicePassthroughArray is an array of CustomDevicePassthrough.
type CustomDevicePassthroughArray []CustomDevicePassthrough
// CustomDevicePassthrough contains the values for the "dev[n]" properties.
type CustomDevicePassthrough struct {
DenyWrite *types.CustomBool `json:"deny-write,omitempty" url:"deny-write,omitempty,int"`
Path string `json:"path" url:"path"`
UID *int `json:"uid,omitempty" url:"uid,omitempty"`
GID *int `json:"gid,omitempty" url:"gid,omitempty"`
Mode *string `json:"mode,omitempty" url:"mode,omitempty"`
}
// CustomNetworkInterfaceArray is an array of CustomNetworkInterface.
type CustomNetworkInterfaceArray []CustomNetworkInterface
@ -153,48 +166,56 @@ type GetResponseBody struct {
// GetResponseData contains the data from a user get response.
type GetResponseData struct {
ConsoleEnabled *types.CustomBool `json:"console,omitempty"`
ConsoleMode *string `json:"cmode,omitempty"`
CPUArchitecture *string `json:"arch,omitempty"`
CPUCores *int `json:"cores,omitempty"`
CPULimit *types.CustomInt `json:"cpulimit,omitempty"`
CPUUnits *int `json:"cpuunits,omitempty"`
DedicatedMemory *int `json:"memory,omitempty"`
Description *string `json:"description,omitempty"`
Digest string `json:"digest"`
DNSDomain *string `json:"searchdomain,omitempty"`
DNSServer *string `json:"nameserver,omitempty"`
Features *CustomFeatures `json:"features,omitempty"`
HookScript *string `json:"hookscript,omitempty"`
Hostname *string `json:"hostname,omitempty"`
Lock *types.CustomBool `json:"lock,omitempty"`
LXCConfiguration *[][2]string `json:"lxc,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"`
Protection *types.CustomBool `json:"protection,omitempty"`
RootFS *CustomRootFS `json:"rootfs,omitempty"`
StartOnBoot *types.CustomBool `json:"onboot,omitempty"`
StartupBehavior *CustomStartupBehavior `json:"startup,omitempty"`
Swap *int `json:"swap,omitempty"`
Tags *string `json:"tags,omitempty"`
Template *types.CustomBool `json:"template,omitempty"`
TTY *int `json:"tty,omitempty"`
Unprivileged *types.CustomBool `json:"unprivileged,omitempty"`
ConsoleEnabled *types.CustomBool `json:"console,omitempty"`
ConsoleMode *string `json:"cmode,omitempty"`
CPUArchitecture *string `json:"arch,omitempty"`
CPUCores *int `json:"cores,omitempty"`
CPULimit *types.CustomInt `json:"cpulimit,omitempty"`
CPUUnits *int `json:"cpuunits,omitempty"`
DedicatedMemory *int `json:"memory,omitempty"`
Description *string `json:"description,omitempty"`
Digest string `json:"digest"`
DNSDomain *string `json:"searchdomain,omitempty"`
DNSServer *string `json:"nameserver,omitempty"`
Features *CustomFeatures `json:"features,omitempty"`
HookScript *string `json:"hookscript,omitempty"`
Hostname *string `json:"hostname,omitempty"`
Lock *types.CustomBool `json:"lock,omitempty"`
LXCConfiguration *[][2]string `json:"lxc,omitempty"`
DevicePassthrough0 *CustomDevicePassthrough `json:"dev0,omitempty"`
DevicePassthrough1 *CustomDevicePassthrough `json:"dev1,omitempty"`
DevicePassthrough2 *CustomDevicePassthrough `json:"dev2,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"`
Protection *types.CustomBool `json:"protection,omitempty"`
RootFS *CustomRootFS `json:"rootfs,omitempty"`
StartOnBoot *types.CustomBool `json:"onboot,omitempty"`
StartupBehavior *CustomStartupBehavior `json:"startup,omitempty"`
Swap *int `json:"swap,omitempty"`
Tags *string `json:"tags,omitempty"`
Template *types.CustomBool `json:"template,omitempty"`
TTY *int `json:"tty,omitempty"`
Unprivileged *types.CustomBool `json:"unprivileged,omitempty"`
}
// GetStatusResponseBody contains the body from a container get status response.
@ -276,6 +297,55 @@ func (r *CustomFeatures) EncodeValues(key string, v *url.Values) error {
return nil
}
// EncodeValues converts a CustomDevicePassthrough struct to a URL value.
func (r *CustomDevicePassthrough) EncodeValues(key string, v *url.Values) error {
var values []string
if r.DenyWrite != nil {
if *r.DenyWrite {
values = append(values, "deny-write=1")
} else {
values = append(values, "deny-write=0")
}
}
if r.Path != "" {
values = append(values, fmt.Sprintf("path=%s", r.Path))
}
if r.UID != nil {
values = append(values, fmt.Sprintf("uid=%d", *r.UID))
}
if r.GID != nil {
values = append(values, fmt.Sprintf("gid=%d", *r.GID))
}
if r.Mode != nil && *r.Mode != "" {
values = append(values, fmt.Sprintf("mode=%s", *r.Mode))
}
if len(values) > 0 {
v.Add(key, strings.Join(values, ","))
}
return nil
}
// EncodeValues converts a CustomDevicePassthroughArray array to multiple URL values.
func (r CustomDevicePassthroughArray) 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 CustomDevicePassthroughArray: %w", err)
}
}
return nil
}
// EncodeValues converts a CustomMountPoint struct to a URL value.
func (r *CustomMountPoint) EncodeValues(key string, v *url.Values) error {
var values []string
@ -587,6 +657,56 @@ func (r *CustomFeatures) UnmarshalJSON(b []byte) error {
return nil
}
// UnmarshalJSON converts a CustomDevicePassthrough string to an object.
func (r *CustomDevicePassthrough) UnmarshalJSON(b []byte) error {
var s string
err := json.Unmarshal(b, &s)
if err != nil {
return fmt.Errorf("unable to unmarshal CustomDevicePassthrough: %w", err)
}
pairs := strings.Split(s, ",")
var path string
for _, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 1 {
path = v[0]
} else if len(v) == 2 {
switch v[0] {
case "deny-write":
bv := types.CustomBool(v[1] == "1")
r.DenyWrite = &bv
case "path":
path = v[1]
case "uid":
iv, err := strconv.Atoi(v[1])
if err != nil {
return fmt.Errorf("unable to unmarshal 'uid': %w", err)
}
r.UID = &iv
case "gid":
iv, err := strconv.Atoi(v[1])
if err != nil {
return fmt.Errorf("unable to unmarshal 'gid': %w", err)
}
r.GID = &iv
case "mode":
r.Mode = &v[1]
}
}
}
r.Path = path
return nil
}
// UnmarshalJSON converts a CustomMountPoint string to an object.
func (r *CustomMountPoint) UnmarshalJSON(b []byte) error {
var s string

View File

@ -10,6 +10,7 @@ import (
"context"
"errors"
"fmt"
"regexp"
"sort"
"strconv"
"strings"
@ -140,6 +141,12 @@ const (
mkMountPointShared = "shared"
mkMountPointSize = "size"
mkMountPointVolume = "volume"
mkDevicePassthroughDenyWrite = "deny_write"
mkDevicePassthrough = "device_passthrough" // #nosec G101
mkDevicePassthroughPath = "path"
mkDevicePassthroughUID = "uid"
mkDevicePassthroughGID = "gid"
mkDevicePassthroughMode = "mode"
mkNetworkInterface = "network_interface"
mkNetworkInterfaceBridge = "bridge"
mkNetworkInterfaceEnabled = "enabled"
@ -680,6 +687,49 @@ func Container() *schema.Resource {
MaxItems: 8,
MinItems: 0,
},
mkDevicePassthrough: {
Type: schema.TypeList,
Description: "Device to pass through to the container",
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkDevicePassthroughDenyWrite: {
Type: schema.TypeBool,
Description: "Deny the container to write to the device",
Optional: true,
Default: false,
},
mkDevicePassthroughGID: {
Type: schema.TypeInt,
Description: "Group ID to be assigned to the device node",
Optional: true,
ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(0)),
},
mkDevicePassthroughMode: {
Type: schema.TypeString,
Description: "Access mode to be set on the device node (e.g. 0666)",
Optional: true,
ValidateDiagFunc: validation.ToDiagFunc(validation.StringMatch(
regexp.MustCompile(`0[0-7]{3}`), "Octal access mode",
)),
},
mkDevicePassthroughPath: {
Type: schema.TypeString,
Description: "Device to pass through to the container",
Required: true,
ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotEmpty),
},
mkDevicePassthroughUID: {
Type: schema.TypeInt,
Description: "Device UID in the container",
Optional: true,
ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(0)),
},
},
},
MaxItems: 8,
MinItems: 0,
},
mkNetworkInterface: {
Type: schema.TypeList,
Description: "The network interfaces",
@ -1209,6 +1259,36 @@ func containerCreateClone(ctx context.Context, d *schema.ResourceData, m interfa
updateBody.Swap = &memorySwap
}
devicePassthrough := d.Get(mkDevicePassthrough).([]interface{})
devicePassthroughArray := make(
containers.CustomDevicePassthroughArray,
len(devicePassthrough),
)
for di, dv := range devicePassthrough {
devicePassthroughMap := dv.(map[string]interface{})
devicePassthroughObject := containers.CustomDevicePassthrough{}
denyWrite := types.CustomBool(
devicePassthroughMap[mkDevicePassthroughDenyWrite].(bool),
)
gid := devicePassthroughMap[mkDevicePassthroughGID].(int)
mode := devicePassthroughMap[mkDevicePassthroughMode].(string)
path := devicePassthroughMap[mkDevicePassthroughPath].(string)
uid := devicePassthroughMap[mkDevicePassthroughUID].(int)
devicePassthroughObject.DenyWrite = &denyWrite
devicePassthroughObject.GID = &gid
devicePassthroughObject.Mode = &mode
devicePassthroughObject.Path = path
devicePassthroughObject.UID = &uid
devicePassthroughArray[di] = devicePassthroughObject
}
updateBody.DevicePassthrough = devicePassthroughArray
networkInterface := d.Get(mkNetworkInterface).([]interface{})
if len(networkInterface) == 0 {
@ -2232,6 +2312,65 @@ func containerRead(ctx context.Context, d *schema.ResourceData, m interface{}) d
initialization[mkInitializationHostname] = ""
}
devicePassthroughArray := []*containers.CustomDevicePassthrough{
containerConfig.DevicePassthrough0,
containerConfig.DevicePassthrough1,
containerConfig.DevicePassthrough2,
containerConfig.DevicePassthrough3,
containerConfig.DevicePassthrough4,
containerConfig.DevicePassthrough5,
containerConfig.DevicePassthrough6,
containerConfig.DevicePassthrough7,
}
devicePassthroughList := make([]interface{}, 0, len(devicePassthroughArray))
for _, dp := range devicePassthroughArray {
if dp == nil {
continue
}
devicePassthrough := map[string]interface{}{}
if dp.DenyWrite != nil {
devicePassthrough[mkDevicePassthroughDenyWrite] = *dp.DenyWrite
} else {
devicePassthrough[mkDevicePassthroughDenyWrite] = false
}
if dp.GID != nil {
devicePassthrough[mkDevicePassthroughGID] = *dp.GID
} else {
devicePassthrough[mkDevicePassthroughGID] = 0
}
if dp.Mode != nil {
devicePassthrough[mkDevicePassthroughMode] = *dp.Mode
} else {
devicePassthrough[mkDevicePassthroughMode] = ""
}
devicePassthrough[mkDevicePassthroughPath] = dp.Path
if dp.UID != nil {
devicePassthrough[mkDevicePassthroughUID] = *dp.UID
} else {
devicePassthrough[mkDevicePassthroughUID] = 0
}
devicePassthroughList = append(devicePassthroughList, devicePassthrough)
}
if len(clone) > 0 {
if len(devicePassthroughList) > 0 {
err := d.Set(mkDevicePassthrough, devicePassthroughList)
diags = append(diags, diag.FromErr(err)...)
}
} else if len(devicePassthroughList) > 0 {
err := d.Set(mkDevicePassthrough, devicePassthroughList)
diags = append(diags, diag.FromErr(err)...)
}
mountPointArray := []*containers.CustomMountPoint{
containerConfig.MountPoint0,
containerConfig.MountPoint1,
@ -2860,6 +2999,40 @@ func containerUpdate(ctx context.Context, d *schema.ResourceData, m interface{})
rebootRequired = true
}
// Prepare the new device passthrough configuration.
if d.HasChange(mkDevicePassthrough) {
_, newDevicePassthrough := d.GetChange(mkDevicePassthrough)
devicePassthrough := newDevicePassthrough.([]interface{})
devicePassthroughArray := make(
containers.CustomDevicePassthroughArray,
len(devicePassthrough),
)
for i, dp := range devicePassthrough {
devicePassthroughMap := dp.(map[string]interface{})
devicePassthroughObject := containers.CustomDevicePassthrough{}
denyWrite := types.CustomBool(devicePassthroughMap[mkDevicePassthroughDenyWrite].(bool))
gid := devicePassthroughMap[mkDevicePassthroughGID].(int)
mode := devicePassthroughMap[mkDevicePassthroughMode].(string)
path := devicePassthroughMap[mkDevicePassthroughPath].(string)
uid := devicePassthroughMap[mkDevicePassthroughUID].(int)
devicePassthroughObject.DenyWrite = &denyWrite
devicePassthroughObject.GID = &gid
devicePassthroughObject.Mode = &mode
devicePassthroughObject.Path = path
devicePassthroughObject.UID = &uid
devicePassthroughArray[i] = devicePassthroughObject
}
updateBody.DevicePassthrough = devicePassthroughArray
rebootRequired = true
}
// Prepare the new mount point configuration.
if d.HasChange(mkMountPoint) {
_, newMountPoints := d.GetChange(mkMountPoint)

View File

@ -41,6 +41,7 @@ func TestContainerSchema(t *testing.T) {
mkInitialization,
mkHookScriptFileID,
mkMemory,
mkDevicePassthrough,
mkMountPoint,
mkOperatingSystem,
mkPoolID,
@ -55,23 +56,24 @@ func TestContainerSchema(t *testing.T) {
})
test.AssertValueTypes(t, s, map[string]schema.ValueType{
mkCPU: schema.TypeList,
mkDescription: schema.TypeString,
mkDisk: schema.TypeList,
mkInitialization: schema.TypeList,
mkHookScriptFileID: schema.TypeString,
mkMemory: schema.TypeList,
mkMountPoint: schema.TypeList,
mkOperatingSystem: schema.TypeList,
mkPoolID: schema.TypeString,
mkProtection: schema.TypeBool,
mkStarted: schema.TypeBool,
mkTags: schema.TypeList,
mkTemplate: schema.TypeBool,
mkUnprivileged: schema.TypeBool,
mkStartOnBoot: schema.TypeBool,
mkFeatures: schema.TypeList,
mkVMID: schema.TypeInt,
mkCPU: schema.TypeList,
mkDescription: schema.TypeString,
mkDisk: schema.TypeList,
mkInitialization: schema.TypeList,
mkHookScriptFileID: schema.TypeString,
mkMemory: schema.TypeList,
mkDevicePassthrough: schema.TypeList,
mkMountPoint: schema.TypeList,
mkOperatingSystem: schema.TypeList,
mkPoolID: schema.TypeString,
mkProtection: schema.TypeBool,
mkStarted: schema.TypeBool,
mkTags: schema.TypeList,
mkTemplate: schema.TypeBool,
mkUnprivileged: schema.TypeBool,
mkStartOnBoot: schema.TypeBool,
mkFeatures: schema.TypeList,
mkVMID: schema.TypeInt,
})
cloneSchema := test.AssertNestedSchemaExistence(t, s, mkClone)
@ -243,6 +245,27 @@ func TestContainerSchema(t *testing.T) {
mkMemorySwap: schema.TypeInt,
})
devicePassthroughSchema := test.AssertNestedSchemaExistence(t, s, mkDevicePassthrough)
test.AssertRequiredArguments(t, devicePassthroughSchema, []string{
mkDevicePassthroughPath,
})
test.AssertOptionalArguments(t, devicePassthroughSchema, []string{
mkDevicePassthroughDenyWrite,
mkDevicePassthroughGID,
mkDevicePassthroughMode,
mkDevicePassthroughUID,
})
test.AssertValueTypes(t, devicePassthroughSchema, map[string]schema.ValueType{
mkDevicePassthroughDenyWrite: schema.TypeBool,
mkDevicePassthroughGID: schema.TypeInt,
mkDevicePassthroughMode: schema.TypeString,
mkDevicePassthroughPath: schema.TypeString,
mkDevicePassthroughUID: schema.TypeInt,
})
mountPointSchema := test.AssertNestedSchemaExistence(t, s, mkMountPoint)
test.AssertOptionalArguments(t, mountPointSchema, []string{