mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-07-01 19:12:59 +00:00
feat(vm): add support for virtiofs
(#1900)
Signed-off-by: Fina Wilke <code@felinira.net> Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Co-authored-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
parent
ad41476962
commit
55b3f7391a
@ -80,6 +80,12 @@ resource "proxmox_virtual_environment_vm" "ubuntu_vm" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
serial_device {}
|
serial_device {}
|
||||||
|
|
||||||
|
virtiofs {
|
||||||
|
mapping = "data_share"
|
||||||
|
cache = "always"
|
||||||
|
direct_io = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "proxmox_virtual_environment_download_file" "latest_ubuntu_22_jammy_qcow2_img" {
|
resource "proxmox_virtual_environment_download_file" "latest_ubuntu_22_jammy_qcow2_img" {
|
||||||
@ -559,6 +565,16 @@ output "ubuntu_vm_public_key" {
|
|||||||
- `virtio-gl` - VirtIO-GPU with 3D acceleration (VirGL). VirGL support needs some extra libraries that aren’t installed by default. See the [Proxmox documentation](https://pve.proxmox.com/pve-docs/pve-admin-guide.html#qm_virtual_machines_settings) section 10.2.8 for more information.
|
- `virtio-gl` - VirtIO-GPU with 3D acceleration (VirGL). VirGL support needs some extra libraries that aren’t installed by default. See the [Proxmox documentation](https://pve.proxmox.com/pve-docs/pve-admin-guide.html#qm_virtual_machines_settings) section 10.2.8 for more information.
|
||||||
- `vmware` - VMware Compatible.
|
- `vmware` - VMware Compatible.
|
||||||
- `clipboard` - (Optional) Enable VNC clipboard by setting to `vnc`. See the [Proxmox documentation](https://pve.proxmox.com/pve-docs/pve-admin-guide.html#qm_virtual_machines_settings) section 10.2.8 for more information.
|
- `clipboard` - (Optional) Enable VNC clipboard by setting to `vnc`. See the [Proxmox documentation](https://pve.proxmox.com/pve-docs/pve-admin-guide.html#qm_virtual_machines_settings) section 10.2.8 for more information.
|
||||||
|
- `virtiofs` - (Optional) Virtiofs share
|
||||||
|
- `mapping` - Identifier of the directory mapping
|
||||||
|
- `cache` - (Optional) The caching mode
|
||||||
|
- `auto`
|
||||||
|
- `always`
|
||||||
|
- `metadata`
|
||||||
|
- `never`
|
||||||
|
- `direct_io` - (Optional) Whether to allow direct io
|
||||||
|
- `expose_acl` - (Optional) Enable POSIX ACLs, implies xattr support
|
||||||
|
- `expose_xattr` - (Optional) Enable support for extended attributes
|
||||||
- `vm_id` - (Optional) The VM identifier.
|
- `vm_id` - (Optional) The VM identifier.
|
||||||
- `hook_script_file_id` - (Optional) The identifier for a file containing a hook script (needs to be executable, e.g. by using the `proxmox_virtual_environment_file.file_mode` attribute).
|
- `hook_script_file_id` - (Optional) The identifier for a file containing a hook script (needs to be executable, e.g. by using the `proxmox_virtual_environment_file.file_mode` attribute).
|
||||||
- `watchdog` - (Optional) The watchdog configuration. Once enabled (by a guest action), the watchdog must be periodically polled by an agent inside the guest or else the watchdog will reset the guest (or execute the respective action specified).
|
- `watchdog` - (Optional) The watchdog configuration. Once enabled (by a guest action), the watchdog must be periodically polled by an agent inside the guest or else the watchdog will reset the guest (or execute the respective action specified).
|
||||||
|
@ -51,7 +51,7 @@ resource "proxmox_virtual_environment_vm" "example_template" {
|
|||||||
interface = "scsi0"
|
interface = "scsi0"
|
||||||
discard = "on"
|
discard = "on"
|
||||||
cache = "writeback"
|
cache = "writeback"
|
||||||
serial = "dead_beef"
|
serial = "dead_beef"
|
||||||
ssd = true
|
ssd = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -396,6 +396,51 @@ func TestAccResourceVM(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
|
// Depends on #1902
|
||||||
|
// {"create virtiofs block", []resource.TestStep{
|
||||||
|
// {
|
||||||
|
// Config: te.RenderConfig(`
|
||||||
|
// resource "proxmox_virtual_environment_hardware_mapping_dir" "test" {
|
||||||
|
// name = "test"
|
||||||
|
|
||||||
|
// map {
|
||||||
|
// node = "{{.NodeName}}"
|
||||||
|
// path = "/mnt"
|
||||||
|
// }
|
||||||
|
// }`, WithRootUser()),
|
||||||
|
// Check: resource.ComposeTestCheckFunc(
|
||||||
|
// ResourceAttributes("proxmox_virtual_environment_hardware_mapping_dir.test", map[string]string{
|
||||||
|
// "name": "test",
|
||||||
|
// "map.0.node": "{{.NodeName}}",
|
||||||
|
// "map.0.path": "/mnt",
|
||||||
|
// }),
|
||||||
|
// ),
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// Config: te.RenderConfig(`
|
||||||
|
// resource "proxmox_virtual_environment_vm" "test_vm" {
|
||||||
|
// node_name = "{{.NodeName}}"
|
||||||
|
// started = false
|
||||||
|
|
||||||
|
// virtiofs {
|
||||||
|
// mapping = "test"
|
||||||
|
// cache = "always"
|
||||||
|
// direct_io = true
|
||||||
|
// expose_acl = false
|
||||||
|
// expose_xattr = false
|
||||||
|
// }
|
||||||
|
// }`, WithRootUser()),
|
||||||
|
// Check: resource.ComposeTestCheckFunc(
|
||||||
|
// ResourceAttributes("proxmox_virtual_environment_vm.test_vm", map[string]string{
|
||||||
|
// "virtiofs.0.mapping": "test",
|
||||||
|
// "virtiofs.0.cache": "always",
|
||||||
|
// "virtiofs.0.direct_io": "true",
|
||||||
|
// "virtiofs.0.expose_acl": "false",
|
||||||
|
// "virtiofs.0.expose_xattr": "false",
|
||||||
|
// }),
|
||||||
|
// ),
|
||||||
|
// },
|
||||||
|
// }},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
131
proxmox/nodes/vms/custom_virtiofs_share.go
Normal file
131
proxmox/nodes/vms/custom_virtiofs_share.go
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package vms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CustomVirtiofsShare handles Virtiofs directory shares.
|
||||||
|
type CustomVirtiofsShare struct {
|
||||||
|
DirId string `json:"dirid" url:"dirid"`
|
||||||
|
Cache *string `json:"cache,omitempty" url:"cache,omitempty"`
|
||||||
|
DirectIo *types.CustomBool `json:"direct-io,omitempty" url:"direct-io,omitempty,int"`
|
||||||
|
ExposeAcl *types.CustomBool `json:"expose-acl,omitempty" url:"expose-acl,omitempty,int"`
|
||||||
|
ExposeXattr *types.CustomBool `json:"expose-xattr,omitempty" url:"expose-xattr,omitempty,int"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomVirtiofsShares handles Virtiofs directory shares.
|
||||||
|
type CustomVirtiofsShares map[string]*CustomVirtiofsShare
|
||||||
|
|
||||||
|
// EncodeValues converts a CustomVirtiofsShare struct to a URL value.
|
||||||
|
func (r *CustomVirtiofsShare) EncodeValues(key string, v *url.Values) error {
|
||||||
|
if r.ExposeAcl != nil && *r.ExposeAcl && r.ExposeXattr != nil && !*r.ExposeXattr {
|
||||||
|
// expose-xattr implies expose-acl
|
||||||
|
return errors.New("expose_xattr must be omitted or true when expose_acl is enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
var values []string
|
||||||
|
values = append(values, fmt.Sprintf("dirid=%s", r.DirId))
|
||||||
|
|
||||||
|
if r.Cache != nil {
|
||||||
|
values = append(values, fmt.Sprintf("cache=%s", *r.Cache))
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.DirectIo != nil {
|
||||||
|
if *r.DirectIo {
|
||||||
|
values = append(values, "direct-io=1")
|
||||||
|
} else {
|
||||||
|
values = append(values, "direct-io=0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.ExposeAcl != nil {
|
||||||
|
if *r.ExposeAcl {
|
||||||
|
values = append(values, "expose-acl=1")
|
||||||
|
} else {
|
||||||
|
values = append(values, "expose-acl=0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.ExposeXattr != nil && (r.ExposeAcl == nil || !*r.ExposeAcl) {
|
||||||
|
// expose-acl implies expose-xattr, omit it when unnecessary for consistency
|
||||||
|
if *r.ExposeXattr {
|
||||||
|
values = append(values, "expose-xattr=1")
|
||||||
|
} else {
|
||||||
|
values = append(values, "expose-xattr=0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Add(key, strings.Join(values, ","))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeValues converts a CustomVirtiofsShares dict to multiple URL values.
|
||||||
|
func (r CustomVirtiofsShares) EncodeValues(key string, v *url.Values) error {
|
||||||
|
for s, d := range r {
|
||||||
|
if err := d.EncodeValues(s, v); err != nil {
|
||||||
|
return fmt.Errorf("failed to encode virtiofs share %s: %w", s, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON converts a CustomVirtiofsShare string to an object.
|
||||||
|
func (r *CustomVirtiofsShare) UnmarshalJSON(b []byte) error {
|
||||||
|
var s string
|
||||||
|
|
||||||
|
if err := json.Unmarshal(b, &s); err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal CustomVirtiofsShare: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs := strings.Split(s, ",")
|
||||||
|
|
||||||
|
for _, p := range pairs {
|
||||||
|
v := strings.Split(strings.TrimSpace(p), "=")
|
||||||
|
|
||||||
|
if len(v) == 1 {
|
||||||
|
r.DirId = v[0]
|
||||||
|
} else if len(v) == 2 {
|
||||||
|
switch v[0] {
|
||||||
|
case "dirid":
|
||||||
|
r.DirId = v[1]
|
||||||
|
case "cache":
|
||||||
|
r.Cache = &v[1]
|
||||||
|
case "direct-io":
|
||||||
|
bv := types.CustomBool(v[1] == "1")
|
||||||
|
r.DirectIo = &bv
|
||||||
|
case "expose-acl":
|
||||||
|
bv := types.CustomBool(v[1] == "1")
|
||||||
|
r.ExposeAcl = &bv
|
||||||
|
case "expose-xattr":
|
||||||
|
bv := types.CustomBool(v[1] == "1")
|
||||||
|
r.ExposeXattr = &bv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// expose-acl implies expose-xattr
|
||||||
|
if r.ExposeAcl != nil && *r.ExposeAcl {
|
||||||
|
if r.ExposeXattr == nil {
|
||||||
|
bv := types.CustomBool(true)
|
||||||
|
r.ExposeAcl = &bv
|
||||||
|
} else if !*r.ExposeXattr {
|
||||||
|
return fmt.Errorf("failed to unmarshal CustomVirtiofsShare: expose-xattr contradicts the value of expose-acl")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
79
proxmox/nodes/vms/custom_virtiofs_share_test.go
Normal file
79
proxmox/nodes/vms/custom_virtiofs_share_test.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package vms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCustomVirtiofsShare_UnmarshalJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
line string
|
||||||
|
want *CustomVirtiofsShare
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "id only virtiofs share",
|
||||||
|
line: `"test"`,
|
||||||
|
want: &CustomVirtiofsShare{
|
||||||
|
DirId: "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "virtiofs share with more details",
|
||||||
|
line: `"folder,cache=always"`,
|
||||||
|
want: &CustomVirtiofsShare{
|
||||||
|
DirId: "folder",
|
||||||
|
Cache: ptr.Ptr("always"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "virtiofs share with flags",
|
||||||
|
line: `"folder,cache=never,direct-io=1,expose-acl=1"`,
|
||||||
|
want: &CustomVirtiofsShare{
|
||||||
|
DirId: "folder",
|
||||||
|
Cache: ptr.Ptr("never"),
|
||||||
|
DirectIo: types.CustomBool(true).Pointer(),
|
||||||
|
ExposeAcl: types.CustomBool(true).Pointer(),
|
||||||
|
ExposeXattr: types.CustomBool(true).Pointer(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "virtiofs share with xattr",
|
||||||
|
line: `"folder,expose-xattr=1"`,
|
||||||
|
want: &CustomVirtiofsShare{
|
||||||
|
DirId: "folder",
|
||||||
|
Cache: nil,
|
||||||
|
DirectIo: types.CustomBool(false).Pointer(),
|
||||||
|
ExposeAcl: types.CustomBool(false).Pointer(),
|
||||||
|
ExposeXattr: types.CustomBool(true).Pointer(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "virtiofs share invalid combination",
|
||||||
|
line: `"folder,expose-acl=1,expose-xattr=0"`,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
r := &CustomVirtiofsShare{}
|
||||||
|
if err := r.UnmarshalJSON([]byte(tt.line)); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -98,6 +98,7 @@ type CreateRequestBody struct {
|
|||||||
USBDevices CustomUSBDevices `json:"usb,omitempty" url:"usb,omitempty"`
|
USBDevices CustomUSBDevices `json:"usb,omitempty" url:"usb,omitempty"`
|
||||||
VGADevice *CustomVGADevice `json:"vga,omitempty" url:"vga,omitempty"`
|
VGADevice *CustomVGADevice `json:"vga,omitempty" url:"vga,omitempty"`
|
||||||
VirtualCPUCount *int64 `json:"vcpus,omitempty" url:"vcpus,omitempty"`
|
VirtualCPUCount *int64 `json:"vcpus,omitempty" url:"vcpus,omitempty"`
|
||||||
|
VirtiofsShares CustomVirtiofsShares `json:"virtiofs,omitempty" url:"virtiofs,omitempty"`
|
||||||
VMGenerationID *string `json:"vmgenid,omitempty" url:"vmgenid,omitempty"`
|
VMGenerationID *string `json:"vmgenid,omitempty" url:"vmgenid,omitempty"`
|
||||||
VMID int `json:"vmid,omitempty" url:"vmid,omitempty"`
|
VMID int `json:"vmid,omitempty" url:"vmid,omitempty"`
|
||||||
VMStateDatastoreID *string `json:"vmstatestorage,omitempty" url:"vmstatestorage,omitempty"`
|
VMStateDatastoreID *string `json:"vmstatestorage,omitempty" url:"vmstatestorage,omitempty"`
|
||||||
@ -320,6 +321,7 @@ type GetResponseData struct {
|
|||||||
WatchdogDevice *CustomWatchdogDevice `json:"watchdog,omitempty"`
|
WatchdogDevice *CustomWatchdogDevice `json:"watchdog,omitempty"`
|
||||||
StorageDevices CustomStorageDevices `json:"-"`
|
StorageDevices CustomStorageDevices `json:"-"`
|
||||||
PCIDevices CustomPCIDevices `json:"-"`
|
PCIDevices CustomPCIDevices `json:"-"`
|
||||||
|
VirtiofsShares CustomVirtiofsShares `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStatusResponseBody contains the body from a VM get status response.
|
// GetStatusResponseBody contains the body from a VM get status response.
|
||||||
@ -469,6 +471,7 @@ func (d *GetResponseData) UnmarshalJSON(b []byte) error {
|
|||||||
|
|
||||||
data.StorageDevices = make(CustomStorageDevices)
|
data.StorageDevices = make(CustomStorageDevices)
|
||||||
data.PCIDevices = make(CustomPCIDevices)
|
data.PCIDevices = make(CustomPCIDevices)
|
||||||
|
data.VirtiofsShares = make(CustomVirtiofsShares)
|
||||||
|
|
||||||
for key, value := range byAttr {
|
for key, value := range byAttr {
|
||||||
for _, prefix := range StorageInterfaces {
|
for _, prefix := range StorageInterfaces {
|
||||||
@ -493,6 +496,15 @@ func (d *GetResponseData) UnmarshalJSON(b []byte) error {
|
|||||||
|
|
||||||
data.PCIDevices[key] = &device
|
data.PCIDevices[key] = &device
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if r := regexp.MustCompile(`^virtiofs\d+$`); r.MatchString(key) {
|
||||||
|
var share CustomVirtiofsShare
|
||||||
|
if err := json.Unmarshal([]byte(`"`+value.(string)+`"`), &share); err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal %s: %w", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data.VirtiofsShares[key] = &share
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*d = GetResponseData(data)
|
*d = GetResponseData(data)
|
||||||
|
@ -28,7 +28,8 @@ func TestUnmarshalGetResponseData(t *testing.T) {
|
|||||||
"scsi22": "%[1]s",
|
"scsi22": "%[1]s",
|
||||||
"hostpci0": "0000:81:00.2",
|
"hostpci0": "0000:81:00.2",
|
||||||
"hostpci1": "host=81:00.4,pcie=0,rombar=1,x-vga=0",
|
"hostpci1": "host=81:00.4,pcie=0,rombar=1,x-vga=0",
|
||||||
"hostpci12": "mapping=mappeddevice,pcie=0,rombar=1,x-vga=0"
|
"hostpci12": "mapping=mappeddevice,pcie=0,rombar=1,x-vga=0",
|
||||||
|
"virtiofs0":"test,cache=always,direct-io=1,expose-acl=1"
|
||||||
}`, "local-lvm:vm-100-disk-0,aio=io_uring,backup=1,cache=none,discard=ignore,replicate=1,size=8G,ssd=1")
|
}`, "local-lvm:vm-100-disk-0,aio=io_uring,backup=1,cache=none,discard=ignore,replicate=1,size=8G,ssd=1")
|
||||||
|
|
||||||
var data GetResponseData
|
var data GetResponseData
|
||||||
@ -57,6 +58,10 @@ func TestUnmarshalGetResponseData(t *testing.T) {
|
|||||||
assert.NotNil(t, data.PCIDevices["hostpci0"])
|
assert.NotNil(t, data.PCIDevices["hostpci0"])
|
||||||
assert.NotNil(t, data.PCIDevices["hostpci1"])
|
assert.NotNil(t, data.PCIDevices["hostpci1"])
|
||||||
assert.NotNil(t, data.PCIDevices["hostpci12"])
|
assert.NotNil(t, data.PCIDevices["hostpci12"])
|
||||||
|
|
||||||
|
assert.NotNil(t, data.VirtiofsShares)
|
||||||
|
assert.Len(t, data.VirtiofsShares, 1)
|
||||||
|
assert.Equal(t, "always", *data.VirtiofsShares["virtiofs0"].Cache)
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertDevice(t *testing.T, dev *CustomStorageDevice) {
|
func assertDevice(t *testing.T, dev *CustomStorageDevice) {
|
||||||
|
@ -268,6 +268,16 @@ func IDEInterfaceValidator() schema.SchemaValidateDiagFunc {
|
|||||||
}, false))
|
}, false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VirtiofsCacheValidator is a schema validation function for virtiofs cache configs.
|
||||||
|
func VirtiofsCacheValidator() schema.SchemaValidateDiagFunc {
|
||||||
|
return validation.ToDiagFunc(validation.StringInSlice([]string{
|
||||||
|
"auto",
|
||||||
|
"always",
|
||||||
|
"metadata",
|
||||||
|
"never",
|
||||||
|
}, false))
|
||||||
|
}
|
||||||
|
|
||||||
// CloudInitInterfaceValidator is a schema validation function that accepts either an IDE interface identifier or an
|
// CloudInitInterfaceValidator is a schema validation function that accepts either an IDE interface identifier or an
|
||||||
// empty string, which is used as the default and means "detect which interface should be used automatically".
|
// empty string, which is used as the default and means "detect which interface should be used automatically".
|
||||||
func CloudInitInterfaceValidator() schema.SchemaValidateDiagFunc {
|
func CloudInitInterfaceValidator() schema.SchemaValidateDiagFunc {
|
||||||
|
@ -129,6 +129,10 @@ const (
|
|||||||
dvVGAClipboard = ""
|
dvVGAClipboard = ""
|
||||||
dvVGAMemory = 16
|
dvVGAMemory = 16
|
||||||
dvVGAType = "std"
|
dvVGAType = "std"
|
||||||
|
dvVirtiofsCache = "auto"
|
||||||
|
dvVirtiofsDirectIo = false
|
||||||
|
dvVirtiofsExposeAcl = false
|
||||||
|
dvVirtiofsExposeXattr = false
|
||||||
dvSCSIHardware = "virtio-scsi-pci"
|
dvSCSIHardware = "virtio-scsi-pci"
|
||||||
dvStopOnDestroy = false
|
dvStopOnDestroy = false
|
||||||
dvHookScript = ""
|
dvHookScript = ""
|
||||||
@ -141,7 +145,8 @@ const (
|
|||||||
maxResourceVirtualEnvironmentVMHostPCIDevices = 16
|
maxResourceVirtualEnvironmentVMHostPCIDevices = 16
|
||||||
maxResourceVirtualEnvironmentVMHostUSBDevices = 4
|
maxResourceVirtualEnvironmentVMHostUSBDevices = 4
|
||||||
// hardcoded /usr/share/perl5/PVE/QemuServer/Memory.pm: "our $MAX_NUMA = 8".
|
// hardcoded /usr/share/perl5/PVE/QemuServer/Memory.pm: "our $MAX_NUMA = 8".
|
||||||
maxResourceVirtualEnvironmentVMNUMADevices = 8
|
maxResourceVirtualEnvironmentVMNUMADevices = 8
|
||||||
|
maxResourceVirtualEnvironmentVirtiofsShares = 8
|
||||||
|
|
||||||
mkRebootAfterCreation = "reboot"
|
mkRebootAfterCreation = "reboot"
|
||||||
mkRebootAfterUpdate = "reboot_after_update"
|
mkRebootAfterUpdate = "reboot_after_update"
|
||||||
@ -286,6 +291,12 @@ const (
|
|||||||
mkSCSIHardware = "scsi_hardware"
|
mkSCSIHardware = "scsi_hardware"
|
||||||
mkHookScriptFileID = "hook_script_file_id"
|
mkHookScriptFileID = "hook_script_file_id"
|
||||||
mkStopOnDestroy = "stop_on_destroy"
|
mkStopOnDestroy = "stop_on_destroy"
|
||||||
|
mkVirtiofs = "virtiofs"
|
||||||
|
mkVirtiofsMapping = "mapping"
|
||||||
|
mkVirtiofsCache = "cache"
|
||||||
|
mkVirtiofsDirectIo = "direct_io"
|
||||||
|
mkVirtiofsExposeAcl = "expose_acl"
|
||||||
|
mkVirtiofsExposeXattr = "expose_xattr"
|
||||||
mkWatchdog = "watchdog"
|
mkWatchdog = "watchdog"
|
||||||
// a workaround for the lack of proper support of default and undefined values in SDK.
|
// a workaround for the lack of proper support of default and undefined values in SDK.
|
||||||
mkWatchdogEnabled = "enabled"
|
mkWatchdogEnabled = "enabled"
|
||||||
@ -1465,6 +1476,51 @@ func VM() *schema.Resource {
|
|||||||
MaxItems: 1,
|
MaxItems: 1,
|
||||||
MinItems: 0,
|
MinItems: 0,
|
||||||
},
|
},
|
||||||
|
mkVirtiofs: {
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Description: "Virtiofs share configuration",
|
||||||
|
Optional: true,
|
||||||
|
DefaultFunc: func() (interface{}, error) {
|
||||||
|
return []interface{}{}, nil
|
||||||
|
},
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
mkVirtiofsMapping: {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Description: "Directory mapping identifier",
|
||||||
|
Required: true,
|
||||||
|
ValidateFunc: validation.StringIsNotEmpty,
|
||||||
|
},
|
||||||
|
mkVirtiofsCache: {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Description: "The caching mode",
|
||||||
|
Optional: true,
|
||||||
|
Default: dvVirtiofsCache,
|
||||||
|
ValidateDiagFunc: VirtiofsCacheValidator(),
|
||||||
|
},
|
||||||
|
mkVirtiofsDirectIo: {
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Description: "Whether to allow direct io",
|
||||||
|
Optional: true,
|
||||||
|
Default: dvVirtiofsDirectIo,
|
||||||
|
},
|
||||||
|
mkVirtiofsExposeAcl: {
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Description: "Enable POSIX ACLs, implies xattr support",
|
||||||
|
Optional: true,
|
||||||
|
Default: dvVirtiofsExposeAcl,
|
||||||
|
},
|
||||||
|
mkVirtiofsExposeXattr: {
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Description: "Enable support for extended attributes",
|
||||||
|
Optional: true,
|
||||||
|
Default: dvVirtiofsExposeXattr,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MaxItems: maxResourceVirtualEnvironmentVirtiofsShares,
|
||||||
|
MinItems: 0,
|
||||||
|
},
|
||||||
mkVMID: {
|
mkVMID: {
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Description: "The VM identifier",
|
Description: "The VM identifier",
|
||||||
@ -1899,6 +1955,7 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
|
|||||||
tabletDevice := types.CustomBool(d.Get(mkTabletDevice).(bool))
|
tabletDevice := types.CustomBool(d.Get(mkTabletDevice).(bool))
|
||||||
template := types.CustomBool(d.Get(mkTemplate).(bool))
|
template := types.CustomBool(d.Get(mkTemplate).(bool))
|
||||||
vga := d.Get(mkVGA).([]interface{})
|
vga := d.Get(mkVGA).([]interface{})
|
||||||
|
virtiofs := d.Get(mkVirtiofs).([]interface{})
|
||||||
watchdog := d.Get(mkWatchdog).([]interface{})
|
watchdog := d.Get(mkWatchdog).([]interface{})
|
||||||
|
|
||||||
updateBody := &vms.UpdateRequestBody{
|
updateBody := &vms.UpdateRequestBody{
|
||||||
@ -2155,6 +2212,11 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
|
|||||||
updateBody.VGADevice = vgaDevice
|
updateBody.VGADevice = vgaDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(virtiofs) > 0 {
|
||||||
|
virtiofsShares := vmGetVirtiofsShares(d)
|
||||||
|
updateBody.VirtiofsShares = virtiofsShares
|
||||||
|
}
|
||||||
|
|
||||||
hookScript := d.Get(mkHookScriptFileID).(string)
|
hookScript := d.Get(mkHookScriptFileID).(string)
|
||||||
currentHookScript := vmConfig.HookScript
|
currentHookScript := vmConfig.HookScript
|
||||||
|
|
||||||
@ -2535,6 +2597,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
|
|||||||
tabletDevice := types.CustomBool(d.Get(mkTabletDevice).(bool))
|
tabletDevice := types.CustomBool(d.Get(mkTabletDevice).(bool))
|
||||||
template := types.CustomBool(d.Get(mkTemplate).(bool))
|
template := types.CustomBool(d.Get(mkTemplate).(bool))
|
||||||
|
|
||||||
|
virtiofsShares := vmGetVirtiofsShares(d)
|
||||||
vgaDevice := vmGetVGADeviceObject(d)
|
vgaDevice := vmGetVGADeviceObject(d)
|
||||||
|
|
||||||
vmIDUntyped, hasVMID := d.GetOk(mkVMID)
|
vmIDUntyped, hasVMID := d.GetOk(mkVMID)
|
||||||
@ -2693,6 +2756,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
|
|||||||
TabletDeviceEnabled: &tabletDevice,
|
TabletDeviceEnabled: &tabletDevice,
|
||||||
Template: &template,
|
Template: &template,
|
||||||
USBDevices: usbDeviceObjects,
|
USBDevices: usbDeviceObjects,
|
||||||
|
VirtiofsShares: virtiofsShares,
|
||||||
VGADevice: vgaDevice,
|
VGADevice: vgaDevice,
|
||||||
VMID: vmID,
|
VMID: vmID,
|
||||||
WatchdogDevice: watchdogObject,
|
WatchdogDevice: watchdogObject,
|
||||||
@ -3347,6 +3411,41 @@ func vmGetTagsString(d *schema.ResourceData) string {
|
|||||||
return strings.Join(sanitizedTags, ";")
|
return strings.Join(sanitizedTags, ";")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func vmGetVirtiofsShares(d *schema.ResourceData) vms.CustomVirtiofsShares {
|
||||||
|
virtiofs := d.Get(mkVirtiofs).([]interface{})
|
||||||
|
virtiofsShares := make(vms.CustomVirtiofsShares, len(virtiofs))
|
||||||
|
|
||||||
|
for i, virtiofsShare := range virtiofs {
|
||||||
|
block := virtiofsShare.(map[string]interface{})
|
||||||
|
|
||||||
|
mapping, _ := block[mkVirtiofsMapping].(string)
|
||||||
|
cache, _ := block[mkVirtiofsCache].(string)
|
||||||
|
direct_io := types.CustomBool(block[mkVirtiofsDirectIo].(bool))
|
||||||
|
expose_acl := types.CustomBool(block[mkVirtiofsExposeAcl].(bool))
|
||||||
|
expose_xattr := types.CustomBool(block[mkVirtiofsExposeXattr].(bool))
|
||||||
|
|
||||||
|
share := vms.CustomVirtiofsShare{
|
||||||
|
DirId: mapping,
|
||||||
|
DirectIo: &direct_io,
|
||||||
|
ExposeAcl: &expose_acl,
|
||||||
|
ExposeXattr: &expose_xattr,
|
||||||
|
}
|
||||||
|
|
||||||
|
if cache != "" {
|
||||||
|
share.Cache = &cache
|
||||||
|
}
|
||||||
|
|
||||||
|
if share.ExposeAcl != nil && *share.ExposeAcl && share.ExposeXattr == nil {
|
||||||
|
bv := types.CustomBool(true)
|
||||||
|
share.ExposeXattr = &bv
|
||||||
|
}
|
||||||
|
|
||||||
|
virtiofsShares[fmt.Sprintf("virtiofs%d", i)] = &share
|
||||||
|
}
|
||||||
|
|
||||||
|
return virtiofsShares
|
||||||
|
}
|
||||||
|
|
||||||
func vmGetVGADeviceObject(d *schema.ResourceData) *vms.CustomVGADevice {
|
func vmGetVGADeviceObject(d *schema.ResourceData) *vms.CustomVGADevice {
|
||||||
vga := d.Get(mkVGA).([]interface{})
|
vga := d.Get(mkVGA).([]interface{})
|
||||||
if len(vga) > 0 && vga[0] != nil {
|
if len(vga) > 0 && vga[0] != nil {
|
||||||
@ -3946,6 +4045,55 @@ func vmReadCustom(
|
|||||||
diags = append(diags, diag.FromErr(err)...)
|
diags = append(diags, diag.FromErr(err)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentVirtiofsList := d.Get(mkVirtiofs).([]interface{})
|
||||||
|
virtiofsMap := map[string]interface{}{}
|
||||||
|
|
||||||
|
for pi, pp := range vmConfig.VirtiofsShares {
|
||||||
|
if pp == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
share := map[string]interface{}{}
|
||||||
|
|
||||||
|
share[mkVirtiofsMapping] = pp.DirId
|
||||||
|
|
||||||
|
if pp.Cache != nil {
|
||||||
|
share[mkVirtiofsCache] = *pp.Cache
|
||||||
|
} else {
|
||||||
|
share[mkVirtiofsCache] = dvVirtiofsCache
|
||||||
|
}
|
||||||
|
|
||||||
|
if pp.DirectIo != nil {
|
||||||
|
share[mkVirtiofsDirectIo] = *pp.DirectIo
|
||||||
|
} else {
|
||||||
|
share[mkVirtiofsDirectIo] = dvVirtiofsDirectIo
|
||||||
|
}
|
||||||
|
|
||||||
|
if pp.ExposeAcl != nil {
|
||||||
|
share[mkVirtiofsExposeAcl] = *pp.ExposeAcl
|
||||||
|
} else {
|
||||||
|
share[mkVirtiofsExposeAcl] = dvVirtiofsExposeAcl
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case pp.ExposeXattr != nil:
|
||||||
|
share[mkVirtiofsExposeXattr] = *pp.ExposeXattr
|
||||||
|
case pp.ExposeAcl != nil && bool(*pp.ExposeAcl):
|
||||||
|
// expose-xattr implies expose-acl
|
||||||
|
share[mkVirtiofsExposeXattr] = true
|
||||||
|
default:
|
||||||
|
share[mkVirtiofsExposeXattr] = dvVirtiofsExposeXattr
|
||||||
|
}
|
||||||
|
|
||||||
|
virtiofsMap[pi] = share
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(clone) == 0 || len(currentVirtiofsList) > 0 {
|
||||||
|
orderedVirtiofsList := utils.OrderedListFromMap(virtiofsMap)
|
||||||
|
err := d.Set(mkVirtiofs, orderedVirtiofsList)
|
||||||
|
diags = append(diags, diag.FromErr(err)...)
|
||||||
|
}
|
||||||
|
|
||||||
// Compare the initialization configuration to the one stored in the state.
|
// Compare the initialization configuration to the one stored in the state.
|
||||||
initialization := map[string]interface{}{}
|
initialization := map[string]interface{}{}
|
||||||
|
|
||||||
@ -5339,6 +5487,17 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
|
|||||||
rebootRequired = true
|
rebootRequired = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prepare the new Virtiofs shares configuration.
|
||||||
|
if d.HasChange(mkVirtiofs) {
|
||||||
|
updateBody.VirtiofsShares = vmGetVirtiofsShares(d)
|
||||||
|
|
||||||
|
for i := len(updateBody.VirtiofsShares); i < maxResourceVirtualEnvironmentVirtiofsShares; i++ {
|
||||||
|
del = append(del, fmt.Sprintf("virtiofs%d", i))
|
||||||
|
}
|
||||||
|
|
||||||
|
rebootRequired = true
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare the new SCSI hardware type
|
// Prepare the new SCSI hardware type
|
||||||
if d.HasChange(mkSCSIHardware) {
|
if d.HasChange(mkSCSIHardware) {
|
||||||
scsiHardware := d.Get(mkSCSIHardware).(string)
|
scsiHardware := d.Get(mkSCSIHardware).(string)
|
||||||
|
@ -64,6 +64,7 @@ func TestVMSchema(t *testing.T) {
|
|||||||
mkStarted,
|
mkStarted,
|
||||||
mkTabletDevice,
|
mkTabletDevice,
|
||||||
mkTemplate,
|
mkTemplate,
|
||||||
|
mkVirtiofs,
|
||||||
mkVMID,
|
mkVMID,
|
||||||
mkSCSIHardware,
|
mkSCSIHardware,
|
||||||
})
|
})
|
||||||
@ -93,6 +94,7 @@ func TestVMSchema(t *testing.T) {
|
|||||||
mkStarted: schema.TypeBool,
|
mkStarted: schema.TypeBool,
|
||||||
mkTabletDevice: schema.TypeBool,
|
mkTabletDevice: schema.TypeBool,
|
||||||
mkTemplate: schema.TypeBool,
|
mkTemplate: schema.TypeBool,
|
||||||
|
mkVirtiofs: schema.TypeList,
|
||||||
mkVMID: schema.TypeInt,
|
mkVMID: schema.TypeInt,
|
||||||
mkSCSIHardware: schema.TypeString,
|
mkSCSIHardware: schema.TypeString,
|
||||||
})
|
})
|
||||||
@ -382,6 +384,23 @@ func TestVMSchema(t *testing.T) {
|
|||||||
mkSerialDeviceDevice: schema.TypeString,
|
mkSerialDeviceDevice: schema.TypeString,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
virtiofsSchema := test.AssertNestedSchemaExistence(t, s, mkVirtiofs)
|
||||||
|
|
||||||
|
test.AssertOptionalArguments(t, virtiofsSchema, []string{
|
||||||
|
mkVirtiofsCache,
|
||||||
|
mkVirtiofsDirectIo,
|
||||||
|
mkVirtiofsExposeAcl,
|
||||||
|
mkVirtiofsExposeXattr,
|
||||||
|
})
|
||||||
|
|
||||||
|
test.AssertValueTypes(t, virtiofsSchema, map[string]schema.ValueType{
|
||||||
|
mkVirtiofsMapping: schema.TypeString,
|
||||||
|
mkVirtiofsCache: schema.TypeString,
|
||||||
|
mkVirtiofsDirectIo: schema.TypeBool,
|
||||||
|
mkVirtiofsExposeAcl: schema.TypeBool,
|
||||||
|
mkVirtiofsExposeXattr: schema.TypeBool,
|
||||||
|
})
|
||||||
|
|
||||||
vgaSchema := test.AssertNestedSchemaExistence(t, s, mkVGA)
|
vgaSchema := test.AssertNestedSchemaExistence(t, s, mkVGA)
|
||||||
|
|
||||||
test.AssertOptionalArguments(t, vgaSchema, []string{
|
test.AssertOptionalArguments(t, vgaSchema, []string{
|
||||||
|
Loading…
Reference in New Issue
Block a user