@@ -18,6 +18,7 @@ import (
1818 "fmt"
1919 "log"
2020 "reflect"
21+ "strings"
2122 "time"
2223
2324 "github.com/hashicorp/terraform-plugin-sdk/helper/schema"
@@ -37,6 +38,108 @@ func resourceBigQueryDatasetAccessRoleDiffSuppress(k, old, new string, d *schema
3738 return false
3839}
3940
41+ // we want to diff suppress any iam_members that are configured as `iam_member`, but stored in state as a different member type
42+ func resourceBigQueryDatasetAccessIamMemberDiffSuppress (k , old , new string , d * schema.ResourceData ) bool {
43+ if primitiveRole , ok := bigqueryAccessRoleToPrimitiveMap [new ]; ok {
44+ return primitiveRole == old
45+ }
46+
47+ if d .Get ("api_updated_member" ) == true {
48+ expectedIamMember := d .Get ("iam_member" ).(string )
49+ parts := strings .SplitAfter (expectedIamMember , ":" )
50+
51+ strippedIamMember := parts [0 ]
52+ if len (parts ) > 1 {
53+ strippedIamMember = parts [1 ]
54+ }
55+
56+ if memberInState := d .Get ("user_by_email" ).(string ); memberInState != "" {
57+ return memberInState == strippedIamMember
58+ }
59+
60+ if memberInState := d .Get ("group_by_email" ).(string ); memberInState != "" {
61+ return memberInState == strippedIamMember
62+ }
63+
64+ if memberInState := d .Get ("domain" ).(string ); memberInState != "" {
65+ return memberInState == strippedIamMember
66+ }
67+
68+ if memberInState := d .Get ("special_group" ).(string ); memberInState != "" {
69+ return memberInState == strippedIamMember
70+ }
71+ }
72+
73+ return false
74+ }
75+
76+ // this function will go through a response's access list and see if the iam_member has been reassigned to a different member_type
77+ // if it has, it will return the member type, and the member
78+ func resourceBigQueryDatasetAccessReassignIamMemberInNestedObjectList (d * schema.ResourceData , meta interface {}, items []interface {}) (member_type string , member interface {}, err error ) {
79+ expectedRole , err := expandNestedBigQueryDatasetAccessRole (d .Get ("role" ), d , meta .(* Config ))
80+ if err != nil {
81+ return "" , nil , err
82+ }
83+ expectedFlattenedRole := flattenNestedBigQueryDatasetAccessRole (expectedRole , d , meta .(* Config ))
84+
85+ expectedIamMember , err := expandNestedBigQueryDatasetAccessIamMember (d .Get ("iam_member" ), d , meta .(* Config ))
86+ if err != nil {
87+ return "" , nil , err
88+ }
89+ expectedFlattenedIamMember := flattenNestedBigQueryDatasetAccessIamMember (expectedIamMember , d , meta .(* Config ))
90+
91+ parts := strings .SplitAfter (expectedFlattenedIamMember .(string ), ":" )
92+
93+ expectedStrippedIamMember := parts [0 ]
94+ if len (parts ) > 1 {
95+ expectedStrippedIamMember = parts [1 ]
96+ }
97+
98+ // Search list for this resource.
99+ for _ , itemRaw := range items {
100+ if itemRaw == nil {
101+ continue
102+ }
103+ item := itemRaw .(map [string ]interface {})
104+
105+ itemRole := flattenNestedBigQueryDatasetAccessRole (item ["role" ], d , meta .(* Config ))
106+ // isEmptyValue check so that if one is nil and the other is "", that's considered a match
107+ if ! (isEmptyValue (reflect .ValueOf (itemRole )) && isEmptyValue (reflect .ValueOf (expectedFlattenedRole ))) && ! reflect .DeepEqual (itemRole , expectedFlattenedRole ) {
108+ log .Printf ("[DEBUG] Skipping item with role= %#v, looking for %#v)" , itemRole , expectedFlattenedRole )
109+ continue
110+ }
111+
112+ itemUserByEmail := flattenNestedBigQueryDatasetAccessUserByEmail (item ["userByEmail" ], d , meta .(* Config ))
113+ if reflect .DeepEqual (itemUserByEmail , expectedStrippedIamMember ) {
114+ log .Printf ("[DEBUG] Iam Member changed to userByEmail= %#v)" , itemUserByEmail )
115+ return "user_by_email" , itemUserByEmail , nil
116+ }
117+ itemGroupByEmail := flattenNestedBigQueryDatasetAccessGroupByEmail (item ["groupByEmail" ], d , meta .(* Config ))
118+ if reflect .DeepEqual (itemGroupByEmail , expectedStrippedIamMember ) {
119+ log .Printf ("[DEBUG] Iam Member changed to groupByEmail= %#v)" , itemGroupByEmail )
120+ return "group_by_email" , itemGroupByEmail , nil
121+ }
122+ itemDomain := flattenNestedBigQueryDatasetAccessDomain (item ["domain" ], d , meta .(* Config ))
123+ if reflect .DeepEqual (itemDomain , expectedStrippedIamMember ) {
124+ log .Printf ("[DEBUG] Iam Member changed to domain= %#v)" , itemDomain )
125+ return "domain" , itemDomain , nil
126+ }
127+ itemSpecialGroup := flattenNestedBigQueryDatasetAccessSpecialGroup (item ["specialGroup" ], d , meta .(* Config ))
128+ if reflect .DeepEqual (itemSpecialGroup , expectedStrippedIamMember ) {
129+ log .Printf ("[DEBUG] Iam Member changed to specialGroup= %#v)" , itemSpecialGroup )
130+ return "special_group" , itemSpecialGroup , nil
131+ }
132+ itemIamMember := flattenNestedBigQueryDatasetAccessIamMember (item ["iamMember" ], d , meta .(* Config ))
133+ if reflect .DeepEqual (itemIamMember , expectedFlattenedIamMember ) {
134+ log .Printf ("[DEBUG] Iam Member stayed as iamMember= %#v)" , itemIamMember )
135+ return "" , nil , nil
136+ }
137+ continue
138+ }
139+ log .Printf ("[DEBUG] Did not find item for resource %q)" , d .Id ())
140+ return "" , nil , nil
141+ }
142+
40143func resourceBigQueryDatasetAccess () * schema.Resource {
41144 return & schema.Resource {
42145 Create : resourceBigQueryDatasetAccessCreate ,
@@ -58,24 +161,27 @@ must contain only letters (a-z, A-Z), numbers (0-9), or
58161underscores (_). The maximum length is 1,024 characters.` ,
59162 },
60163 "domain" : {
61- Type : schema .TypeString ,
62- Optional : true ,
63- ForceNew : true ,
164+ Type : schema .TypeString ,
165+ Optional : true ,
166+ ForceNew : true ,
167+ DiffSuppressFunc : resourceBigQueryDatasetAccessIamMemberDiffSuppress ,
64168 Description : `A domain to grant access to. Any users signed in with the
65169domain specified will be granted the specified access` ,
66170 ExactlyOneOf : []string {"user_by_email" , "group_by_email" , "domain" , "special_group" , "iam_member" , "view" },
67171 },
68172 "group_by_email" : {
69- Type : schema .TypeString ,
70- Optional : true ,
71- ForceNew : true ,
72- Description : `An email address of a Google Group to grant access to.` ,
73- ExactlyOneOf : []string {"user_by_email" , "group_by_email" , "domain" , "special_group" , "iam_member" , "view" },
173+ Type : schema .TypeString ,
174+ Optional : true ,
175+ ForceNew : true ,
176+ DiffSuppressFunc : resourceBigQueryDatasetAccessIamMemberDiffSuppress ,
177+ Description : `An email address of a Google Group to grant access to.` ,
178+ ExactlyOneOf : []string {"user_by_email" , "group_by_email" , "domain" , "special_group" , "iam_member" , "view" },
74179 },
75180 "iam_member" : {
76- Type : schema .TypeString ,
77- Optional : true ,
78- ForceNew : true ,
181+ Type : schema .TypeString ,
182+ Optional : true ,
183+ ForceNew : true ,
184+ DiffSuppressFunc : resourceBigQueryDatasetAccessIamMemberDiffSuppress ,
79185 Description : `Some other type of member that appears in the IAM Policy but isn't a user,
80186group, domain, or special group. For example: 'allUsers'` ,
81187 ExactlyOneOf : []string {"user_by_email" , "group_by_email" , "domain" , "special_group" , "iam_member" , "view" },
@@ -93,9 +199,10 @@ counterparts, and will show a diff post-create. See
93199[official docs](https://cloud.google.com/bigquery/docs/access-control).` ,
94200 },
95201 "special_group" : {
96- Type : schema .TypeString ,
97- Optional : true ,
98- ForceNew : true ,
202+ Type : schema .TypeString ,
203+ Optional : true ,
204+ ForceNew : true ,
205+ DiffSuppressFunc : resourceBigQueryDatasetAccessIamMemberDiffSuppress ,
99206 Description : `A special group to grant access to. Possible values include:
100207
101208
@@ -112,9 +219,10 @@ counterparts, and will show a diff post-create. See
112219 ExactlyOneOf : []string {"user_by_email" , "group_by_email" , "domain" , "special_group" , "iam_member" , "view" },
113220 },
114221 "user_by_email" : {
115- Type : schema .TypeString ,
116- Optional : true ,
117- ForceNew : true ,
222+ Type : schema .TypeString ,
223+ Optional : true ,
224+ ForceNew : true ,
225+ DiffSuppressFunc : resourceBigQueryDatasetAccessIamMemberDiffSuppress ,
118226 Description : `An email address of a user to grant access to. For example:
119227120228 ExactlyOneOf : []string {"user_by_email" , "group_by_email" , "domain" , "special_group" , "iam_member" , "view" },
@@ -155,6 +263,11 @@ is 1,024 characters.`,
155263 },
156264 ExactlyOneOf : []string {"user_by_email" , "group_by_email" , "domain" , "special_group" , "iam_member" , "view" },
157265 },
266+ "api_updated_member" : {
267+ Type : schema .TypeBool ,
268+ Computed : true ,
269+ 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" ,
270+ },
158271 "project" : {
159272 Type : schema .TypeString ,
160273 Optional : true ,
@@ -254,6 +367,26 @@ func resourceBigQueryDatasetAccessCreate(d *schema.ResourceData, meta interface{
254367
255368 log .Printf ("[DEBUG] Finished creating DatasetAccess %q: %#v" , d .Id (), res )
256369
370+ // by default, we are not updating the member
371+ d .Set ("api_updated_member" , false )
372+
373+ // iam_member is a generalized attribute, if the API can map it to a different member type on the backend, it will return
374+ // the correct member_type in the response. If it cannot be mapped to a different member type, it will stay in iam_member.
375+ if iamMemberProp != "" {
376+ member_type , member , err := resourceBigQueryDatasetAccessReassignIamMemberInNestedObjectList (d , meta , res ["access" ].([]interface {}))
377+ if err != nil {
378+ fmt .Println (err )
379+ }
380+
381+ // if the member type changed, we set that member_type in state (it's already in the response) and we clear iam_member
382+ // and we set "api_updated_member" to true to acknowledge that we are making this change
383+ if member_type != "" {
384+ d .Set (member_type , member .(string ))
385+ d .Set ("iam_member" , "" )
386+ d .Set ("api_updated_member" , true )
387+ }
388+ }
389+
257390 return resourceBigQueryDatasetAccessRead (d , meta )
258391}
259392
0 commit comments