github.com/mponton/terratest@v0.44.0/modules/gcp/compute.go (about)

     1  package gcp
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"path"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/mponton/terratest/modules/retry"
    12  	"google.golang.org/api/compute/v1"
    13  
    14  	"github.com/mponton/terratest/modules/logger"
    15  	"github.com/mponton/terratest/modules/random"
    16  	"github.com/mponton/terratest/modules/testing"
    17  	"golang.org/x/oauth2/google"
    18  )
    19  
    20  // Corresponds to a GCP Compute Instance (https://cloud.google.com/compute/docs/instances/)
    21  type Instance struct {
    22  	projectID string
    23  	*compute.Instance
    24  }
    25  
    26  // Corresponds to a GCP Image (https://cloud.google.com/compute/docs/images)
    27  type Image struct {
    28  	projectID string
    29  	*compute.Image
    30  }
    31  
    32  // Corresponds to a GCP Zonal Instance Group (https://cloud.google.com/compute/docs/instance-groups/)
    33  type ZonalInstanceGroup struct {
    34  	projectID string
    35  	*compute.InstanceGroup
    36  }
    37  
    38  // Corresponds to a GCP Regional Instance Group (https://cloud.google.com/compute/docs/instance-groups/)
    39  type RegionalInstanceGroup struct {
    40  	projectID string
    41  	*compute.InstanceGroup
    42  }
    43  
    44  type InstanceGroup interface {
    45  	GetInstanceIds(t testing.TestingT) []string
    46  	GetInstanceIdsE(t testing.TestingT) ([]string, error)
    47  }
    48  
    49  // FetchInstance queries GCP to return an instance of the (GCP Compute) Instance type
    50  func FetchInstance(t testing.TestingT, projectID string, name string) *Instance {
    51  	instance, err := FetchInstanceE(t, projectID, name)
    52  	if err != nil {
    53  		t.Fatal(err)
    54  	}
    55  
    56  	return instance
    57  }
    58  
    59  // FetchInstance queries GCP to return an instance of the (GCP Compute) Instance type
    60  func FetchInstanceE(t testing.TestingT, projectID string, name string) (*Instance, error) {
    61  	logger.Logf(t, "Getting Compute Instance %s", name)
    62  
    63  	ctx := context.Background()
    64  	service, err := NewComputeServiceE(t)
    65  	if err != nil {
    66  		t.Fatal(err)
    67  	}
    68  
    69  	// If we want to fetch an Instance without knowing its Zone, we have to query GCP for all Instances in the project
    70  	// and match on name.
    71  	instanceAggregatedList, err := service.Instances.AggregatedList(projectID).Context(ctx).Do()
    72  	if err != nil {
    73  		return nil, fmt.Errorf("Instances.AggregatedList(%s) got error: %v", projectID, err)
    74  	}
    75  
    76  	for _, instanceList := range instanceAggregatedList.Items {
    77  		for _, instance := range instanceList.Instances {
    78  			if name == instance.Name {
    79  				return &Instance{projectID, instance}, nil
    80  			}
    81  		}
    82  	}
    83  
    84  	return nil, fmt.Errorf("Compute Instance %s could not be found in project %s", name, projectID)
    85  }
    86  
    87  // FetchImage queries GCP to return a new instance of the (GCP Compute) Image type
    88  func FetchImage(t testing.TestingT, projectID string, name string) *Image {
    89  	image, err := FetchImageE(t, projectID, name)
    90  	if err != nil {
    91  		t.Fatal(err)
    92  	}
    93  
    94  	return image
    95  }
    96  
    97  // FetchImage queries GCP to return a new instance of the (GCP Compute) Image type
    98  func FetchImageE(t testing.TestingT, projectID string, name string) (*Image, error) {
    99  	logger.Logf(t, "Getting Image %s", name)
   100  
   101  	ctx := context.Background()
   102  	service, err := NewComputeServiceE(t)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	req := service.Images.Get(projectID, name)
   108  	image, err := req.Context(ctx).Do()
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	return &Image{projectID, image}, nil
   114  }
   115  
   116  // FetchRegionalInstanceGroup queries GCP to return a new instance of the Regional Instance Group type
   117  func FetchRegionalInstanceGroup(t testing.TestingT, projectID string, region string, name string) *RegionalInstanceGroup {
   118  	instanceGroup, err := FetchRegionalInstanceGroupE(t, projectID, region, name)
   119  	if err != nil {
   120  		t.Fatal(err)
   121  	}
   122  
   123  	return instanceGroup
   124  }
   125  
   126  // FetchRegionalInstanceGroup queries GCP to return a new instance of the Regional Instance Group type
   127  func FetchRegionalInstanceGroupE(t testing.TestingT, projectID string, region string, name string) (*RegionalInstanceGroup, error) {
   128  	logger.Logf(t, "Getting Regional Instance Group %s", name)
   129  
   130  	ctx := context.Background()
   131  	service, err := NewComputeServiceE(t)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	req := service.RegionInstanceGroups.Get(projectID, region, name)
   137  	instanceGroup, err := req.Context(ctx).Do()
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	return &RegionalInstanceGroup{projectID, instanceGroup}, nil
   143  }
   144  
   145  // FetchZonalInstanceGroup queries GCP to return a new instance of the Regional Instance Group type
   146  func FetchZonalInstanceGroup(t testing.TestingT, projectID string, zone string, name string) *ZonalInstanceGroup {
   147  	instanceGroup, err := FetchZonalInstanceGroupE(t, projectID, zone, name)
   148  	if err != nil {
   149  		t.Fatal(err)
   150  	}
   151  
   152  	return instanceGroup
   153  }
   154  
   155  // FetchZonalInstanceGroup queries GCP to return a new instance of the Regional Instance Group type
   156  func FetchZonalInstanceGroupE(t testing.TestingT, projectID string, zone string, name string) (*ZonalInstanceGroup, error) {
   157  	logger.Logf(t, "Getting Zonal Instance Group %s", name)
   158  
   159  	ctx := context.Background()
   160  	service, err := NewComputeServiceE(t)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	req := service.InstanceGroups.Get(projectID, zone, name)
   166  	instanceGroup, err := req.Context(ctx).Do()
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  
   171  	return &ZonalInstanceGroup{projectID, instanceGroup}, nil
   172  }
   173  
   174  // GetPublicIP gets the public IP address of the given Compute Instance.
   175  func (i *Instance) GetPublicIp(t testing.TestingT) string {
   176  	ip, err := i.GetPublicIpE(t)
   177  	if err != nil {
   178  		t.Fatal(err)
   179  	}
   180  	return ip
   181  }
   182  
   183  // GetPublicIpE gets the public IP address of the given Compute Instance.
   184  func (i *Instance) GetPublicIpE(t testing.TestingT) (string, error) {
   185  	// If there are no accessConfigs specified, then this instance will have no external internet access:
   186  	// https://cloud.google.com/compute/docs/reference/rest/v1/instances.
   187  	if len(i.NetworkInterfaces[0].AccessConfigs) == 0 {
   188  		return "", fmt.Errorf("Attempted to get public IP of Compute Instance %s, but that Compute Instance does not have a public IP address", i.Name)
   189  	}
   190  
   191  	ip := i.NetworkInterfaces[0].AccessConfigs[0].NatIP
   192  
   193  	return ip, nil
   194  }
   195  
   196  // GetLabels returns all the tags for the given Compute Instance.
   197  func (i *Instance) GetLabels(t testing.TestingT) map[string]string {
   198  	return i.Labels
   199  }
   200  
   201  // GetZone returns the Zone in which the Compute Instance is located.
   202  func (i *Instance) GetZone(t testing.TestingT) string {
   203  	return ZoneUrlToZone(i.Zone)
   204  }
   205  
   206  // SetLabels adds the tags to the given Compute Instance.
   207  func (i *Instance) SetLabels(t testing.TestingT, labels map[string]string) {
   208  	err := i.SetLabelsE(t, labels)
   209  	if err != nil {
   210  		t.Fatal(err)
   211  	}
   212  }
   213  
   214  // SetLabelsE adds the tags to the given Compute Instance.
   215  func (i *Instance) SetLabelsE(t testing.TestingT, labels map[string]string) error {
   216  	logger.Logf(t, "Adding labels to instance %s in zone %s", i.Name, i.Zone)
   217  
   218  	ctx := context.Background()
   219  	service, err := NewComputeServiceE(t)
   220  	if err != nil {
   221  		return err
   222  	}
   223  
   224  	req := compute.InstancesSetLabelsRequest{Labels: labels, LabelFingerprint: i.LabelFingerprint}
   225  	if _, err := service.Instances.SetLabels(i.projectID, i.GetZone(t), i.Name, &req).Context(ctx).Do(); err != nil {
   226  		return fmt.Errorf("Instances.SetLabels(%s) got error: %v", i.Name, err)
   227  	}
   228  
   229  	return nil
   230  }
   231  
   232  // GetMetadata gets the given Compute Instance's metadata
   233  func (i *Instance) GetMetadata(t testing.TestingT) []*compute.MetadataItems {
   234  	return i.Metadata.Items
   235  }
   236  
   237  // SetMetadata sets the given Compute Instance's metadata
   238  func (i *Instance) SetMetadata(t testing.TestingT, metadata map[string]string) {
   239  	err := i.SetMetadataE(t, metadata)
   240  	if err != nil {
   241  		t.Fatal(err)
   242  	}
   243  }
   244  
   245  // SetLabelsE adds the given metadata map to the existing metadata of the given Compute Instance.
   246  func (i *Instance) SetMetadataE(t testing.TestingT, metadata map[string]string) error {
   247  	logger.Logf(t, "Adding metadata to instance %s in zone %s", i.Name, i.Zone)
   248  
   249  	ctx := context.Background()
   250  	service, err := NewInstancesServiceE(t)
   251  	if err != nil {
   252  		return err
   253  	}
   254  
   255  	metadataItems := newMetadata(t, i.Metadata, metadata)
   256  	req := service.SetMetadata(i.projectID, i.GetZone(t), i.Name, metadataItems)
   257  	if _, err := req.Context(ctx).Do(); err != nil {
   258  		return fmt.Errorf("Instances.SetMetadata(%s) got error: %v", i.Name, err)
   259  	}
   260  
   261  	return nil
   262  }
   263  
   264  // newMetadata takes in a Compute Instance's existing metadata plus a new set of key-value pairs and returns an updated
   265  // metadata object.
   266  func newMetadata(t testing.TestingT, oldMetadata *compute.Metadata, kvs map[string]string) *compute.Metadata {
   267  	items := []*compute.MetadataItems{}
   268  
   269  	for key, val := range kvs {
   270  		item := &compute.MetadataItems{
   271  			Key:   key,
   272  			Value: &val,
   273  		}
   274  
   275  		items = append(oldMetadata.Items, item)
   276  	}
   277  
   278  	newMetadata := &compute.Metadata{
   279  		Fingerprint: oldMetadata.Fingerprint,
   280  		Items:       items,
   281  	}
   282  
   283  	return newMetadata
   284  }
   285  
   286  // Add the given public SSH key to the Compute Instance. Users can SSH in with the given username.
   287  func (i *Instance) AddSshKey(t testing.TestingT, username string, publicKey string) {
   288  	err := i.AddSshKeyE(t, username, publicKey)
   289  	if err != nil {
   290  		t.Fatal(err)
   291  	}
   292  }
   293  
   294  // Add the given public SSH key to the Compute Instance. Users can SSH in with the given username.
   295  func (i *Instance) AddSshKeyE(t testing.TestingT, username string, publicKey string) error {
   296  	logger.Logf(t, "Adding SSH Key to Compute Instance %s for username %s\n", i.Name, username)
   297  
   298  	// We represent the key in the format required per GCP docs (https://cloud.google.com/compute/docs/instances/adding-removing-ssh-keys)
   299  	publicKeyFormatted := strings.TrimSpace(publicKey)
   300  	sshKeyFormatted := fmt.Sprintf("%s:%s %s", username, publicKeyFormatted, username)
   301  
   302  	metadata := map[string]string{
   303  		"ssh-keys": sshKeyFormatted,
   304  	}
   305  
   306  	err := i.SetMetadataE(t, metadata)
   307  	if err != nil {
   308  		return fmt.Errorf("Failed to add SSH key to Compute Instance: %s", err)
   309  	}
   310  
   311  	return nil
   312  }
   313  
   314  // DeleteImage deletes the given Compute Image.
   315  func (i *Image) DeleteImage(t testing.TestingT) {
   316  	err := i.DeleteImageE(t)
   317  	if err != nil {
   318  		t.Fatal(err)
   319  	}
   320  }
   321  
   322  // DeleteImageE deletes the given Compute Image.
   323  func (i *Image) DeleteImageE(t testing.TestingT) error {
   324  	logger.Logf(t, "Destroying Image %s", i.Name)
   325  
   326  	ctx := context.Background()
   327  	service, err := NewComputeServiceE(t)
   328  	if err != nil {
   329  		return err
   330  	}
   331  
   332  	if _, err := service.Images.Delete(i.projectID, i.Name).Context(ctx).Do(); err != nil {
   333  		return fmt.Errorf("Images.Delete(%s) got error: %v", i.Name, err)
   334  	}
   335  
   336  	return nil
   337  }
   338  
   339  // GetInstanceIds gets the IDs of Instances in the given Instance Group.
   340  func (ig *ZonalInstanceGroup) GetInstanceIds(t testing.TestingT) []string {
   341  	ids, err := ig.GetInstanceIdsE(t)
   342  	if err != nil {
   343  		t.Fatal(err)
   344  	}
   345  	return ids
   346  }
   347  
   348  // GetInstanceIdsE gets the IDs of Instances in the given Zonal Instance Group.
   349  func (ig *ZonalInstanceGroup) GetInstanceIdsE(t testing.TestingT) ([]string, error) {
   350  	logger.Logf(t, "Get instances for Zonal Instance Group %s", ig.Name)
   351  
   352  	ctx := context.Background()
   353  	service, err := NewComputeServiceE(t)
   354  	if err != nil {
   355  		return nil, err
   356  	}
   357  
   358  	requestBody := &compute.InstanceGroupsListInstancesRequest{
   359  		InstanceState: "ALL",
   360  	}
   361  
   362  	instanceIDs := []string{}
   363  	zone := ZoneUrlToZone(ig.Zone)
   364  
   365  	req := service.InstanceGroups.ListInstances(ig.projectID, zone, ig.Name, requestBody)
   366  
   367  	err = req.Pages(ctx, func(page *compute.InstanceGroupsListInstances) error {
   368  		for _, instance := range page.Items {
   369  			// For some reason service.InstanceGroups.ListInstances returns us a collection
   370  			// with Instance URLs and we need only the Instance ID for the next call. Use
   371  			// the path functions to chop the Instance ID off the end of the URL.
   372  			instanceID := path.Base(instance.Instance)
   373  			instanceIDs = append(instanceIDs, instanceID)
   374  		}
   375  		return nil
   376  	})
   377  	if err != nil {
   378  		return nil, fmt.Errorf("InstanceGroups.ListInstances(%s) got error: %v", ig.Name, err)
   379  	}
   380  
   381  	return instanceIDs, nil
   382  }
   383  
   384  // GetInstanceIds gets the IDs of Instances in the given Regional Instance Group.
   385  func (ig *RegionalInstanceGroup) GetInstanceIds(t testing.TestingT) []string {
   386  	ids, err := ig.GetInstanceIdsE(t)
   387  	if err != nil {
   388  		t.Fatal(err)
   389  	}
   390  	return ids
   391  }
   392  
   393  // GetInstanceIdsE gets the IDs of Instances in the given Regional Instance Group.
   394  func (ig *RegionalInstanceGroup) GetInstanceIdsE(t testing.TestingT) ([]string, error) {
   395  	logger.Logf(t, "Get instances for Regional Instance Group %s", ig.Name)
   396  
   397  	ctx := context.Background()
   398  
   399  	service, err := NewComputeServiceE(t)
   400  	if err != nil {
   401  		return nil, err
   402  	}
   403  
   404  	requestBody := &compute.RegionInstanceGroupsListInstancesRequest{
   405  		InstanceState: "ALL",
   406  	}
   407  
   408  	instanceIDs := []string{}
   409  	region := RegionUrlToRegion(ig.Region)
   410  
   411  	req := service.RegionInstanceGroups.ListInstances(ig.projectID, region, ig.Name, requestBody)
   412  
   413  	err = req.Pages(ctx, func(page *compute.RegionInstanceGroupsListInstances) error {
   414  		for _, instance := range page.Items {
   415  			// For some reason service.InstanceGroups.ListInstances returns us a collection
   416  			// with Instance URLs and we need only the Instance ID for the next call. Use
   417  			// the path functions to chop the Instance ID off the end of the URL.
   418  			instanceID := path.Base(instance.Instance)
   419  			instanceIDs = append(instanceIDs, instanceID)
   420  		}
   421  		return nil
   422  	})
   423  	if err != nil {
   424  		return nil, fmt.Errorf("InstanceGroups.ListInstances(%s) got error: %v", ig.Name, err)
   425  	}
   426  
   427  	return instanceIDs, nil
   428  }
   429  
   430  // Return a collection of Instance structs from the given Instance Group
   431  func (ig *ZonalInstanceGroup) GetInstances(t testing.TestingT, projectId string) []*Instance {
   432  	return getInstances(t, ig, projectId)
   433  }
   434  
   435  // Return a collection of Instance structs from the given Instance Group
   436  func (ig *ZonalInstanceGroup) GetInstancesE(t testing.TestingT, projectId string) ([]*Instance, error) {
   437  	return getInstancesE(t, ig, projectId)
   438  }
   439  
   440  // Return a collection of Instance structs from the given Instance Group
   441  func (ig *RegionalInstanceGroup) GetInstances(t testing.TestingT, projectId string) []*Instance {
   442  	return getInstances(t, ig, projectId)
   443  }
   444  
   445  // Return a collection of Instance structs from the given Instance Group
   446  func (ig *RegionalInstanceGroup) GetInstancesE(t testing.TestingT, projectId string) ([]*Instance, error) {
   447  	return getInstancesE(t, ig, projectId)
   448  }
   449  
   450  // getInstancesE returns a collection of Instance structs from the given Instance Group
   451  func getInstances(t testing.TestingT, ig InstanceGroup, projectId string) []*Instance {
   452  	instances, err := getInstancesE(t, ig, projectId)
   453  	if err != nil {
   454  		t.Fatal(err)
   455  	}
   456  
   457  	return instances
   458  }
   459  
   460  // getInstancesE returns a collection of Instance structs from the given Instance Group
   461  func getInstancesE(t testing.TestingT, ig InstanceGroup, projectId string) ([]*Instance, error) {
   462  	instanceIds, err := ig.GetInstanceIdsE(t)
   463  	if err != nil {
   464  		return nil, fmt.Errorf("Failed to get Instance Group IDs: %s", err)
   465  	}
   466  
   467  	var instances []*Instance
   468  
   469  	for _, instanceId := range instanceIds {
   470  		instance, err := FetchInstanceE(t, projectId, instanceId)
   471  		if err != nil {
   472  			return nil, fmt.Errorf("Failed to get Instance: %s", err)
   473  		}
   474  
   475  		instances = append(instances, instance)
   476  	}
   477  
   478  	return instances, nil
   479  }
   480  
   481  // GetPublicIps returns a slice of the public IPs from the given Instance Group
   482  func (ig *ZonalInstanceGroup) GetPublicIps(t testing.TestingT, projectId string) []string {
   483  	return getPublicIps(t, ig, projectId)
   484  }
   485  
   486  // GetPublicIpsE returns a slice of the public IPs from the given Instance Group
   487  func (ig *ZonalInstanceGroup) GetPublicIpsE(t testing.TestingT, projectId string) ([]string, error) {
   488  	return getPublicIpsE(t, ig, projectId)
   489  }
   490  
   491  // GetPublicIps returns a slice of the public IPs from the given Instance Group
   492  func (ig *RegionalInstanceGroup) GetPublicIps(t testing.TestingT, projectId string) []string {
   493  	return getPublicIps(t, ig, projectId)
   494  }
   495  
   496  // GetPublicIpsE returns a slice of the public IPs from the given Instance Group
   497  func (ig *RegionalInstanceGroup) GetPublicIpsE(t testing.TestingT, projectId string) ([]string, error) {
   498  	return getPublicIpsE(t, ig, projectId)
   499  }
   500  
   501  // getPublicIps a slice of the public IPs from the given Instance Group
   502  func getPublicIps(t testing.TestingT, ig InstanceGroup, projectId string) []string {
   503  	ips, err := getPublicIpsE(t, ig, projectId)
   504  	if err != nil {
   505  		t.Fatal(err)
   506  	}
   507  
   508  	return ips
   509  }
   510  
   511  // getPublicIpsE a slice of the public IPs from the given Instance Group
   512  func getPublicIpsE(t testing.TestingT, ig InstanceGroup, projectId string) ([]string, error) {
   513  	instances, err := getInstancesE(t, ig, projectId)
   514  	if err != nil {
   515  		return nil, fmt.Errorf("Failed to get Compute Instances from Instance Group: %s", err)
   516  	}
   517  
   518  	var ips []string
   519  
   520  	for _, instance := range instances {
   521  		ip := instance.GetPublicIp(t)
   522  		ips = append(ips, ip)
   523  	}
   524  
   525  	return ips, nil
   526  }
   527  
   528  // getRandomInstance returns a randomly selected Instance from the Regional Instance Group
   529  func (ig *ZonalInstanceGroup) GetRandomInstance(t testing.TestingT) *Instance {
   530  	return getRandomInstance(t, ig, ig.Name, ig.Region, ig.Size, ig.projectID)
   531  }
   532  
   533  // getRandomInstanceE returns a randomly selected Instance from the Regional Instance Group
   534  func (ig *ZonalInstanceGroup) GetRandomInstanceE(t testing.TestingT) (*Instance, error) {
   535  	return getRandomInstanceE(t, ig, ig.Name, ig.Region, ig.Size, ig.projectID)
   536  }
   537  
   538  // getRandomInstance returns a randomly selected Instance from the Regional Instance Group
   539  func (ig *RegionalInstanceGroup) GetRandomInstance(t testing.TestingT) *Instance {
   540  	return getRandomInstance(t, ig, ig.Name, ig.Region, ig.Size, ig.projectID)
   541  }
   542  
   543  // getRandomInstanceE returns a randomly selected Instance from the Regional Instance Group
   544  func (ig *RegionalInstanceGroup) GetRandomInstanceE(t testing.TestingT) (*Instance, error) {
   545  	return getRandomInstanceE(t, ig, ig.Name, ig.Region, ig.Size, ig.projectID)
   546  }
   547  
   548  func getRandomInstance(t testing.TestingT, ig InstanceGroup, name string, region string, size int64, projectID string) *Instance {
   549  	instance, err := getRandomInstanceE(t, ig, name, region, size, projectID)
   550  	if err != nil {
   551  		t.Fatal(err)
   552  	}
   553  
   554  	return instance
   555  }
   556  
   557  func getRandomInstanceE(t testing.TestingT, ig InstanceGroup, name string, region string, size int64, projectID string) (*Instance, error) {
   558  	instanceIDs := ig.GetInstanceIds(t)
   559  	if len(instanceIDs) == 0 {
   560  		return nil, fmt.Errorf("Could not find any instances in Regional Instance Group or Zonal Instance Group %s in Region %s", name, region)
   561  	}
   562  
   563  	clusterSize := int(size)
   564  	if len(instanceIDs) != clusterSize {
   565  		return nil, fmt.Errorf("Expected Regional Instance Group or Zonal Instance Group %s in Region %s to have %d instances, but found %d", name, region, clusterSize, len(instanceIDs))
   566  	}
   567  
   568  	randIndex := random.Random(0, clusterSize-1)
   569  	instanceID := instanceIDs[randIndex]
   570  	instance := FetchInstance(t, projectID, instanceID)
   571  
   572  	return instance, nil
   573  }
   574  
   575  // NewComputeService creates a new Compute service, which is used to make GCE API calls.
   576  func NewComputeService(t testing.TestingT) *compute.Service {
   577  	client, err := NewComputeServiceE(t)
   578  	if err != nil {
   579  		t.Fatal(err)
   580  	}
   581  	return client
   582  }
   583  
   584  // NewComputeServiceE creates a new Compute service, which is used to make GCE API calls.
   585  func NewComputeServiceE(t testing.TestingT) (*compute.Service, error) {
   586  	ctx := context.Background()
   587  
   588  	// Retrieve the Google OAuth token using a retry loop as it can sometimes return an error.
   589  	// e.g: oauth2: cannot fetch token: Post https://oauth2.googleapis.com/token: net/http: TLS handshake timeout
   590  	// This is loosely based on https://github.com/kubernetes/kubernetes/blob/7e8de5422cb5ad76dd0c147cf4336220d282e34b/pkg/cloudprovider/providers/gce/gce.go#L831.
   591  
   592  	description := "Attempting to request a Google OAuth2 token"
   593  	maxRetries := 6
   594  	timeBetweenRetries := 10 * time.Second
   595  
   596  	var client *http.Client
   597  
   598  	msg, retryErr := retry.DoWithRetryE(t, description, maxRetries, timeBetweenRetries, func() (string, error) {
   599  		rawClient, err := google.DefaultClient(ctx, compute.CloudPlatformScope)
   600  		if err != nil {
   601  			return "Error retrieving default GCP client", err
   602  		}
   603  		client = rawClient
   604  		return "Successfully retrieved default GCP client", nil
   605  	})
   606  	logger.Logf(t, msg)
   607  
   608  	if retryErr != nil {
   609  		return nil, retryErr
   610  	}
   611  
   612  	return compute.New(client)
   613  }
   614  
   615  // NewInstancesService creates a new InstancesService service, which is used to make a subset of GCE API calls.
   616  func NewInstancesService(t testing.TestingT) *compute.InstancesService {
   617  	client, err := NewInstancesServiceE(t)
   618  	if err != nil {
   619  		t.Fatal(err)
   620  	}
   621  	return client
   622  }
   623  
   624  // NewInstancesServiceE creates a new InstancesService service, which is used to make a subset of GCE API calls.
   625  func NewInstancesServiceE(t testing.TestingT) (*compute.InstancesService, error) {
   626  	service, err := NewComputeServiceE(t)
   627  	if err != nil {
   628  		return nil, fmt.Errorf("Failed to get new Instances Service\n")
   629  	}
   630  
   631  	return service.Instances, nil
   632  }
   633  
   634  // Return a random, valid name for GCP resources. Many resources in GCP requires lowercase letters only.
   635  func RandomValidGcpName() string {
   636  	id := strings.ToLower(random.UniqueId())
   637  	instanceName := fmt.Sprintf("terratest-%s", id)
   638  
   639  	return instanceName
   640  }