From 46a3d2fd47eedf5d3985906513a630d5a26a4b2d Mon Sep 17 00:00:00 2001 From: Nick Jacques Date: Fri, 16 Mar 2018 13:57:42 -0500 Subject: [PATCH] Add deletion protection to resource_compute_instance (#1205) --- google/resource_compute_instance.go | 50 +++++-- google/resource_compute_instance_test.go | 130 ++++++++++++++++++ website/docs/r/compute_instance.html.markdown | 2 + 3 files changed, 169 insertions(+), 13 deletions(-) diff --git a/google/resource_compute_instance.go b/google/resource_compute_instance.go index edbb3cf6e1f..82f07e56109 100644 --- a/google/resource_compute_instance.go +++ b/google/resource_compute_instance.go @@ -552,6 +552,12 @@ func resourceComputeInstance() *schema.Resource { Optional: true, }, + "deletion_protection": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "label_fingerprint": &schema.Schema{ Type: schema.TypeString, Computed: true, @@ -717,19 +723,20 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err // Create the instance information instance := &computeBeta.Instance{ - CanIpForward: d.Get("can_ip_forward").(bool), - Description: d.Get("description").(string), - Disks: disks, - MachineType: machineType.SelfLink, - Metadata: metadata, - Name: d.Get("name").(string), - NetworkInterfaces: networkInterfaces, - Tags: resourceInstanceTags(d), - Labels: expandLabels(d), - ServiceAccounts: expandServiceAccounts(d.Get("service_account").([]interface{})), - GuestAccelerators: accels, - MinCpuPlatform: d.Get("min_cpu_platform").(string), - Scheduling: scheduling, + CanIpForward: d.Get("can_ip_forward").(bool), + Description: d.Get("description").(string), + Disks: disks, + MachineType: machineType.SelfLink, + Metadata: metadata, + Name: d.Get("name").(string), + NetworkInterfaces: networkInterfaces, + Tags: resourceInstanceTags(d), + Labels: expandLabels(d), + ServiceAccounts: expandServiceAccounts(d.Get("service_account").([]interface{})), + GuestAccelerators: accels, + MinCpuPlatform: d.Get("min_cpu_platform").(string), + Scheduling: scheduling, + DeletionProtection: d.Get("deletion_protection").(bool), } log.Printf("[INFO] Requesting instance creation") @@ -898,6 +905,7 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error d.Set("guest_accelerator", flattenGuestAccelerators(instance.GuestAccelerators)) d.Set("cpu_platform", instance.CpuPlatform) d.Set("min_cpu_platform", instance.MinCpuPlatform) + d.Set("deletion_protection", instance.DeletionProtection) d.Set("self_link", ConvertSelfLinkToV1(instance.SelfLink)) d.Set("instance_id", fmt.Sprintf("%d", instance.Id)) d.Set("project", project) @@ -1268,6 +1276,22 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err scopesChange = !oScopes.Equal(nScopes) } + if d.HasChange("deletion_protection") { + nDeletionProtection := d.Get("deletion_protection").(bool) + + op, err := config.clientCompute.Instances.SetDeletionProtection(project, zone, d.Id()).DeletionProtection(nDeletionProtection).Do() + if err != nil { + return fmt.Errorf("Error updating deletion protection flag: %s", err) + } + + opErr := computeOperationWaitTime(config.clientCompute, op, project, "deletion protection to update", int(d.Timeout(schema.TimeoutUpdate).Minutes())) + if opErr != nil { + return opErr + } + + d.SetPartial("deletion_protection") + } + // Attributes which can only be changed if the instance is stopped if scopesChange || d.HasChange("service_account.0.email") || d.HasChange("machine_type") || d.HasChange("min_cpu_platform") { if !d.Get("allow_stopping_for_update").(bool) { diff --git a/google/resource_compute_instance_test.go b/google/resource_compute_instance_test.go index 43af6b9a2b8..4697e240dbf 100644 --- a/google/resource_compute_instance_test.go +++ b/google/resource_compute_instance_test.go @@ -35,6 +35,10 @@ func TestAccComputeInstance_basic1(t *testing.T) { testAccCheckComputeInstanceMetadata(&instance, "foo", "bar"), testAccCheckComputeInstanceMetadata(&instance, "baz", "qux"), testAccCheckComputeInstanceDisk(&instance, instanceName, true, true), + // by default, DeletionProtection is implicitly false. This should be false on any + // instance resource without an explicit deletion_protection = true declaration. + // Other tests check explicit true/false configs: TestAccComputeInstance_deletionProtectionExplicit[True | False] + testAccCheckComputeInstanceHasConfiguredDeletionProtection(&instance, false), ), }, resource.TestStep{ @@ -951,6 +955,67 @@ func TestAccComputeInstance_minCpuPlatform(t *testing.T) { }) } +func TestAccComputeInstance_deletionProtectionExplicitFalse(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_basic_deletionProtectionFalse(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceHasConfiguredDeletionProtection(&instance, false), + ), + }, + }, + }) +} + +func TestAccComputeInstance_deletionProtectionExplicitTrueAndUpdateFalse(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_basic_deletionProtectionTrue(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceHasConfiguredDeletionProtection(&instance, true), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_instance.foobar", + ImportState: true, + ImportStateId: fmt.Sprintf("%s/%s/%s", getTestProjectFromEnv(), "us-central1-a", instanceName), + ImportStateVerifyIgnore: []string{"create_timeout"}, + }, + // Update deletion_protection to false, otherwise the test harness can't delete the instance + resource.TestStep{ + Config: testAccComputeInstance_basic_deletionProtectionFalse(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceHasConfiguredDeletionProtection(&instance, false), + ), + }, + }, + }) +} + func TestAccComputeInstance_primaryAliasIpRange(t *testing.T) { t.Parallel() @@ -1466,6 +1531,16 @@ func testAccCheckComputeInstanceHasAssignedIP(s *terraform.State) error { return nil } +func testAccCheckComputeInstanceHasConfiguredDeletionProtection(instance *compute.Instance, configuredDeletionProtection bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + if instance.DeletionProtection != configuredDeletionProtection { + return fmt.Errorf("Wrong deletion protection flag: expected %t, got %t", configuredDeletionProtection, instance.DeletionProtection) + } + + return nil + } +} + func testAccComputeInstance_basic(instance string) string { return fmt.Sprintf(` resource "google_compute_instance" "foobar" { @@ -1474,6 +1549,7 @@ resource "google_compute_instance" "foobar" { zone = "us-central1-a" can_ip_forward = false tags = ["foo", "bar"] + //deletion_protection = false is implicit in this config due to default value boot_disk { initialize_params{ @@ -1609,6 +1685,60 @@ resource "google_compute_instance" "foobar" { `, instance) } +func testAccComputeInstance_basic_deletionProtectionFalse(instance string) string { + return fmt.Sprintf(` +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + can_ip_forward = false + tags = ["foo", "bar"] + deletion_protection = false + + boot_disk { + initialize_params{ + image = "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20160803" + } + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } +} +`, instance) +} + +func testAccComputeInstance_basic_deletionProtectionTrue(instance string) string { + return fmt.Sprintf(` +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + can_ip_forward = false + tags = ["foo", "bar"] + deletion_protection = true + + boot_disk { + initialize_params{ + image = "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20160803" + } + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } +} +`, instance) +} + // Update zone to ForceNew, and change metadata k/v entirely // Generates diff mismatch func testAccComputeInstance_forceNewAndChangeMetadata(instance string) string { diff --git a/website/docs/r/compute_instance.html.markdown b/website/docs/r/compute_instance.html.markdown index d3bb0de5fd0..9563e93fef4 100644 --- a/website/docs/r/compute_instance.html.markdown +++ b/website/docs/r/compute_instance.html.markdown @@ -90,6 +90,8 @@ The following arguments are supported: * `description` - (Optional) A brief description of this resource. +* `deletion_protection` - (Optional) Enable deletion protection on this instance. Defaults to false. + * `guest_accelerator` - (Optional) List of the type and count of accelerator cards attached to the instance. Structure documented below. * `labels` - (Optional) A set of key/value label pairs to assign to the instance.