From cea65a8a129168581451ecda5b84be8ddd2e3a3e Mon Sep 17 00:00:00 2001 From: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Date: Thu, 14 Mar 2024 22:23:02 -0400 Subject: [PATCH] feat(vm): add support for `protection` attribute (#1126) Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> --- docs/resources/virtual_environment_vm.md | 7 ++-- fwprovider/tests/resource_vm_test.go | 41 +++++++++++++++++++----- proxmox/nodes/vms/vms_types.go | 2 +- proxmoxtf/resource/vm/vm.go | 38 ++++++++++++++++++++++ 4 files changed, 75 insertions(+), 13 deletions(-) diff --git a/docs/resources/virtual_environment_vm.md b/docs/resources/virtual_environment_vm.md index fd0c6db3..57e38e7b 100644 --- a/docs/resources/virtual_environment_vm.md +++ b/docs/resources/virtual_environment_vm.md @@ -447,10 +447,9 @@ output "ubuntu_vm_public_key" { - `win11` - Windows 11 - `wvista` - Windows Vista. - `wxp` - Windows XP. -- `pool_id` - (Optional) The identifier for a pool to assign the virtual machine - to. -- `reboot` - (Optional) Reboot the VM after initial creation. (defaults - to `false`) +- `pool_id` - (Optional) The identifier for a pool to assign the virtual machine to. +- `protection` - (Optional) Sets the protection flag of the VM. This will disable the remove VM and remove disk operations (defaults to `false`). +- `reboot` - (Optional) Reboot the VM after initial creation. (defaults to `false`) - `serial_device` - (Optional) A serial device (multiple blocks supported). - `device` - (Optional) The device (defaults to `socket`). - `/dev/*` - A host serial device. diff --git a/fwprovider/tests/resource_vm_test.go b/fwprovider/tests/resource_vm_test.go index 5661ebe0..e13305b6 100644 --- a/fwprovider/tests/resource_vm_test.go +++ b/fwprovider/tests/resource_vm_test.go @@ -20,9 +20,9 @@ func TestAccResourceVM(t *testing.T) { tests := []struct { name string - step resource.TestStep + step []resource.TestStep }{ - {"multiline description", resource.TestStep{ + {"multiline description", []resource.TestStep{{ Config: providerConfig + ` resource "proxmox_virtual_environment_vm" "test_vm1" { node_name = "pve" @@ -37,8 +37,8 @@ func TestAccResourceVM(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("proxmox_virtual_environment_vm.test_vm1", "description", "my\ndescription\nvalue"), ), - }}, - {"single line description", resource.TestStep{ + }}}, + {"single line description", []resource.TestStep{{ Config: providerConfig + ` resource "proxmox_virtual_environment_vm" "test_vm2" { node_name = "pve" @@ -49,8 +49,8 @@ func TestAccResourceVM(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("proxmox_virtual_environment_vm.test_vm2", "description", "my description value"), ), - }}, - {"no description", resource.TestStep{ + }}}, + {"no description", []resource.TestStep{{ Config: ` resource "proxmox_virtual_environment_vm" "test_vm3" { node_name = "pve" @@ -61,7 +61,32 @@ func TestAccResourceVM(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("proxmox_virtual_environment_vm.test_vm3", "description", ""), ), - }}, + }}}, + { + "protection", []resource.TestStep{{ + Config: ` + resource "proxmox_virtual_environment_vm" "test_vm4" { + node_name = "pve" + started = false + + protection = true + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("proxmox_virtual_environment_vm.test_vm4", "protection", "true"), + ), + }, { + Config: ` + resource "proxmox_virtual_environment_vm" "test_vm4" { + node_name = "pve" + started = false + + protection = false + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("proxmox_virtual_environment_vm.test_vm4", "protection", "false"), + ), + }}, + }, } accProviders := testAccMuxProviders(context.Background(), t) @@ -73,7 +98,7 @@ func TestAccResourceVM(t *testing.T) { resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: accProviders, - Steps: []resource.TestStep{tt.step}, + Steps: tt.step, }) }) } diff --git a/proxmox/nodes/vms/vms_types.go b/proxmox/nodes/vms/vms_types.go index fe8e47e2..4791e7c5 100644 --- a/proxmox/nodes/vms/vms_types.go +++ b/proxmox/nodes/vms/vms_types.go @@ -238,7 +238,7 @@ type CreateRequestBody struct { CPUUnits *int `json:"cpuunits,omitempty" url:"cpuunits,omitempty"` DedicatedMemory *int `json:"memory,omitempty" url:"memory,omitempty"` Delete []string `json:"delete,omitempty" url:"delete,omitempty,comma"` - DeletionProtection *types.CustomBool `json:"protection,omitempty" url:"force,omitempty,int"` + DeletionProtection *types.CustomBool `json:"protection,omitempty" url:"protection,omitempty,int"` Description *string `json:"description,omitempty" url:"description,omitempty"` EFIDisk *CustomEFIDisk `json:"efidisk0,omitempty" url:"efidisk0,omitempty"` FloatingMemory *int `json:"balloon,omitempty" url:"balloon,omitempty"` diff --git a/proxmoxtf/resource/vm/vm.go b/proxmoxtf/resource/vm/vm.go index 06d5aed9..75c3f758 100644 --- a/proxmoxtf/resource/vm/vm.go +++ b/proxmoxtf/resource/vm/vm.go @@ -104,6 +104,7 @@ const ( dvNetworkDeviceMTU = 0 dvOperatingSystemType = "other" dvPoolID = "" + dvProtection = false dvSerialDeviceDevice = "socket" dvSMBIOSFamily = "" dvSMBIOSManufacturer = "" @@ -242,6 +243,7 @@ const ( mkOperatingSystem = "operating_system" mkOperatingSystemType = "type" mkPoolID = "pool_id" + mkProtection = "protection" mkSerialDevice = "serial_device" mkSerialDeviceDevice = "device" mkSMBIOS = "smbios" @@ -1195,6 +1197,12 @@ func VM() *schema.Resource { Optional: true, Default: dvPoolID, }, + mkProtection: { + Type: schema.TypeBool, + Description: "Sets the protection flag of the VM. This will disable the remove VM and remove disk operations", + Optional: true, + Default: dvProtection, + }, mkSerialDevice: { Type: schema.TypeList, Description: "The serial devices", @@ -1822,6 +1830,7 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d serialDevice := d.Get(mkSerialDevice).([]interface{}) onBoot := types.CustomBool(d.Get(mkOnBoot).(bool)) tabletDevice := types.CustomBool(d.Get(mkTabletDevice).(bool)) + protection := types.CustomBool(d.Get(mkProtection).(bool)) template := types.CustomBool(d.Get(mkTemplate).(bool)) vga := d.Get(mkVGA).([]interface{}) @@ -2061,6 +2070,11 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d updateBody.TabletDeviceEnabled = &tabletDevice } + //nolint:gosimple + if protection != dvProtection { + updateBody.DeletionProtection = &protection + } + if len(tags) > 0 { tagString := vmGetTagsString(d) updateBody.Tags = &tagString @@ -2433,6 +2447,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) operatingSystemType := operatingSystem[mkOperatingSystemType].(string) poolID := d.Get(mkPoolID).(string) + protection := types.CustomBool(d.Get(mkProtection).(bool)) serialDevices := vmGetSerialDeviceList(d) @@ -2548,6 +2563,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) CPUSockets: &cpuSockets, CPUUnits: &cpuUnits, DedicatedMemory: &memoryDedicated, + DeletionProtection: &protection, EFIDisk: efiDisk, TPMState: tpmState, FloatingMemory: &memoryFloating, @@ -4672,6 +4688,23 @@ func vmReadPrimitiveValues( diags = append(diags, diag.FromErr(err)...) } + currentProtection := d.Get(mkProtection).(bool) + + //nolint:gosimple + if len(clone) == 0 || currentProtection != dvProtection { + if vmConfig.DeletionProtection != nil { + err = d.Set( + mkProtection, + bool(*vmConfig.DeletionProtection), + ) + } else { + // Default value of "protection" is "0" according to the API documentation. + err = d.Set(mkProtection, false) + } + + diags = append(diags, diag.FromErr(err)...) + } + if !d.Get(mkTemplate).(bool) { err = d.Set(mkStarted, vmStatus.Status == "running") diags = append(diags, diag.FromErr(err)...) @@ -4878,6 +4911,11 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D updateBody.Name = &name } + if d.HasChange(mkProtection) { + protection := types.CustomBool(d.Get(mkProtection).(bool)) + updateBody.DeletionProtection = &protection + } + if d.HasChange(mkTabletDevice) { tabletDevice := types.CustomBool(d.Get(mkTabletDevice).(bool)) updateBody.TabletDeviceEnabled = &tabletDevice