Skip to content

Commit 15937c3

Browse files
chrisstmodular-magician
authored andcommitted
Change IAM binding to be authoritative
1 parent 66d08da commit 15937c3

13 files changed

+172
-121
lines changed

google/iam.go

+19
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,25 @@ func iamPolicyReadModifyWrite(updater ResourceIamUpdater, modify iamPolicyModify
123123
return nil
124124
}
125125

126+
// Takes a single binding and will either overwrite the same role in a list or append it to the end
127+
func overwriteBinding(bindings []*cloudresourcemanager.Binding, overwrite *cloudresourcemanager.Binding) []*cloudresourcemanager.Binding {
128+
var found bool
129+
130+
for i, b := range bindings {
131+
if b.Role == overwrite.Role {
132+
bindings[i] = overwrite
133+
found = true
134+
break
135+
}
136+
}
137+
138+
if !found {
139+
bindings = append(bindings, overwrite)
140+
}
141+
142+
return bindings
143+
}
144+
126145
// Merge multiple Bindings such that Bindings with the same Role result in
127146
// a single Binding with combined Members
128147
func mergeBindings(bindings []*cloudresourcemanager.Binding) []*cloudresourcemanager.Binding {

google/resource_google_project_iam_policy_test.go

+81-59
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,62 @@ func testAccCheckGoogleProjectIamPolicyExists(projectRes, policyRes, pid string)
169169
}
170170
}
171171

172+
func TestIamOverwriteBinding(t *testing.T) {
173+
table := []struct {
174+
input []*cloudresourcemanager.Binding
175+
override cloudresourcemanager.Binding
176+
expect []cloudresourcemanager.Binding
177+
}{
178+
{
179+
input: []*cloudresourcemanager.Binding{
180+
{
181+
Role: "role-1",
182+
Members: []string{"member-1", "member-2"},
183+
},
184+
},
185+
override: cloudresourcemanager.Binding{
186+
Role: "role-1",
187+
Members: []string{"new-member"},
188+
},
189+
expect: []cloudresourcemanager.Binding{
190+
{
191+
Role: "role-1",
192+
Members: []string{"new-member"},
193+
},
194+
},
195+
},
196+
{
197+
input: []*cloudresourcemanager.Binding{
198+
{
199+
Role: "role-1",
200+
Members: []string{"member-1", "member-2"},
201+
},
202+
},
203+
override: cloudresourcemanager.Binding{
204+
Role: "role-2",
205+
Members: []string{"member-3"},
206+
},
207+
expect: []cloudresourcemanager.Binding{
208+
{
209+
Role: "role-1",
210+
Members: []string{"member-1", "member-2"},
211+
},
212+
{
213+
Role: "role-2",
214+
Members: []string{"member-3"},
215+
},
216+
},
217+
},
218+
}
219+
220+
for _, test := range table {
221+
got := overwriteBinding(test.input, &test.override)
222+
if !reflect.DeepEqual(derefBindings(got), test.expect) {
223+
t.Errorf("OverwriteIamBinding failed.\nGot %+v\nWant %+v", derefBindings(got), test.expect)
224+
}
225+
}
226+
}
227+
172228
func TestIamMergeBindings(t *testing.T) {
173229
table := []struct {
174230
input []*cloudresourcemanager.Binding
@@ -177,95 +233,61 @@ func TestIamMergeBindings(t *testing.T) {
177233
{
178234
input: []*cloudresourcemanager.Binding{
179235
{
180-
Role: "role-1",
181-
Members: []string{
182-
"member-1",
183-
"member-2",
184-
},
236+
Role: "role-1",
237+
Members: []string{"member-1", "member-2"},
185238
},
186239
{
187-
Role: "role-1",
188-
Members: []string{
189-
"member-3",
190-
},
240+
Role: "role-1",
241+
Members: []string{"member-3"},
191242
},
192243
},
193244
expect: []cloudresourcemanager.Binding{
194245
{
195-
Role: "role-1",
196-
Members: []string{
197-
"member-1",
198-
"member-2",
199-
"member-3",
200-
},
246+
Role: "role-1",
247+
Members: []string{"member-1", "member-2", "member-3"},
201248
},
202249
},
203250
},
204251
{
205252
input: []*cloudresourcemanager.Binding{
206253
{
207-
Role: "role-1",
208-
Members: []string{
209-
"member-3",
210-
"member-4",
211-
},
254+
Role: "role-1",
255+
Members: []string{"member-3", "member-4"},
212256
},
213257
{
214-
Role: "role-1",
215-
Members: []string{
216-
"member-2",
217-
"member-1",
218-
},
258+
Role: "role-1",
259+
Members: []string{"member-2", "member-1"},
219260
},
220261
{
221-
Role: "role-2",
222-
Members: []string{
223-
"member-1",
224-
},
262+
Role: "role-2",
263+
Members: []string{"member-1"},
225264
},
226265
{
227-
Role: "role-1",
228-
Members: []string{
229-
"member-5",
230-
},
266+
Role: "role-1",
267+
Members: []string{"member-5"},
231268
},
232269
{
233-
Role: "role-3",
234-
Members: []string{
235-
"member-1",
236-
},
270+
Role: "role-3",
271+
Members: []string{"member-1"},
237272
},
238273
{
239-
Role: "role-2",
240-
Members: []string{
241-
"member-2",
242-
},
274+
Role: "role-2",
275+
Members: []string{"member-2"},
243276
},
244277
{Role: "empty-role", Members: []string{}},
245278
},
246279
expect: []cloudresourcemanager.Binding{
247280
{
248-
Role: "role-1",
249-
Members: []string{
250-
"member-1",
251-
"member-2",
252-
"member-3",
253-
"member-4",
254-
"member-5",
255-
},
281+
Role: "role-1",
282+
Members: []string{"member-1", "member-2", "member-3", "member-4", "member-5"},
256283
},
257284
{
258-
Role: "role-2",
259-
Members: []string{
260-
"member-1",
261-
"member-2",
262-
},
285+
Role: "role-2",
286+
Members: []string{"member-1", "member-2"},
263287
},
264288
{
265-
Role: "role-3",
266-
Members: []string{
267-
"member-1",
268-
},
289+
Role: "role-3",
290+
Members: []string{"member-1"},
269291
},
270292
},
271293
},
@@ -416,7 +438,7 @@ data "google_iam_policy" "expanded" {
416438
417439
]
418440
}
419-
441+
420442
binding {
421443
role = "roles/viewer"
422444
members = [

google/resource_iam_binding.go

+4-40
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ var iamBindingSchema = map[string]*schema.Schema{
3131

3232
func ResourceIamBinding(parentSpecificSchema map[string]*schema.Schema, newUpdaterFunc newResourceIamUpdaterFunc) *schema.Resource {
3333
return &schema.Resource{
34-
Create: resourceIamBindingCreate(newUpdaterFunc),
34+
Create: resourceIamBindingCreateUpdate(newUpdaterFunc),
3535
Read: resourceIamBindingRead(newUpdaterFunc),
36-
Update: resourceIamBindingUpdate(newUpdaterFunc),
36+
Update: resourceIamBindingCreateUpdate(newUpdaterFunc),
3737
Delete: resourceIamBindingDelete(newUpdaterFunc),
3838
Schema: mergeSchemas(iamBindingSchema, parentSpecificSchema),
3939
}
@@ -47,7 +47,7 @@ func ResourceIamBindingWithImport(parentSpecificSchema map[string]*schema.Schema
4747
return r
4848
}
4949

50-
func resourceIamBindingCreate(newUpdaterFunc newResourceIamUpdaterFunc) schema.CreateFunc {
50+
func resourceIamBindingCreateUpdate(newUpdaterFunc newResourceIamUpdaterFunc) func(*schema.ResourceData, interface{}) error {
5151
return func(d *schema.ResourceData, meta interface{}) error {
5252
config := meta.(*Config)
5353
updater, err := newUpdaterFunc(d, config)
@@ -57,11 +57,7 @@ func resourceIamBindingCreate(newUpdaterFunc newResourceIamUpdaterFunc) schema.C
5757

5858
p := getResourceIamBinding(d)
5959
err = iamPolicyReadModifyWrite(updater, func(ep *cloudresourcemanager.Policy) error {
60-
// Creating a binding does not remove existing members if they are not in the provided members list.
61-
// This prevents removing existing permission without the user's knowledge.
62-
// Instead, a diff is shown in that case after creation. Subsequent calls to update will remove any
63-
// existing members not present in the provided list.
64-
ep.Bindings = mergeBindings(append(ep.Bindings, p))
60+
ep.Bindings = overwriteBinding(ep.Bindings, p)
6561
return nil
6662
})
6763
if err != nil {
@@ -151,38 +147,6 @@ func iamBindingImport(resourceIdParser resourceIdParserFunc) schema.StateFunc {
151147
}
152148
}
153149

154-
func resourceIamBindingUpdate(newUpdaterFunc newResourceIamUpdaterFunc) schema.UpdateFunc {
155-
return func(d *schema.ResourceData, meta interface{}) error {
156-
config := meta.(*Config)
157-
updater, err := newUpdaterFunc(d, config)
158-
if err != nil {
159-
return err
160-
}
161-
162-
binding := getResourceIamBinding(d)
163-
err = iamPolicyReadModifyWrite(updater, func(p *cloudresourcemanager.Policy) error {
164-
var found bool
165-
for pos, b := range p.Bindings {
166-
if b.Role != binding.Role {
167-
continue
168-
}
169-
found = true
170-
p.Bindings[pos] = binding
171-
break
172-
}
173-
if !found {
174-
p.Bindings = append(p.Bindings, binding)
175-
}
176-
return nil
177-
})
178-
if err != nil {
179-
return err
180-
}
181-
182-
return resourceIamBindingRead(newUpdaterFunc)(d, meta)
183-
}
184-
}
185-
186150
func resourceIamBindingDelete(newUpdaterFunc newResourceIamUpdaterFunc) schema.DeleteFunc {
187151
return func(d *schema.ResourceData, meta interface{}) error {
188152
config := meta.(*Config)

website/docs/d/google_iam_policy.html.markdown

+4-4
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ data "google_iam_policy" "admin" {
2626
role = "roles/storage.objectViewer"
2727
2828
members = [
29-
"user:jane@example.com",
29+
"user:alice@gmail.com",
3030
]
3131
}
32-
32+
3333
audit_config {
3434
service = "cloudkms.googleapis.com"
3535
audit_log_configs = [
@@ -73,11 +73,11 @@ each accept the following arguments:
7373
See the [IAM Roles](https://cloud.google.com/compute/docs/access/iam) documentation for a complete list of roles.
7474
Note that custom roles must be of the format `[projects|organizations]/{parent-name}/roles/{role-name}`.
7575

76-
* `members` (Required) - An array of identites that will be granted the privilege in the `role`.
76+
* `members` (Required) - An array of identites that will be granted the privilege in the `role`. For more details on format and restrictions see https://cloud.google.com/billing/reference/rest/v1/Policy#Binding
7777
Each entry can have one of the following values:
7878
* **allUsers**: A special identifier that represents anyone who is on the internet; with or without a Google account. It **can't** be used with the `google_project` resource.
7979
* **allAuthenticatedUsers**: A special identifier that represents anyone who is authenticated with a Google account or a service account. It **can't** be used with the `google_project` resource.
80-
* **user:{emailid}**: An email address that represents a specific Google account. For example, [email protected] or [email protected].
80+
* **user:{emailid}**: An email address that represents a specific Google account. For example, [email protected].
8181
* **serviceAccount:{emailid}**: An email address that represents a service account. For example, [email protected].
8282
* **group:{emailid}**: An email address that represents a Google group. For example, [email protected].
8383
* **domain:{domain}**: A G Suite domain (primary, instead of alias) name that represents all the users of that domain. For example, google.com or example.com.

website/docs/r/google_billing_account_iam_binding.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ an existing Google Cloud Platform Billing Account.
1515
`google_billing_account_iam_member` for the __same role__ or they will fight over
1616
what your policy should be.
1717

18+
~> **Note:** On create, this resource will overwrite members of any existing roles.
19+
Use `terraform import` and inspect the `terraform plan` output to ensure
20+
your existing members are preserved.
21+
1822
## Example Usage
1923

2024
```hcl
@@ -23,7 +27,7 @@ resource "google_billing_account_iam_binding" "binding" {
2327
role = "roles/billing.viewer"
2428
2529
members = [
26-
"user:jane@example.com",
30+
"user:alice@gmail.com",
2731
]
2832
}
2933
```
@@ -36,7 +40,7 @@ The following arguments are supported:
3640

3741
* `role` - (Required) The role that should be applied.
3842

39-
* `members` - (Required) A list of users that the role should apply to.
43+
* `members` - (Required) A list of users that the role should apply to. For more details on format and restrictions see https://cloud.google.com/billing/reference/rest/v1/Policy#Binding
4044

4145
## Attributes Reference
4246

website/docs/r/google_billing_account_iam_member.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ the IAM policy for an existing Google Cloud Platform Billing Account.
2121
resource "google_billing_account_iam_member" "binding" {
2222
billing_account_id = "00AA00-000AAA-00AA0A"
2323
role = "roles/billing.viewer"
24-
member = "user:jane@example.com"
24+
member = "user:alice@gmail.com"
2525
}
2626
```
2727

@@ -33,8 +33,8 @@ The following arguments are supported:
3333

3434
* `role` - (Required) The role that should be applied.
3535

36-
* `member` - (Required) The user that the role should apply to.
37-
36+
* `member` - (Required) The user that the role should apply to. For more details on format and restrictions see https://cloud.google.com/billing/reference/rest/v1/Policy#Binding
37+
3838
## Attributes Reference
3939

4040
In addition to the arguments listed above, the following computed attributes are

website/docs/r/google_folder_iam_binding.html.markdown

+7-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ an existing Google Cloud Platform folder.
1515
`google_folder_iam_policy` or they will fight over what your policy
1616
should be.
1717

18+
~> **Note:** On create, this resource will overwrite members of any existing roles.
19+
Use `terraform import` and inspect the `terraform plan` output to ensure
20+
your existing members are preserved.
21+
1822
## Example Usage
1923

2024
```hcl
@@ -28,7 +32,7 @@ resource "google_folder_iam_binding" "admin" {
2832
role = "roles/editor"
2933
3034
members = [
31-
"user:jane@example.com",
35+
"user:alice@gmail.com",
3236
]
3337
}
3438
```
@@ -41,10 +45,11 @@ The following arguments are supported:
4145

4246
* `members` (Required) - An array of identites that will be granted the privilege in the `role`.
4347
Each entry can have one of the following values:
44-
* **user:{emailid}**: An email address that represents a specific Google account. For example, alice@gmail.com or joe@example.com.
48+
* **user:{emailid}**: An email address that is associated with a specific Google account. For example, [email protected].
4549
* **serviceAccount:{emailid}**: An email address that represents a service account. For example, [email protected].
4650
* **group:{emailid}**: An email address that represents a Google group. For example, [email protected].
4751
* **domain:{domain}**: A G Suite domain (primary, instead of alias) name that represents all the users of that domain. For example, google.com or example.com.
52+
* For more details on format and restrictions see https://cloud.google.com/billing/reference/rest/v1/Policy#Binding
4853

4954
* `role` - (Required) The role that should be applied. Only one
5055
`google_folder_iam_binding` can be used per role. Note that custom roles must be of the format

0 commit comments

Comments
 (0)