Skip to content

Commit

Permalink
Bigtable: table deletion protection support (#6722) (#4975)
Browse files Browse the repository at this point in the history
Co-authored-by: Cameron Thornton <[email protected]>
Co-authored-by: Edward Sun <[email protected]>
Co-authored-by: Luca Prete <[email protected]>
Co-authored-by: Stephen Lewis (Burrows) <[email protected]>
Co-authored-by: Aleksandr Averbukh <[email protected]>
Co-authored-by: Edward Sun <[email protected]>
Co-authored-by: Jay Sanghani <[email protected]>
Co-authored-by: Sarah French <[email protected]>
Co-authored-by: Stephen Lewis (Burrows) <[email protected]>
Co-authored-by: AlfatahB <[email protected]>
Co-authored-by: Kevin Si <[email protected]>
Co-authored-by: Zhenhua Li <[email protected]>
Co-authored-by: Mikołaj Siedlarek <[email protected]>
Co-authored-by: Avi Dave <[email protected]>
Co-authored-by: Grigory Solomin <[email protected]>
Co-authored-by: Avi Dave <[email protected]>
Co-authored-by: Ryan Oaks <[email protected]>
Co-authored-by: Haruaki OTAKE <[email protected]>
Co-authored-by: x <[email protected]>
Co-authored-by: sakuya9t <[email protected]>
Co-authored-by: Luca Prete <[email protected]>
Co-authored-by: aniketkumarj <[email protected]>
Co-authored-by: Terrence Ryan <[email protected]>
Co-authored-by: Sam Levenick <[email protected]>
Co-authored-by: Shotaro Kohama <[email protected]>
Co-authored-by: Francis (Feng) Liu <[email protected]>
Co-authored-by: bohengy <[email protected]>
Co-authored-by: Søren Hansen <[email protected]>
Co-authored-by: Ilia Lazebnik <[email protected]>
Co-authored-by: t-indumathy <[email protected]>
Co-authored-by: AarshDhokai <[email protected]>
Co-authored-by: Joost Buskermolen <[email protected]>
Co-authored-by: Benjamin Berriot <[email protected]>
Co-authored-by: Scott Suarez <[email protected]>
Co-authored-by: Carl Yeksigian <[email protected]>
Co-authored-by: Shuya Ma <[email protected]>
Co-authored-by: iperetz-goo <[email protected]>
Co-authored-by: Riley Karson <[email protected]>
Co-authored-by: Thomas Rodgers <[email protected]>
Co-authored-by: Daniel Vega-Myhre <[email protected]>
Co-authored-by: Neha Vellanki <[email protected]>
Co-authored-by: gfxcc <[email protected]>
Co-authored-by: Lingkai Shen <[email protected]>
Co-authored-by: Stenal P Jolly <[email protected]>
Co-authored-by: Mohamed Fouad <[email protected]>
Signed-off-by: Modular Magician <[email protected]>

Signed-off-by: Modular Magician <[email protected]>
Co-authored-by: Cameron Thornton <[email protected]>
Co-authored-by: Edward Sun <[email protected]>
Co-authored-by: Luca Prete <[email protected]>
Co-authored-by: Stephen Lewis (Burrows) <[email protected]>
Co-authored-by: Aleksandr Averbukh <[email protected]>
Co-authored-by: Edward Sun <[email protected]>
Co-authored-by: Jay Sanghani <[email protected]>
Co-authored-by: Sarah French <[email protected]>
Co-authored-by: Stephen Lewis (Burrows) <[email protected]>
Co-authored-by: AlfatahB <[email protected]>
Co-authored-by: Kevin Si <[email protected]>
Co-authored-by: Zhenhua Li <[email protected]>
Co-authored-by: Mikołaj Siedlarek <[email protected]>
Co-authored-by: Avi Dave <[email protected]>
Co-authored-by: Grigory Solomin <[email protected]>
Co-authored-by: Avi Dave <[email protected]>
Co-authored-by: Ryan Oaks <[email protected]>
Co-authored-by: Haruaki OTAKE <[email protected]>
Co-authored-by: x <[email protected]>
Co-authored-by: sakuya9t <[email protected]>
Co-authored-by: Luca Prete <[email protected]>
Co-authored-by: aniketkumarj <[email protected]>
Co-authored-by: Terrence Ryan <[email protected]>
Co-authored-by: Sam Levenick <[email protected]>
Co-authored-by: Shotaro Kohama <[email protected]>
Co-authored-by: Francis (Feng) Liu <[email protected]>
Co-authored-by: bohengy <[email protected]>
Co-authored-by: Søren Hansen <[email protected]>
Co-authored-by: Ilia Lazebnik <[email protected]>
Co-authored-by: t-indumathy <[email protected]>
Co-authored-by: AarshDhokai <[email protected]>
Co-authored-by: Joost Buskermolen <[email protected]>
Co-authored-by: Benjamin Berriot <[email protected]>
Co-authored-by: Scott Suarez <[email protected]>
Co-authored-by: Carl Yeksigian <[email protected]>
Co-authored-by: Shuya Ma <[email protected]>
Co-authored-by: iperetz-goo <[email protected]>
Co-authored-by: Riley Karson <[email protected]>
Co-authored-by: Thomas Rodgers <[email protected]>
Co-authored-by: Daniel Vega-Myhre <[email protected]>
Co-authored-by: Neha Vellanki <[email protected]>
Co-authored-by: gfxcc <[email protected]>
Co-authored-by: Lingkai Shen <[email protected]>
Co-authored-by: Stenal P Jolly <[email protected]>
Co-authored-by: Mohamed Fouad <[email protected]>
  • Loading branch information
Show file tree
Hide file tree
Showing 3 changed files with 240 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .changelog/6722.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
```release-note:enhancement
bigtable: supported table deletion protection in terraform

```
44 changes: 44 additions & 0 deletions google-beta/resource_bigtable_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"cloud.google.com/go/bigtable"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

func resourceBigtableTable() *schema.Resource {
Expand Down Expand Up @@ -76,6 +77,15 @@ func resourceBigtableTable() *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_protection": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringInSlice([]string{"PROTECTED", "UNPROTECTED"}, false),
Elem: &schema.Schema{Type: schema.TypeString},
Description: `A field to make the table protected against data loss i.e. when set to PROTECTED, deleting the table, the column families in the table, and the instance containing the table would be prohibited. If not provided, currently deletion protection will be set to UNPROTECTED as it is the API default value.`,
},
},
UseJSONNumber: true,
}
Expand Down Expand Up @@ -109,6 +119,15 @@ func resourceBigtableTableCreate(d *schema.ResourceData, meta interface{}) error
tableId := d.Get("name").(string)
tblConf := bigtable.TableConf{TableID: tableId}

// Check if deletion protection is given
// If not given, currently tblConf.DeletionProtection will be set to false in the API
deletionProtection := d.Get("deletion_protection")
if deletionProtection == "PROTECTED" {
tblConf.DeletionProtection = bigtable.Protected
} else if deletionProtection == "UNPROTECTED" {
tblConf.DeletionProtection = bigtable.Unprotected
}

// Set the split keys if given.
if v, ok := d.GetOk("split_keys"); ok {
tblConf.SplitKeys = convertStringArr(v.([]interface{}))
Expand Down Expand Up @@ -188,6 +207,18 @@ func resourceBigtableTableRead(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("Error setting column_family: %s", err)
}

deletionProtection := table.DeletionProtection
if deletionProtection == bigtable.Protected {
if err := d.Set("deletion_protection", "PROTECTED"); err != nil {
return fmt.Errorf("Error setting deletion_protection: %s", err)
}
} else if deletionProtection == bigtable.Unprotected {
if err := d.Set("deletion_protection", "UNPROTECTED"); err != nil {
return fmt.Errorf("Error setting deletion_protection: %s", err)
}
} else {
return fmt.Errorf("Error setting deletion_protection, it should be either PROTECTED or UNPROTECTED")
}
return nil
}

Expand Down Expand Up @@ -240,6 +271,19 @@ func resourceBigtableTableUpdate(d *schema.ResourceData, meta interface{}) error
}
}

if d.HasChange("deletion_protection") {
deletionProtection := d.Get("deletion_protection")
if deletionProtection == "PROTECTED" {
if err := c.UpdateTableWithDeletionProtection(ctx, name, bigtable.Protected); err != nil {
return fmt.Errorf("Error updating deletion protection in table %v: %s", name, err)
}
} else if deletionProtection == "UNPROTECTED" {
if err := c.UpdateTableWithDeletionProtection(ctx, name, bigtable.Unprotected); err != nil {
return fmt.Errorf("Error updating deletion protection in table %v: %s", name, err)
}
}
}

return resourceBigtableTableRead(d, meta)
}

Expand Down
192 changes: 192 additions & 0 deletions google-beta/resource_bigtable_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package google
import (
"context"
"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
Expand Down Expand Up @@ -86,6 +87,125 @@ func TestAccBigtableTable_family(t *testing.T) {
})
}

func TestAccBigtableTable_deletion_protection_protected(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))
family := fmt.Sprintf("tf-test-%s", randString(t, 10))

vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckBigtableTableDestroyProducer(t),
Steps: []resource.TestStep{
// creating a table with a column family and deletion protection equals to protected
{
Config: testAccBigtableTable_deletion_protection(instanceName, tableName, "PROTECTED", family),
},
{
ResourceName: "google_bigtable_table.table",
ImportState: true,
ImportStateVerify: true,
},
// it is not possible to delete column families in the table with deletion protection equals to protected
{
Config: testAccBigtableTable(instanceName, tableName),
ExpectError: regexp.MustCompile(".*deletion protection field is set to true.*"),
},
// it is not possible to delete the table because of deletion protection equals to protected
{
Config: testAccBigtableTable_destroyTable(instanceName),
ExpectError: regexp.MustCompile(".*deletion protection field is set to true.*"),
},
// changing deletion protection field to unprotected without changing the column families
// checking if the table and the column family exists
{
Config: testAccBigtableTable_deletion_protection(instanceName, tableName, "UNPROTECTED", family),
Check: resource.ComposeTestCheckFunc(
testAccBigtableColumnFamilyExists(t, "google_bigtable_table.table", family),
),
},
{
ResourceName: "google_bigtable_table.table",
ImportState: true,
ImportStateVerify: true,
},
// destroying the table is possible when deletion protection is equals to unprotected
{
Config: testAccBigtableTable_destroyTable(instanceName),
},
{
ResourceName: "google_bigtable_instance.instance",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"deletion_protection", "instance_type"},
},
},
})
}

func TestAccBigtableTable_deletion_protection_unprotected(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))
family := fmt.Sprintf("tf-test-%s", randString(t, 10))

vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckBigtableTableDestroyProducer(t),
Steps: []resource.TestStep{
// creating a table with a column family and deletion protection equals to unprotected
{
Config: testAccBigtableTable_deletion_protection(instanceName, tableName, "UNPROTECTED", family),
},
{
ResourceName: "google_bigtable_table.table",
ImportState: true,
ImportStateVerify: true,
},
// removing the column family is possible because the deletion protection field is unprotected
{
Config: testAccBigtableTable(instanceName, tableName),
},
{
ResourceName: "google_bigtable_table.table",
ImportState: true,
ImportStateVerify: true,
},
// changing the deletion protection field to protected
{
Config: testAccBigtableTable_deletion_protection(instanceName, tableName, "PROTECTED", family),
},
{
ResourceName: "google_bigtable_table.table",
ImportState: true,
ImportStateVerify: true,
},
// it is not possible to delete the table because of deletion protection equals to protected
{
Config: testAccBigtableTable_destroyTable(instanceName),
ExpectError: regexp.MustCompile(".*deletion protection field is set to true.*"),
},
// changing the deletion protection field to unprotected so that the sources can properly be destroyed
{
Config: testAccBigtableTable_deletion_protection(instanceName, tableName, "UNPROTECTED", family),
},
{
ResourceName: "google_bigtable_table.table",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccBigtableTable_familyMany(t *testing.T) {
// bigtable instance does not use the shared HTTP client, this test creates an instance
skipIfVcr(t)
Expand Down Expand Up @@ -173,6 +293,36 @@ func testAccCheckBigtableTableDestroyProducer(t *testing.T) func(s *terraform.St
}
}

func testAccBigtableColumnFamilyExists(t *testing.T, table_name_space, family 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"])
}
for _, data := range flattenColumnFamily(table.Families) {
if data["family"] != family {
return fmt.Errorf("Error checking column family. Could not find column family %s in %s.", family, rs.Primary.Attributes["name"])
}
}

return nil
}
}

func testAccBigtableTable(instanceName, tableName string) string {
return fmt.Sprintf(`
resource "google_bigtable_instance" "instance" {
Expand Down Expand Up @@ -239,6 +389,32 @@ resource "google_bigtable_table" "table" {
`, instanceName, instanceName, tableName, family)
}

func testAccBigtableTable_deletion_protection(instanceName, tableName, deletionProtection, 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.name
deletion_protection = "%s"
column_family {
family = "%s"
}
}
`, instanceName, instanceName, tableName, deletionProtection, family)
}

func testAccBigtableTable_familyMany(instanceName, tableName, family string) string {
return fmt.Sprintf(`
resource "google_bigtable_instance" "instance" {
Expand Down Expand Up @@ -300,3 +476,19 @@ resource "google_bigtable_table" "table" {
}
`, instanceName, instanceName, tableName, family, family, family)
}

func testAccBigtableTable_destroyTable(instanceName 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
}
`, instanceName, instanceName)
}

0 comments on commit fada168

Please sign in to comment.