From 33ea4b6e82447e4545107d1ad415242fa2dcd60e Mon Sep 17 00:00:00 2001 From: The Magician Date: Tue, 16 Apr 2024 15:44:41 -0700 Subject: [PATCH] Add resource tags to BigQuery Table (#10455) (#7247) [upstream:c896dbe9a664c43d36e8acbe38b8b90a01e77326] Signed-off-by: Modular Magician --- .changelog/10455.txt | 3 + .../bigquery/resource_bigquery_table.go | 28 ++- .../bigquery/resource_bigquery_table_test.go | 182 ++++++++++++++++++ 3 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 .changelog/10455.txt diff --git a/.changelog/10455.txt b/.changelog/10455.txt new file mode 100644 index 0000000000..31c114a1d2 --- /dev/null +++ b/.changelog/10455.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +bigquery: added `resource_tags` field to `google_bigquery_table` resource +``` \ No newline at end of file diff --git a/google-beta/services/bigquery/resource_bigquery_table.go b/google-beta/services/bigquery/resource_bigquery_table.go index 55afca223c..3e6e2a9b01 100644 --- a/google-beta/services/bigquery/resource_bigquery_table.go +++ b/google-beta/services/bigquery/resource_bigquery_table.go @@ -1307,6 +1307,12 @@ func ResourceBigQueryTable() *schema.Resource { }, }, }, + "resource_tags": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: `The tags attached to this table. Tag keys are globally unique. Tag key is expected to be in the namespaced format, for example "123456789012/environment" where 123456789012 is the ID of the parent organization or project resource for this tag key. Tag value is expected to be the short name, for example "Production".`, + }, }, UseJSONNumber: true, } @@ -1421,6 +1427,8 @@ func resourceTable(d *schema.ResourceData, meta interface{}) (*bigquery.Table, e table.TableConstraints = tableConstraints } + table.ResourceTags = tpgresource.ExpandStringMap(d, "resource_tags") + return table, nil } @@ -1694,6 +1702,10 @@ func resourceBigQueryTableRead(d *schema.ResourceData, meta interface{}) error { } } + if err := d.Set("resource_tags", res.ResourceTags); err != nil { + return fmt.Errorf("Error setting resource tags: %s", err) + } + // TODO: Update when the Get API fields for TableReplicationInfo are available in the client library. url, err := tpgresource.ReplaceVars(d, config, "{{BigQueryBasePath}}projects/{{project}}/datasets/{{dataset_id}}/tables/{{table_id}}") if err != nil { @@ -1774,6 +1786,10 @@ func resourceBigQueryTableColumnDrop(config *transport_tpg.Config, userAgent str return err } + if table.Schema == nil { + return nil + } + newTableFields := map[string]bool{} for _, field := range table.Schema.Fields { newTableFields[field.Name] = true @@ -1809,8 +1825,18 @@ func resourceBigQueryTableColumnDrop(config *transport_tpg.Config, userAgent str func resourceBigQueryTableDelete(d *schema.ResourceData, meta interface{}) error { if d.Get("deletion_protection").(bool) { - return fmt.Errorf("cannot destroy instance without setting deletion_protection=false and running `terraform apply`") + return fmt.Errorf("cannot destroy table %v without setting deletion_protection=false and running `terraform apply`", d.Id()) + } + if v, ok := d.GetOk("resource_tags"); ok { + var resourceTags []string + + for k, v := range v.(map[string]interface{}) { + resourceTags = append(resourceTags, fmt.Sprintf("%s:%s", k, v.(string))) + } + + return fmt.Errorf("cannot destroy table %v without clearing the following resource tags: %v", d.Id(), resourceTags) } + config := meta.(*transport_tpg.Config) userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) if err != nil { diff --git a/google-beta/services/bigquery/resource_bigquery_table_test.go b/google-beta/services/bigquery/resource_bigquery_table_test.go index e1c5728d5b..37a2c5a76a 100644 --- a/google-beta/services/bigquery/resource_bigquery_table_test.go +++ b/google-beta/services/bigquery/resource_bigquery_table_test.go @@ -1554,6 +1554,56 @@ func TestAccBigQueryTable_TableReplicationInfo_WithReplicationInterval(t *testin }) } +func TestAccBigQueryTable_ResourceTags(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "project_id": envvar.GetTestProjectFromEnv(), + "dataset_id": fmt.Sprintf("tf_test_dataset_%s", acctest.RandString(t, 10)), + "table_id": fmt.Sprintf("tf_test_table_%s", acctest.RandString(t, 10)), + "tag_key_name1": fmt.Sprintf("tf_test_tag_key1_%s", acctest.RandString(t, 10)), + "tag_value_name1": fmt.Sprintf("tf_test_tag_value1_%s", acctest.RandString(t, 10)), + "tag_key_name2": fmt.Sprintf("tf_test_tag_key2_%s", acctest.RandString(t, 10)), + "tag_value_name2": fmt.Sprintf("tf_test_tag_value2_%s", acctest.RandString(t, 10)), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + CheckDestroy: testAccCheckBigQueryTableDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccBigQueryTableWithResourceTags(context), + }, + { + ResourceName: "google_bigquery_table.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"deletion_protection"}, + }, + { + Config: testAccBigQueryTableWithResourceTagsUpdate(context), + }, + { + ResourceName: "google_bigquery_table.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"deletion_protection"}, + }, + // testAccBigQueryTableWithResourceTagsDestroy must be called at the end of this test to clear the resource tag bindings of the table before deletion. + { + Config: testAccBigQueryTableWithResourceTagsDestroy(context), + }, + { + ResourceName: "google_bigquery_table.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"deletion_protection"}, + }, + }, + }) +} + func testAccCheckBigQueryExtData(t *testing.T, expectedQuoteChar string) resource.TestCheckFunc { return func(s *terraform.State) error { for _, rs := range s.RootModule().Resources { @@ -3923,6 +3973,138 @@ resource "time_sleep" "wait_10_seconds_last" { `, sourceDatasetID, sourceTableID, sourceMVJobID, sourceDatasetID, sourceMVID, sourceDatasetID, sourceTableID, projectID, sourceMVID, replicaDatasetID, replicaMVID, projectID, sourceMVID, replicationIntervalExpr, dropMVJobID, sourceDatasetID, sourceMVID) } +func testAccBigQueryTableWithResourceTags(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_tags_tag_key" "key1" { + provider = google-beta + + parent = "projects/%{project_id}" + short_name = "%{tag_key_name1}" +} + +resource "google_tags_tag_value" "value1" { + provider = google-beta + + parent = "tagKeys/${google_tags_tag_key.key1.name}" + short_name = "%{tag_value_name1}" +} + +resource "google_bigquery_dataset" "test" { + provider = google-beta + + dataset_id = "%{dataset_id}" +} + +resource "google_bigquery_table" "test" { + provider = google-beta + + deletion_protection = false + dataset_id = "${google_bigquery_dataset.test.dataset_id}" + table_id = "%{table_id}" + resource_tags = { + "%{project_id}/${google_tags_tag_key.key1.short_name}" = "${google_tags_tag_value.value1.short_name}" + } +} +`, context) +} + +func testAccBigQueryTableWithResourceTagsUpdate(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_tags_tag_key" "key1" { + provider = google-beta + + parent = "projects/%{project_id}" + short_name = "%{tag_key_name1}" +} + +resource "google_tags_tag_value" "value1" { + provider = google-beta + + parent = "tagKeys/${google_tags_tag_key.key1.name}" + short_name = "%{tag_value_name1}" +} + +resource "google_tags_tag_key" "key2" { + provider = google-beta + + parent = "projects/%{project_id}" + short_name = "%{tag_key_name2}" +} + +resource "google_tags_tag_value" "value2" { + provider = google-beta + + parent = "tagKeys/${google_tags_tag_key.key2.name}" + short_name = "%{tag_value_name2}" +} + +resource "google_bigquery_dataset" "test" { + provider = google-beta + + dataset_id = "%{dataset_id}" +} + +resource "google_bigquery_table" "test" { + provider = google-beta + + deletion_protection = false + dataset_id = "${google_bigquery_dataset.test.dataset_id}" + table_id = "%{table_id}" + resource_tags = { + "%{project_id}/${google_tags_tag_key.key1.short_name}" = "${google_tags_tag_value.value1.short_name}" + "%{project_id}/${google_tags_tag_key.key2.short_name}" = "${google_tags_tag_value.value2.short_name}" + } +} +`, context) +} + +func testAccBigQueryTableWithResourceTagsDestroy(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_tags_tag_key" "key1" { + provider = google-beta + + parent = "projects/%{project_id}" + short_name = "%{tag_key_name1}" +} + +resource "google_tags_tag_value" "value1" { + provider = google-beta + + parent = "tagKeys/${google_tags_tag_key.key1.name}" + short_name = "%{tag_value_name1}" +} + +resource "google_tags_tag_key" "key2" { + provider = google-beta + + parent = "projects/%{project_id}" + short_name = "%{tag_key_name2}" +} + +resource "google_tags_tag_value" "value2" { + provider = google-beta + + parent = "tagKeys/${google_tags_tag_key.key2.name}" + short_name = "%{tag_value_name2}" +} + +resource "google_bigquery_dataset" "test" { + provider = google-beta + + dataset_id = "%{dataset_id}" +} + +resource "google_bigquery_table" "test" { + provider = google-beta + + deletion_protection = false + dataset_id = "${google_bigquery_dataset.test.dataset_id}" + table_id = "%{table_id}" + resource_tags = {} +} +`, context) +} + var TEST_CSV = `lifelock,LifeLock,,web,Tempe,AZ,1-May-07,6850000,USD,b lifelock,LifeLock,,web,Tempe,AZ,1-Oct-06,6000000,USD,a lifelock,LifeLock,,web,Tempe,AZ,1-Jan-08,25000000,USD,c