diff --git a/docs/resources/virtual_environment_container.md b/docs/resources/virtual_environment_container.md index b09d5167..0d09efe8 100644 --- a/docs/resources/virtual_environment_container.md +++ b/docs/resources/virtual_environment_container.md @@ -45,6 +45,12 @@ resource "proxmox_virtual_environment_container" "ubuntu_container" { template_file_id = proxmox_virtual_environment_file.ubuntu_container_template.id type = "ubuntu" } + + mount_point { + volume = "/mnt/bindmounts/shared" + path = "/shared" + } + } resource "proxmox_virtual_environment_file" "ubuntu_container_template" { @@ -136,6 +142,23 @@ output "ubuntu_container_public_key" { - `dedicated` - (Optional) The dedicated memory in megabytes (defaults to `512`). - `swap` - (Optional) The swap size in megabytes (defaults to `0`). +- `mount_point` + - `acl` (Optional) Explicitly enable or disable ACL support. + - `backup` (Optional) Whether to include the mount point in backups (only + used for volume mount points). + - `mount_options` (Optional) List of extra mount options. + - `path` (Required) Path to the mount point as seen from inside the + container. + - `quota` (Optional) Enable user quotas inside the container (not supported + with ZFS subvolumes). + - `read_only` (Optional) Read-only mount point. + - `replicate` (Optional) Will include this volume to a storage replica job. + - `shared` (Optional) Mark this non-volume mount point as available on all + nodes. + - `size` (Optional) Volume size (only for ZFS storage backed mount points). + Can be specified with a unit suffix (e.g. `10G`). + - `volume` (Required) Volume, device or directory to mount into the + container. - `network_interface` - (Optional) A network interface (multiple blocks supported). - `bridge` - (Optional) The name of the network bridge (defaults @@ -143,7 +166,7 @@ output "ubuntu_container_public_key" { - `enabled` - (Optional) Whether to enable the network device (defaults to `true`). - `firewall` - (Optional) Whether this interface's firewall rules should be - used (defaults to `false`). + used (defaults to `false`). - `mac_address` - (Optional) The MAC address. - `mtu` - (Optional) Maximum transfer unit of the interface. Cannot be larger than the bridge's MTU. diff --git a/example/resource_virtual_environment_container.tf b/example/resource_virtual_environment_container.tf index 0e8dde14..68b41d88 100644 --- a/example/resource_virtual_environment_container.tf +++ b/example/resource_virtual_environment_container.tf @@ -62,6 +62,11 @@ resource "proxmox_virtual_environment_container" "example" { hostname = "terraform-provider-proxmox-example-lxc" } + mount_point { + volume = "/mnt/bindmounts/shared" + path = "/shared" + } + node_name = data.proxmox_virtual_environment_nodes.example.names[0] pool_id = proxmox_virtual_environment_pool.example.id vm_id = 2043 diff --git a/proxmox/nodes/containers/client.go b/proxmox/nodes/containers/client.go index 4f474996..67c11646 100644 --- a/proxmox/nodes/containers/client.go +++ b/proxmox/nodes/containers/client.go @@ -12,6 +12,7 @@ import ( "github.com/bpg/terraform-provider-proxmox/proxmox/api" "github.com/bpg/terraform-provider-proxmox/proxmox/firewall" containerfirewall "github.com/bpg/terraform-provider-proxmox/proxmox/nodes/containers/firewall" + "github.com/bpg/terraform-provider-proxmox/proxmox/nodes/tasks" ) // Client is an interface for accessing the Proxmox container API. @@ -34,6 +35,13 @@ func (c *Client) ExpandPath(path string) string { return ep } +// Tasks returns a client for managing container tasks. +func (c *Client) Tasks() *tasks.Client { + return &tasks.Client{ + Client: c.Client, + } +} + // Firewall returns a client for managing the container firewall. func (c *Client) Firewall() firewall.API { return &containerfirewall.Client{ diff --git a/proxmox/nodes/containers/containers.go b/proxmox/nodes/containers/containers.go index 386e6489..3ec0f440 100644 --- a/proxmox/nodes/containers/containers.go +++ b/proxmox/nodes/containers/containers.go @@ -27,15 +27,36 @@ func (c *Client) CloneContainer(ctx context.Context, d *CloneRequestBody) error } // CreateContainer creates a container. -func (c *Client) CreateContainer(ctx context.Context, d *CreateRequestBody) error { - err := c.DoRequest(ctx, http.MethodPost, c.basePath(), d, nil) +func (c *Client) CreateContainer(ctx context.Context, d *CreateRequestBody, timeout int) error { + taskID, err := c.CreateContainerAsync(ctx, d) if err != nil { - return fmt.Errorf("error creating container: %w", err) + return err + } + + err = c.Tasks().WaitForTask(ctx, *taskID, timeout, 5) + if err != nil { + return fmt.Errorf("error waiting for container created: %w", err) } return nil } +// CreateContainerAsync creates a container asynchronously. +func (c *Client) CreateContainerAsync(ctx context.Context, d *CreateRequestBody) (*string, error) { + resBody := &CreateResponseBody{} + + err := c.DoRequest(ctx, http.MethodPost, c.basePath(), d, resBody) + if err != nil { + return nil, fmt.Errorf("error creating container: %w", err) + } + + if resBody.Data == nil { + return nil, api.ErrNoDataObjectInResponse + } + + return resBody.Data, nil +} + // DeleteContainer deletes a container. func (c *Client) DeleteContainer(ctx context.Context) error { err := c.DoRequest(ctx, http.MethodDelete, c.ExpandPath(""), nil, nil) @@ -99,15 +120,36 @@ func (c *Client) ShutdownContainer(ctx context.Context, d *ShutdownRequestBody) } // StartContainer starts a container. -func (c *Client) StartContainer(ctx context.Context) error { - err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("status/start"), nil, nil) +func (c *Client) StartContainer(ctx context.Context, timeout int) error { + taskID, err := c.StartContainerAsync(ctx) if err != nil { - return fmt.Errorf("error starting container: %w", err) + return err + } + + err = c.Tasks().WaitForTask(ctx, *taskID, timeout, 5) + if err != nil { + return fmt.Errorf("error waiting for container start: %w", err) } return nil } +// StartContainerAsync starts a container asynchronously. +func (c *Client) StartContainerAsync(ctx context.Context) (*string, error) { + resBody := &StartResponseBody{} + + err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("status/start"), nil, resBody) + if err != nil { + return nil, fmt.Errorf("error starting container: %w", err) + } + + if resBody.Data == nil { + return nil, api.ErrNoDataObjectInResponse + } + + return resBody.Data, nil +} + // StopContainer stops a container immediately. func (c *Client) StopContainer(ctx context.Context) error { err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("status/stop"), nil, nil) diff --git a/proxmox/nodes/containers/containers_types.go b/proxmox/nodes/containers/containers_types.go index 1ad3a3f3..ddc1b36c 100644 --- a/proxmox/nodes/containers/containers_types.go +++ b/proxmox/nodes/containers/containers_types.go @@ -84,7 +84,7 @@ type CustomFeatures struct { type CustomMountPoint struct { ACL *types.CustomBool `json:"acl,omitempty" url:"acl,omitempty,int"` Backup *types.CustomBool `json:"backup,omitempty" url:"backup,omitempty,int"` - DiskSize *string `json:"size,omitempty" url:"size,omitempty"` + DiskSize *string `json:"size,omitempty" url:"size,omitempty"` // read-only Enabled bool `json:"-" url:"-"` MountOptions *[]string `json:"mountoptions,omitempty" url:"mountoptions,omitempty"` MountPoint string `json:"mp" url:"mp"` @@ -141,6 +141,11 @@ type CustomStartupBehavior struct { Up *int `json:"up,omitempty" url:"up,omitempty"` } +// CreateResponseBody contains the body from a container create response. +type CreateResponseBody struct { + Data *string `json:"data,omitempty"` +} + // GetResponseBody contains the body from a user get response. type GetResponseBody struct { Data *GetResponseData `json:"data,omitempty"` @@ -164,10 +169,14 @@ type GetResponseData struct { 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"` + 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"` @@ -207,6 +216,11 @@ type GetStatusResponseData struct { VMID *int `json:"vmid,omitempty"` } +// StartResponseBody contains the body from a container start response. +type StartResponseBody struct { + Data *string `json:"data,omitempty"` +} + // RebootRequestBody contains the body for a container reboot request. type RebootRequestBody struct { Timeout *int `json:"timeout,omitempty" url:"timeout,omitempty"` @@ -288,7 +302,7 @@ func (r *CustomMountPoint) EncodeValues(key string, v *url.Values) error { if r.MountOptions != nil { if len(*r.MountOptions) > 0 { - values = append(values, fmt.Sprintf("mount=%s", strings.Join(*r.MountOptions, ";"))) + values = append(values, fmt.Sprintf("mountoptions=%s", strings.Join(*r.MountOptions, ";"))) } } @@ -311,7 +325,7 @@ func (r *CustomMountPoint) EncodeValues(key string, v *url.Values) error { } if r.Replicate != nil { - if *r.ReadOnly { + if *r.Replicate { values = append(values, "replicate=1") } else { values = append(values, "replicate=0") diff --git a/proxmoxtf/resource/container.go b/proxmoxtf/resource/container.go index d8305e3e..dd1e4ec7 100644 --- a/proxmoxtf/resource/container.go +++ b/proxmoxtf/resource/container.go @@ -46,6 +46,13 @@ const ( dvResourceVirtualEnvironmentContainerFeaturesNesting = false dvResourceVirtualEnvironmentContainerMemoryDedicated = 512 dvResourceVirtualEnvironmentContainerMemorySwap = 0 + dvResourceVirtualEnvironmentContainerMountPointACL = false + dvResourceVirtualEnvironmentContainerMountPointBackup = true + dvResourceVirtualEnvironmentContainerMountPointQuota = false + dvResourceVirtualEnvironmentContainerMountPointReadOnly = false + dvResourceVirtualEnvironmentContainerMountPointReplicate = true + dvResourceVirtualEnvironmentContainerMountPointShared = false + dvResourceVirtualEnvironmentContainerMountPointSize = "" dvResourceVirtualEnvironmentContainerNetworkInterfaceBridge = "vmbr0" dvResourceVirtualEnvironmentContainerNetworkInterfaceEnabled = true dvResourceVirtualEnvironmentContainerNetworkInterfaceFirewall = false @@ -97,6 +104,17 @@ const ( mkResourceVirtualEnvironmentContainerMemory = "memory" mkResourceVirtualEnvironmentContainerMemoryDedicated = "dedicated" mkResourceVirtualEnvironmentContainerMemorySwap = "swap" + mkResourceVirtualEnvironmentContainerMountPoint = "mount_point" + mkResourceVirtualEnvironmentContainerMountPointACL = "acl" + mkResourceVirtualEnvironmentContainerMountPointBackup = "backup" + mkResourceVirtualEnvironmentContainerMountPointMountOptions = "mount_options" + mkResourceVirtualEnvironmentContainerMountPointPath = "path" + mkResourceVirtualEnvironmentContainerMountPointQuota = "quota" + mkResourceVirtualEnvironmentContainerMountPointReadOnly = "read_only" + mkResourceVirtualEnvironmentContainerMountPointReplicate = "replicate" + mkResourceVirtualEnvironmentContainerMountPointShared = "shared" + mkResourceVirtualEnvironmentContainerMountPointSize = "size" + mkResourceVirtualEnvironmentContainerMountPointVolume = "volume" mkResourceVirtualEnvironmentContainerNetworkInterface = "network_interface" mkResourceVirtualEnvironmentContainerNetworkInterfaceBridge = "bridge" mkResourceVirtualEnvironmentContainerNetworkInterfaceEnabled = "enabled" @@ -493,6 +511,78 @@ func Container() *schema.Resource { MaxItems: 1, MinItems: 0, }, + mkResourceVirtualEnvironmentContainerMountPoint: { + Type: schema.TypeList, + Description: "A mount point", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentContainerMountPointACL: { + Type: schema.TypeBool, + Description: "Explicitly enable or disable ACL support", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerMountPointACL, + }, + mkResourceVirtualEnvironmentContainerMountPointBackup: { + Type: schema.TypeBool, + Description: "Whether to include the mount point in backups (only used for volume mount points)", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerMountPointBackup, + }, + mkResourceVirtualEnvironmentContainerMountPointMountOptions: { + Type: schema.TypeList, + Description: "Extra mount options.", + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + mkResourceVirtualEnvironmentContainerMountPointPath: { + Type: schema.TypeString, + Description: "Path to the mount point as seen from inside the container", + Required: true, + }, + mkResourceVirtualEnvironmentContainerMountPointQuota: { + Type: schema.TypeBool, + Description: "Enable user quotas inside the container (not supported with zfs subvolumes)", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerMountPointQuota, + }, + mkResourceVirtualEnvironmentContainerMountPointReadOnly: { + Type: schema.TypeBool, + Description: "Read-only mount point", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerMountPointReadOnly, + }, + mkResourceVirtualEnvironmentContainerMountPointReplicate: { + Type: schema.TypeBool, + Description: "Will include this volume to a storage replica job", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerMountPointReplicate, + }, + mkResourceVirtualEnvironmentContainerMountPointShared: { + Type: schema.TypeBool, + Description: "Mark this non-volume mount point as available on all nodes", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerMountPointShared, + }, + mkResourceVirtualEnvironmentContainerMountPointSize: { + Type: schema.TypeString, + Description: "Volume size (only used for volume mount points)", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerMountPointSize, + ValidateDiagFunc: getFileSizeValidator(), + }, + mkResourceVirtualEnvironmentContainerMountPointVolume: { + Type: schema.TypeString, + Description: "Volume, device or directory to mount into the container", + Required: true, + }, + }, + }, + MaxItems: 8, + MinItems: 0, + }, mkResourceVirtualEnvironmentContainerNetworkInterface: { Type: schema.TypeList, Description: "The network interfaces", @@ -1206,11 +1296,60 @@ func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interf memoryDedicated := memoryBlock[mkResourceVirtualEnvironmentContainerMemoryDedicated].(int) memorySwap := memoryBlock[mkResourceVirtualEnvironmentContainerMemorySwap].(int) + mountPoint := d.Get(mkResourceVirtualEnvironmentContainerMountPoint).([]interface{}) + mountPointArray := make(containers.CustomMountPointArray, 0, len(mountPoint)) + + for _, mp := range mountPoint { + mountPointMap := mp.(map[string]interface{}) + mountPointObject := containers.CustomMountPoint{} + + acl := types.CustomBool(mountPointMap[mkResourceVirtualEnvironmentContainerMountPointACL].(bool)) + backup := types.CustomBool(mountPointMap[mkResourceVirtualEnvironmentContainerMountPointBackup].(bool)) + mountOptions := mountPointMap[mkResourceVirtualEnvironmentContainerMountPointMountOptions].([]interface{}) + path := mountPointMap[mkResourceVirtualEnvironmentContainerMountPointPath].(string) + quota := types.CustomBool(mountPointMap[mkResourceVirtualEnvironmentContainerMountPointQuota].(bool)) + readOnly := types.CustomBool(mountPointMap[mkResourceVirtualEnvironmentContainerMountPointReadOnly].(bool)) + replicate := types.CustomBool(mountPointMap[mkResourceVirtualEnvironmentContainerMountPointReplicate].(bool)) + shared := types.CustomBool(mountPointMap[mkResourceVirtualEnvironmentContainerMountPointShared].(bool)) + size := mountPointMap[mkResourceVirtualEnvironmentContainerMountPointSize].(string) + volume := mountPointMap[mkResourceVirtualEnvironmentContainerMountPointVolume].(string) + + mountPointObject.ACL = &acl + mountPointObject.Backup = &backup + mountPointObject.MountPoint = path + mountPointObject.Quota = "a + mountPointObject.ReadOnly = &readOnly + mountPointObject.Replicate = &replicate + mountPointObject.Shared = &shared + + if len(size) > 0 { + var ds types.DiskSize + + ds, err = types.ParseDiskSize(size) + if err != nil { + return diag.Errorf("invalid disk size: %s", err.Error()) + } + + mountPointObject.Volume = fmt.Sprintf("%s:%d", volume, ds.InGigabytes()) + } else { + mountPointObject.Volume = volume + } + + if len(mountOptions) > 0 { + mountOptionsArray := make([]string, 0, len(mountPoint)) + + for _, option := range mountOptions { + mountOptionsArray = append(mountOptionsArray, option.(string)) + } + + mountPointObject.MountOptions = &mountOptionsArray + } + + mountPointArray = append(mountPointArray, mountPointObject) + } + networkInterface := d.Get(mkResourceVirtualEnvironmentContainerNetworkInterface).([]interface{}) - networkInterfaceArray := make( - containers.CustomNetworkInterfaceArray, - len(networkInterface), - ) + networkInterfaceArray := make(containers.CustomNetworkInterfaceArray, len(networkInterface)) for ni, nv := range networkInterface { networkInterfaceMap := nv.(map[string]interface{}) @@ -1307,6 +1446,7 @@ func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interf DatastoreID: &diskDatastoreID, DedicatedMemory: &memoryDedicated, Features: &features, + MountPoints: mountPointArray, NetworkInterfaces: networkInterfaceArray, OSTemplateFileVolume: &operatingSystemTemplateFileID, OSType: &operatingSystemType, @@ -1352,7 +1492,7 @@ func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interf createBody.Tags = &tagsString } - err = api.Node(nodeName).Container(0).CreateContainer(ctx, &createBody) + err = api.Node(nodeName).Container(0).CreateContainer(ctx, &createBody, 60) if err != nil { return diag.FromErr(err) } @@ -1391,7 +1531,7 @@ func containerCreateStart(ctx context.Context, d *schema.ResourceData, m interfa containerAPI := api.Node(nodeName).Container(vmID) // Start the container and wait for it to reach a running state before continuing. - err = containerAPI.StartContainer(ctx) + err = containerAPI.StartContainer(ctx, 60) if err != nil { return diag.FromErr(err) } @@ -1745,6 +1885,93 @@ func containerRead(ctx context.Context, d *schema.ResourceData, m interface{}) d initialization[mkResourceVirtualEnvironmentContainerInitializationHostname] = "" } + mountPointArray := []*containers.CustomMountPoint{ + 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 + } + + mountPoint := map[string]interface{}{} + + if mp.ACL != nil { + mountPoint[mkResourceVirtualEnvironmentContainerMountPointACL] = *mp.ACL + } else { + mountPoint[mkResourceVirtualEnvironmentContainerMountPointACL] = false + } + + if mp.Backup != nil { + mountPoint[mkResourceVirtualEnvironmentContainerMountPointBackup] = *mp.Backup + } else { + mountPoint[mkResourceVirtualEnvironmentContainerMountPointBackup] = true + } + + if mp.MountOptions != nil { + mountPoint[mkResourceVirtualEnvironmentContainerMountPointMountOptions] = *mp.MountOptions + } else { + mountPoint[mkResourceVirtualEnvironmentContainerMountPointMountOptions] = []string{} + } + + mountPoint[mkResourceVirtualEnvironmentContainerMountPointPath] = mp.MountPoint + + if mp.Quota != nil { + mountPoint[mkResourceVirtualEnvironmentContainerMountPointQuota] = *mp.Quota + } else { + mountPoint[mkResourceVirtualEnvironmentContainerMountPointQuota] = false + } + + if mp.ReadOnly != nil { + mountPoint[mkResourceVirtualEnvironmentContainerMountPointReadOnly] = *mp.ReadOnly + } else { + mountPoint[mkResourceVirtualEnvironmentContainerMountPointReadOnly] = false + } + + if mp.Replicate != nil { + mountPoint[mkResourceVirtualEnvironmentContainerMountPointReplicate] = *mp.Replicate + } else { + mountPoint[mkResourceVirtualEnvironmentContainerMountPointReplicate] = true + } + + if mp.Shared != nil { + mountPoint[mkResourceVirtualEnvironmentContainerMountPointShared] = *mp.Shared + } else { + mountPoint[mkResourceVirtualEnvironmentContainerMountPointShared] = false + } + + if mp.DiskSize != nil { + mountPoint[mkResourceVirtualEnvironmentContainerMountPointSize] = *mp.DiskSize + } else { + mountPoint[mkResourceVirtualEnvironmentContainerMountPointSize] = "" + } + + mountPoint[mkResourceVirtualEnvironmentContainerMountPointVolume] = mp.Volume + + mountPointList = append(mountPointList, mountPoint) + } + + currentMountPoint := d.Get(mkResourceVirtualEnvironmentContainerMountPoint).([]interface{}) + + if len(clone) > 0 { + if len(currentMountPoint) > 0 { + err := d.Set(mkResourceVirtualEnvironmentContainerMountPoint, mountPointList) + diags = append(diags, diag.FromErr(err)...) + } + } else if len(mountPointList) > 0 { + err := d.Set(mkResourceVirtualEnvironmentContainerMountPoint, mountPointList) + diags = append(diags, diag.FromErr(err)...) + } + var ipConfigList []interface{} networkInterfaceArray := []*containers.CustomNetworkInterface{ @@ -1945,7 +2172,7 @@ func containerRead(ctx context.Context, d *schema.ResourceData, m interface{}) d } if len(clone) > 0 { - if len(currentMemory) > 0 { + if len(currentOperatingSystem) > 0 { err := d.Set( mkResourceVirtualEnvironmentContainerOperatingSystem, []interface{}{operatingSystem}, @@ -2189,6 +2416,55 @@ func containerUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) rebootRequired = true } + // Prepare the new mount point configuration. + if d.HasChange(mkResourceVirtualEnvironmentContainerMountPoint) { + mountPoint := d.Get(mkResourceVirtualEnvironmentContainerMountPoint).([]interface{}) + mountPointArray := make( + containers.CustomMountPointArray, + len(mountPoint), + ) + + for i, mp := range mountPoint { + mountPointMap := mp.(map[string]interface{}) + mountPointObject := containers.CustomMountPoint{} + + acl := types.CustomBool(mountPointMap[mkResourceVirtualEnvironmentContainerMountPointACL].(bool)) + backup := types.CustomBool(mountPointMap[mkResourceVirtualEnvironmentContainerMountPointBackup].(bool)) + mountOptions := mountPointMap[mkResourceVirtualEnvironmentContainerMountPointMountOptions].([]interface{}) + path := mountPointMap[mkResourceVirtualEnvironmentContainerMountPointPath].(string) + quota := types.CustomBool(mountPointMap[mkResourceVirtualEnvironmentContainerMountPointQuota].(bool)) + readOnly := types.CustomBool(mountPointMap[mkResourceVirtualEnvironmentContainerMountPointReadOnly].(bool)) + replicate := types.CustomBool(mountPointMap[mkResourceVirtualEnvironmentContainerMountPointReplicate].(bool)) + shared := types.CustomBool(mountPointMap[mkResourceVirtualEnvironmentContainerMountPointShared].(bool)) + volume := mountPointMap[mkResourceVirtualEnvironmentContainerMountPointVolume].(string) + + mountPointObject.ACL = &acl + mountPointObject.Backup = &backup + mountPointObject.MountPoint = path + mountPointObject.Quota = "a + mountPointObject.ReadOnly = &readOnly + mountPointObject.Replicate = &replicate + mountPointObject.Shared = &shared + mountPointObject.Volume = volume + + if len(mountOptions) > 0 { + mountOptionsArray := make([]string, 0, len(mountPoint)) + + for _, option := range mountOptions { + mountOptionsArray = append(mountOptionsArray, option.(string)) + } + + mountPointObject.MountOptions = &mountOptionsArray + } + + mountPointArray[i] = mountPointObject + } + + updateBody.MountPoints = mountPointArray + + rebootRequired = true + } + // Prepare the new network interface configuration. networkInterface := d.Get(mkResourceVirtualEnvironmentContainerNetworkInterface).([]interface{}) @@ -2319,7 +2595,7 @@ func containerUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) if d.HasChange(mkResourceVirtualEnvironmentContainerStarted) && !bool(template) { if started { - e = containerAPI.StartContainer(ctx) + e = containerAPI.StartContainer(ctx, 60) if e != nil { return diag.FromErr(e) } diff --git a/proxmoxtf/resource/container_test.go b/proxmoxtf/resource/container_test.go index 0ecfcf4f..57f84fa9 100644 --- a/proxmoxtf/resource/container_test.go +++ b/proxmoxtf/resource/container_test.go @@ -40,6 +40,7 @@ func TestContainerSchema(t *testing.T) { mkResourceVirtualEnvironmentContainerDisk, mkResourceVirtualEnvironmentContainerInitialization, mkResourceVirtualEnvironmentContainerMemory, + mkResourceVirtualEnvironmentContainerMountPoint, mkResourceVirtualEnvironmentContainerOperatingSystem, mkResourceVirtualEnvironmentContainerPoolID, mkResourceVirtualEnvironmentContainerStarted, @@ -56,6 +57,7 @@ func TestContainerSchema(t *testing.T) { mkResourceVirtualEnvironmentContainerDisk: schema.TypeList, mkResourceVirtualEnvironmentContainerInitialization: schema.TypeList, mkResourceVirtualEnvironmentContainerMemory: schema.TypeList, + mkResourceVirtualEnvironmentContainerMountPoint: schema.TypeList, mkResourceVirtualEnvironmentContainerOperatingSystem: schema.TypeList, mkResourceVirtualEnvironmentContainerPoolID: schema.TypeString, mkResourceVirtualEnvironmentContainerStarted: schema.TypeBool, @@ -229,6 +231,32 @@ func TestContainerSchema(t *testing.T) { mkResourceVirtualEnvironmentContainerMemorySwap: schema.TypeInt, }) + mountPointSchema := test.AssertNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentContainerMountPoint) + + test.AssertOptionalArguments(t, mountPointSchema, []string{ + mkResourceVirtualEnvironmentContainerMountPointACL, + mkResourceVirtualEnvironmentContainerMountPointBackup, + mkResourceVirtualEnvironmentContainerMountPointMountOptions, + mkResourceVirtualEnvironmentContainerMountPointQuota, + mkResourceVirtualEnvironmentContainerMountPointReadOnly, + mkResourceVirtualEnvironmentContainerMountPointReplicate, + mkResourceVirtualEnvironmentContainerMountPointShared, + mkResourceVirtualEnvironmentContainerMountPointSize, + }) + + test.AssertValueTypes(t, mountPointSchema, map[string]schema.ValueType{ + mkResourceVirtualEnvironmentContainerMountPointACL: schema.TypeBool, + mkResourceVirtualEnvironmentContainerMountPointBackup: schema.TypeBool, + mkResourceVirtualEnvironmentContainerMountPointMountOptions: schema.TypeList, + mkResourceVirtualEnvironmentContainerMountPointPath: schema.TypeString, + mkResourceVirtualEnvironmentContainerMountPointQuota: schema.TypeBool, + mkResourceVirtualEnvironmentContainerMountPointReadOnly: schema.TypeBool, + mkResourceVirtualEnvironmentContainerMountPointReplicate: schema.TypeBool, + mkResourceVirtualEnvironmentContainerMountPointShared: schema.TypeBool, + mkResourceVirtualEnvironmentContainerMountPointSize: schema.TypeString, + mkResourceVirtualEnvironmentContainerMountPointVolume: schema.TypeString, + }) + networkInterfaceSchema := test.AssertNestedSchemaExistence( t, s, diff --git a/proxmoxtf/resource/utils.go b/proxmoxtf/resource/utils.go index 52d5f148..eb184cac 100644 --- a/proxmoxtf/resource/utils.go +++ b/proxmoxtf/resource/utils.go @@ -211,7 +211,6 @@ func getFileIDValidator() schema.SchemaValidateDiagFunc { }) } -//nolint:unused func getFileSizeValidator() schema.SchemaValidateDiagFunc { return validation.ToDiagFunc(func(i interface{}, k string) ([]string, []error) { v, ok := i.(string)