diff --git a/docs/resources/lxc.md b/docs/resources/lxc.md index 3eed69b4..734b3283 100644 --- a/docs/resources/lxc.md +++ b/docs/resources/lxc.md @@ -78,12 +78,12 @@ resource "proxmox_lxc" "multiple_mountpoints" { // Device Mount Point mountpoint { - key = "2" - slot = 2 - storage = "/dev/sdg" - volume = "/dev/sdg" - mp = "/mnt/container/device-mount-point" - size = "32G" + key = "2" + slot = 2 + storage = "/dev/sdg" + volume = "/dev/sdg" + mp = "/mnt/container/device-mount-point" + size = "32G" } network { @@ -127,6 +127,14 @@ resource "proxmox_lxc" "advanced_features" { storage = "/mnt/host/nfs" mp = "/mnt/container/nfs" size = "250G" + + // mountoptions can be omitted: all options will be disabled. + // Each option should be set to true as follows: + // Don't set options to false; simply omit them. + mountoptions = { + "lazytime" : true, + "nodev" : true + } } network { @@ -219,6 +227,7 @@ The following arguments may be optionally defined when using this resource: * `quota` - A boolean for enabling user quotas inside the container for this mount point. Default is `false`. * `replicate` - A boolean for including this volume in a storage replica job. Default is `false`. * `shared` - A boolean for marking the volume as available on all nodes. Default is `false`. + * `mountoptions` - A map for setting the lxc mount options disks. Default is `null` meaning all options disabled. * `nameserver` - The DNS server IP address used by the container. If neither `nameserver` nor `searchdomain` are specified, the values of the Proxmox host will be used by default. * `network` - An object defining a network interface for the container. Can be specified multiple times. diff --git a/proxmox/resource_lxc.go b/proxmox/resource_lxc.go index cb157903..97b95264 100644 --- a/proxmox/resource_lxc.go +++ b/proxmox/resource_lxc.go @@ -222,6 +222,30 @@ func resourceLxc() *schema.Resource { Optional: true, Computed: true, }, + "mountoptions": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeBool, + }, + ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { + allowedOptions := map[string]bool{ + "lazytime": true, + "noexec": true, + "nosuid": true, + "nodev": true, + "discard": true, + "noatime": true, + } + for k := range val.(map[string]interface{}) { + if _, ok := allowedOptions[k]; !ok { + errs = append(errs, fmt.Errorf("invalid mount option %s", k)) + } + } + return warns, errs + }, + Description: "Map of mount options. Allowed options: discard, lazytime, noatime, noexec, nosuid, nodev", + }, }, }, }, @@ -515,6 +539,18 @@ func resourceLxcCreate(ctx context.Context, d *schema.ResourceData, meta interfa mountpoints := d.Get("mountpoint").([]interface{}) if len(mountpoints) > 0 { lxcMountpoints := DevicesListToDevices(mountpoints, "slot") + // Then handle `mountoptions` inside each mountpoint + for _, mp := range lxcMountpoints { + if mountoptions, ok := mp["mountoptions"]; ok { + if len(mountoptions.([]interface{})) > 0 { + // Assign first element to mountoptions + mp["mountoptions"] = mountoptions.([]interface{})[0] + } else { + // If empty, delete mountoptions from this mountpoint + delete(mp, "mountoptions") + } + } + } config.Mountpoints = lxcMountpoints } @@ -707,6 +743,10 @@ func resourceLxcUpdate(ctx context.Context, d *schema.ResourceData, meta interfa oldSet, newSet := d.GetChange("mountpoint") oldMounts := DevicesListToMapByKey(oldSet.([]interface{}), "key") newMounts := DevicesListToMapByKey(newSet.([]interface{}), "key") + + // Removing mountoptions if no options provided + newMounts = formatMountOptions(newMounts) + processLxcDiskChanges(ctx, oldMounts, newMounts, pconf, vmr) lxcMountpoints := DevicesListToDevices(newSet.([]interface{}), "slot") @@ -942,6 +982,10 @@ func processLxcDiskChanges( if ok { for k, v := range prevDisk { + if k == "mountoptions" { + continue // No override of mountoptions by preDisk, necessary in case newDisk mountoptions null + } + _, ok := newDisk[k] if !ok { newDisk[k] = v @@ -1079,3 +1123,30 @@ func processLxcNetworkChanges(ctx context.Context, prevNetworks []map[string]int return nil } + +// Remove options set to false and mountoptions block if empty +func formatMountOptions(mountpoints KeyedDeviceMap) KeyedDeviceMap { + // Initialize the result map to store cleaned mountpoints + cleanedMounts := make(KeyedDeviceMap) + + // Iterate over each device in the KeyedDeviceMap + for key, mount := range mountpoints { + // Get mountoptions, and immediately check if it is a valid map with length > 0 + if optionsMap, ok := mount["mountoptions"].(map[string]interface{}); ok { + + // Remove false options + for optionKey, optionValue := range optionsMap { + if !optionValue.(bool) { + delete(optionsMap, optionKey) + } + } + if len(optionsMap) > 0 { + mount["mountoptions"] = optionsMap + } else { + delete(mount, "mountoptions") // Delete if it's not valid or if it's empty + } + } + cleanedMounts[key] = mount + } + return cleanedMounts +}