@@ -28,46 +28,87 @@ func BootstrapIamMembers(t *testing.T, members []IamMember) {
28
28
}
29
29
client := config .NewResourceManagerClient (config .UserAgent )
30
30
31
- // Get the project since we need its number, id, and policy.
32
- project , err := client .Projects .Get (envvar .GetTestProjectFromEnv ()).Do ()
33
- if err != nil {
34
- t .Fatalf ("Error getting project with id %q: %s" , project .ProjectId , err )
31
+ // Separate the given members into two groups: project-level vs. org-level
32
+ var projectMembers []IamMember
33
+ var orgMembers []IamMember
34
+ for _ , member := range members {
35
+ // If the member has an {organization_id} token, we'll handle it as an org binding
36
+ if strings .Contains (member .Member , "{organization_id}" ) {
37
+ orgMembers = append (orgMembers , member )
38
+ } else {
39
+ // Otherwise, treat as project-level (this also covers {project_number} or none)
40
+ projectMembers = append (projectMembers , member )
41
+ }
35
42
}
36
43
37
- // Create the bindings we need to add to the policy.
38
- var newBindings []* cloudresourcemanager.Binding
39
- for _ , member := range members {
40
- newBindings = append (newBindings , & cloudresourcemanager.Binding {
41
- Role : member .Role ,
42
- Members : []string {strings .ReplaceAll (member .Member , "{project_number}" , strconv .FormatInt (project .ProjectNumber , 10 ))},
43
- })
44
+ if len (projectMembers ) > 0 {
45
+ // Get the project since we need its number, id, and policy.
46
+ project , err := client .Projects .Get (envvar .GetTestProjectFromEnv ()).Do ()
47
+ if err != nil {
48
+ t .Fatalf ("Error getting project with id %q: %s" , project .ProjectId , err )
49
+ }
50
+
51
+ var projectBindings []* cloudresourcemanager.Binding
52
+ for _ , pm := range projectMembers {
53
+ replacedMember := strings .ReplaceAll (pm .Member , "{project_number}" , strconv .FormatInt (project .ProjectNumber , 10 ))
54
+ projectBindings = append (projectBindings , & cloudresourcemanager.Binding {
55
+ Role : pm .Role ,
56
+ Members : []string {replacedMember },
57
+ })
58
+ }
59
+ applyProjectIamBindings (t , client , project .ProjectId , projectBindings )
44
60
}
45
61
62
+ if len (orgMembers ) > 0 {
63
+ // Get the organization ID from environment if any
64
+ orgId := envvar .GetTestOrgFromEnv (t )
65
+ if orgId == "" {
66
+ t .Fatal ("Error: Org-level IAM was requested, but no organization ID was set in the environment." )
67
+ }
68
+
69
+ var orgBindings []* cloudresourcemanager.Binding
70
+ for _ , om := range orgMembers {
71
+ replacedMember := strings .ReplaceAll (om .Member , "{organization_id}" , orgId )
72
+ orgBindings = append (orgBindings , & cloudresourcemanager.Binding {
73
+ Role : om .Role ,
74
+ Members : []string {replacedMember },
75
+ })
76
+ }
77
+ orgName := "organizations/" + orgId
78
+ applyOrgIamBindings (t , client , orgName , orgBindings )
79
+ }
80
+ }
81
+
82
+ func applyProjectIamBindings (t * testing.T ,
83
+ client * cloudresourcemanager.Service ,
84
+ projectId string ,
85
+ newBindings []* cloudresourcemanager.Binding ) {
86
+
46
87
// Retry bootstrapping with exponential backoff for concurrent writes
47
88
backoff := time .Second
48
89
for {
49
90
getPolicyRequest := & cloudresourcemanager.GetIamPolicyRequest {}
50
- policy , err := client .Projects .GetIamPolicy (project . ProjectId , getPolicyRequest ).Do ()
91
+ policy , err := client .Projects .GetIamPolicy (projectId , getPolicyRequest ).Do ()
51
92
if transport_tpg .IsGoogleApiErrorWithCode (err , 429 ) {
52
- t .Logf ("[DEBUG] 429 while attempting to read policy for project %s, waiting %v before attempting again" , project . ProjectId , backoff )
93
+ t .Logf ("[DEBUG] 429 while attempting to read policy for project %s, waiting %v before attempting again" , projectId , backoff )
53
94
time .Sleep (backoff )
54
95
continue
55
96
} else if err != nil {
56
- t .Fatalf ("Error getting iam policy for project %s: %v\n " , project . ProjectId , err )
97
+ t .Fatalf ("Error getting iam policy for project %s: %v\n " , projectId , err )
57
98
}
58
99
59
100
mergedBindings := tpgiamresource .MergeBindings (append (policy .Bindings , newBindings ... ))
60
101
61
102
if tpgiamresource .CompareBindings (policy .Bindings , mergedBindings ) {
62
- t .Logf ("[DEBUG] All bindings already present for project %s" , project . ProjectId )
103
+ t .Logf ("[DEBUG] All bindings already present for project %s" , projectId )
63
104
break
64
105
}
65
106
// The policy must change.
66
107
policy .Bindings = mergedBindings
67
108
setPolicyRequest := & cloudresourcemanager.SetIamPolicyRequest {Policy : policy }
68
- policy , err = client .Projects .SetIamPolicy (project . ProjectId , setPolicyRequest ).Do ()
109
+ policy , err = client .Projects .SetIamPolicy (projectId , setPolicyRequest ).Do ()
69
110
if err == nil {
70
- t .Logf ("[DEBUG] Waiting for IAM bootstrapping to propagate for project %s." , project . ProjectId )
111
+ t .Logf ("[DEBUG] Waiting for IAM bootstrapping to propagate for project %s." , projectId )
71
112
time .Sleep (3 * time .Minute )
72
113
break
73
114
}
@@ -76,10 +117,57 @@ func BootstrapIamMembers(t *testing.T, members []IamMember) {
76
117
time .Sleep (backoff )
77
118
backoff = backoff * 2
78
119
if backoff > 30 * time .Second {
79
- t .Fatalf ("Error applying IAM policy to %s: Too many conflicts. Latest error: %s" , project . ProjectId , err )
120
+ t .Fatalf ("Error applying IAM policy to %s: Too many conflicts. Latest error: %s" , projectId , err )
80
121
}
81
122
continue
82
123
}
83
124
t .Fatalf ("Error setting project iam policy: %v" , err )
84
125
}
85
126
}
127
+
128
+ func applyOrgIamBindings (
129
+ t * testing.T ,
130
+ client * cloudresourcemanager.Service ,
131
+ orgName string ,
132
+ newBindings []* cloudresourcemanager.Binding ) {
133
+
134
+ // Retry bootstrapping with exponential backoff for concurrent writes
135
+ backoff := time .Second
136
+ for {
137
+ getPolicyRequest := & cloudresourcemanager.GetIamPolicyRequest {}
138
+ policy , err := client .Organizations .GetIamPolicy (orgName , getPolicyRequest ).Do ()
139
+ if transport_tpg .IsGoogleApiErrorWithCode (err , 429 ) {
140
+ t .Logf ("[DEBUG] 429 while attempting to read policy for org %s, waiting %v before attempting again" , orgName , backoff )
141
+ time .Sleep (backoff )
142
+ continue
143
+ } else if err != nil {
144
+ t .Fatalf ("Error getting iam policy for org %s: %v\n " , orgName , err )
145
+ }
146
+
147
+ mergedBindings := tpgiamresource .MergeBindings (append (policy .Bindings , newBindings ... ))
148
+
149
+ if tpgiamresource .CompareBindings (policy .Bindings , mergedBindings ) {
150
+ t .Logf ("[DEBUG] All bindings already present for org %s" , orgName )
151
+ break
152
+ }
153
+ // The policy must change.
154
+ policy .Bindings = mergedBindings
155
+ setPolicyRequest := & cloudresourcemanager.SetIamPolicyRequest {Policy : policy }
156
+ policy , err = client .Organizations .SetIamPolicy (orgName , setPolicyRequest ).Do ()
157
+ if err == nil {
158
+ t .Logf ("[DEBUG] Waiting for IAM bootstrapping to propagate for org %s." , orgName )
159
+ time .Sleep (3 * time .Minute )
160
+ break
161
+ }
162
+ if tpgresource .IsConflictError (err ) {
163
+ t .Logf ("[DEBUG]: Concurrent policy changes, restarting read-modify-write after %s" , backoff )
164
+ time .Sleep (backoff )
165
+ backoff = backoff * 2
166
+ if backoff > 30 * time .Second {
167
+ t .Fatalf ("Error applying IAM policy to %s: Too many conflicts. Latest error: %s" , orgName , err )
168
+ }
169
+ continue
170
+ }
171
+ t .Fatalf ("Error setting org iam policy: %v" , err )
172
+ }
173
+ }
0 commit comments