Skip to content

Commit 8ee555f

Browse files
Move IAM bootstrap to its own file, improve error messages (#7424) (#5321)
* Move IAM bootstrap to its own file, improve errors Also bootstrap roles/cloudbuild.builds.builder for cloudbuild service agent. * Re-add BootstrapConfig (accidentally deleted) * Fix wrong variable name * Bootstrap the role previously hardcoded for pubsub * Move error message back into bootstrap function This will dedup the code that calls this function. It now returns a boolean and sends the more useful error through t.Error. * Bootstrap the permissions for pubsub service agent * Bootstrap the role in the correct test * Fix formatting Signed-off-by: Modular Magician <[email protected]>
1 parent d86f392 commit 8ee555f

10 files changed

+202
-170
lines changed

.changelog/7424.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:none
2+
3+
```

google-beta/bootstrap_iam_test.go

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package google
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"testing"
7+
8+
cloudresourcemanager "google.golang.org/api/cloudresourcemanager/v1"
9+
)
10+
11+
// BootstrapAllPSARoles ensures that the given project's IAM
12+
// policy grants the given service agents the given roles.
13+
// prefix is usually "service-" and indicates the service agent should have the
14+
// given prefix before the project number.
15+
// This is important to bootstrap because using iam policy resources means that
16+
// deleting them removes permissions for concurrent tests.
17+
// Return whether the bindings changed.
18+
func BootstrapAllPSARoles(t *testing.T, prefix string, agentNames, roles []string) bool {
19+
config := BootstrapConfig(t)
20+
if config == nil {
21+
t.Fatal("Could not bootstrap a config for BootstrapAllPSARoles.")
22+
}
23+
client := config.NewResourceManagerClient(config.UserAgent)
24+
25+
// Get the project since we need its number, id, and policy.
26+
project, err := client.Projects.Get(GetTestProjectFromEnv()).Do()
27+
if err != nil {
28+
t.Fatalf("Error getting project with id %q: %s", project.ProjectId, err)
29+
}
30+
31+
getPolicyRequest := &cloudresourcemanager.GetIamPolicyRequest{}
32+
policy, err := client.Projects.GetIamPolicy(project.ProjectId, getPolicyRequest).Do()
33+
if err != nil {
34+
t.Fatalf("Error getting project iam policy: %v", err)
35+
}
36+
37+
members := make([]string, len(agentNames))
38+
for i, agentName := range agentNames {
39+
members[i] = fmt.Sprintf("serviceAccount:%s%d@%s.iam.gserviceaccount.com", prefix, project.ProjectNumber, agentName)
40+
}
41+
42+
// Create the bindings we need to add to the policy.
43+
var newBindings []*cloudresourcemanager.Binding
44+
for _, role := range roles {
45+
newBindings = append(newBindings, &cloudresourcemanager.Binding{
46+
Role: role,
47+
Members: members,
48+
})
49+
}
50+
51+
mergedBindings := MergeBindings(append(policy.Bindings, newBindings...))
52+
53+
if !compareBindings(policy.Bindings, mergedBindings) {
54+
addedBindings := missingBindings(policy.Bindings, mergedBindings)
55+
for _, missingBinding := range addedBindings {
56+
log.Printf("[DEBUG] Adding binding: %+v", missingBinding)
57+
}
58+
// The policy must change.
59+
policy.Bindings = mergedBindings
60+
setPolicyRequest := &cloudresourcemanager.SetIamPolicyRequest{Policy: policy}
61+
policy, err = client.Projects.SetIamPolicy(project.ProjectId, setPolicyRequest).Do()
62+
if err != nil {
63+
t.Fatalf("Error setting project iam policy: %v", err)
64+
}
65+
msg := "Added the following bindings to the test project's IAM policy:\n"
66+
for _, binding := range addedBindings {
67+
msg += fmt.Sprintf("Members: %q, Role: %q\n", binding.Members, binding.Role)
68+
}
69+
msg += "Retry the test in a few minutes."
70+
t.Error(msg)
71+
return true
72+
}
73+
return false
74+
}
75+
76+
// BootstrapAllPSARole is a version of BootstrapAllPSARoles for granting a
77+
// single role to multiple service agents.
78+
func BootstrapAllPSARole(t *testing.T, prefix string, agentNames []string, role string) bool {
79+
return BootstrapAllPSARoles(t, prefix, agentNames, []string{role})
80+
}
81+
82+
// BootstrapPSARoles is a version of BootstrapAllPSARoles for granting roles to
83+
// a single service agent.
84+
func BootstrapPSARoles(t *testing.T, prefix, agentName string, roles []string) bool {
85+
return BootstrapAllPSARoles(t, prefix, []string{agentName}, roles)
86+
}
87+
88+
// BootstrapPSARole is a simplified version of BootstrapPSARoles for granting a
89+
// single role to a single service agent.
90+
func BootstrapPSARole(t *testing.T, prefix, agentName, role string) bool {
91+
return BootstrapPSARoles(t, prefix, agentName, []string{role})
92+
}
93+
94+
// Returns a map representing iam bindings that are in the first map but not the second.
95+
func missingBindingsMap(aMap, bMap map[iamBindingKey]map[string]struct{}) map[iamBindingKey]map[string]struct{} {
96+
results := make(map[iamBindingKey]map[string]struct{})
97+
for key, aMembers := range aMap {
98+
if bMembers, ok := bMap[key]; ok {
99+
// The key is in both maps.
100+
resultMembers := make(map[string]struct{})
101+
102+
for aMember := range aMembers {
103+
if _, ok := bMembers[aMember]; !ok {
104+
// The member is in a but not in b.
105+
resultMembers[aMember] = struct{}{}
106+
}
107+
}
108+
for bMember := range bMembers {
109+
if _, ok := aMembers[bMember]; !ok {
110+
// The member is in b but not in a.
111+
resultMembers[bMember] = struct{}{}
112+
}
113+
}
114+
115+
if len(resultMembers) > 0 {
116+
results[key] = resultMembers
117+
}
118+
} else {
119+
// The key is in map a but not map b.
120+
results[key] = aMembers
121+
}
122+
}
123+
124+
for key, bMembers := range bMap {
125+
if _, ok := aMap[key]; !ok {
126+
// The key is in map b but not map a.
127+
results[key] = bMembers
128+
}
129+
}
130+
131+
return results
132+
}
133+
134+
// Returns the bindings that are in the first set of bindings but not the second.
135+
func missingBindings(a, b []*cloudresourcemanager.Binding) []*cloudresourcemanager.Binding {
136+
aMap := createIamBindingsMap(a)
137+
bMap := createIamBindingsMap(b)
138+
139+
var results []*cloudresourcemanager.Binding
140+
for key, membersSet := range missingBindingsMap(aMap, bMap) {
141+
members := make([]string, 0, len(membersSet))
142+
for member := range membersSet {
143+
members = append(members, member)
144+
}
145+
results = append(results, &cloudresourcemanager.Binding{
146+
Role: key.Role,
147+
Members: members,
148+
})
149+
}
150+
return results
151+
}

google-beta/bootstrap_utils_test.go

Lines changed: 1 addition & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -525,86 +525,7 @@ func BootstrapProject(t *testing.T, projectID, billingAccount string, services [
525525
return project
526526
}
527527

528-
// BootstrapAllPSARoles ensures that the given project's IAM
529-
// policy grants the given service agents the given roles.
530-
// This is important to bootstrap because using iam policy resources means that
531-
// deleting them removes permissions for concurrent tests.
532-
// Return whether the policy changed.
533-
func BootstrapAllPSARoles(t *testing.T, agentNames, roles []string) bool {
534-
config := BootstrapConfig(t)
535-
if config == nil {
536-
t.Fatal("Could not bootstrap a config for BootstrapAllPSARoles.")
537-
return false
538-
}
539-
client := config.NewResourceManagerClient(config.UserAgent)
540-
541-
// Get the project since we need its number, id, and policy.
542-
project, err := client.Projects.Get(GetTestProjectFromEnv()).Do()
543-
if err != nil {
544-
t.Fatalf("Error getting project with id %q: %s", project.ProjectId, err)
545-
return false
546-
}
547-
548-
getPolicyRequest := &cloudresourcemanager.GetIamPolicyRequest{}
549-
policy, err := client.Projects.GetIamPolicy(project.ProjectId, getPolicyRequest).Do()
550-
if err != nil {
551-
t.Fatalf("Error getting project iam policy: %v", err)
552-
return false
553-
}
554-
555-
var members []string
556-
for _, agentName := range agentNames {
557-
member := fmt.Sprintf("serviceAccount:service-%d@%s.iam.gserviceaccount.com", project.ProjectNumber, agentName)
558-
members = append(members, member)
559-
}
560-
561-
// Create the bindings we need to add to the policy.
562-
var newBindings []*cloudresourcemanager.Binding
563-
for _, role := range roles {
564-
newBindings = append(newBindings, &cloudresourcemanager.Binding{
565-
Role: role,
566-
Members: members,
567-
})
568-
}
569-
570-
mergedBindings := MergeBindings(append(policy.Bindings, newBindings...))
571-
572-
if !compareBindings(policy.Bindings, mergedBindings) {
573-
for _, missingBinding := range missingBindings(policy.Bindings, mergedBindings) {
574-
log.Printf("[DEBUG] Missing binding: %v", missingBinding)
575-
}
576-
// The policy must change.
577-
policy.Bindings = mergedBindings
578-
setPolicyRequest := &cloudresourcemanager.SetIamPolicyRequest{Policy: policy}
579-
policy, err = client.Projects.SetIamPolicy(project.ProjectId, setPolicyRequest).Do()
580-
if err != nil {
581-
t.Fatalf("Error setting project iam policy: %v", err)
582-
return false
583-
}
584-
return true
585-
}
586-
587-
return false
588-
}
589-
590-
// BootstrapAllPSARole is a version of BootstrapAllPSARoles for granting a
591-
// single role to multiple service agents.
592-
func BootstrapAllPSARole(t *testing.T, agentNames []string, role string) bool {
593-
return BootstrapAllPSARoles(t, agentNames, []string{role})
594-
}
595-
596-
// BootstrapPSARoles is a version of BootstrapAllPSARoles for granting roles to
597-
// a single service agent.
598-
func BootstrapPSARoles(t *testing.T, agentName string, roles []string) bool {
599-
return BootstrapAllPSARoles(t, []string{agentName}, roles)
600-
}
601-
602-
// BootstrapPSARole is a simplified version of BootstrapPSARoles for granting a
603-
// single role to a single service agent.
604-
func BootstrapPSARole(t *testing.T, agentName, role string) bool {
605-
return BootstrapPSARoles(t, agentName, []string{role})
606-
}
607-
528+
// BootstrapConfig returns a Config pulled from the environment.
608529
func BootstrapConfig(t *testing.T) *Config {
609530
if v := os.Getenv("TF_ACC"); v == "" {
610531
t.Skip("Acceptance tests and bootstrapping skipped unless env 'TF_ACC' set")

google-beta/iam.go

Lines changed: 0 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -447,65 +447,6 @@ func compareBindings(a, b []*cloudresourcemanager.Binding) bool {
447447
return reflect.DeepEqual(aMap, bMap)
448448
}
449449

450-
// Returns a map representing iam bindings that are in one map but not the other.
451-
func missingBindingsMap(aMap, bMap map[iamBindingKey]map[string]struct{}) map[iamBindingKey]map[string]struct{} {
452-
results := make(map[iamBindingKey]map[string]struct{})
453-
for key, aMembers := range aMap {
454-
if bMembers, ok := bMap[key]; ok {
455-
// The key is in both maps.
456-
resultMembers := make(map[string]struct{})
457-
458-
for aMember := range aMembers {
459-
if _, ok := bMembers[aMember]; !ok {
460-
// The member is in a but not in b.
461-
resultMembers[aMember] = struct{}{}
462-
}
463-
}
464-
for bMember := range bMembers {
465-
if _, ok := aMembers[bMember]; !ok {
466-
// The member is in b but not in a.
467-
resultMembers[bMember] = struct{}{}
468-
}
469-
}
470-
471-
if len(resultMembers) > 0 {
472-
results[key] = resultMembers
473-
}
474-
} else {
475-
// The key is in map a but not map b.
476-
results[key] = aMembers
477-
}
478-
}
479-
480-
for key, bMembers := range bMap {
481-
if _, ok := aMap[key]; !ok {
482-
// The key is in map b but not map a.
483-
results[key] = bMembers
484-
}
485-
}
486-
487-
return results
488-
}
489-
490-
// Returns the bindings that are in one set of bindings and not the other.
491-
func missingBindings(a, b []*cloudresourcemanager.Binding) []*cloudresourcemanager.Binding {
492-
aMap := createIamBindingsMap(a)
493-
bMap := createIamBindingsMap(b)
494-
495-
var results []*cloudresourcemanager.Binding
496-
for key, membersSet := range missingBindingsMap(aMap, bMap) {
497-
members := make([]string, 0, len(membersSet))
498-
for member := range membersSet {
499-
members = append(members, member)
500-
}
501-
results = append(results, &cloudresourcemanager.Binding{
502-
Role: key.Role,
503-
Members: members,
504-
})
505-
}
506-
return results
507-
}
508-
509450
func compareAuditConfigs(a, b []*cloudresourcemanager.AuditConfig) bool {
510451
aMap := createIamAuditConfigsMap(a)
511452
bMap := createIamAuditConfigsMap(b)

0 commit comments

Comments
 (0)