diff --git a/.changelog/6817.txt b/.changelog/6817.txt new file mode 100644 index 0000000000..4d15d7317b --- /dev/null +++ b/.changelog/6817.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +bigtable: added support for abandoning GC policy +``` diff --git a/google/resource_bigtable_gc_policy.go b/google/resource_bigtable_gc_policy.go index f809e52fab..0a51a06172 100644 --- a/google/resource_bigtable_gc_policy.go +++ b/google/resource_bigtable_gc_policy.go @@ -164,6 +164,15 @@ func resourceBigtableGCPolicy() *schema.Resource { ForceNew: true, Description: `The ID of the project in which the resource belongs. If it is not provided, the provider project is used.`, }, + + "deletion_policy": { + Type: schema.TypeString, + Optional: true, + Description: `The deletion policy for the GC policy. Setting ABANDON allows the resource + to be abandoned rather than deleted. This is useful for GC policy as it cannot be deleted + in a replicated instance. Possible values are: "ABANDON".`, + ValidateFunc: validation.StringInSlice([]string{"ABANDON", ""}, false), + }, }, UseJSONNumber: true, } @@ -367,6 +376,14 @@ func gcPolicyToGCRuleString(gc bigtable.GCPolicy, topLevel bool) (map[string]int func resourceBigtableGCPolicyDestroy(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) + + if deletionPolicy := d.Get("deletion_policy"); deletionPolicy == "ABANDON" { + // Allows for the GC policy to be abandoned without deletion to avoid possible + // deletion failure in a replicated instance. + log.Printf("[WARN] The GC policy is abandoned") + return nil + } + userAgent, err := generateUserAgentString(d, config.userAgent) if err != nil { return err diff --git a/google/resource_bigtable_gc_policy_test.go b/google/resource_bigtable_gc_policy_test.go index 337f3b3d11..8e4a6d3d81 100644 --- a/google/resource_bigtable_gc_policy_test.go +++ b/google/resource_bigtable_gc_policy_test.go @@ -37,6 +37,39 @@ func TestAccBigtableGCPolicy_basic(t *testing.T) { }) } +func TestAccBigtableGCPolicy_abandoned(t *testing.T) { + // bigtable instance does not use the shared HTTP client, this test creates an instance + skipIfVcr(t) + t.Parallel() + + instanceName := fmt.Sprintf("tf-test-%s", randString(t, 10)) + tableName := fmt.Sprintf("tf-test-%s", randString(t, 10)) + familyName := fmt.Sprintf("tf-test-%s", randString(t, 10)) + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckBigtableGCPolicyDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccBigtableGCPolicyToBeAbandoned(instanceName, tableName, familyName), + Check: resource.ComposeTestCheckFunc( + testAccBigtableGCPolicyExists( + t, "google_bigtable_gc_policy.policy", false), + ), + }, + // Verify that the remote infrastructure GC policy still exists after it is removed in the config. + { + Config: testAccBigtableGCPolicyNoPolicy(instanceName, tableName, familyName), + Check: resource.ComposeTestCheckFunc( + testAccBigtableRemoteGCPolicyExists( + t, "google_bigtable_table.table"), + ), + }, + }, + }) +} + func TestAccBigtableGCPolicy_swapOffDeprecated(t *testing.T) { // bigtable instance does not use the shared HTTP client, this test creates an instance skipIfVcr(t) @@ -522,6 +555,46 @@ func testAccBigtableGCPolicyExists(t *testing.T, n string, compareGcRules bool) } } +func testAccBigtableRemoteGCPolicyExists(t *testing.T, table_name_space string) resource.TestCheckFunc { + var ctx = context.Background() + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[table_name_space] + if !ok { + return fmt.Errorf("Table not found: %s", table_name_space) + } + + config := googleProviderConfig(t) + c, err := config.BigTableClientFactory(config.userAgent).NewAdminClient(config.Project, rs.Primary.Attributes["instance_name"]) + if err != nil { + return fmt.Errorf("Error starting admin client. %s", err) + } + + defer c.Close() + + table, err := c.TableInfo(ctx, rs.Primary.Attributes["name"]) + if err != nil { + return fmt.Errorf("Error retrieving table. Could not find %s in %s.", rs.Primary.Attributes["name"], rs.Primary.Attributes["instance_name"]) + } + + // We expect a single local column family in the table. + family, ok := rs.Primary.Attributes["column_family.0.family"] + if !ok { + return fmt.Errorf("Error retrieving the local family") + } + + for _, familyInfo := range table.FamilyInfos { + if familyInfo.Name == family { + if familyInfo.GCPolicy == "" { + return fmt.Errorf("The remote GC policy is missing in family %s", family) + } + return nil + } + } + + return fmt.Errorf("Error retrieving GC policy. Could not find the column family") + } +} + func testAccBigtableCanWriteData(t *testing.T, n string, numberOfRows int) resource.TestCheckFunc { var ctx = context.Background() return func(s *terraform.State) error { @@ -668,6 +741,68 @@ resource "google_bigtable_gc_policy" "policy" { `, instanceName, instanceName, tableName, family, family) } +func testAccBigtableGCPolicyToBeAbandoned(instanceName, tableName, family string) string { + return fmt.Sprintf(` +resource "google_bigtable_instance" "instance" { + name = "%s" + + cluster { + cluster_id = "%s" + zone = "us-central1-b" + } + + instance_type = "DEVELOPMENT" + deletion_protection = false +} + +resource "google_bigtable_table" "table" { + name = "%s" + instance_name = google_bigtable_instance.instance.id + + column_family { + family = "%s" + } +} + +resource "google_bigtable_gc_policy" "policy" { + instance_name = google_bigtable_instance.instance.id + table = google_bigtable_table.table.name + column_family = "%s" + + max_age { + duration = "72h" + } + + deletion_policy = "ABANDON" +} +`, instanceName, instanceName, tableName, family, family) +} + +func testAccBigtableGCPolicyNoPolicy(instanceName, tableName, family string) string { + return fmt.Sprintf(` +resource "google_bigtable_instance" "instance" { + name = "%s" + + cluster { + cluster_id = "%s" + zone = "us-central1-b" + } + + instance_type = "DEVELOPMENT" + deletion_protection = false +} + +resource "google_bigtable_table" "table" { + name = "%s" + instance_name = google_bigtable_instance.instance.id + + column_family { + family = "%s" + } +} +`, instanceName, instanceName, tableName, family) +} + func testAccBigtableGCPolicyUnion(instanceName, tableName, family string) string { return fmt.Sprintf(` resource "google_bigtable_instance" "instance" { diff --git a/website/docs/r/bigtable_gc_policy.html.markdown b/website/docs/r/bigtable_gc_policy.html.markdown index 1c4983cb6a..5d09461574 100644 --- a/website/docs/r/bigtable_gc_policy.html.markdown +++ b/website/docs/r/bigtable_gc_policy.html.markdown @@ -45,6 +45,7 @@ resource "google_bigtable_gc_policy" "policy" { instance_name = google_bigtable_instance.instance.name table = google_bigtable_table.table.name column_family = "name" + deletion_policy = "ABANDON" max_age { duration = "168h" @@ -59,6 +60,7 @@ resource "google_bigtable_gc_policy" "policy" { instance_name = google_bigtable_instance.instance.name table = google_bigtable_table.table.name column_family = "name" + deletion_policy = "ABANDON" mode = "UNION" @@ -101,6 +103,7 @@ resource "google_bigtable_gc_policy" "policy" { instance_name = google_bigtable_instance.instance.id table = google_bigtable_table.table.name column_family = "cf1" + deletion_policy = "ABANDON" gc_rules = <