Skip to content

Commit 84cee6d

Browse files
Implemented simpler IAM bootstrapping logic (#12796) (#20964)
[upstream:daf0011808cd1191c64d6796e0ec7949560923c0] Signed-off-by: Modular Magician <[email protected]>
1 parent c9e79fe commit 84cee6d

File tree

3 files changed

+68
-37
lines changed

3 files changed

+68
-37
lines changed

.changelog/12796.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/acctest/bootstrap_iam_test_utils.go

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,25 @@ package acctest
55
import (
66
"fmt"
77
"log"
8+
"strconv"
9+
"strings"
810
"testing"
11+
"time"
912

1013
"github.com/hashicorp/terraform-provider-google/google/envvar"
1114
"github.com/hashicorp/terraform-provider-google/google/tpgiamresource"
1215
cloudresourcemanager "google.golang.org/api/cloudresourcemanager/v1"
1316
)
1417

15-
// BootstrapAllPSARoles ensures that the given project's IAM
16-
// policy grants the given service agents the given roles.
17-
// prefix is usually "service-" and indicates the service agent should have the
18-
// given prefix before the project number.
19-
// This is important to bootstrap because using iam policy resources means that
20-
// deleting them removes permissions for concurrent tests.
21-
// Return whether the bindings changed.
22-
func BootstrapAllPSARoles(t *testing.T, prefix string, agentNames, roles []string) bool {
18+
type IamMember struct {
19+
Member, Role string
20+
}
21+
22+
// BootstrapIamMembers ensures that a given set of member/role pairs exist in the default
23+
// test project. This should be used to avoid race conditions that can happen on the
24+
// default project due to parallel tests managing the same member/role pairings. Members
25+
// will have `{project_number}` replaced with the default test project's project number.
26+
func BootstrapIamMembers(t *testing.T, members []IamMember) {
2327
config := BootstrapConfig(t)
2428
if config == nil {
2529
t.Fatal("Could not bootstrap a config for BootstrapAllPSARoles.")
@@ -38,17 +42,12 @@ func BootstrapAllPSARoles(t *testing.T, prefix string, agentNames, roles []strin
3842
t.Fatalf("Error getting project iam policy: %v", err)
3943
}
4044

41-
members := make([]string, len(agentNames))
42-
for i, agentName := range agentNames {
43-
members[i] = fmt.Sprintf("serviceAccount:%s%d@%s.iam.gserviceaccount.com", prefix, project.ProjectNumber, agentName)
44-
}
45-
4645
// Create the bindings we need to add to the policy.
4746
var newBindings []*cloudresourcemanager.Binding
48-
for _, role := range roles {
47+
for _, member := range members {
4948
newBindings = append(newBindings, &cloudresourcemanager.Binding{
50-
Role: role,
51-
Members: members,
49+
Role: member.Role,
50+
Members: []string{strings.ReplaceAll(member.Member, "{project_number}", strconv.FormatInt(project.ProjectNumber, 10))},
5251
})
5352
}
5453

@@ -70,10 +69,32 @@ func BootstrapAllPSARoles(t *testing.T, prefix string, agentNames, roles []strin
7069
for _, binding := range addedBindings {
7170
msg += fmt.Sprintf("Members: %q, Role: %q\n", binding.Members, binding.Role)
7271
}
73-
msg += "Retry the test in a few minutes."
74-
t.Error(msg)
75-
return true
72+
msg += "Waiting for IAM to propagate."
73+
t.Log(msg)
74+
time.Sleep(3 * time.Minute)
75+
}
76+
}
77+
78+
// BootstrapAllPSARoles ensures that the given project's IAM
79+
// policy grants the given service agents the given roles.
80+
// prefix is usually "service-" and indicates the service agent should have the
81+
// given prefix before the project number.
82+
// This is important to bootstrap because using iam policy resources means that
83+
// deleting them removes permissions for concurrent tests.
84+
// Return whether the bindings changed.
85+
func BootstrapAllPSARoles(t *testing.T, prefix string, agentNames, roles []string) bool {
86+
var members []IamMember
87+
for _, agentName := range agentNames {
88+
member := fmt.Sprintf("serviceAccount:%s{project_number}@%s.iam.gserviceaccount.com", prefix, agentName)
89+
for _, role := range roles {
90+
members = append(members, IamMember{
91+
Member: member,
92+
Role: role,
93+
})
94+
}
7695
}
96+
BootstrapIamMembers(t, members)
97+
// Always return false because we now wait for IAM propagation.
7798
return false
7899
}
79100

google/services/composer/resource_composer_environment_test.go

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,29 @@ const testComposerNetworkPrefix = "tf-test-composer-net"
2424
const testComposerBucketPrefix = "tf-test-composer-bucket"
2525
const testComposerNetworkAttachmentPrefix = "tf-test-composer-nta"
2626

27-
func allComposerServiceAgents() []string {
28-
return []string{
29-
"cloudcomposer-accounts",
30-
"compute-system",
31-
"container-engine-robot",
32-
"gcp-sa-artifactregistry",
33-
"gcp-sa-pubsub",
34-
}
27+
func bootstrapComposerServiceAgents(t *testing.T) {
28+
acctest.BootstrapIamMembers(t, []acctest.IamMember{
29+
{
30+
Member: "serviceAccount:service-{project_number}@cloudcomposer-accounts.iam.gserviceaccount.com",
31+
Role: "roles/cloudkms.cryptoKeyEncrypterDecrypter",
32+
},
33+
{
34+
Member: "serviceAccount:service-{project_number}@compute-system.iam.gserviceaccount.com",
35+
Role: "roles/cloudkms.cryptoKeyEncrypterDecrypter",
36+
},
37+
{
38+
Member: "serviceAccount:service-{project_number}@container-engine-robot.iam.gserviceaccount.com",
39+
Role: "roles/cloudkms.cryptoKeyEncrypterDecrypter",
40+
},
41+
{
42+
Member: "serviceAccount:service-{project_number}@gcp-sa-artifactregistry.iam.gserviceaccount.com",
43+
Role: "roles/cloudkms.cryptoKeyEncrypterDecrypter",
44+
},
45+
{
46+
Member: "serviceAccount:service-{project_number}@gcp-sa-pubsub.iam.gserviceaccount.com",
47+
Role: "roles/cloudkms.cryptoKeyEncrypterDecrypter",
48+
},
49+
})
3550
}
3651

3752
// Checks environment creation with minimum required information.
@@ -246,7 +261,7 @@ func TestAccComposerEnvironment_withEncryptionConfigComposer1(t *testing.T) {
246261

247262
kms := acctest.BootstrapKMSKeyWithPurposeInLocationAndName(t, "ENCRYPT_DECRYPT", "us-central1", "tf-bootstrap-composer1-key1")
248263
pid := envvar.GetTestProjectFromEnv()
249-
grantServiceAgentsRole(t, "service-", allComposerServiceAgents(), "roles/cloudkms.cryptoKeyEncrypterDecrypter")
264+
bootstrapComposerServiceAgents(t)
250265
envName := fmt.Sprintf("%s-%d", testComposerEnvironmentPrefix, acctest.RandInt(t))
251266
network := fmt.Sprintf("%s-%d", testComposerNetworkPrefix, acctest.RandInt(t))
252267
subnetwork := network + "-1"
@@ -283,7 +298,7 @@ func TestAccComposerEnvironment_withEncryptionConfigComposer2(t *testing.T) {
283298

284299
kms := acctest.BootstrapKMSKeyWithPurposeInLocationAndName(t, "ENCRYPT_DECRYPT", "us-central1", "tf-bootstrap-composer2-key1")
285300
pid := envvar.GetTestProjectFromEnv()
286-
grantServiceAgentsRole(t, "service-", allComposerServiceAgents(), "roles/cloudkms.cryptoKeyEncrypterDecrypter")
301+
bootstrapComposerServiceAgents(t)
287302
envName := fmt.Sprintf("%s-%d", testComposerEnvironmentPrefix, acctest.RandInt(t))
288303
network := fmt.Sprintf("%s-%d", testComposerNetworkPrefix, acctest.RandInt(t))
289304
subnetwork := network + "-1"
@@ -969,14 +984,6 @@ func TestAccComposerEnvironment_fixPyPiPackages(t *testing.T) {
969984
})
970985
}
971986

972-
// This bootstraps the IAM roles needed for the service agents.
973-
func grantServiceAgentsRole(t *testing.T, prefix string, agentNames []string, role string) {
974-
if acctest.BootstrapAllPSARole(t, prefix, agentNames, role) {
975-
// Fail this test run because the policy needs time to reconcile.
976-
t.Fatal("Stopping test because permissions were added.")
977-
}
978-
}
979-
980987
func testAccComposerEnvironmentDestroyProducer(t *testing.T) func(s *terraform.State) error {
981988
return func(s *terraform.State) error {
982989
config := acctest.GoogleProviderConfig(t)

0 commit comments

Comments
 (0)