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 }