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

     1  package gcp
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"strings"
     7  
     8  	"github.com/mponton/terratest/modules/collections"
     9  	"github.com/mponton/terratest/modules/logger"
    10  	"github.com/mponton/terratest/modules/random"
    11  	"github.com/mponton/terratest/modules/testing"
    12  	"google.golang.org/api/compute/v1"
    13  )
    14  
    15  // You can set this environment variable to force Terratest to use a specific Region rather than a random one. This is
    16  // convenient when iterating locally.
    17  const regionOverrideEnvVarName = "TERRATEST_GCP_REGION"
    18  
    19  // You can set this environment variable to force Terratest to use a specific Zone rather than a random one. This is
    20  // convenient when iterating locally.
    21  const zoneOverrideEnvVarName = "TERRATEST_GCP_ZONE"
    22  
    23  // Some GCP API calls require a GCP Region. We typically require the user to set one explicitly, but in some
    24  // cases, this doesn't make sense (e.g., for fetching the list of regions in an account), so for those cases, we use
    25  // this Region as a default.
    26  const defaultRegion = "us-west1"
    27  
    28  // Some GCP API calls require a GCP Zone. We typically require the user to set one explicitly, but in some
    29  // cases, this doesn't make sense (e.g., for fetching the list of regions in an account), so for those cases, we use
    30  // this Zone as a default.
    31  const defaultZone = "us-west1-b"
    32  
    33  // GetRandomRegion gets a randomly chosen GCP Region. If approvedRegions is not empty, this will be a Region from the approvedRegions
    34  // list; otherwise, this method will fetch the latest list of regions from the GCP APIs and pick one of those. If
    35  // forbiddenRegions is not empty, this method will make sure the returned Region is not in the forbiddenRegions list.
    36  func GetRandomRegion(t testing.TestingT, projectID string, approvedRegions []string, forbiddenRegions []string) string {
    37  	region, err := GetRandomRegionE(t, projectID, approvedRegions, forbiddenRegions)
    38  	if err != nil {
    39  		t.Fatal(err)
    40  	}
    41  	return region
    42  }
    43  
    44  // GetRandomRegionE gets a randomly chosen GCP Region. If approvedRegions is not empty, this will be a Region from the approvedRegions
    45  // list; otherwise, this method will fetch the latest list of regions from the GCP APIs and pick one of those. If
    46  // forbiddenRegions is not empty, this method will make sure the returned Region is not in the forbiddenRegions list.
    47  func GetRandomRegionE(t testing.TestingT, projectID string, approvedRegions []string, forbiddenRegions []string) (string, error) {
    48  	regionFromEnvVar := os.Getenv(regionOverrideEnvVarName)
    49  	if regionFromEnvVar != "" {
    50  		logger.Logf(t, "Using GCP Region %s from environment variable %s", regionFromEnvVar, regionOverrideEnvVarName)
    51  		return regionFromEnvVar, nil
    52  	}
    53  
    54  	regionsToPickFrom := approvedRegions
    55  
    56  	if len(regionsToPickFrom) == 0 {
    57  		allRegions, err := GetAllGcpRegionsE(t, projectID)
    58  		if err != nil {
    59  			return "", err
    60  		}
    61  		regionsToPickFrom = allRegions
    62  	}
    63  
    64  	regionsToPickFrom = collections.ListSubtract(regionsToPickFrom, forbiddenRegions)
    65  	region := random.RandomString(regionsToPickFrom)
    66  
    67  	logger.Logf(t, "Using Region %s", region)
    68  	return region, nil
    69  }
    70  
    71  // GetRandomZone gets a randomly chosen GCP Zone. If approvedRegions is not empty, this will be a Zone from the approvedZones
    72  // list; otherwise, this method will fetch the latest list of Zones from the GCP APIs and pick one of those. If
    73  // forbiddenZones is not empty, this method will make sure the returned Region is not in the forbiddenZones list.
    74  func GetRandomZone(t testing.TestingT, projectID string, approvedZones []string, forbiddenZones []string, forbiddenRegions []string) string {
    75  	zone, err := GetRandomZoneE(t, projectID, approvedZones, forbiddenZones, forbiddenRegions)
    76  	if err != nil {
    77  		t.Fatal(err)
    78  	}
    79  	return zone
    80  }
    81  
    82  // GetRandomZoneE gets a randomly chosen GCP Zone. If approvedRegions is not empty, this will be a Zone from the approvedZones
    83  // list; otherwise, this method will fetch the latest list of Zones from the GCP APIs and pick one of those. If
    84  // forbiddenZones is not empty, this method will make sure the returned Region is not in the forbiddenZones list.
    85  func GetRandomZoneE(t testing.TestingT, projectID string, approvedZones []string, forbiddenZones []string, forbiddenRegions []string) (string, error) {
    86  	zoneFromEnvVar := os.Getenv(zoneOverrideEnvVarName)
    87  	if zoneFromEnvVar != "" {
    88  		logger.Logf(t, "Using GCP Zone %s from environment variable %s", zoneFromEnvVar, zoneOverrideEnvVarName)
    89  		return zoneFromEnvVar, nil
    90  	}
    91  
    92  	zonesToPickFrom := approvedZones
    93  
    94  	if len(zonesToPickFrom) == 0 {
    95  		allZones, err := GetAllGcpZonesE(t, projectID)
    96  		if err != nil {
    97  			return "", err
    98  		}
    99  		zonesToPickFrom = allZones
   100  	}
   101  
   102  	zonesToPickFrom = collections.ListSubtract(zonesToPickFrom, forbiddenZones)
   103  
   104  	var zonesToPickFromFiltered []string
   105  	for _, zone := range zonesToPickFrom {
   106  		if !isInRegions(zone, forbiddenRegions) {
   107  			zonesToPickFromFiltered = append(zonesToPickFromFiltered, zone)
   108  		}
   109  	}
   110  
   111  	zone := random.RandomString(zonesToPickFromFiltered)
   112  
   113  	return zone, nil
   114  }
   115  
   116  // GetRandomZoneForRegion gets a randomly chosen GCP Zone in the given Region.
   117  func GetRandomZoneForRegion(t testing.TestingT, projectID string, region string) string {
   118  	zone, err := GetRandomZoneForRegionE(t, projectID, region)
   119  	if err != nil {
   120  		t.Fatal(err)
   121  	}
   122  	return zone
   123  }
   124  
   125  // GetRandomZoneForRegionE gets a randomly chosen GCP Zone in the given Region.
   126  func GetRandomZoneForRegionE(t testing.TestingT, projectID string, region string) (string, error) {
   127  	zoneFromEnvVar := os.Getenv(zoneOverrideEnvVarName)
   128  	if zoneFromEnvVar != "" {
   129  		logger.Logf(t, "Using GCP Zone %s from environment variable %s", zoneFromEnvVar, zoneOverrideEnvVarName)
   130  		return zoneFromEnvVar, nil
   131  	}
   132  
   133  	allZones, err := GetAllGcpZonesE(t, projectID)
   134  	if err != nil {
   135  		return "", err
   136  	}
   137  
   138  	zonesToPickFrom := []string{}
   139  
   140  	for _, zone := range allZones {
   141  		if strings.Contains(zone, region) {
   142  			zonesToPickFrom = append(zonesToPickFrom, zone)
   143  		}
   144  	}
   145  
   146  	zone := random.RandomString(zonesToPickFrom)
   147  
   148  	logger.Logf(t, "Using Zone %s", zone)
   149  	return zone, nil
   150  }
   151  
   152  // GetAllGcpRegions gets the list of GCP regions available in this account.
   153  func GetAllGcpRegions(t testing.TestingT, projectID string) []string {
   154  	out, err := GetAllGcpRegionsE(t, projectID)
   155  	if err != nil {
   156  		t.Fatal(err)
   157  	}
   158  	return out
   159  }
   160  
   161  // GetAllGcpRegionsE gets the list of GCP regions available in this account.
   162  func GetAllGcpRegionsE(t testing.TestingT, projectID string) ([]string, error) {
   163  	logger.Log(t, "Looking up all GCP regions available in this account")
   164  
   165  	// Note that NewComputeServiceE creates a context, but it appears to be empty so we keep the code simpler by
   166  	// creating a new one here
   167  	ctx := context.Background()
   168  
   169  	service, err := NewComputeServiceE(t)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	req := service.Regions.List(projectID)
   175  
   176  	regions := []string{}
   177  	err = req.Pages(ctx, func(page *compute.RegionList) error {
   178  		for _, region := range page.Items {
   179  			regions = append(regions, region.Name)
   180  		}
   181  		return err
   182  	})
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  
   187  	return regions, nil
   188  }
   189  
   190  // GetAllGcpZones gets the list of GCP Zones available in this account.
   191  func GetAllGcpZones(t testing.TestingT, projectID string) []string {
   192  	out, err := GetAllGcpZonesE(t, projectID)
   193  	if err != nil {
   194  		t.Fatal(err)
   195  	}
   196  	return out
   197  }
   198  
   199  // GetAllGcpZonesE gets the list of GCP Zones available in this account.
   200  func GetAllGcpZonesE(t testing.TestingT, projectID string) ([]string, error) {
   201  	// Note that NewComputeServiceE creates a context, but it appears to be empty so we keep the code simpler by
   202  	// creating a new one here
   203  	ctx := context.Background()
   204  
   205  	service, err := NewComputeServiceE(t)
   206  	if err != nil {
   207  		return nil, err
   208  	}
   209  
   210  	req := service.Zones.List(projectID)
   211  
   212  	zones := []string{}
   213  	err = req.Pages(ctx, func(page *compute.ZoneList) error {
   214  		for _, zone := range page.Items {
   215  			zones = append(zones, zone.Name)
   216  		}
   217  		return err
   218  	})
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  
   223  	return zones, nil
   224  }
   225  
   226  // Given a GCP Zone URL formatted like https://www.googleapis.com/compute/v1/projects/project-123456/zones/asia-east1-b,
   227  // return "asia-east1-b".
   228  // Todo: Improve sanity checking on this function by using a RegEx with capture groups
   229  func ZoneUrlToZone(zoneUrl string) string {
   230  	tokens := strings.Split(zoneUrl, "/")
   231  	return tokens[len(tokens)-1]
   232  }
   233  
   234  // Given a GCP Zone URL formatted like https://www.googleapis.com/compute/v1/projects/project-123456/regions/southamerica-east1,
   235  // return "southamerica-east1".
   236  // Todo: Improve sanity checking on this function by using a RegEx with capture groups
   237  func RegionUrlToRegion(zoneUrl string) string {
   238  	tokens := strings.Split(zoneUrl, "/")
   239  	return tokens[len(tokens)-1]
   240  }
   241  
   242  // Returns true if the given zone is in any of the given regions
   243  func isInRegions(zone string, regions []string) bool {
   244  	for _, region := range regions {
   245  		if isInRegion(zone, region) {
   246  			return true
   247  		}
   248  	}
   249  
   250  	return false
   251  }
   252  
   253  // Returns true if the given zone is in the given region
   254  func isInRegion(zone string, region string) bool {
   255  	return strings.Contains(zone, region)
   256  }