Skip to content

Commit

Permalink
fix bug with data access iam member (#3741)
Browse files Browse the repository at this point in the history
* fix bug with data access iam member

* update to still allow iam_member if prefix is not in map

* update to add special groups without prefixes to map

* updates post create rather than using customize diff

* clean up map

* undo unnecessary changes to nested_query

* add comment
  • Loading branch information
megan07 authored Aug 18, 2020
1 parent c28e684 commit c591599
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 13 deletions.
14 changes: 13 additions & 1 deletion products/bigquery/terraform.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,20 @@ overrides: !ruby/object:Overrides::ResourceOverrides
# converting IAM roles set in state to their primitive equivalents
# before comparison.
custom_expand: "templates/terraform/custom_expand/bigquery_access_role.go.erb"
iamMember: !ruby/object:Overrides::Terraform::PropertyOverride
diff_suppress_func: resourceBigQueryDatasetAccessIamMemberDiffSuppress
userByEmail: !ruby/object:Overrides::Terraform::PropertyOverride
diff_suppress_func: resourceBigQueryDatasetAccessIamMemberDiffSuppress
groupByEmail: !ruby/object:Overrides::Terraform::PropertyOverride
diff_suppress_func: resourceBigQueryDatasetAccessIamMemberDiffSuppress
specialGroup: !ruby/object:Overrides::Terraform::PropertyOverride
diff_suppress_func: resourceBigQueryDatasetAccessIamMemberDiffSuppress
domain: !ruby/object:Overrides::Terraform::PropertyOverride
diff_suppress_func: resourceBigQueryDatasetAccessIamMemberDiffSuppress
custom_code: !ruby/object:Provider::Terraform::CustomCode
constants: templates/terraform/constants/bigquery_dataset_access.go
constants: templates/terraform/constants/bigquery_dataset_access.go.erb
post_create: templates/terraform/post_create/bigquery_dataset_access.go.erb
extra_schema_entry: templates/terraform/extra_schema_entry/bigquery_dataset_access.go.erb
Job: !ruby/object:Overrides::Terraform::ResourceOverride
import_format: ["projects/{{project}}/jobs/{{job_id}}"]
skip_delete: true
Expand Down
12 changes: 0 additions & 12 deletions templates/terraform/constants/bigquery_dataset_access.go

This file was deleted.

114 changes: 114 additions & 0 deletions templates/terraform/constants/bigquery_dataset_access.go.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
var bigqueryAccessRoleToPrimitiveMap = map[string]string {
"roles/bigquery.dataOwner": "OWNER",
"roles/bigquery.dataEditor": "WRITER",
"roles/bigquery.dataViewer": "READER",
}

func resourceBigQueryDatasetAccessRoleDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
if primitiveRole, ok := bigqueryAccessRoleToPrimitiveMap[new]; ok {
return primitiveRole == old
}
return false
}

// we want to diff suppress any iam_members that are configured as `iam_member`, but stored in state as a different member type
func resourceBigQueryDatasetAccessIamMemberDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
if primitiveRole, ok := bigqueryAccessRoleToPrimitiveMap[new]; ok {
return primitiveRole == old
}

if d.Get("api_updated_member") == true {
expectedIamMember := d.Get("iam_member").(string)
parts := strings.SplitAfter(expectedIamMember, ":")

strippedIamMember := parts[0]
if len(parts) > 1 {
strippedIamMember = parts[1]
}

if memberInState := d.Get("user_by_email").(string); memberInState != "" {
return memberInState == strippedIamMember
}

if memberInState := d.Get("group_by_email").(string); memberInState != "" {
return memberInState == strippedIamMember
}

if memberInState := d.Get("domain").(string); memberInState != "" {
return memberInState == strippedIamMember
}

if memberInState := d.Get("special_group").(string); memberInState != "" {
return memberInState == strippedIamMember
}
}

return false
}

// this function will go through a response's access list and see if the iam_member has been reassigned to a different member_type
// if it has, it will return the member type, and the member
func resourceBigQueryDatasetAccessReassignIamMemberInNestedObjectList(d *schema.ResourceData, meta interface{}, items []interface{}) (member_type string, member interface{}, err error) {
expectedRole, err := expandNestedBigQueryDatasetAccessRole(d.Get("role"), d, meta.(*Config))
if err != nil {
return "", nil, err
}
expectedFlattenedRole := flattenNestedBigQueryDatasetAccessRole(expectedRole, d, meta.(*Config))

expectedIamMember, err := expandNestedBigQueryDatasetAccessIamMember(d.Get("iam_member"), d, meta.(*Config))
if err != nil {
return "", nil, err
}
expectedFlattenedIamMember := flattenNestedBigQueryDatasetAccessIamMember(expectedIamMember, d, meta.(*Config))

parts := strings.SplitAfter(expectedFlattenedIamMember.(string), ":")

expectedStrippedIamMember := parts[0]
if len(parts) > 1 {
expectedStrippedIamMember = parts[1]
}

// Search list for this resource.
for _, itemRaw := range items {
if itemRaw == nil {
continue
}
item := itemRaw.(map[string]interface{})

itemRole := flattenNestedBigQueryDatasetAccessRole(item["role"], d, meta.(*Config))
// isEmptyValue check so that if one is nil and the other is "", that's considered a match
if !(isEmptyValue(reflect.ValueOf(itemRole)) && isEmptyValue(reflect.ValueOf(expectedFlattenedRole))) && !reflect.DeepEqual(itemRole, expectedFlattenedRole) {
log.Printf("[DEBUG] Skipping item with role= %#v, looking for %#v)", itemRole, expectedFlattenedRole)
continue
}

itemUserByEmail := flattenNestedBigQueryDatasetAccessUserByEmail(item["userByEmail"], d, meta.(*Config))
if reflect.DeepEqual(itemUserByEmail, expectedStrippedIamMember) {
log.Printf("[DEBUG] Iam Member changed to userByEmail= %#v)", itemUserByEmail)
return "user_by_email", itemUserByEmail, nil
}
itemGroupByEmail := flattenNestedBigQueryDatasetAccessGroupByEmail(item["groupByEmail"], d, meta.(*Config))
if reflect.DeepEqual(itemGroupByEmail, expectedStrippedIamMember) {
log.Printf("[DEBUG] Iam Member changed to groupByEmail= %#v)", itemGroupByEmail)
return "group_by_email", itemGroupByEmail, nil
}
itemDomain := flattenNestedBigQueryDatasetAccessDomain(item["domain"], d, meta.(*Config))
if reflect.DeepEqual(itemDomain, expectedStrippedIamMember) {
log.Printf("[DEBUG] Iam Member changed to domain= %#v)", itemDomain)
return "domain", itemDomain, nil
}
itemSpecialGroup := flattenNestedBigQueryDatasetAccessSpecialGroup(item["specialGroup"], d, meta.(*Config))
if reflect.DeepEqual(itemSpecialGroup, expectedStrippedIamMember) {
log.Printf("[DEBUG] Iam Member changed to specialGroup= %#v)", itemSpecialGroup)
return "special_group", itemSpecialGroup, nil
}
itemIamMember := flattenNestedBigQueryDatasetAccessIamMember(item["iamMember"], d, meta.(*Config))
if reflect.DeepEqual(itemIamMember, expectedFlattenedIamMember) {
log.Printf("[DEBUG] Iam Member stayed as iamMember= %#v)", itemIamMember)
return "", nil, nil
}
continue
}
log.Printf("[DEBUG] Did not find item for resource %q)", d.Id())
return "", nil, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<%# The license inside this block applies to this file.
# Copyright 2020 Google Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-%>
"api_updated_member": {
Type: schema.TypeBool,
Computed: true,
Description: "If true, represents that that the iam_member in the config was translated to a different member type by the API, and is stored in state as a different member type",
},
34 changes: 34 additions & 0 deletions templates/terraform/post_create/bigquery_dataset_access.go.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<%# The license inside this block applies to this file.
# Copyright 2020 Google Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-%>

// by default, we are not updating the member
d.Set("api_updated_member", false)

// iam_member is a generalized attribute, if the API can map it to a different member type on the backend, it will return
// the correct member_type in the response. If it cannot be mapped to a different member type, it will stay in iam_member.
if iamMemberProp != "" {
member_type, member, err := resourceBigQueryDatasetAccessReassignIamMemberInNestedObjectList(d, meta, res["access"].([]interface{}))
if err != nil {
fmt.Println(err)
}

// if the member type changed, we set that member_type in state (it's already in the response) and we clear iam_member
// and we set "api_updated_member" to true to acknowledge that we are making this change
if member_type != "" {
d.Set(member_type, member.(string))
d.Set("iam_member", "")
d.Set("api_updated_member", true)
}
}
104 changes: 104 additions & 0 deletions third_party/terraform/tests/resource_bigquery_dataset_access_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,58 @@ func TestAccBigQueryDatasetAccess_predefinedRole(t *testing.T) {
})
}

func TestAccBigQueryDatasetAccess_iamMember(t *testing.T) {
t.Parallel()

datasetID := fmt.Sprintf("tf_test_%s", randString(t, 10))
sinkName := fmt.Sprintf("tf_test_%s", randString(t, 10))

vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccBigQueryDatasetAccess_iamMember(datasetID, sinkName),
},
},
})
}

func TestAccBigQueryDatasetAccess_allUsers(t *testing.T) {
t.Parallel()

datasetID := fmt.Sprintf("tf_test_%s", randString(t, 10))

vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccBigQueryDatasetAccess_allUsers(datasetID),
},
{
Config: testAccBigQueryDatasetAccess_allAuthenticatedUsers(datasetID),
},
},
})
}

func TestAccBigQueryDatasetAccess_allAuthenticatedUsers(t *testing.T) {
t.Parallel()

datasetID := fmt.Sprintf("tf_test_%s", randString(t, 10))

vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccBigQueryDatasetAccess_allAuthenticatedUsers(datasetID),
},
},
})
}

func testAccCheckBigQueryDatasetAccessPresent(t *testing.T, n string, expected map[string]interface{}) resource.TestCheckFunc {
return testAccCheckBigQueryDatasetAccess(t, n, expected, true)
}
Expand Down Expand Up @@ -283,3 +335,55 @@ resource "google_bigquery_dataset" "dataset" {
}
`, role, datasetID)
}

func testAccBigQueryDatasetAccess_iamMember(datasetID, sinkName string) string {
return fmt.Sprintf(`
resource "google_bigquery_dataset_access" "dns_query_sink" {
dataset_id = google_bigquery_dataset.dataset.dataset_id
role = "roles/bigquery.dataEditor"
iam_member = google_logging_project_sink.logging_sink.writer_identity
}
resource "google_bigquery_dataset" "dataset" {
dataset_id = "%s"
}
resource "google_logging_project_sink" "logging_sink" {
name = "%s_logging_project_sink"
destination = "bigquery.googleapis.com/${google_bigquery_dataset.dataset.id}"
filter = "resource.type=\"dns_query\""
unique_writer_identity = true
}
`, datasetID, sinkName)
}

func testAccBigQueryDatasetAccess_allUsers(datasetID string) string {
return fmt.Sprintf(`
resource "google_bigquery_dataset_access" "dns_query_sink" {
dataset_id = google_bigquery_dataset.dataset.dataset_id
role = "roles/bigquery.dataEditor"
iam_member = "allUsers"
}
resource "google_bigquery_dataset" "dataset" {
dataset_id = "%s"
}
`, datasetID)
}

func testAccBigQueryDatasetAccess_allAuthenticatedUsers(datasetID string) string {
return fmt.Sprintf(`
resource "google_bigquery_dataset_access" "dns_query_sink" {
dataset_id = google_bigquery_dataset.dataset.dataset_id
role = "roles/bigquery.dataEditor"
iam_member = "allAuthenticatedUsers"
}
resource "google_bigquery_dataset" "dataset" {
dataset_id = "%s"
}
`, datasetID)
}

0 comments on commit c591599

Please sign in to comment.