Skip to content

Commit fada168

Browse files
modular-magicianc2thornEdward SunLuca Pretemelinath
authored
Bigtable: table deletion protection support (#6722) (#4975)
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]>
1 parent 99bb678 commit fada168

File tree

3 files changed

+240
-0
lines changed

3 files changed

+240
-0
lines changed

.changelog/6722.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
```release-note:enhancement
2+
bigtable: supported table deletion protection in terraform
3+
4+
```

google-beta/resource_bigtable_table.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"cloud.google.com/go/bigtable"
1010
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1112
)
1213

1314
func resourceBigtableTable() *schema.Resource {
@@ -76,6 +77,15 @@ func resourceBigtableTable() *schema.Resource {
7677
ForceNew: true,
7778
Description: `The ID of the project in which the resource belongs. If it is not provided, the provider project is used.`,
7879
},
80+
81+
"deletion_protection": {
82+
Type: schema.TypeString,
83+
Optional: true,
84+
Computed: true,
85+
ValidateFunc: validation.StringInSlice([]string{"PROTECTED", "UNPROTECTED"}, false),
86+
Elem: &schema.Schema{Type: schema.TypeString},
87+
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.`,
88+
},
7989
},
8090
UseJSONNumber: true,
8191
}
@@ -109,6 +119,15 @@ func resourceBigtableTableCreate(d *schema.ResourceData, meta interface{}) error
109119
tableId := d.Get("name").(string)
110120
tblConf := bigtable.TableConf{TableID: tableId}
111121

122+
// Check if deletion protection is given
123+
// If not given, currently tblConf.DeletionProtection will be set to false in the API
124+
deletionProtection := d.Get("deletion_protection")
125+
if deletionProtection == "PROTECTED" {
126+
tblConf.DeletionProtection = bigtable.Protected
127+
} else if deletionProtection == "UNPROTECTED" {
128+
tblConf.DeletionProtection = bigtable.Unprotected
129+
}
130+
112131
// Set the split keys if given.
113132
if v, ok := d.GetOk("split_keys"); ok {
114133
tblConf.SplitKeys = convertStringArr(v.([]interface{}))
@@ -188,6 +207,18 @@ func resourceBigtableTableRead(d *schema.ResourceData, meta interface{}) error {
188207
return fmt.Errorf("Error setting column_family: %s", err)
189208
}
190209

210+
deletionProtection := table.DeletionProtection
211+
if deletionProtection == bigtable.Protected {
212+
if err := d.Set("deletion_protection", "PROTECTED"); err != nil {
213+
return fmt.Errorf("Error setting deletion_protection: %s", err)
214+
}
215+
} else if deletionProtection == bigtable.Unprotected {
216+
if err := d.Set("deletion_protection", "UNPROTECTED"); err != nil {
217+
return fmt.Errorf("Error setting deletion_protection: %s", err)
218+
}
219+
} else {
220+
return fmt.Errorf("Error setting deletion_protection, it should be either PROTECTED or UNPROTECTED")
221+
}
191222
return nil
192223
}
193224

@@ -240,6 +271,19 @@ func resourceBigtableTableUpdate(d *schema.ResourceData, meta interface{}) error
240271
}
241272
}
242273

274+
if d.HasChange("deletion_protection") {
275+
deletionProtection := d.Get("deletion_protection")
276+
if deletionProtection == "PROTECTED" {
277+
if err := c.UpdateTableWithDeletionProtection(ctx, name, bigtable.Protected); err != nil {
278+
return fmt.Errorf("Error updating deletion protection in table %v: %s", name, err)
279+
}
280+
} else if deletionProtection == "UNPROTECTED" {
281+
if err := c.UpdateTableWithDeletionProtection(ctx, name, bigtable.Unprotected); err != nil {
282+
return fmt.Errorf("Error updating deletion protection in table %v: %s", name, err)
283+
}
284+
}
285+
}
286+
243287
return resourceBigtableTableRead(d, meta)
244288
}
245289

google-beta/resource_bigtable_table_test.go

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package google
33
import (
44
"context"
55
"fmt"
6+
"regexp"
67
"testing"
78

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

90+
func TestAccBigtableTable_deletion_protection_protected(t *testing.T) {
91+
// bigtable instance does not use the shared HTTP client, this test creates an instance
92+
skipIfVcr(t)
93+
t.Parallel()
94+
95+
instanceName := fmt.Sprintf("tf-test-%s", randString(t, 10))
96+
tableName := fmt.Sprintf("tf-test-%s", randString(t, 10))
97+
family := fmt.Sprintf("tf-test-%s", randString(t, 10))
98+
99+
vcrTest(t, resource.TestCase{
100+
PreCheck: func() { testAccPreCheck(t) },
101+
Providers: testAccProviders,
102+
CheckDestroy: testAccCheckBigtableTableDestroyProducer(t),
103+
Steps: []resource.TestStep{
104+
// creating a table with a column family and deletion protection equals to protected
105+
{
106+
Config: testAccBigtableTable_deletion_protection(instanceName, tableName, "PROTECTED", family),
107+
},
108+
{
109+
ResourceName: "google_bigtable_table.table",
110+
ImportState: true,
111+
ImportStateVerify: true,
112+
},
113+
// it is not possible to delete column families in the table with deletion protection equals to protected
114+
{
115+
Config: testAccBigtableTable(instanceName, tableName),
116+
ExpectError: regexp.MustCompile(".*deletion protection field is set to true.*"),
117+
},
118+
// it is not possible to delete the table because of deletion protection equals to protected
119+
{
120+
Config: testAccBigtableTable_destroyTable(instanceName),
121+
ExpectError: regexp.MustCompile(".*deletion protection field is set to true.*"),
122+
},
123+
// changing deletion protection field to unprotected without changing the column families
124+
// checking if the table and the column family exists
125+
{
126+
Config: testAccBigtableTable_deletion_protection(instanceName, tableName, "UNPROTECTED", family),
127+
Check: resource.ComposeTestCheckFunc(
128+
testAccBigtableColumnFamilyExists(t, "google_bigtable_table.table", family),
129+
),
130+
},
131+
{
132+
ResourceName: "google_bigtable_table.table",
133+
ImportState: true,
134+
ImportStateVerify: true,
135+
},
136+
// destroying the table is possible when deletion protection is equals to unprotected
137+
{
138+
Config: testAccBigtableTable_destroyTable(instanceName),
139+
},
140+
{
141+
ResourceName: "google_bigtable_instance.instance",
142+
ImportState: true,
143+
ImportStateVerify: true,
144+
ImportStateVerifyIgnore: []string{"deletion_protection", "instance_type"},
145+
},
146+
},
147+
})
148+
}
149+
150+
func TestAccBigtableTable_deletion_protection_unprotected(t *testing.T) {
151+
// bigtable instance does not use the shared HTTP client, this test creates an instance
152+
skipIfVcr(t)
153+
t.Parallel()
154+
155+
instanceName := fmt.Sprintf("tf-test-%s", randString(t, 10))
156+
tableName := fmt.Sprintf("tf-test-%s", randString(t, 10))
157+
family := fmt.Sprintf("tf-test-%s", randString(t, 10))
158+
159+
vcrTest(t, resource.TestCase{
160+
PreCheck: func() { testAccPreCheck(t) },
161+
Providers: testAccProviders,
162+
CheckDestroy: testAccCheckBigtableTableDestroyProducer(t),
163+
Steps: []resource.TestStep{
164+
// creating a table with a column family and deletion protection equals to unprotected
165+
{
166+
Config: testAccBigtableTable_deletion_protection(instanceName, tableName, "UNPROTECTED", family),
167+
},
168+
{
169+
ResourceName: "google_bigtable_table.table",
170+
ImportState: true,
171+
ImportStateVerify: true,
172+
},
173+
// removing the column family is possible because the deletion protection field is unprotected
174+
{
175+
Config: testAccBigtableTable(instanceName, tableName),
176+
},
177+
{
178+
ResourceName: "google_bigtable_table.table",
179+
ImportState: true,
180+
ImportStateVerify: true,
181+
},
182+
// changing the deletion protection field to protected
183+
{
184+
Config: testAccBigtableTable_deletion_protection(instanceName, tableName, "PROTECTED", family),
185+
},
186+
{
187+
ResourceName: "google_bigtable_table.table",
188+
ImportState: true,
189+
ImportStateVerify: true,
190+
},
191+
// it is not possible to delete the table because of deletion protection equals to protected
192+
{
193+
Config: testAccBigtableTable_destroyTable(instanceName),
194+
ExpectError: regexp.MustCompile(".*deletion protection field is set to true.*"),
195+
},
196+
// changing the deletion protection field to unprotected so that the sources can properly be destroyed
197+
{
198+
Config: testAccBigtableTable_deletion_protection(instanceName, tableName, "UNPROTECTED", family),
199+
},
200+
{
201+
ResourceName: "google_bigtable_table.table",
202+
ImportState: true,
203+
ImportStateVerify: true,
204+
},
205+
},
206+
})
207+
}
208+
89209
func TestAccBigtableTable_familyMany(t *testing.T) {
90210
// bigtable instance does not use the shared HTTP client, this test creates an instance
91211
skipIfVcr(t)
@@ -173,6 +293,36 @@ func testAccCheckBigtableTableDestroyProducer(t *testing.T) func(s *terraform.St
173293
}
174294
}
175295

296+
func testAccBigtableColumnFamilyExists(t *testing.T, table_name_space, family string) resource.TestCheckFunc {
297+
var ctx = context.Background()
298+
return func(s *terraform.State) error {
299+
rs, ok := s.RootModule().Resources[table_name_space]
300+
if !ok {
301+
return fmt.Errorf("Table not found: %s", table_name_space)
302+
}
303+
304+
config := googleProviderConfig(t)
305+
c, err := config.BigTableClientFactory(config.userAgent).NewAdminClient(config.Project, rs.Primary.Attributes["instance_name"])
306+
if err != nil {
307+
return fmt.Errorf("Error starting admin client. %s", err)
308+
}
309+
310+
defer c.Close()
311+
312+
table, err := c.TableInfo(ctx, rs.Primary.Attributes["name"])
313+
if err != nil {
314+
return fmt.Errorf("Error retrieving table. Could not find %s in %s.", rs.Primary.Attributes["name"], rs.Primary.Attributes["instance_name"])
315+
}
316+
for _, data := range flattenColumnFamily(table.Families) {
317+
if data["family"] != family {
318+
return fmt.Errorf("Error checking column family. Could not find column family %s in %s.", family, rs.Primary.Attributes["name"])
319+
}
320+
}
321+
322+
return nil
323+
}
324+
}
325+
176326
func testAccBigtableTable(instanceName, tableName string) string {
177327
return fmt.Sprintf(`
178328
resource "google_bigtable_instance" "instance" {
@@ -239,6 +389,32 @@ resource "google_bigtable_table" "table" {
239389
`, instanceName, instanceName, tableName, family)
240390
}
241391

392+
func testAccBigtableTable_deletion_protection(instanceName, tableName, deletionProtection, family string) string {
393+
return fmt.Sprintf(`
394+
resource "google_bigtable_instance" "instance" {
395+
name = "%s"
396+
397+
cluster {
398+
cluster_id = "%s"
399+
zone = "us-central1-b"
400+
}
401+
402+
instance_type = "DEVELOPMENT"
403+
deletion_protection = false
404+
}
405+
406+
resource "google_bigtable_table" "table" {
407+
name = "%s"
408+
instance_name = google_bigtable_instance.instance.name
409+
deletion_protection = "%s"
410+
411+
column_family {
412+
family = "%s"
413+
}
414+
}
415+
`, instanceName, instanceName, tableName, deletionProtection, family)
416+
}
417+
242418
func testAccBigtableTable_familyMany(instanceName, tableName, family string) string {
243419
return fmt.Sprintf(`
244420
resource "google_bigtable_instance" "instance" {
@@ -300,3 +476,19 @@ resource "google_bigtable_table" "table" {
300476
}
301477
`, instanceName, instanceName, tableName, family, family, family)
302478
}
479+
480+
func testAccBigtableTable_destroyTable(instanceName string) string {
481+
return fmt.Sprintf(`
482+
resource "google_bigtable_instance" "instance" {
483+
name = "%s"
484+
485+
cluster {
486+
cluster_id = "%s"
487+
zone = "us-central1-b"
488+
}
489+
490+
instance_type = "DEVELOPMENT"
491+
deletion_protection = false
492+
}
493+
`, instanceName, instanceName)
494+
}

0 commit comments

Comments
 (0)