From ef4afcc5bd1a4c8fcb59ebe837734c8932288789 Mon Sep 17 00:00:00 2001 From: The Magician Date: Mon, 16 Dec 2024 13:41:03 -0800 Subject: [PATCH] Add condition and accessPolicyVersion to BQ dataset access (#12475) (#20707) [upstream:8dd1c9d4e77baf88a270c8a6f8bdbc431e7c8d49] Signed-off-by: Modular Magician --- .changelog/12475.txt | 6 + .../bigquery/resource_bigquery_dataset.go | 138 +++++++++++++++- .../resource_bigquery_dataset_access.go | 147 +++++++++++++++++- .../resource_bigquery_dataset_access_test.go | 58 +++++++ ...esource_bigquery_dataset_generated_test.go | 41 ----- .../resource_bigquery_dataset_test.go | 79 ++++++++++ website/docs/r/bigquery_dataset.html.markdown | 27 ++++ .../r/bigquery_dataset_access.html.markdown | 27 ++++ 8 files changed, 476 insertions(+), 47 deletions(-) create mode 100644 .changelog/12475.txt diff --git a/.changelog/12475.txt b/.changelog/12475.txt new file mode 100644 index 00000000000..8048c5b7b59 --- /dev/null +++ b/.changelog/12475.txt @@ -0,0 +1,6 @@ +```release-note:enhancement +bigquery: added `condition` field to `google_bigquery_dataset_access` resource +``` +```release-note:enhancement +bigquery: added `condition` field to `google_bigquery_dataset` resource +``` \ No newline at end of file diff --git a/google/services/bigquery/resource_bigquery_dataset.go b/google/services/bigquery/resource_bigquery_dataset.go index de494667383..5cecfc82ce4 100644 --- a/google/services/bigquery/resource_bigquery_dataset.go +++ b/google/services/bigquery/resource_bigquery_dataset.go @@ -318,6 +318,40 @@ destroying the resource will fail if tables are present.`, func bigqueryDatasetAccessSchema() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ + "condition": { + Type: schema.TypeList, + Optional: true, + Description: `Condition for the binding. If CEL expression in this field is true, this +access binding will be considered.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "expression": { + Type: schema.TypeString, + Required: true, + Description: `Textual representation of an expression in Common Expression Language syntax.`, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: `Description of the expression. This is a longer text which describes the expression, +e.g. when hovered over it in a UI.`, + }, + "location": { + Type: schema.TypeString, + Optional: true, + Description: `String indicating the location of the expression for error reporting, e.g. a file +name and a position in the file.`, + }, + "title": { + Type: schema.TypeString, + Optional: true, + Description: `Title for the expression, i.e. a short string describing its purpose. +This can be used e.g. in UIs which allow to enter the expression.`, + }, + }, + }, + }, "dataset": { Type: schema.TypeList, Optional: true, @@ -563,7 +597,7 @@ func resourceBigQueryDatasetCreate(d *schema.ResourceData, meta interface{}) err obj["labels"] = labelsProp } - url, err := tpgresource.ReplaceVars(d, config, "{{BigQueryBasePath}}projects/{{project}}/datasets") + url, err := tpgresource.ReplaceVars(d, config, "{{BigQueryBasePath}}projects/{{project}}/datasets?accessPolicyVersion=3") if err != nil { return err } @@ -635,6 +669,7 @@ func resourceBigQueryDatasetRead(d *schema.ResourceData, meta interface{}) error } headers := make(http.Header) + url = url + "?accessPolicyVersion=3" res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ Config: config, Method: "GET", @@ -843,7 +878,7 @@ func resourceBigQueryDatasetUpdate(d *schema.ResourceData, meta interface{}) err obj["labels"] = labelsProp } - url, err := tpgresource.ReplaceVars(d, config, "{{BigQueryBasePath}}projects/{{project}}/datasets/{{dataset_id}}") + url, err := tpgresource.ReplaceVars(d, config, "{{BigQueryBasePath}}projects/{{project}}/datasets/{{dataset_id}}?accessPolicyVersion=3") if err != nil { return err } @@ -975,6 +1010,7 @@ func flattenBigQueryDatasetAccess(v interface{}, d *schema.ResourceData, config "view": flattenBigQueryDatasetAccessView(original["view"], d, config), "dataset": flattenBigQueryDatasetAccessDataset(original["dataset"], d, config), "routine": flattenBigQueryDatasetAccessRoutine(original["routine"], d, config), + "condition": flattenBigQueryDatasetAccessCondition(original["condition"], d, config), }) } return transformed @@ -1103,6 +1139,41 @@ func flattenBigQueryDatasetAccessRoutineRoutineId(v interface{}, d *schema.Resou return v } +func flattenBigQueryDatasetAccessCondition(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["expression"] = + flattenBigQueryDatasetAccessConditionExpression(original["expression"], d, config) + transformed["title"] = + flattenBigQueryDatasetAccessConditionTitle(original["title"], d, config) + transformed["description"] = + flattenBigQueryDatasetAccessConditionDescription(original["description"], d, config) + transformed["location"] = + flattenBigQueryDatasetAccessConditionLocation(original["location"], d, config) + return []interface{}{transformed} +} +func flattenBigQueryDatasetAccessConditionExpression(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenBigQueryDatasetAccessConditionTitle(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenBigQueryDatasetAccessConditionDescription(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenBigQueryDatasetAccessConditionLocation(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + func flattenBigQueryDatasetCreationTime(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { // Handles the string fixed64 format if strVal, ok := v.(string); ok { @@ -1378,6 +1449,13 @@ func expandBigQueryDatasetAccess(v interface{}, d tpgresource.TerraformResourceD transformed["routine"] = transformedRoutine } + transformedCondition, err := expandBigQueryDatasetAccessCondition(original["condition"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedCondition); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["condition"] = transformedCondition + } + req = append(req, transformed) } return req, nil @@ -1561,6 +1639,62 @@ func expandBigQueryDatasetAccessRoutineRoutineId(v interface{}, d tpgresource.Te return v, nil } +func expandBigQueryDatasetAccessCondition(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedExpression, err := expandBigQueryDatasetAccessConditionExpression(original["expression"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedExpression); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["expression"] = transformedExpression + } + + transformedTitle, err := expandBigQueryDatasetAccessConditionTitle(original["title"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedTitle); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["title"] = transformedTitle + } + + transformedDescription, err := expandBigQueryDatasetAccessConditionDescription(original["description"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDescription); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["description"] = transformedDescription + } + + transformedLocation, err := expandBigQueryDatasetAccessConditionLocation(original["location"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedLocation); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["location"] = transformedLocation + } + + return transformed, nil +} + +func expandBigQueryDatasetAccessConditionExpression(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandBigQueryDatasetAccessConditionTitle(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandBigQueryDatasetAccessConditionDescription(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandBigQueryDatasetAccessConditionLocation(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + func expandBigQueryDatasetDatasetReference(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { transformed := make(map[string]interface{}) transformedDatasetId, err := expandBigQueryDatasetDatasetReferenceDatasetId(d.Get("dataset_id"), d, config) diff --git a/google/services/bigquery/resource_bigquery_dataset_access.go b/google/services/bigquery/resource_bigquery_dataset_access.go index ac53a3e5dc5..cc0a9b282b4 100644 --- a/google/services/bigquery/resource_bigquery_dataset_access.go +++ b/google/services/bigquery/resource_bigquery_dataset_access.go @@ -179,6 +179,45 @@ func ResourceBigQueryDatasetAccess() *schema.Resource { must contain only letters (a-z, A-Z), numbers (0-9), or underscores (_). The maximum length is 1,024 characters.`, }, + "condition": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: `Condition for the binding. If CEL expression in this field is true, this +access binding will be considered.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "expression": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `Textual representation of an expression in Common Expression Language syntax.`, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `Description of the expression. This is a longer text which describes the expression, +e.g. when hovered over it in a UI.`, + }, + "location": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `String indicating the location of the expression for error reporting, e.g. a file +name and a position in the file.`, + }, + "title": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `Title for the expression, i.e. a short string describing its purpose. +This can be used e.g. in UIs which allow to enter the expression.`, + }, + }, + }, + }, "dataset": { Type: schema.TypeList, Optional: true, @@ -439,6 +478,12 @@ func resourceBigQueryDatasetAccessCreate(d *schema.ResourceData, meta interface{ } else if v, ok := d.GetOkExists("routine"); !tpgresource.IsEmptyValue(reflect.ValueOf(routineProp)) && (ok || !reflect.DeepEqual(v, routineProp)) { obj["routine"] = routineProp } + conditionProp, err := expandNestedBigQueryDatasetAccessCondition(d.Get("condition"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("condition"); !tpgresource.IsEmptyValue(reflect.ValueOf(conditionProp)) && (ok || !reflect.DeepEqual(v, conditionProp)) { + obj["condition"] = conditionProp + } lockName, err := tpgresource.ReplaceVars(d, config, "{{dataset_id}}") if err != nil { @@ -447,7 +492,7 @@ func resourceBigQueryDatasetAccessCreate(d *schema.ResourceData, meta interface{ transport_tpg.MutexStore.Lock(lockName) defer transport_tpg.MutexStore.Unlock(lockName) - url, err := tpgresource.ReplaceVars(d, config, "{{BigQueryBasePath}}projects/{{project}}/datasets/{{dataset_id}}") + url, err := tpgresource.ReplaceVars(d, config, "{{BigQueryBasePath}}projects/{{project}}/datasets/{{dataset_id}}?accessPolicyVersion=3") if err != nil { return err } @@ -534,7 +579,7 @@ func resourceBigQueryDatasetAccessRead(d *schema.ResourceData, meta interface{}) return err } - url, err := tpgresource.ReplaceVars(d, config, "{{BigQueryBasePath}}projects/{{project}}/datasets/{{dataset_id}}") + url, err := tpgresource.ReplaceVars(d, config, "{{BigQueryBasePath}}projects/{{project}}/datasets/{{dataset_id}}?accessPolicyVersion=3") if err != nil { return err } @@ -609,6 +654,9 @@ func resourceBigQueryDatasetAccessRead(d *schema.ResourceData, meta interface{}) if err := d.Set("routine", flattenNestedBigQueryDatasetAccessRoutine(res["routine"], d, config)); err != nil { return fmt.Errorf("Error reading DatasetAccess: %s", err) } + if err := d.Set("condition", flattenNestedBigQueryDatasetAccessCondition(res["condition"], d, config)); err != nil { + return fmt.Errorf("Error reading DatasetAccess: %s", err) + } return nil } @@ -635,7 +683,7 @@ func resourceBigQueryDatasetAccessDelete(d *schema.ResourceData, meta interface{ transport_tpg.MutexStore.Lock(lockName) defer transport_tpg.MutexStore.Unlock(lockName) - url, err := tpgresource.ReplaceVars(d, config, "{{BigQueryBasePath}}projects/{{project}}/datasets/{{dataset_id}}") + url, err := tpgresource.ReplaceVars(d, config, "{{BigQueryBasePath}}projects/{{project}}/datasets/{{dataset_id}}?accessPolicyVersion=3") if err != nil { return err } @@ -806,6 +854,41 @@ func flattenNestedBigQueryDatasetAccessRoutineRoutineId(v interface{}, d *schema return v } +func flattenNestedBigQueryDatasetAccessCondition(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["expression"] = + flattenNestedBigQueryDatasetAccessConditionExpression(original["expression"], d, config) + transformed["title"] = + flattenNestedBigQueryDatasetAccessConditionTitle(original["title"], d, config) + transformed["description"] = + flattenNestedBigQueryDatasetAccessConditionDescription(original["description"], d, config) + transformed["location"] = + flattenNestedBigQueryDatasetAccessConditionLocation(original["location"], d, config) + return []interface{}{transformed} +} +func flattenNestedBigQueryDatasetAccessConditionExpression(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenNestedBigQueryDatasetAccessConditionTitle(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenNestedBigQueryDatasetAccessConditionDescription(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenNestedBigQueryDatasetAccessConditionLocation(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + func expandNestedBigQueryDatasetAccessDatasetId(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { return v, nil } @@ -1003,6 +1086,62 @@ func expandNestedBigQueryDatasetAccessRoutineRoutineId(v interface{}, d tpgresou return v, nil } +func expandNestedBigQueryDatasetAccessCondition(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedExpression, err := expandNestedBigQueryDatasetAccessConditionExpression(original["expression"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedExpression); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["expression"] = transformedExpression + } + + transformedTitle, err := expandNestedBigQueryDatasetAccessConditionTitle(original["title"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedTitle); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["title"] = transformedTitle + } + + transformedDescription, err := expandNestedBigQueryDatasetAccessConditionDescription(original["description"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDescription); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["description"] = transformedDescription + } + + transformedLocation, err := expandNestedBigQueryDatasetAccessConditionLocation(original["location"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedLocation); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["location"] = transformedLocation + } + + return transformed, nil +} + +func expandNestedBigQueryDatasetAccessConditionExpression(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandNestedBigQueryDatasetAccessConditionTitle(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandNestedBigQueryDatasetAccessConditionDescription(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandNestedBigQueryDatasetAccessConditionLocation(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + func flattenNestedBigQueryDatasetAccess(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) { var v interface{} var ok bool @@ -1198,7 +1337,7 @@ func resourceBigQueryDatasetAccessPatchDeleteEncoder(d *schema.ResourceData, met // extracting list of objects. func resourceBigQueryDatasetAccessListForPatch(d *schema.ResourceData, meta interface{}) ([]interface{}, error) { config := meta.(*transport_tpg.Config) - url, err := tpgresource.ReplaceVars(d, config, "{{BigQueryBasePath}}projects/{{project}}/datasets/{{dataset_id}}") + url, err := tpgresource.ReplaceVars(d, config, "{{BigQueryBasePath}}projects/{{project}}/datasets/{{dataset_id}}?accessPolicyVersion=3") if err != nil { return nil, err } diff --git a/google/services/bigquery/resource_bigquery_dataset_access_test.go b/google/services/bigquery/resource_bigquery_dataset_access_test.go index ae1af93bcd5..1f7a7fe9a69 100644 --- a/google/services/bigquery/resource_bigquery_dataset_access_test.go +++ b/google/services/bigquery/resource_bigquery_dataset_access_test.go @@ -294,6 +294,40 @@ func TestAccBigQueryDatasetAccess_userByEmailWithMixedCase(t *testing.T) { }) } +func TestAccBigQueryDatasetAccess_withCondition(t *testing.T) { + t.Parallel() + + datasetID := fmt.Sprintf("tf_test_%s", acctest.RandString(t, 10)) + saID := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + + expected := map[string]interface{}{ + "condition": map[string]interface{}{ + "description": "Request after midnight of 2019-12-31", + "expression": "request.time > timestamp(\"2020-01-01T00:00:00Z\")", + "location": "any.file.anywhere", + "title": "test-condition", + }, + "role": "OWNER", + "userByEmail": fmt.Sprintf("%s@%s.iam.gserviceaccount.com", saID, envvar.GetTestProjectFromEnv()), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccBigQueryDatasetAccess_withCondition(datasetID, saID), + Check: testAccCheckBigQueryDatasetAccessPresent(t, "google_bigquery_dataset.dataset", expected), + }, + { + // Destroy step instead of CheckDestroy so we can check the access is removed without deleting the dataset + Config: testAccBigQueryDatasetAccess_destroy(datasetID, "dataset"), + Check: testAccCheckBigQueryDatasetAccessAbsent(t, "google_bigquery_dataset.dataset", expected), + }, + }, + }) +} + func TestAccBigQueryDatasetAccess_groupByEmailWithMixedCase(t *testing.T) { t.Parallel() @@ -577,3 +611,27 @@ resource "google_bigquery_dataset" "dataset" { } `, accessType, email, datasetID) } + +func testAccBigQueryDatasetAccess_withCondition(datasetID, saID string) string { + return fmt.Sprintf(` +resource "google_bigquery_dataset_access" "withCondition" { + dataset_id = google_bigquery_dataset.dataset.dataset_id + role = "OWNER" + user_by_email = google_service_account.bqowner.email + condition { + title = "test-condition" + description = "Request after midnight of 2019-12-31" + expression = "request.time > timestamp(\"2020-01-01T00:00:00Z\")" + location = "any.file.anywhere" + } +} + +resource "google_bigquery_dataset" "dataset" { + dataset_id = "%s" +} + +resource "google_service_account" "bqowner" { + account_id = "%s" +} +`, datasetID, saID) +} diff --git a/google/services/bigquery/resource_bigquery_dataset_generated_test.go b/google/services/bigquery/resource_bigquery_dataset_generated_test.go index a76e4fba59f..6cbc0c934ad 100644 --- a/google/services/bigquery/resource_bigquery_dataset_generated_test.go +++ b/google/services/bigquery/resource_bigquery_dataset_generated_test.go @@ -410,47 +410,6 @@ resource "google_service_account" "bqowner" { `, context) } -func TestAccBigQueryDataset_bigqueryDatasetExternalReferenceAwsTestExample(t *testing.T) { - t.Parallel() - - context := map[string]interface{}{ - "random_suffix": acctest.RandString(t, 10), - } - - acctest.VcrTest(t, resource.TestCase{ - PreCheck: func() { acctest.AccTestPreCheck(t) }, - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), - CheckDestroy: testAccCheckBigQueryDatasetDestroyProducer(t), - Steps: []resource.TestStep{ - { - Config: testAccBigQueryDataset_bigqueryDatasetExternalReferenceAwsTestExample(context), - }, - { - ResourceName: "google_bigquery_dataset.dataset", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"labels", "terraform_labels"}, - }, - }, - }) -} - -func testAccBigQueryDataset_bigqueryDatasetExternalReferenceAwsTestExample(context map[string]interface{}) string { - return acctest.Nprintf(` -resource "google_bigquery_dataset" "dataset" { - dataset_id = "tf_test_example_dataset%{random_suffix}" - friendly_name = "test" - description = "This is a test description" - location = "aws-us-east-1" - - external_dataset_reference { - external_source = "aws-glue://arn:aws:glue:us-east-1:772042918353:database/db_other_formats_external" - connection = "projects/bigquerytestdefault/locations/aws-us-east-1/connections/external_test-connection" - } -} -`, context) -} - func TestAccBigQueryDataset_bigqueryDatasetResourceTagsExample(t *testing.T) { t.Parallel() diff --git a/google/services/bigquery/resource_bigquery_dataset_test.go b/google/services/bigquery/resource_bigquery_dataset_test.go index cb0f031072e..35b9210e803 100644 --- a/google/services/bigquery/resource_bigquery_dataset_test.go +++ b/google/services/bigquery/resource_bigquery_dataset_test.go @@ -299,6 +299,15 @@ func TestAccBigQueryDataset_access(t *testing.T) { ImportStateVerify: true, ImportStateVerifyIgnore: []string{"labels", "terraform_labels"}, }, + { + Config: testAccBigQueryDatasetWithConditionAccess(datasetID), + }, + { + ResourceName: "google_bigquery_dataset.access_test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "terraform_labels"}, + }, { Config: testAccBigQueryDatasetWithViewAccess(datasetID, otherDatasetID, otherTableID), }, @@ -455,6 +464,31 @@ func TestAccBigQueryDataset_bigqueryDatasetResourceTags_update(t *testing.T) { }, }) } + +func TestAccBigQueryDataset_bigqueryDatasetExternalReferenceAws(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckBigQueryDatasetDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccBigQueryDataset_bigqueryDatasetExternalReferenceAws(context), + }, + { + ResourceName: "google_bigquery_dataset.dataset", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "terraform_labels", "etag", "last_modified_time"}, + }, + }, + }) +} func testAccAddTable(t *testing.T, datasetID string, tableID string) resource.TestCheckFunc { // Not actually a check, but adds a table independently of terraform return func(s *terraform.State) error { @@ -671,6 +705,35 @@ resource "google_bigquery_dataset" "access_test" { `, datasetID) } +func testAccBigQueryDatasetWithConditionAccess(datasetID string) string { + return fmt.Sprintf(` +resource "google_bigquery_dataset" "access_test" { + dataset_id = "%s" + + access { + role = "OWNER" + user_by_email = "Joe@example.com" + } + + access { + role = "READER" + user_by_email = "Joe@example.com" + condition { + title = "test-condition" + description = "Request after midnight of 2019-12-31" + expression = "request.time > timestamp(\"2020-01-01T00:00:00Z\")" + location = "any.file.anywhere" + } + } + + labels = { + env = "foo" + default_table_expiration_ms = 3600000 + } +} +`, datasetID) +} + func testAccBigQueryDatasetWithThreeAccess(datasetID string) string { return fmt.Sprintf(` resource "google_bigquery_dataset" "access_test" { @@ -904,3 +967,19 @@ resource "google_bigquery_dataset" "dataset" { } `, context) } + +func testAccBigQueryDataset_bigqueryDatasetExternalReferenceAws(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_bigquery_dataset" "dataset" { + dataset_id = "dataset%{random_suffix}" + friendly_name = "test" + description = "This is a test description" + location = "aws-us-east-1" + + external_dataset_reference { + external_source = "aws-glue://arn:aws:glue:us-east-1:772042918353:database/db_other_formats_external" + connection = "projects/bigquerytestdefault/locations/aws-us-east-1/connections/external_test-connection" + } +} +`, context) +} diff --git a/website/docs/r/bigquery_dataset.html.markdown b/website/docs/r/bigquery_dataset.html.markdown index e1ea4d076dc..e01e05b8b97 100644 --- a/website/docs/r/bigquery_dataset.html.markdown +++ b/website/docs/r/bigquery_dataset.html.markdown @@ -445,6 +445,12 @@ destroying the resource will fail if tables are present. needs to be granted again via an update operation. Structure is [documented below](#nested_routine). +* `condition` - + (Optional) + Condition for the binding. If CEL expression in this field is true, this + access binding will be considered. + Structure is [documented below](#nested_condition). + The `view` block supports: @@ -501,6 +507,27 @@ destroying the resource will fail if tables are present. A-Z), numbers (0-9), or underscores (_). The maximum length is 256 characters. +The `condition` block supports: + +* `expression` - + (Required) + Textual representation of an expression in Common Expression Language syntax. + +* `title` - + (Optional) + Title for the expression, i.e. a short string describing its purpose. + This can be used e.g. in UIs which allow to enter the expression. + +* `description` - + (Optional) + Description of the expression. This is a longer text which describes the expression, + e.g. when hovered over it in a UI. + +* `location` - + (Optional) + String indicating the location of the expression for error reporting, e.g. a file + name and a position in the file. + The `external_dataset_reference` block supports: * `external_source` - diff --git a/website/docs/r/bigquery_dataset_access.html.markdown b/website/docs/r/bigquery_dataset_access.html.markdown index 9747bb2f033..62de4acd8bb 100644 --- a/website/docs/r/bigquery_dataset_access.html.markdown +++ b/website/docs/r/bigquery_dataset_access.html.markdown @@ -228,6 +228,12 @@ The following arguments are supported: needs to be granted again via an update operation. Structure is [documented below](#nested_routine). +* `condition` - + (Optional) + Condition for the binding. If CEL expression in this field is true, this + access binding will be considered. + Structure is [documented below](#nested_condition). + * `project` - (Optional) The ID of the project in which the resource belongs. If it is not provided, the provider project is used. @@ -287,6 +293,27 @@ The following arguments are supported: A-Z), numbers (0-9), or underscores (_). The maximum length is 256 characters. +The `condition` block supports: + +* `expression` - + (Required) + Textual representation of an expression in Common Expression Language syntax. + +* `title` - + (Optional) + Title for the expression, i.e. a short string describing its purpose. + This can be used e.g. in UIs which allow to enter the expression. + +* `description` - + (Optional) + Description of the expression. This is a longer text which describes the expression, + e.g. when hovered over it in a UI. + +* `location` - + (Optional) + String indicating the location of the expression for error reporting, e.g. a file + name and a position in the file. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are exported: