| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| package acctest |
| |
| import ( |
| "context" |
| "fmt" |
| "log" |
| "os" |
| "strings" |
| "testing" |
| "time" |
| |
| "github.com/hashicorp/terraform-provider-google-beta/google-beta/envvar" |
| tpgcompute "github.com/hashicorp/terraform-provider-google-beta/google-beta/services/compute" |
| "github.com/hashicorp/terraform-provider-google-beta/google-beta/services/privateca" |
| "github.com/hashicorp/terraform-provider-google-beta/google-beta/services/resourcemanager" |
| tpgservicenetworking "github.com/hashicorp/terraform-provider-google-beta/google-beta/services/servicenetworking" |
| "github.com/hashicorp/terraform-provider-google-beta/google-beta/services/sql" |
| "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgiamresource" |
| "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" |
| transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" |
| |
| "google.golang.org/api/cloudbilling/v1" |
| cloudkms "google.golang.org/api/cloudkms/v1" |
| cloudresourcemanager "google.golang.org/api/cloudresourcemanager/v1" |
| iam "google.golang.org/api/iam/v1" |
| "google.golang.org/api/iamcredentials/v1" |
| "google.golang.org/api/servicenetworking/v1" |
| "google.golang.org/api/serviceusage/v1" |
| sqladmin "google.golang.org/api/sqladmin/v1beta4" |
| ) |
| |
| var SharedKeyRing = "tftest-shared-keyring-1" |
| var SharedCryptoKey = map[string]string{ |
| "ENCRYPT_DECRYPT": "tftest-shared-key-1", |
| "ASYMMETRIC_SIGN": "tftest-shared-sign-key-1", |
| "ASYMMETRIC_DECRYPT": "tftest-shared-decrypt-key-1", |
| } |
| |
| type BootstrappedKMS struct { |
| *cloudkms.KeyRing |
| *cloudkms.CryptoKey |
| } |
| |
| func BootstrapKMSKey(t *testing.T) BootstrappedKMS { |
| return BootstrapKMSKeyInLocation(t, "global") |
| } |
| |
| func BootstrapKMSKeyInLocation(t *testing.T, locationID string) BootstrappedKMS { |
| return BootstrapKMSKeyWithPurposeInLocation(t, "ENCRYPT_DECRYPT", locationID) |
| } |
| |
| // BootstrapKMSKeyWithPurpose returns a KMS key in the "global" location. |
| // See BootstrapKMSKeyWithPurposeInLocation. |
| func BootstrapKMSKeyWithPurpose(t *testing.T, purpose string) BootstrappedKMS { |
| return BootstrapKMSKeyWithPurposeInLocation(t, purpose, "global") |
| } |
| |
| /** |
| * BootstrapKMSKeyWithPurposeInLocation will return a KMS key in a |
| * particular location with the given purpose that can be used |
| * in tests that are testing KMS integration with other resources. |
| * |
| * This will either return an existing key or create one if it hasn't been created |
| * in the project yet. The motivation is because keyrings don't get deleted and we |
| * don't want a linear growth of disabled keyrings in a project. We also don't want |
| * to incur the overhead of creating a new project for each test that needs to use |
| * a KMS key. |
| **/ |
| func BootstrapKMSKeyWithPurposeInLocation(t *testing.T, purpose, locationID string) BootstrappedKMS { |
| return BootstrapKMSKeyWithPurposeInLocationAndName(t, purpose, locationID, SharedCryptoKey[purpose]) |
| } |
| |
| func BootstrapKMSKeyWithPurposeInLocationAndName(t *testing.T, purpose, locationID, keyShortName string) BootstrappedKMS { |
| config := BootstrapConfig(t) |
| if config == nil { |
| return BootstrappedKMS{ |
| &cloudkms.KeyRing{}, |
| &cloudkms.CryptoKey{}, |
| } |
| } |
| |
| projectID := envvar.GetTestProjectFromEnv() |
| keyRingParent := fmt.Sprintf("projects/%s/locations/%s", projectID, locationID) |
| keyRingName := fmt.Sprintf("%s/keyRings/%s", keyRingParent, SharedKeyRing) |
| keyParent := fmt.Sprintf("projects/%s/locations/%s/keyRings/%s", projectID, locationID, SharedKeyRing) |
| keyName := fmt.Sprintf("%s/cryptoKeys/%s", keyParent, keyShortName) |
| |
| // Get or Create the hard coded shared keyring for testing |
| kmsClient := config.NewKmsClient(config.UserAgent) |
| keyRing, err := kmsClient.Projects.Locations.KeyRings.Get(keyRingName).Do() |
| if err != nil { |
| if transport_tpg.IsGoogleApiErrorWithCode(err, 404) { |
| keyRing, err = kmsClient.Projects.Locations.KeyRings.Create(keyRingParent, &cloudkms.KeyRing{}). |
| KeyRingId(SharedKeyRing).Do() |
| if err != nil { |
| t.Errorf("Unable to bootstrap KMS key. Cannot create keyRing: %s", err) |
| } |
| } else { |
| t.Errorf("Unable to bootstrap KMS key. Cannot retrieve keyRing: %s", err) |
| } |
| } |
| |
| if keyRing == nil { |
| t.Fatalf("Unable to bootstrap KMS key. keyRing is nil!") |
| } |
| |
| // Get or Create the hard coded, shared crypto key for testing |
| cryptoKey, err := kmsClient.Projects.Locations.KeyRings.CryptoKeys.Get(keyName).Do() |
| if err != nil { |
| if transport_tpg.IsGoogleApiErrorWithCode(err, 404) { |
| algos := map[string]string{ |
| "ENCRYPT_DECRYPT": "GOOGLE_SYMMETRIC_ENCRYPTION", |
| "ASYMMETRIC_SIGN": "RSA_SIGN_PKCS1_4096_SHA512", |
| "ASYMMETRIC_DECRYPT": "RSA_DECRYPT_OAEP_4096_SHA512", |
| } |
| template := cloudkms.CryptoKeyVersionTemplate{ |
| Algorithm: algos[purpose], |
| } |
| |
| newKey := cloudkms.CryptoKey{ |
| Purpose: purpose, |
| VersionTemplate: &template, |
| } |
| |
| cryptoKey, err = kmsClient.Projects.Locations.KeyRings.CryptoKeys.Create(keyParent, &newKey). |
| CryptoKeyId(keyShortName).Do() |
| if err != nil { |
| t.Errorf("Unable to bootstrap KMS key. Cannot create new CryptoKey: %s", err) |
| } |
| |
| } else { |
| t.Errorf("Unable to bootstrap KMS key. Cannot call CryptoKey service: %s", err) |
| } |
| } |
| |
| if cryptoKey == nil { |
| t.Fatalf("Unable to bootstrap KMS key. CryptoKey is nil!") |
| } |
| |
| return BootstrappedKMS{ |
| keyRing, |
| cryptoKey, |
| } |
| } |
| |
| var serviceAccountPrefix = "tf-bootstrap-sa-" |
| var serviceAccountDisplay = "Bootstrapped Service Account for Terraform tests" |
| |
| // Some tests need a second service account, other than the test runner, to assert functionality on. |
| // This provides a well-known service account that can be used when dynamically creating a service |
| // account isn't an option. |
| func getOrCreateServiceAccount(config *transport_tpg.Config, project, serviceAccountEmail string) (*iam.ServiceAccount, error) { |
| name := fmt.Sprintf("projects/%s/serviceAccounts/%s@%s.iam.gserviceaccount.com", project, serviceAccountEmail, project) |
| log.Printf("[DEBUG] Verifying %s as bootstrapped service account.\n", name) |
| |
| sa, err := config.NewIamClient(config.UserAgent).Projects.ServiceAccounts.Get(name).Do() |
| if err != nil && !transport_tpg.IsGoogleApiErrorWithCode(err, 404) { |
| return nil, err |
| } |
| |
| if sa == nil { |
| log.Printf("[DEBUG] Account missing. Creating %s as bootstrapped service account.\n", name) |
| sa = &iam.ServiceAccount{ |
| DisplayName: serviceAccountDisplay, |
| } |
| |
| r := &iam.CreateServiceAccountRequest{ |
| AccountId: serviceAccountEmail, |
| ServiceAccount: sa, |
| } |
| sa, err = config.NewIamClient(config.UserAgent).Projects.ServiceAccounts.Create("projects/"+project, r).Do() |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| return sa, nil |
| } |
| |
| // In order to test impersonation we need to grant the testRunner's account the ability to grant tokens |
| // on a different service account. Granting permissions takes time and there is no operation to wait on |
| // so instead this creates a single service account once per test-suite with the correct permissions. |
| // The first time this test is run it will fail, but subsequent runs will succeed. |
| func impersonationServiceAccountPermissions(config *transport_tpg.Config, sa *iam.ServiceAccount, testRunner string) error { |
| log.Printf("[DEBUG] Setting service account permissions.\n") |
| policy := iam.Policy{ |
| Bindings: []*iam.Binding{}, |
| } |
| |
| binding := &iam.Binding{ |
| Role: "roles/iam.serviceAccountTokenCreator", |
| Members: []string{"serviceAccount:" + sa.Email, "serviceAccount:" + testRunner}, |
| } |
| policy.Bindings = append(policy.Bindings, binding) |
| |
| // Overwrite the roles each time on this service account. This is because this account is |
| // only created for the test suite and will stop snowflaking of permissions to get tests |
| // to run. Overwriting permissions on 1 service account shouldn't affect others. |
| _, err := config.NewIamClient(config.UserAgent).Projects.ServiceAccounts.SetIamPolicy(sa.Name, &iam.SetIamPolicyRequest{ |
| Policy: &policy, |
| }).Do() |
| if err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| // A separate testId should be used for each test, to create separate service accounts for each, |
| // and avoid race conditions where the policy of the same service account is being modified by 2 |
| // tests at once. This is needed as long as the function overwrites the policy on every run. |
| func BootstrapServiceAccount(t *testing.T, testId, testRunner string) string { |
| project := envvar.GetTestProjectFromEnv() |
| serviceAccountEmail := serviceAccountPrefix + testId |
| |
| config := BootstrapConfig(t) |
| if config == nil { |
| return "" |
| } |
| |
| sa, err := getOrCreateServiceAccount(config, project, serviceAccountEmail) |
| if err != nil { |
| t.Fatalf("Bootstrapping failed. Cannot retrieve service account, %s", err) |
| } |
| |
| err = impersonationServiceAccountPermissions(config, sa, testRunner) |
| if err != nil { |
| t.Fatalf("Bootstrapping failed. Cannot set service account permissions, %s", err) |
| } |
| |
| return sa.Email |
| } |
| |
| const SharedTestADDomainPrefix = "tf-bootstrap-ad" |
| |
| func BootstrapSharedTestADDomain(t *testing.T, testId string, networkName string) string { |
| project := envvar.GetTestProjectFromEnv() |
| sharedADDomain := fmt.Sprintf("%s.%s.com", SharedTestADDomainPrefix, testId) |
| adDomainName := fmt.Sprintf("projects/%s/locations/global/domains/%s", project, sharedADDomain) |
| |
| config := BootstrapConfig(t) |
| if config == nil { |
| return "" |
| } |
| |
| log.Printf("[DEBUG] Getting shared test active directory domain %q", adDomainName) |
| getURL := fmt.Sprintf("%s%s", config.ActiveDirectoryBasePath, adDomainName) |
| _, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ |
| Config: config, |
| Method: "GET", |
| Project: project, |
| RawURL: getURL, |
| UserAgent: config.UserAgent, |
| Timeout: 4 * time.Minute, |
| }) |
| if err != nil && transport_tpg.IsGoogleApiErrorWithCode(err, 404) { |
| log.Printf("[DEBUG] AD domain %q not found, bootstrapping", sharedADDomain) |
| postURL := fmt.Sprintf("%sprojects/%s/locations/global/domains?domainName=%s", config.ActiveDirectoryBasePath, project, sharedADDomain) |
| domainObj := map[string]interface{}{ |
| "locations": []string{"us-central1"}, |
| "reservedIpRange": "10.0.1.0/24", |
| "authorizedNetworks": []string{fmt.Sprintf("projects/%s/global/networks/%s", project, networkName)}, |
| } |
| |
| _, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ |
| Config: config, |
| Method: "POST", |
| Project: project, |
| RawURL: postURL, |
| UserAgent: config.UserAgent, |
| Body: domainObj, |
| Timeout: 60 * time.Minute, |
| }) |
| if err != nil { |
| t.Fatalf("Error bootstrapping shared active directory domain %q: %s", adDomainName, err) |
| } |
| |
| log.Printf("[DEBUG] Waiting for active directory domain creation to finish") |
| } |
| |
| _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ |
| Config: config, |
| Method: "GET", |
| Project: project, |
| RawURL: getURL, |
| UserAgent: config.UserAgent, |
| Timeout: 4 * time.Minute, |
| }) |
| |
| if err != nil { |
| t.Fatalf("Error getting shared active directory domain %q: %s", adDomainName, err) |
| } |
| |
| return sharedADDomain |
| } |
| |
| const SharedTestNetworkPrefix = "tf-bootstrap-net-" |
| |
| // BootstrapSharedTestNetwork will return a persistent compute network for a |
| // test or set of tests. |
| // |
| // Usage 1 |
| // Resources like service_networking_connection use a consumer network and |
| // create a complementing tenant network which we don't control. These tenant |
| // networks never get cleaned up and they can accumulate to the point where a |
| // limit is reached for the organization. By reusing a consumer network across |
| // test runs, we can reduce the number of tenant networks that are needed. |
| // See b/146351146 for more context. |
| // |
| // Usage 2 |
| // Bootstrap networks used in tests (gke clusters, dataproc clusters...) |
| // to avoid traffic to the default network |
| // |
| // testId specifies the test for which a shared network is used/initialized. |
| // Note that if the network is being used for a service_networking_connection, |
| // the same testId should generally not be used across tests, to avoid race |
| // conditions where multiple tests attempt to modify the connection at once. |
| // |
| // Returns the name of a network, creating it if it hasn't been created in the |
| // test project. |
| func BootstrapSharedTestNetwork(t *testing.T, testId string) string { |
| project := envvar.GetTestProjectFromEnv() |
| networkName := SharedTestNetworkPrefix + testId |
| |
| config := BootstrapConfig(t) |
| if config == nil { |
| return "" |
| } |
| |
| log.Printf("[DEBUG] Getting shared test network %q", networkName) |
| _, err := config.NewComputeClient(config.UserAgent).Networks.Get(project, networkName).Do() |
| if err != nil && transport_tpg.IsGoogleApiErrorWithCode(err, 404) { |
| log.Printf("[DEBUG] Network %q not found, bootstrapping", networkName) |
| url := fmt.Sprintf("%sprojects/%s/global/networks", config.ComputeBasePath, project) |
| netObj := map[string]interface{}{ |
| "name": networkName, |
| "autoCreateSubnetworks": false, |
| } |
| |
| res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ |
| Config: config, |
| Method: "POST", |
| Project: project, |
| RawURL: url, |
| UserAgent: config.UserAgent, |
| Body: netObj, |
| Timeout: 4 * time.Minute, |
| }) |
| if err != nil { |
| t.Fatalf("Error bootstrapping shared test network %q: %s", networkName, err) |
| } |
| |
| log.Printf("[DEBUG] Waiting for network creation to finish") |
| err = tpgcompute.ComputeOperationWaitTime(config, res, project, "Error bootstrapping shared test network", config.UserAgent, 4*time.Minute) |
| if err != nil { |
| t.Fatalf("Error bootstrapping shared test network %q: %s", networkName, err) |
| } |
| } |
| |
| network, err := config.NewComputeClient(config.UserAgent).Networks.Get(project, networkName).Do() |
| if err != nil { |
| t.Errorf("Error getting shared test network %q: %s", networkName, err) |
| } |
| if network == nil { |
| t.Fatalf("Error getting shared test network %q: is nil", networkName) |
| } |
| return network.Name |
| } |
| |
| type AddressSettings struct { |
| PrefixLength int |
| } |
| |
| func AddressWithPrefixLength(prefixLength int) func(*AddressSettings) { |
| return func(settings *AddressSettings) { |
| settings.PrefixLength = prefixLength |
| } |
| } |
| |
| func NewAddressSettings(options ...func(*AddressSettings)) *AddressSettings { |
| settings := &AddressSettings{ |
| PrefixLength: 16, // default prefix length |
| } |
| |
| for _, o := range options { |
| o(settings) |
| } |
| return settings |
| } |
| |
| const SharedTestGlobalAddressPrefix = "tf-bootstrap-addr-" |
| |
| // params are the functions to set compute global address |
| func BootstrapSharedTestGlobalAddress(t *testing.T, testId string, params ...func(*AddressSettings)) string { |
| project := envvar.GetTestProjectFromEnv() |
| addressName := SharedTestGlobalAddressPrefix + testId |
| networkName := BootstrapSharedTestNetwork(t, testId) |
| networkId := fmt.Sprintf("projects/%v/global/networks/%v", project, networkName) |
| |
| config := BootstrapConfig(t) |
| if config == nil { |
| return "" |
| } |
| |
| log.Printf("[DEBUG] Getting shared test global address %q", addressName) |
| _, err := config.NewComputeClient(config.UserAgent).GlobalAddresses.Get(project, addressName).Do() |
| if err != nil && transport_tpg.IsGoogleApiErrorWithCode(err, 404) { |
| log.Printf("[DEBUG] Global address %q not found, bootstrapping", addressName) |
| url := fmt.Sprintf("%sprojects/%s/global/addresses", config.ComputeBasePath, project) |
| |
| settings := NewAddressSettings(params...) |
| |
| netObj := map[string]interface{}{ |
| "name": addressName, |
| "address_type": "INTERNAL", |
| "purpose": "VPC_PEERING", |
| "prefix_length": settings.PrefixLength, |
| "network": networkId, |
| } |
| |
| res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ |
| Config: config, |
| Method: "POST", |
| Project: project, |
| RawURL: url, |
| UserAgent: config.UserAgent, |
| Body: netObj, |
| Timeout: 4 * time.Minute, |
| }) |
| if err != nil { |
| t.Fatalf("Error bootstrapping shared test global address %q: %s", addressName, err) |
| } |
| |
| log.Printf("[DEBUG] Waiting for global address creation to finish") |
| err = tpgcompute.ComputeOperationWaitTime(config, res, project, "Error bootstrapping shared test global address", config.UserAgent, 4*time.Minute) |
| if err != nil { |
| t.Fatalf("Error bootstrapping shared test global address %q: %s", addressName, err) |
| } |
| } |
| |
| address, err := config.NewComputeClient(config.UserAgent).GlobalAddresses.Get(project, addressName).Do() |
| if err != nil { |
| t.Errorf("Error getting shared test global address %q: %s", addressName, err) |
| } |
| if address == nil { |
| t.Fatalf("Error getting shared test global address %q: is nil", addressName) |
| } |
| return address.Name |
| } |
| |
| type ServiceNetworkSettings struct { |
| PrefixLength int |
| ParentService string |
| } |
| |
| func ServiceNetworkWithPrefixLength(prefixLength int) func(*ServiceNetworkSettings) { |
| return func(settings *ServiceNetworkSettings) { |
| settings.PrefixLength = prefixLength |
| } |
| } |
| |
| func ServiceNetworkWithParentService(parentService string) func(*ServiceNetworkSettings) { |
| return func(settings *ServiceNetworkSettings) { |
| settings.ParentService = parentService |
| } |
| } |
| |
| func NewServiceNetworkSettings(options ...func(*ServiceNetworkSettings)) *ServiceNetworkSettings { |
| settings := &ServiceNetworkSettings{ |
| PrefixLength: 16, // default prefix length |
| ParentService: "servicenetworking.googleapis.com", // default parent service |
| } |
| |
| for _, o := range options { |
| o(settings) |
| } |
| return settings |
| } |
| |
| // BootstrapSharedServiceNetworkingConnection will create a shared network |
| // if it hasn't been created in the test project, a global address |
| // if it hasn't been created in the test project, and a service networking connection |
| // if it hasn't been created in the test project. |
| // |
| // params are the functions to set compute global address |
| // |
| // BootstrapSharedServiceNetworkingConnection returns a persistent compute network name |
| // for a test or set of tests. |
| // |
| // To delete a service networking conneciton, all of the service instances that use that connection |
| // must be deleted first. After the service instances are deleted, some service producers delay the deletion |
| // utnil a waiting period has passed. For example, after four days that you delete a SQL instance, |
| // the service networking connection can be deleted. |
| // That is the reason to use the shared service networking connection for thest resources. |
| // https://cloud.google.com/vpc/docs/configure-private-services-access#removing-connection |
| // |
| // testId specifies the test for which a shared network and a gobal address are used/initialized. |
| func BootstrapSharedServiceNetworkingConnection(t *testing.T, testId string, params ...func(*ServiceNetworkSettings)) string { |
| settings := NewServiceNetworkSettings(params...) |
| parentService := "services/" + settings.ParentService |
| projectId := envvar.GetTestProjectFromEnv() |
| |
| config := BootstrapConfig(t) |
| if config == nil { |
| return "" |
| } |
| |
| // Get project number by calling the API |
| crmClient := config.NewResourceManagerClient(config.UserAgent) |
| project, err := crmClient.Projects.Get(projectId).Do() |
| if err != nil { |
| t.Fatalf("Error getting project: %s", err) |
| } |
| |
| networkName := SharedTestNetworkPrefix + testId |
| networkId := fmt.Sprintf("projects/%v/global/networks/%v", project.ProjectNumber, networkName) |
| globalAddressName := BootstrapSharedTestGlobalAddress(t, testId, AddressWithPrefixLength(settings.PrefixLength)) |
| |
| readCall := config.NewServiceNetworkingClient(config.UserAgent).Services.Connections.List(parentService).Network(networkId) |
| if config.UserProjectOverride { |
| readCall.Header().Add("X-Goog-User-Project", projectId) |
| } |
| response, err := readCall.Do() |
| if err != nil { |
| t.Errorf("Error getting shared test service networking connection: %s", err) |
| } |
| |
| var connection *servicenetworking.Connection |
| for _, c := range response.Connections { |
| if c.Network == networkId { |
| connection = c |
| break |
| } |
| } |
| |
| if connection == nil { |
| log.Printf("[DEBUG] Service networking connection not found, bootstrapping") |
| |
| connection := &servicenetworking.Connection{ |
| Network: networkId, |
| ReservedPeeringRanges: []string{globalAddressName}, |
| } |
| |
| createCall := config.NewServiceNetworkingClient(config.UserAgent).Services.Connections.Create(parentService, connection) |
| if config.UserProjectOverride { |
| createCall.Header().Add("X-Goog-User-Project", projectId) |
| } |
| op, err := createCall.Do() |
| if err != nil { |
| t.Fatalf("Error bootstrapping shared test service networking connection: %s", err) |
| } |
| |
| log.Printf("[DEBUG] Waiting for service networking connection creation to finish") |
| if err := tpgservicenetworking.ServiceNetworkingOperationWaitTimeHW(config, op, "Create Service Networking Connection", config.UserAgent, projectId, 4*time.Minute); err != nil { |
| t.Fatalf("Error bootstrapping shared test service networking connection: %s", err) |
| } |
| } |
| |
| log.Printf("[DEBUG] Getting shared test service networking connection") |
| |
| return networkName |
| } |
| |
| var SharedServicePerimeterProjectPrefix = "tf-bootstrap-sp-" |
| |
| func BootstrapServicePerimeterProjects(t *testing.T, desiredProjects int) []*cloudresourcemanager.Project { |
| config := BootstrapConfig(t) |
| if config == nil { |
| return nil |
| } |
| |
| org := envvar.GetTestOrgFromEnv(t) |
| |
| // The filter endpoint works differently if you provide both the parent id and parent type, and |
| // doesn't seem to allow for prefix matching. Don't change this to include the parent type unless |
| // that API behavior changes. |
| prefixFilter := fmt.Sprintf("id:%s* parent.id:%s", SharedServicePerimeterProjectPrefix, org) |
| res, err := config.NewResourceManagerClient(config.UserAgent).Projects.List().Filter(prefixFilter).Do() |
| if err != nil { |
| t.Fatalf("Error getting shared test projects: %s", err) |
| } |
| |
| projects := res.Projects |
| for len(projects) < desiredProjects { |
| pid := SharedServicePerimeterProjectPrefix + RandString(t, 10) |
| project := &cloudresourcemanager.Project{ |
| ProjectId: pid, |
| Name: "TF Service Perimeter Test", |
| Parent: &cloudresourcemanager.ResourceId{ |
| Type: "organization", |
| Id: org, |
| }, |
| } |
| op, err := config.NewResourceManagerClient(config.UserAgent).Projects.Create(project).Do() |
| if err != nil { |
| t.Fatalf("Error bootstrapping shared test project: %s", err) |
| } |
| |
| opAsMap, err := tpgresource.ConvertToMap(op) |
| if err != nil { |
| t.Fatalf("Error bootstrapping shared test project: %s", err) |
| } |
| |
| err = resourcemanager.ResourceManagerOperationWaitTime(config, opAsMap, "creating project", config.UserAgent, 4) |
| if err != nil { |
| t.Fatalf("Error bootstrapping shared test project: %s", err) |
| } |
| |
| p, err := config.NewResourceManagerClient(config.UserAgent).Projects.Get(pid).Do() |
| if err != nil { |
| t.Fatalf("Error getting shared test project: %s", err) |
| } |
| projects = append(projects, p) |
| } |
| |
| return projects |
| } |
| |
| // BootstrapProject will create or get a project named |
| // "<projectIDPrefix><projectIDSuffix>" that will persist across test runs, |
| // where projectIDSuffix is based off of getTestProjectFromEnv(). The reason |
| // for the naming is to isolate bootstrapped projects by test environment. |
| // Given the existing projects being used by our team, the prefix provided to |
| // this function can be no longer than 18 characters. |
| func BootstrapProject(t *testing.T, projectIDPrefix, billingAccount string, services []string) *cloudresourcemanager.Project { |
| config := BootstrapConfig(t) |
| if config == nil { |
| return nil |
| } |
| |
| projectIDSuffix := strings.Replace(envvar.GetTestProjectFromEnv(), "ci-test-project-", "", 1) |
| projectID := projectIDPrefix + projectIDSuffix |
| |
| crmClient := config.NewResourceManagerClient(config.UserAgent) |
| |
| project, err := crmClient.Projects.Get(projectID).Do() |
| if err != nil { |
| if !transport_tpg.IsGoogleApiErrorWithCode(err, 403) { |
| t.Fatalf("Error getting bootstrapped project: %s", err) |
| } |
| org := envvar.GetTestOrgFromEnv(t) |
| |
| op, err := crmClient.Projects.Create(&cloudresourcemanager.Project{ |
| ProjectId: projectID, |
| Name: "Bootstrapped Test Project", |
| Parent: &cloudresourcemanager.ResourceId{ |
| Type: "organization", |
| Id: org, |
| }, |
| }).Do() |
| if err != nil { |
| t.Fatalf("Error creating bootstrapped test project: %s", err) |
| } |
| |
| opAsMap, err := tpgresource.ConvertToMap(op) |
| if err != nil { |
| t.Fatalf("Error converting create project operation to map: %s", err) |
| } |
| |
| err = resourcemanager.ResourceManagerOperationWaitTime(config, opAsMap, "creating project", config.UserAgent, 4*time.Minute) |
| if err != nil { |
| t.Fatalf("Error waiting for create project operation: %s", err) |
| } |
| |
| project, err = crmClient.Projects.Get(projectID).Do() |
| if err != nil { |
| t.Fatalf("Error getting bootstrapped project: %s", err) |
| } |
| |
| } |
| |
| if project.LifecycleState == "DELETE_REQUESTED" { |
| _, err := crmClient.Projects.Undelete(projectID, &cloudresourcemanager.UndeleteProjectRequest{}).Do() |
| if err != nil { |
| t.Fatalf("Error undeleting bootstrapped project: %s", err) |
| } |
| } |
| |
| if billingAccount != "" { |
| billingClient := config.NewBillingClient(config.UserAgent) |
| var pbi *cloudbilling.ProjectBillingInfo |
| err = transport_tpg.Retry(transport_tpg.RetryOptions{ |
| RetryFunc: func() error { |
| var reqErr error |
| pbi, reqErr = billingClient.Projects.GetBillingInfo(resourcemanager.PrefixedProject(projectID)).Do() |
| return reqErr |
| }, |
| Timeout: 30 * time.Second, |
| }) |
| if err != nil { |
| t.Fatalf("Error getting billing info for project %q: %v", projectID, err) |
| } |
| if strings.TrimPrefix(pbi.BillingAccountName, "billingAccounts/") != billingAccount { |
| pbi.BillingAccountName = "billingAccounts/" + billingAccount |
| err := transport_tpg.Retry(transport_tpg.RetryOptions{ |
| RetryFunc: func() error { |
| _, err := config.NewBillingClient(config.UserAgent).Projects.UpdateBillingInfo(resourcemanager.PrefixedProject(projectID), pbi).Do() |
| return err |
| }, |
| Timeout: 2 * time.Minute, |
| }) |
| if err != nil { |
| t.Fatalf("Error setting billing account for project %q to %q: %s", projectID, billingAccount, err) |
| } |
| } |
| } |
| |
| if len(services) > 0 { |
| |
| enabledServices, err := resourcemanager.ListCurrentlyEnabledServices(projectID, "", config.UserAgent, config, 1*time.Minute) |
| if err != nil { |
| t.Fatalf("Error listing services for project %q: %s", projectID, err) |
| } |
| |
| servicesToEnable := make([]string, 0, len(services)) |
| for _, service := range services { |
| if _, ok := enabledServices[service]; !ok { |
| servicesToEnable = append(servicesToEnable, service) |
| } |
| } |
| |
| if len(servicesToEnable) > 0 { |
| if err := resourcemanager.EnableServiceUsageProjectServices(servicesToEnable, projectID, "", config.UserAgent, config, 10*time.Minute); err != nil { |
| t.Fatalf("Error enabling services for project %q: %s", projectID, err) |
| } |
| } |
| } |
| |
| return project |
| } |
| |
| // BootstrapConfig returns a Config pulled from the environment. |
| func BootstrapConfig(t *testing.T) *transport_tpg.Config { |
| if v := os.Getenv("TF_ACC"); v == "" { |
| t.Skip("Acceptance tests and bootstrapping skipped unless env 'TF_ACC' set") |
| return nil |
| } |
| |
| config := &transport_tpg.Config{ |
| Credentials: envvar.GetTestCredsFromEnv(), |
| Project: envvar.GetTestProjectFromEnv(), |
| Region: envvar.GetTestRegionFromEnv(), |
| Zone: envvar.GetTestZoneFromEnv(), |
| } |
| |
| transport_tpg.ConfigureBasePaths(config) |
| |
| if err := config.LoadAndValidate(context.Background()); err != nil { |
| t.Fatalf("Bootstrapping failed. Unable to load test config: %s", err) |
| } |
| return config |
| } |
| |
| // SQL Instance names are not reusable for a week after deletion |
| const SharedTestSQLInstanceNamePrefix = "tf-bootstrap-" |
| |
| // BootstrapSharedSQLInstanceBackupRun will return a shared SQL db instance that |
| // has a backup created for it. |
| func BootstrapSharedSQLInstanceBackupRun(t *testing.T) string { |
| project := envvar.GetTestProjectFromEnv() |
| |
| config := BootstrapConfig(t) |
| if config == nil { |
| return "" |
| } |
| |
| log.Printf("[DEBUG] Getting list of existing sql instances") |
| |
| instances, err := config.NewSqlAdminClient(config.UserAgent).Instances.List(project).Do() |
| if err != nil { |
| t.Fatalf("Unable to bootstrap SQL Instance. Cannot retrieve instance list: %s", err) |
| } |
| |
| var bootstrapInstance *sqladmin.DatabaseInstance |
| |
| // Look for any existing bootstrap instances |
| for _, i := range instances.Items { |
| if strings.HasPrefix(i.Name, SharedTestSQLInstanceNamePrefix) { |
| bootstrapInstance = i |
| break |
| } |
| } |
| |
| if bootstrapInstance == nil { |
| bootstrapInstanceName := SharedTestSQLInstanceNamePrefix + RandString(t, 10) |
| log.Printf("[DEBUG] Bootstrap SQL Instance not found, bootstrapping new instance %s", bootstrapInstanceName) |
| |
| backupConfig := &sqladmin.BackupConfiguration{ |
| Enabled: true, |
| PointInTimeRecoveryEnabled: true, |
| } |
| settings := &sqladmin.Settings{ |
| Tier: "db-f1-micro", |
| BackupConfiguration: backupConfig, |
| } |
| bootstrapInstance = &sqladmin.DatabaseInstance{ |
| Name: bootstrapInstanceName, |
| Region: "us-central1", |
| Settings: settings, |
| DatabaseVersion: "POSTGRES_11", |
| } |
| |
| var op *sqladmin.Operation |
| err = transport_tpg.Retry(transport_tpg.RetryOptions{ |
| RetryFunc: func() (operr error) { |
| op, operr = config.NewSqlAdminClient(config.UserAgent).Instances.Insert(project, bootstrapInstance).Do() |
| return operr |
| }, |
| Timeout: 20 * time.Minute, |
| ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{transport_tpg.IsSqlOperationInProgressError}, |
| }) |
| if err != nil { |
| t.Fatalf("Error, failed to create instance %s: %s", bootstrapInstance.Name, err) |
| } |
| err = sql.SqlAdminOperationWaitTime(config, op, project, "Create Instance", config.UserAgent, 40*time.Minute) |
| if err != nil { |
| t.Fatalf("Error, failed to create instance %s: %s", bootstrapInstance.Name, err) |
| } |
| } |
| |
| // Look for backups in bootstrap instance |
| res, err := config.NewSqlAdminClient(config.UserAgent).BackupRuns.List(project, bootstrapInstance.Name).Do() |
| if err != nil { |
| t.Fatalf("Unable to bootstrap SQL Instance. Cannot retrieve backup list: %s", err) |
| } |
| backupsList := res.Items |
| if len(backupsList) == 0 { |
| log.Printf("[DEBUG] No backups found for %s, creating backup", bootstrapInstance.Name) |
| backupRun := &sqladmin.BackupRun{ |
| Instance: bootstrapInstance.Name, |
| } |
| |
| var op *sqladmin.Operation |
| err = transport_tpg.Retry(transport_tpg.RetryOptions{ |
| RetryFunc: func() (operr error) { |
| op, operr = config.NewSqlAdminClient(config.UserAgent).BackupRuns.Insert(project, bootstrapInstance.Name, backupRun).Do() |
| return operr |
| }, |
| Timeout: 20 * time.Minute, |
| ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{transport_tpg.IsSqlOperationInProgressError}, |
| }) |
| if err != nil { |
| t.Fatalf("Error, failed to create instance backup: %s", err) |
| } |
| err = sql.SqlAdminOperationWaitTime(config, op, project, "Backup Instance", config.UserAgent, 20*time.Minute) |
| if err != nil { |
| t.Fatalf("Error, failed to create instance backup: %s", err) |
| } |
| } |
| |
| return bootstrapInstance.Name |
| } |
| |
| func BootstrapSharedCaPoolInLocation(t *testing.T, location string) string { |
| project := envvar.GetTestProjectFromEnv() |
| poolName := "static-ca-pool" |
| |
| config := BootstrapConfig(t) |
| if config == nil { |
| return "" |
| } |
| |
| log.Printf("[DEBUG] Getting shared CA pool %q", poolName) |
| url := fmt.Sprintf("%sprojects/%s/locations/%s/caPools/%s", config.PrivatecaBasePath, project, location, poolName) |
| _, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ |
| Config: config, |
| Method: "GET", |
| Project: project, |
| RawURL: url, |
| UserAgent: config.UserAgent, |
| }) |
| if err != nil { |
| log.Printf("[DEBUG] CA pool %q not found, bootstrapping", poolName) |
| poolObj := map[string]interface{}{ |
| "tier": "ENTERPRISE", |
| } |
| createUrl := fmt.Sprintf("%sprojects/%s/locations/%s/caPools?caPoolId=%s", config.PrivatecaBasePath, project, location, poolName) |
| res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ |
| Config: config, |
| Method: "POST", |
| Project: project, |
| RawURL: createUrl, |
| UserAgent: config.UserAgent, |
| Body: poolObj, |
| Timeout: 4 * time.Minute, |
| }) |
| if err != nil { |
| t.Fatalf("Error bootstrapping shared CA pool %q: %s", poolName, err) |
| } |
| |
| log.Printf("[DEBUG] Waiting for CA pool creation to finish") |
| var opRes map[string]interface{} |
| err = privateca.PrivatecaOperationWaitTimeWithResponse( |
| config, res, &opRes, project, "Creating CA pool", config.UserAgent, |
| 4*time.Minute) |
| if err != nil { |
| t.Errorf("Error getting shared CA pool %q: %s", poolName, err) |
| } |
| _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ |
| Config: config, |
| Method: "GET", |
| Project: project, |
| RawURL: url, |
| UserAgent: config.UserAgent, |
| }) |
| if err != nil { |
| t.Errorf("Error getting shared CA pool %q: %s", poolName, err) |
| } |
| } |
| return poolName |
| } |
| |
| func BootstrapSubnet(t *testing.T, subnetName string, networkName string) string { |
| projectID := envvar.GetTestProjectFromEnv() |
| region := envvar.GetTestRegionFromEnv() |
| |
| config := BootstrapConfig(t) |
| if config == nil { |
| t.Fatal("Could not bootstrap config.") |
| } |
| |
| computeService := config.NewComputeClient(config.UserAgent) |
| if computeService == nil { |
| t.Fatal("Could not create compute client.") |
| } |
| |
| // In order to create a networkAttachment we need to bootstrap a subnet. |
| _, err := computeService.Subnetworks.Get(projectID, region, subnetName).Do() |
| if err != nil && transport_tpg.IsGoogleApiErrorWithCode(err, 404) { |
| log.Printf("[DEBUG] Subnet %q not found, bootstrapping", subnetName) |
| |
| networkUrl := fmt.Sprintf("%sprojects/%s/global/networks/%s", config.ComputeBasePath, projectID, networkName) |
| url := fmt.Sprintf("%sprojects/%s/regions/%s/subnetworks", config.ComputeBasePath, projectID, region) |
| |
| subnetObj := map[string]interface{}{ |
| "name": subnetName, |
| "region ": region, |
| "network": networkUrl, |
| "ipCidrRange": "10.77.0.0/20", |
| } |
| |
| res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ |
| Config: config, |
| Method: "POST", |
| Project: projectID, |
| RawURL: url, |
| UserAgent: config.UserAgent, |
| Body: subnetObj, |
| Timeout: 4 * time.Minute, |
| }) |
| |
| log.Printf("Response is, %s", res) |
| if err != nil { |
| t.Fatalf("Error bootstrapping test subnet %s: %s", subnetName, err) |
| } |
| |
| log.Printf("[DEBUG] Waiting for network creation to finish") |
| err = tpgcompute.ComputeOperationWaitTime(config, res, projectID, "Error bootstrapping test subnet", config.UserAgent, 4*time.Minute) |
| if err != nil { |
| t.Fatalf("Error bootstrapping test subnet %s: %s", subnetName, err) |
| } |
| } |
| |
| subnet, err := computeService.Subnetworks.Get(projectID, region, subnetName).Do() |
| |
| if subnet == nil { |
| t.Fatalf("Error getting test subnet %s: is nil", subnetName) |
| } |
| |
| if err != nil { |
| t.Fatalf("Error getting test subnet %s: %s", subnetName, err) |
| } |
| return subnet.Name |
| } |
| |
| func BootstrapNetworkAttachment(t *testing.T, networkAttachmentName string, subnetName string) string { |
| projectID := envvar.GetTestProjectFromEnv() |
| region := envvar.GetTestRegionFromEnv() |
| |
| config := BootstrapConfig(t) |
| if config == nil { |
| return "" |
| } |
| |
| computeService := config.NewComputeClient(config.UserAgent) |
| if computeService == nil { |
| return "" |
| } |
| |
| networkAttachment, err := computeService.NetworkAttachments.Get(projectID, region, networkAttachmentName).Do() |
| if err != nil && transport_tpg.IsGoogleApiErrorWithCode(err, 404) { |
| // Create Network Attachment Here. |
| log.Printf("[DEBUG] Network Attachment %s not found, bootstrapping", networkAttachmentName) |
| url := fmt.Sprintf("%sprojects/%s/regions/%s/networkAttachments", config.ComputeBasePath, projectID, region) |
| |
| subnetURL := fmt.Sprintf("%sprojects/%s/regions/%s/subnetworks/%s", config.ComputeBasePath, projectID, region, subnetName) |
| networkAttachmentObj := map[string]interface{}{ |
| "name": networkAttachmentName, |
| "region": region, |
| "subnetworks": []string{subnetURL}, |
| "connectionPreference": "ACCEPT_AUTOMATIC", |
| } |
| |
| res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ |
| Config: config, |
| Method: "POST", |
| Project: projectID, |
| RawURL: url, |
| UserAgent: config.UserAgent, |
| Body: networkAttachmentObj, |
| Timeout: 4 * time.Minute, |
| }) |
| if err != nil { |
| t.Fatalf("Error bootstrapping test Network Attachment %s: %s", networkAttachmentName, err) |
| } |
| |
| log.Printf("[DEBUG] Waiting for network creation to finish") |
| err = tpgcompute.ComputeOperationWaitTime(config, res, projectID, "Error bootstrapping shared test subnet", config.UserAgent, 4*time.Minute) |
| if err != nil { |
| t.Fatalf("Error bootstrapping test Network Attachment %s: %s", networkAttachmentName, err) |
| } |
| } |
| |
| networkAttachment, err = computeService.NetworkAttachments.Get(projectID, region, networkAttachmentName).Do() |
| |
| if networkAttachment == nil { |
| t.Fatalf("Error getting test network attachment %s: is nil", networkAttachmentName) |
| } |
| |
| if err != nil { |
| t.Fatalf("Error getting test Network Attachment %s: %s", networkAttachmentName, err) |
| } |
| |
| return networkAttachment.Name |
| } |
| |
| // The default network within GCP already comes pre configured with |
| // certain firewall rules open to allow internal communication. As we |
| // are boostrapping a network for dataproc tests, we need to additionally |
| // open up similar rules to allow the nodes to talk to each other |
| // internally as part of their configuration or this will just hang. |
| const SharedTestFirewallPrefix = "tf-bootstrap-firewall-" |
| |
| func BootstrapFirewallForDataprocSharedNetwork(t *testing.T, firewallName string, networkName string) string { |
| project := envvar.GetTestProjectFromEnv() |
| firewallName = SharedTestFirewallPrefix + firewallName |
| |
| config := BootstrapConfig(t) |
| if config == nil { |
| return "" |
| } |
| |
| log.Printf("[DEBUG] Getting Firewall %q for Network %q", firewallName, networkName) |
| _, err := config.NewComputeClient(config.UserAgent).Firewalls.Get(project, firewallName).Do() |
| if err != nil && transport_tpg.IsGoogleApiErrorWithCode(err, 404) { |
| log.Printf("[DEBUG] firewallName %q not found, bootstrapping", firewallName) |
| url := fmt.Sprintf("%sprojects/%s/global/firewalls", config.ComputeBasePath, project) |
| |
| networkId := fmt.Sprintf("projects/%s/global/networks/%s", project, networkName) |
| allowObj := []interface{}{ |
| map[string]interface{}{ |
| "IPProtocol": "icmp", |
| }, |
| map[string]interface{}{ |
| "IPProtocol": "tcp", |
| "ports": []string{"0-65535"}, |
| }, |
| map[string]interface{}{ |
| "IPProtocol": "udp", |
| "ports": []string{"0-65535"}, |
| }, |
| } |
| |
| firewallObj := map[string]interface{}{ |
| "name": firewallName, |
| "network": networkId, |
| "allowed": allowObj, |
| } |
| |
| res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ |
| Config: config, |
| Method: "POST", |
| Project: project, |
| RawURL: url, |
| UserAgent: config.UserAgent, |
| Body: firewallObj, |
| Timeout: 4 * time.Minute, |
| }) |
| if err != nil { |
| t.Fatalf("Error bootstrapping Firewall %q for Network %q: %s", firewallName, networkName, err) |
| } |
| |
| log.Printf("[DEBUG] Waiting for Firewall creation to finish") |
| err = tpgcompute.ComputeOperationWaitTime(config, res, project, "Error bootstrapping Firewall", config.UserAgent, 4*time.Minute) |
| if err != nil { |
| t.Fatalf("Error bootstrapping Firewall %q: %s", firewallName, err) |
| } |
| } |
| |
| firewall, err := config.NewComputeClient(config.UserAgent).Firewalls.Get(project, firewallName).Do() |
| if err != nil { |
| t.Errorf("Error getting Firewall %q: %s", firewallName, err) |
| } |
| if firewall == nil { |
| t.Fatalf("Error getting Firewall %q: is nil", firewallName) |
| } |
| return firewall.Name |
| } |
| |
| func SetupProjectsAndGetAccessToken(org, billing, pid, service string, config *transport_tpg.Config) (string, error) { |
| // Create project-1 and project-2 |
| rmService := config.NewResourceManagerClient(config.UserAgent) |
| |
| project := &cloudresourcemanager.Project{ |
| ProjectId: pid, |
| Name: pid, |
| Parent: &cloudresourcemanager.ResourceId{ |
| Id: org, |
| Type: "organization", |
| }, |
| } |
| |
| var op *cloudresourcemanager.Operation |
| err := transport_tpg.Retry(transport_tpg.RetryOptions{ |
| RetryFunc: func() (reqErr error) { |
| op, reqErr = rmService.Projects.Create(project).Do() |
| return reqErr |
| }, |
| Timeout: 5 * time.Minute, |
| }) |
| if err != nil { |
| return "", err |
| } |
| |
| // Wait for the operation to complete |
| opAsMap, err := tpgresource.ConvertToMap(op) |
| if err != nil { |
| return "", err |
| } |
| |
| waitErr := resourcemanager.ResourceManagerOperationWaitTime(config, opAsMap, "creating project", config.UserAgent, 5*time.Minute) |
| if waitErr != nil { |
| return "", waitErr |
| } |
| |
| ba := &cloudbilling.ProjectBillingInfo{ |
| BillingAccountName: fmt.Sprintf("billingAccounts/%s", billing), |
| } |
| _, err = config.NewBillingClient(config.UserAgent).Projects.UpdateBillingInfo(resourcemanager.PrefixedProject(pid), ba).Do() |
| if err != nil { |
| return "", err |
| } |
| |
| p2 := fmt.Sprintf("%s-2", pid) |
| project.ProjectId = p2 |
| project.Name = fmt.Sprintf("%s-2", pid) |
| |
| err = transport_tpg.Retry(transport_tpg.RetryOptions{ |
| RetryFunc: func() (reqErr error) { |
| op, reqErr = rmService.Projects.Create(project).Do() |
| return reqErr |
| }, |
| Timeout: 5 * time.Minute, |
| }) |
| if err != nil { |
| return "", err |
| } |
| |
| // Wait for the operation to complete |
| opAsMap, err = tpgresource.ConvertToMap(op) |
| if err != nil { |
| return "", err |
| } |
| |
| waitErr = resourcemanager.ResourceManagerOperationWaitTime(config, opAsMap, "creating project", config.UserAgent, 5*time.Minute) |
| if waitErr != nil { |
| return "", waitErr |
| } |
| |
| _, err = config.NewBillingClient(config.UserAgent).Projects.UpdateBillingInfo(resourcemanager.PrefixedProject(p2), ba).Do() |
| if err != nil { |
| return "", err |
| } |
| |
| // Enable the appropriate service in project-2 only |
| suService := config.NewServiceUsageClient(config.UserAgent) |
| |
| serviceReq := &serviceusage.BatchEnableServicesRequest{ |
| ServiceIds: []string{fmt.Sprintf("%s.googleapis.com", service)}, |
| } |
| |
| _, err = suService.Services.BatchEnable(fmt.Sprintf("projects/%s", p2), serviceReq).Do() |
| if err != nil { |
| return "", err |
| } |
| |
| // Enable the test runner to create service accounts and get an access token on behalf of |
| // the project 1 service account |
| curEmail, err := transport_tpg.GetCurrentUserEmail(config, config.UserAgent) |
| if err != nil { |
| return "", err |
| } |
| |
| proj1SATokenCreator := &cloudresourcemanager.Binding{ |
| Members: []string{fmt.Sprintf("serviceAccount:%s", curEmail)}, |
| Role: "roles/iam.serviceAccountTokenCreator", |
| } |
| |
| proj1SACreator := &cloudresourcemanager.Binding{ |
| Members: []string{fmt.Sprintf("serviceAccount:%s", curEmail)}, |
| Role: "roles/iam.serviceAccountCreator", |
| } |
| |
| bindings := tpgiamresource.MergeBindings([]*cloudresourcemanager.Binding{proj1SATokenCreator, proj1SACreator}) |
| |
| p, err := rmService.Projects.GetIamPolicy(pid, |
| &cloudresourcemanager.GetIamPolicyRequest{ |
| Options: &cloudresourcemanager.GetPolicyOptions{ |
| RequestedPolicyVersion: tpgiamresource.IamPolicyVersion, |
| }, |
| }).Do() |
| if err != nil { |
| return "", err |
| } |
| |
| p.Bindings = tpgiamresource.MergeBindings(append(p.Bindings, bindings...)) |
| _, err = config.NewResourceManagerClient(config.UserAgent).Projects.SetIamPolicy(pid, |
| &cloudresourcemanager.SetIamPolicyRequest{ |
| Policy: p, |
| UpdateMask: "bindings,etag,auditConfigs", |
| }).Do() |
| if err != nil { |
| return "", err |
| } |
| |
| // Create a service account for project-1 |
| serviceAccountEmail := serviceAccountPrefix + service |
| sa1, err := getOrCreateServiceAccount(config, pid, serviceAccountEmail) |
| if err != nil { |
| return "", err |
| } |
| |
| // Add permissions to service accounts |
| |
| // Permission needed for user_project_override |
| proj2ServiceUsageBinding := &cloudresourcemanager.Binding{ |
| Members: []string{fmt.Sprintf("serviceAccount:%s", sa1.Email)}, |
| Role: "roles/serviceusage.serviceUsageConsumer", |
| } |
| |
| // Admin permission for service |
| proj2ServiceAdminBinding := &cloudresourcemanager.Binding{ |
| Members: []string{fmt.Sprintf("serviceAccount:%s", sa1.Email)}, |
| Role: fmt.Sprintf("roles/%s.admin", service), |
| } |
| |
| bindings = tpgiamresource.MergeBindings([]*cloudresourcemanager.Binding{proj2ServiceUsageBinding, proj2ServiceAdminBinding}) |
| |
| // For KMS test only |
| if service == "cloudkms" { |
| proj2CryptoKeyBinding := &cloudresourcemanager.Binding{ |
| Members: []string{fmt.Sprintf("serviceAccount:%s", sa1.Email)}, |
| Role: "roles/cloudkms.cryptoKeyEncrypter", |
| } |
| |
| bindings = tpgiamresource.MergeBindings(append(bindings, proj2CryptoKeyBinding)) |
| } |
| |
| p, err = rmService.Projects.GetIamPolicy(p2, |
| &cloudresourcemanager.GetIamPolicyRequest{ |
| Options: &cloudresourcemanager.GetPolicyOptions{ |
| RequestedPolicyVersion: tpgiamresource.IamPolicyVersion, |
| }, |
| }).Do() |
| if err != nil { |
| return "", err |
| } |
| |
| p.Bindings = tpgiamresource.MergeBindings(append(p.Bindings, bindings...)) |
| _, err = config.NewResourceManagerClient(config.UserAgent).Projects.SetIamPolicy(p2, |
| &cloudresourcemanager.SetIamPolicyRequest{ |
| Policy: p, |
| UpdateMask: "bindings,etag,auditConfigs", |
| }).Do() |
| if err != nil { |
| return "", err |
| } |
| |
| // The token creator IAM API call returns success long before the policy is |
| // actually usable. Wait a solid 2 minutes to ensure we can use it. |
| time.Sleep(2 * time.Minute) |
| |
| iamCredsService := config.NewIamCredentialsClient(config.UserAgent) |
| tokenRequest := &iamcredentials.GenerateAccessTokenRequest{ |
| Lifetime: "300s", |
| Scope: []string{"https://www.googleapis.com/auth/cloud-platform"}, |
| } |
| atResp, err := iamCredsService.Projects.ServiceAccounts.GenerateAccessToken(fmt.Sprintf("projects/-/serviceAccounts/%s", sa1.Email), tokenRequest).Do() |
| if err != nil { |
| return "", err |
| } |
| |
| accessToken := atResp.AccessToken |
| |
| return accessToken, nil |
| } |
| |
| const sharedTagKeyPrefix = "tf-bootstrap-tagkey" |
| |
| func BootstrapSharedTestTagKey(t *testing.T, testId string) string { |
| org := envvar.GetTestOrgFromEnv(t) |
| sharedTagKey := fmt.Sprintf("%s-%s", sharedTagKeyPrefix, testId) |
| tagKeyName := fmt.Sprintf("%s/%s", org, sharedTagKey) |
| |
| config := BootstrapConfig(t) |
| if config == nil { |
| return "" |
| } |
| |
| log.Printf("[DEBUG] Getting shared test tag key %q", sharedTagKey) |
| getURL := fmt.Sprintf("%stagKeys/namespaced?name=%s", config.TagsBasePath, tagKeyName) |
| _, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ |
| Config: config, |
| Method: "GET", |
| Project: config.Project, |
| RawURL: getURL, |
| UserAgent: config.UserAgent, |
| Timeout: 2 * time.Minute, |
| }) |
| if err != nil && transport_tpg.IsGoogleApiErrorWithCode(err, 403) { |
| log.Printf("[DEBUG] TagKey %q not found, bootstrapping", sharedTagKey) |
| tagKeyObj := map[string]interface{}{ |
| "parent": "organizations/" + org, |
| "shortName": sharedTagKey, |
| "description": "Bootstrapped tag key for Terraform Acceptance testing", |
| } |
| |
| _, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ |
| Config: config, |
| Method: "POST", |
| Project: config.Project, |
| RawURL: config.TagsBasePath + "tagKeys/", |
| UserAgent: config.UserAgent, |
| Body: tagKeyObj, |
| Timeout: 10 * time.Minute, |
| }) |
| if err != nil { |
| t.Fatalf("Error bootstrapping shared tag key %q: %s", sharedTagKey, err) |
| } |
| |
| log.Printf("[DEBUG] Waiting for shared tag key creation to finish") |
| } |
| |
| _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ |
| Config: config, |
| Method: "GET", |
| Project: config.Project, |
| RawURL: getURL, |
| UserAgent: config.UserAgent, |
| Timeout: 2 * time.Minute, |
| }) |
| |
| if err != nil { |
| t.Fatalf("Error getting shared tag key %q: %s", sharedTagKey, err) |
| } |
| |
| return sharedTagKey |
| } |
| |
| const sharedTagValuePrefix = "tf-bootstrap-tagvalue" |
| |
| func BootstrapSharedTestTagValue(t *testing.T, testId string, tagKey string) string { |
| org := envvar.GetTestOrgFromEnv(t) |
| sharedTagValue := fmt.Sprintf("%s-%s", sharedTagValuePrefix, testId) |
| tagKeyName := fmt.Sprintf("%s/%s", org, tagKey) |
| tagValueName := fmt.Sprintf("%s/%s", tagKeyName, sharedTagValue) |
| |
| config := BootstrapConfig(t) |
| if config == nil { |
| return "" |
| } |
| |
| log.Printf("[DEBUG] Getting shared test tag value %q", sharedTagValue) |
| getURL := fmt.Sprintf("%stagValues/namespaced?name=%s", config.TagsBasePath, tagValueName) |
| _, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ |
| Config: config, |
| Method: "GET", |
| Project: config.Project, |
| RawURL: getURL, |
| UserAgent: config.UserAgent, |
| Timeout: 2 * time.Minute, |
| }) |
| if err != nil && transport_tpg.IsGoogleApiErrorWithCode(err, 403) { |
| log.Printf("[DEBUG] TagValue %q not found, bootstrapping", sharedTagValue) |
| log.Printf("[DEBUG] Fetching permanent id for tagkey %s", tagKeyName) |
| tagKeyGetURL := fmt.Sprintf("%stagKeys/namespaced?name=%s", config.TagsBasePath, tagKeyName) |
| tagKeyResponse, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ |
| Config: config, |
| Method: "GET", |
| Project: config.Project, |
| RawURL: tagKeyGetURL, |
| UserAgent: config.UserAgent, |
| Timeout: 2 * time.Minute, |
| }) |
| if err != nil { |
| t.Fatalf("Error getting tag key id for %s : %s", tagKeyName, err) |
| } |
| tagKeyObj := map[string]interface{}{ |
| "parent": tagKeyResponse["name"].(string), |
| "shortName": sharedTagValue, |
| "description": "Bootstrapped tag value for Terraform Acceptance testing", |
| } |
| |
| _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ |
| Config: config, |
| Method: "POST", |
| Project: config.Project, |
| RawURL: config.TagsBasePath + "tagValues/", |
| UserAgent: config.UserAgent, |
| Body: tagKeyObj, |
| Timeout: 10 * time.Minute, |
| }) |
| if err != nil { |
| t.Fatalf("Error bootstrapping shared tag value %q: %s", sharedTagValue, err) |
| } |
| |
| log.Printf("[DEBUG] Waiting for shared tag value creation to finish") |
| } |
| |
| _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ |
| Config: config, |
| Method: "GET", |
| Project: config.Project, |
| RawURL: getURL, |
| UserAgent: config.UserAgent, |
| Timeout: 2 * time.Minute, |
| }) |
| |
| if err != nil { |
| t.Fatalf("Error getting shared tag value %q: %s", sharedTagValue, err) |
| } |
| |
| return sharedTagValue |
| } |