Skip to content

Commit e9b31ac

Browse files
yerniyazNmelinath
andauthored
add iam bootstraping support for organizations (#13384)
Co-authored-by: Stephen Lewis (Burrows) <[email protected]>
1 parent 29492ae commit e9b31ac

File tree

1 file changed

+106
-18
lines changed

1 file changed

+106
-18
lines changed

mmv1/third_party/terraform/acctest/bootstrap_iam_test_utils.go

+106-18
Original file line numberDiff line numberDiff line change
@@ -28,46 +28,87 @@ func BootstrapIamMembers(t *testing.T, members []IamMember) {
2828
}
2929
client := config.NewResourceManagerClient(config.UserAgent)
3030

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+
}
3542
}
3643

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)
4460
}
4561

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+
4687
// Retry bootstrapping with exponential backoff for concurrent writes
4788
backoff := time.Second
4889
for {
4990
getPolicyRequest := &cloudresourcemanager.GetIamPolicyRequest{}
50-
policy, err := client.Projects.GetIamPolicy(project.ProjectId, getPolicyRequest).Do()
91+
policy, err := client.Projects.GetIamPolicy(projectId, getPolicyRequest).Do()
5192
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)
5394
time.Sleep(backoff)
5495
continue
5596
} 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)
5798
}
5899

59100
mergedBindings := tpgiamresource.MergeBindings(append(policy.Bindings, newBindings...))
60101

61102
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)
63104
break
64105
}
65106
// The policy must change.
66107
policy.Bindings = mergedBindings
67108
setPolicyRequest := &cloudresourcemanager.SetIamPolicyRequest{Policy: policy}
68-
policy, err = client.Projects.SetIamPolicy(project.ProjectId, setPolicyRequest).Do()
109+
policy, err = client.Projects.SetIamPolicy(projectId, setPolicyRequest).Do()
69110
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)
71112
time.Sleep(3 * time.Minute)
72113
break
73114
}
@@ -76,10 +117,57 @@ func BootstrapIamMembers(t *testing.T, members []IamMember) {
76117
time.Sleep(backoff)
77118
backoff = backoff * 2
78119
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)
80121
}
81122
continue
82123
}
83124
t.Fatalf("Error setting project iam policy: %v", err)
84125
}
85126
}
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

Comments
 (0)