diff --git a/cloudstack/resource_cloudstack_disk.go b/cloudstack/resource_cloudstack_disk.go index dc325bca..a879b9cb 100644 --- a/cloudstack/resource_cloudstack_disk.go +++ b/cloudstack/resource_cloudstack_disk.go @@ -92,13 +92,19 @@ func resourceCloudStackDisk() *schema.Resource { ForceNew: true, }, - "tags": tagsSchema(), - "reattach_on_change": { Type: schema.TypeBool, Optional: true, Default: false, }, + + "delete_protection": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + + "tags": tagsSchema(), }, } } @@ -147,6 +153,19 @@ func resourceCloudStackDiskCreate(d *schema.ResourceData, meta interface{}) erro // Set the volume ID and partials d.SetId(r.Id) + // Set delete protection using UpdateVolume + if v, ok := d.GetOk("delete_protection"); ok { + p := cs.Volume.NewUpdateVolumeParams() + p.SetId(d.Id()) + p.SetDeleteprotection(v.(bool)) + + _, err := cs.Volume.UpdateVolume(p) + if err != nil { + return fmt.Errorf( + "Error updating the delete protection for disk %s: %s", name, err) + } + } + // Set tags if necessary err = setTags(cs, d, "Volume") if err != nil { @@ -278,6 +297,19 @@ func resourceCloudStackDiskUpdate(d *schema.ResourceData, meta interface{}) erro } } + // Check if the delete protection has changed and if so, update the delete protection + if d.HasChange("delete_protection") { + p := cs.Volume.NewUpdateVolumeParams() + p.SetId(d.Id()) + p.SetDeleteprotection(d.Get("delete_protection").(bool)) + + _, err := cs.Volume.UpdateVolume(p) + if err != nil { + return fmt.Errorf( + "Error updating the delete protection for disk %s: %s", name, err) + } + } + return resourceCloudStackDiskRead(d, meta) } diff --git a/cloudstack/resource_cloudstack_disk_test.go b/cloudstack/resource_cloudstack_disk_test.go index 76031a62..1bf850bc 100644 --- a/cloudstack/resource_cloudstack_disk_test.go +++ b/cloudstack/resource_cloudstack_disk_test.go @@ -21,6 +21,7 @@ package cloudstack import ( "fmt" + "regexp" "testing" "github.com/apache/cloudstack-go/v2/cloudstack" @@ -122,6 +123,45 @@ func TestAccCloudStackDisk_import(t *testing.T) { }) } +func TestAccCloudStackDisk_deleteProtection(t *testing.T) { + var disk cloudstack.Volume + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackDiskDestroy, + Steps: []resource.TestStep{ + { + // create disk with delete protection enabled + Config: fmt.Sprintf(testAccCloudStackDisk_deleteProtection, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackDiskExists("cloudstack_disk.foo", &disk), + resource.TestCheckResourceAttr("cloudstack_disk.foo", "delete_protection", "true"), + ), + }, + { + // attempt to destroy disk. expected to fail due to delete protection is enabled + Config: fmt.Sprintf(testAccCloudStackDisk_deleteProtection, true), + Destroy: true, + ExpectError: regexp.MustCompile(".*has delete protection enabled and cannot be deleted."), + }, + { + // disable delete protection + Config: fmt.Sprintf(testAccCloudStackDisk_deleteProtection, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackDiskExists("cloudstack_disk.foo", &disk), + resource.TestCheckResourceAttr("cloudstack_disk.foo", "delete_protection", "false"), + ), + }, + { + // destroy disk. expected to pass due to disk protection is disabledd + Config: fmt.Sprintf(testAccCloudStackDisk_deleteProtection, false), + Destroy: true, + }, + }, + }) +} + func testAccCheckCloudStackDiskExists( n string, disk *cloudstack.Volume) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -252,3 +292,15 @@ resource "cloudstack_disk" "foo" { virtual_machine_id = cloudstack_instance.foobar.id zone = cloudstack_instance.foobar.zone }` + +const testAccCloudStackDisk_deleteProtection = ` +resource "cloudstack_disk" "foo" { + name = "terraform-disk" + attach = false + disk_offering = "Small" + zone = "Sandbox-simulator" + delete_protection = %t + tags = { + terraform-tag = "true" + } +}` diff --git a/cloudstack/resource_cloudstack_instance.go b/cloudstack/resource_cloudstack_instance.go index 6a38ddb4..03bd75f2 100644 --- a/cloudstack/resource_cloudstack_instance.go +++ b/cloudstack/resource_cloudstack_instance.go @@ -249,6 +249,12 @@ func resourceCloudStackInstance() *schema.Resource { Optional: true, }, + "delete_protection": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "tags": tagsSchema(), }, } @@ -479,6 +485,18 @@ func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{}) d.SetId(r.Id) + // Set delete protection using UpdateVirtualMachine + if v, ok := d.GetOk("delete_protection"); ok { + p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id()) + p.SetDeleteprotection(v.(bool)) + + _, err := cs.VirtualMachine.UpdateVirtualMachine(p) + if err != nil { + return fmt.Errorf( + "Error updating the delete protection for instance %s: %s", name, err) + } + } + // Set tags if necessary if err = setTags(cs, d, "userVm"); err != nil { return fmt.Errorf("Error setting tags on the new instance %s: %s", name, err) @@ -873,6 +891,18 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) } } + // Check if the delete protection has changed and if so, update the deleteprotection + if d.HasChange("delete_protection") { + p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id()) + p.SetDeleteprotection(d.Get("delete_protection").(bool)) + + _, err := cs.VirtualMachine.UpdateVirtualMachine(p) + if err != nil { + return fmt.Errorf( + "Error updating the delete_protection for instance %s: %s", name, err) + } + } + return resourceCloudStackInstanceRead(d, meta) } diff --git a/cloudstack/resource_cloudstack_instance_test.go b/cloudstack/resource_cloudstack_instance_test.go index 5979aaaf..8632c966 100644 --- a/cloudstack/resource_cloudstack_instance_test.go +++ b/cloudstack/resource_cloudstack_instance_test.go @@ -295,6 +295,45 @@ func TestAccCloudStackInstance_userData(t *testing.T) { }) } +func TestAccCloudStackInstance_deleteProtection(t *testing.T) { + var instance cloudstack.VirtualMachine + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackDiskDestroy, + Steps: []resource.TestStep{ + { + // create vm with delete protection enabled + Config: fmt.Sprintf(testAccCloudStackInstance_deleteProtection, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackInstanceExists("cloudstack_instance.foobar", &instance), + resource.TestCheckResourceAttr("cloudstack_instance.foobar", "delete_protection", "true"), + ), + }, + { + // attempt to destroy vm. expected to fail due to delete protection is enabled + Config: fmt.Sprintf(testAccCloudStackInstance_deleteProtection, true), + Destroy: true, + ExpectError: regexp.MustCompile(".*has delete protection enabled and cannot be deleted."), + }, + { + // disable delete protection + Config: fmt.Sprintf(testAccCloudStackInstance_deleteProtection, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackInstanceExists("cloudstack_instance.foobar", &instance), + resource.TestCheckResourceAttr("cloudstack_instance.foobar", "delete_protection", "false"), + ), + }, + { + // destroy vm. expected to pass due to disk protection is disabledd + Config: fmt.Sprintf(testAccCloudStackInstance_deleteProtection, false), + Destroy: true, + }, + }, + }) +} + func testAccCheckCloudStackInstanceExists( n string, instance *cloudstack.VirtualMachine) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -576,3 +615,27 @@ ${random_bytes.string.base64} EOF EOFTF }` + +const testAccCloudStackInstance_deleteProtection = ` +resource "cloudstack_network" "foo" { + name = "terraform-network" + display_text = "terraform-network" + cidr = "10.1.1.0/24" + network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" + zone = "Sandbox-simulator" +} + +resource "cloudstack_instance" "foobar" { + name = "terraform-test" + display_name = "terraform-test" + service_offering= "Small Instance" + network_id = cloudstack_network.foo.id + template = "CentOS 5.6 (64-bit) no GUI (Simulator)" + zone = "Sandbox-simulator" + user_data = "foobar\nfoo\nbar" + expunge = true + delete_protection = %t + tags = { + terraform-tag = "true" + } +}` diff --git a/website/docs/r/disk.html.markdown b/website/docs/r/disk.html.markdown index 1f4e0528..05d2dfb7 100644 --- a/website/docs/r/disk.html.markdown +++ b/website/docs/r/disk.html.markdown @@ -56,6 +56,9 @@ The following arguments are supported: * `reattach_on_change` - (Optional) Determines whether or not to detach the disk volume from the virtual machine on disk offering or size change. +* `delete_protection` - (Optional) Set delete protection for the volume. If true, the volume will be protected from deletion. + Note: If the volume is managed by another service like autoscaling groups or CKS, delete protection will be ignored. + ## Attributes Reference The following attributes are exported: diff --git a/website/docs/r/instance.html.markdown b/website/docs/r/instance.html.markdown index ebf3f20f..285e64bc 100644 --- a/website/docs/r/instance.html.markdown +++ b/website/docs/r/instance.html.markdown @@ -175,9 +175,11 @@ The following arguments are supported: * `user_data` - (Optional) The user data to provide when launching the instance. This can be either plain text or base64 encoded text. -* `userdata_id` - (Optional) The ID of a registered CloudStack user data to use for this instance. Cannot be used together with `user_data`. +* `userdata_id` - (Optional) The ID of a registered CloudStack user data to use for this instance. + Cannot be used together with `user_data`. -* `userdata_details` - (Optional) A map of key-value pairs to pass as parameters to the user data script. Only valid when `userdata_id` is specified. Keys must match the parameter names defined in the user data. +* `userdata_details` - (Optional) A map of key-value pairs to pass as parameters to the user data script. + Only valid when `userdata_id` is specified. Keys must match the parameter names defined in the user data. * `keypair` - (Optional) The name of the SSH key pair that will be used to access this instance. (Mutual exclusive with keypairs) @@ -192,6 +194,9 @@ The following arguments are supported: * `boot_mode` - (Optional) The boot mode of the instance. Can only be specified when uefi is true. Valid options are 'Legacy' and 'Secure'. +* `delete_protection` - (Optional) Set delete protection for the virtual machine. If true, the instance will be protected from deletion. + Note: If the instance is managed by another service like autoscaling groups or CKS, delete protection will be ignored. + ## Attributes Reference The following attributes are exported: