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:
parent
c57dc78119
commit
7cbd1b46fa
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -51,6 +51,7 @@ type CreateRequestBody struct {
|
||||
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"`
|
||||
@ -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
|
||||
|
||||
@ -169,6 +182,14 @@ type GetResponseData struct {
|
||||
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"`
|
||||
@ -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
|
||||
|
@ -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)
|
||||
|
@ -41,6 +41,7 @@ func TestContainerSchema(t *testing.T) {
|
||||
mkInitialization,
|
||||
mkHookScriptFileID,
|
||||
mkMemory,
|
||||
mkDevicePassthrough,
|
||||
mkMountPoint,
|
||||
mkOperatingSystem,
|
||||
mkPoolID,
|
||||
@ -61,6 +62,7 @@ func TestContainerSchema(t *testing.T) {
|
||||
mkInitialization: schema.TypeList,
|
||||
mkHookScriptFileID: schema.TypeString,
|
||||
mkMemory: schema.TypeList,
|
||||
mkDevicePassthrough: schema.TypeList,
|
||||
mkMountPoint: schema.TypeList,
|
||||
mkOperatingSystem: schema.TypeList,
|
||||
mkPoolID: schema.TypeString,
|
||||
@ -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{
|
||||
|
Loading…
Reference in New Issue
Block a user