github.com/someshkoli/terratest@v0.41.1/modules/aws/vpc.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 8 "github.com/aws/aws-sdk-go/aws" 9 "github.com/aws/aws-sdk-go/service/ec2" 10 "github.com/gruntwork-io/terratest/modules/random" 11 "github.com/gruntwork-io/terratest/modules/testing" 12 "github.com/stretchr/testify/require" 13 ) 14 15 // Vpc is an Amazon Virtual Private Cloud. 16 type Vpc struct { 17 Id string // The ID of the VPC 18 Name string // The name of the VPC 19 Subnets []Subnet // A list of subnets in the VPC 20 Tags map[string]string // The tags associated with the VPC 21 } 22 23 // Subnet is a subnet in an availability zone. 24 type Subnet struct { 25 Id string // The ID of the Subnet 26 AvailabilityZone string // The Availability Zone the subnet is in 27 DefaultForAz bool // If the subnet is default for the Availability Zone 28 Tags map[string]string // The tags associated with the subnet 29 } 30 31 const vpcIDFilterName = "vpc-id" 32 const resourceTypeFilterName = "resource-id" 33 const resourceIdFilterName = "resource-type" 34 const vpcResourceTypeFilterValue = "vpc" 35 const subnetResourceTypeFilterValue = "subnet" 36 const isDefaultFilterName = "isDefault" 37 const isDefaultFilterValue = "true" 38 const defaultVPCName = "Default" 39 40 // GetDefaultVpc fetches information about the default VPC in the given region. 41 func GetDefaultVpc(t testing.TestingT, region string) *Vpc { 42 vpc, err := GetDefaultVpcE(t, region) 43 require.NoError(t, err) 44 return vpc 45 } 46 47 // GetDefaultVpcE fetches information about the default VPC in the given region. 48 func GetDefaultVpcE(t testing.TestingT, region string) (*Vpc, error) { 49 defaultVpcFilter := ec2.Filter{Name: aws.String(isDefaultFilterName), Values: []*string{aws.String(isDefaultFilterValue)}} 50 vpcs, err := GetVpcsE(t, []*ec2.Filter{&defaultVpcFilter}, region) 51 52 numVpcs := len(vpcs) 53 if numVpcs != 1 { 54 return nil, fmt.Errorf("Expected to find one default VPC in region %s but found %s", region, strconv.Itoa(numVpcs)) 55 } 56 57 return vpcs[0], err 58 } 59 60 // GetVpcById fetches information about a VPC with given Id in the given region. 61 func GetVpcById(t testing.TestingT, vpcId string, region string) *Vpc { 62 vpc, err := GetVpcByIdE(t, vpcId, region) 63 require.NoError(t, err) 64 return vpc 65 } 66 67 // GetVpcByIdE fetches information about a VPC with given Id in the given region. 68 func GetVpcByIdE(t testing.TestingT, vpcId string, region string) (*Vpc, error) { 69 vpcIdFilter := ec2.Filter{Name: aws.String(vpcIDFilterName), Values: []*string{&vpcId}} 70 vpcs, err := GetVpcsE(t, []*ec2.Filter{&vpcIdFilter}, region) 71 72 numVpcs := len(vpcs) 73 if numVpcs != 1 { 74 return nil, fmt.Errorf("Expected to find one VPC with ID %s in region %s but found %s", vpcId, region, strconv.Itoa(numVpcs)) 75 } 76 77 return vpcs[0], err 78 } 79 80 // GetVpcsE fetches informations about VPCs from given regions limited by filters 81 func GetVpcsE(t testing.TestingT, filters []*ec2.Filter, region string) ([]*Vpc, error) { 82 client, err := NewEc2ClientE(t, region) 83 if err != nil { 84 return nil, err 85 } 86 87 vpcs, err := client.DescribeVpcs(&ec2.DescribeVpcsInput{Filters: filters}) 88 if err != nil { 89 return nil, err 90 } 91 92 numVpcs := len(vpcs.Vpcs) 93 retVal := make([]*Vpc, numVpcs) 94 95 for i, vpc := range vpcs.Vpcs { 96 subnets, err := GetSubnetsForVpcE(t, aws.StringValue(vpc.VpcId), region) 97 if err != nil { 98 return nil, err 99 } 100 101 tags, err := GetTagsForVpcE(t, aws.StringValue(vpc.VpcId), region) 102 if err != nil { 103 return nil, err 104 } 105 106 retVal[i] = &Vpc{Id: aws.StringValue(vpc.VpcId), Name: FindVpcName(vpc), Subnets: subnets, Tags: tags} 107 } 108 109 return retVal, nil 110 } 111 112 // FindVpcName extracts the VPC name from its tags (if any). Fall back to "Default" if it's the default VPC or empty string 113 // otherwise. 114 func FindVpcName(vpc *ec2.Vpc) string { 115 for _, tag := range vpc.Tags { 116 if *tag.Key == "Name" { 117 return *tag.Value 118 } 119 } 120 121 if *vpc.IsDefault { 122 return defaultVPCName 123 } 124 125 return "" 126 } 127 128 // GetSubnetsForVpc gets the subnets in the specified VPC. 129 func GetSubnetsForVpc(t testing.TestingT, vpcID string, region string) []Subnet { 130 subnets, err := GetSubnetsForVpcE(t, vpcID, region) 131 if err != nil { 132 t.Fatal(err) 133 } 134 return subnets 135 } 136 137 // GetSubnetsForVpcE gets the subnets in the specified VPC. 138 func GetSubnetsForVpcE(t testing.TestingT, vpcID string, region string) ([]Subnet, error) { 139 client, err := NewEc2ClientE(t, region) 140 if err != nil { 141 return nil, err 142 } 143 144 vpcIDFilter := ec2.Filter{Name: aws.String(vpcIDFilterName), Values: []*string{&vpcID}} 145 subnetOutput, err := client.DescribeSubnets(&ec2.DescribeSubnetsInput{Filters: []*ec2.Filter{&vpcIDFilter}}) 146 if err != nil { 147 return nil, err 148 } 149 150 subnets := []Subnet{} 151 152 for _, ec2Subnet := range subnetOutput.Subnets { 153 subnetTags := GetTagsForSubnet(t, *ec2Subnet.SubnetId, region) 154 subnet := Subnet{Id: aws.StringValue(ec2Subnet.SubnetId), AvailabilityZone: aws.StringValue(ec2Subnet.AvailabilityZone), DefaultForAz: aws.BoolValue(ec2Subnet.DefaultForAz), Tags: subnetTags} 155 subnets = append(subnets, subnet) 156 } 157 158 return subnets, nil 159 } 160 161 // GetTagsForVpc gets the tags for the specified VPC. 162 func GetTagsForVpc(t testing.TestingT, vpcID string, region string) map[string]string { 163 tags, err := GetTagsForVpcE(t, vpcID, region) 164 require.NoError(t, err) 165 166 return tags 167 } 168 169 // GetTagsForVpcE gets the tags for the specified VPC. 170 func GetTagsForVpcE(t testing.TestingT, vpcID string, region string) (map[string]string, error) { 171 client, err := NewEc2ClientE(t, region) 172 require.NoError(t, err) 173 174 vpcResourceTypeFilter := ec2.Filter{Name: aws.String(resourceIdFilterName), Values: []*string{aws.String(vpcResourceTypeFilterValue)}} 175 vpcResourceIdFilter := ec2.Filter{Name: aws.String(resourceTypeFilterName), Values: []*string{&vpcID}} 176 tagsOutput, err := client.DescribeTags(&ec2.DescribeTagsInput{Filters: []*ec2.Filter{&vpcResourceTypeFilter, &vpcResourceIdFilter}}) 177 require.NoError(t, err) 178 179 tags := map[string]string{} 180 for _, tag := range tagsOutput.Tags { 181 tags[aws.StringValue(tag.Key)] = aws.StringValue(tag.Value) 182 } 183 184 return tags, nil 185 } 186 187 // GetDefaultSubnetIDsForVpc gets the ids of the subnets that are the default subnet for the AvailabilityZone 188 func GetDefaultSubnetIDsForVpc(t testing.TestingT, vpc Vpc) []string { 189 subnetIDs, err := GetDefaultSubnetIDsForVpcE(t, vpc) 190 require.NoError(t, err) 191 return subnetIDs 192 } 193 194 // GetDefaultSubnetIDsForVpcE gets the ids of the subnets that are the default subnet for the AvailabilityZone 195 func GetDefaultSubnetIDsForVpcE(t testing.TestingT, vpc Vpc) ([]string, error) { 196 if vpc.Name != defaultVPCName { 197 // You cannot create a default subnet in a nondefault VPC 198 // https://docs.aws.amazon.com/vpc/latest/userguide/default-vpc.html 199 return nil, fmt.Errorf("Only default VPCs have default subnets but VPC with id %s is not default VPC", vpc.Id) 200 } 201 subnetIDs := []string{} 202 numSubnets := len(vpc.Subnets) 203 if numSubnets == 0 { 204 return nil, fmt.Errorf("Expected to find at least one subnet in vpc with ID %s but found zero", vpc.Id) 205 } 206 207 for _, subnet := range vpc.Subnets { 208 if subnet.DefaultForAz { 209 subnetIDs = append(subnetIDs, subnet.Id) 210 } 211 } 212 return subnetIDs, nil 213 } 214 215 // GetTagsForSubnet gets the tags for the specified subnet. 216 func GetTagsForSubnet(t testing.TestingT, subnetId string, region string) map[string]string { 217 tags, err := GetTagsForSubnetE(t, subnetId, region) 218 require.NoError(t, err) 219 220 return tags 221 } 222 223 // GetTagsForSubnetE gets the tags for the specified subnet. 224 func GetTagsForSubnetE(t testing.TestingT, subnetId string, region string) (map[string]string, error) { 225 client, err := NewEc2ClientE(t, region) 226 require.NoError(t, err) 227 228 subnetResourceTypeFilter := ec2.Filter{Name: aws.String(resourceIdFilterName), Values: []*string{aws.String(subnetResourceTypeFilterValue)}} 229 subnetResourceIdFilter := ec2.Filter{Name: aws.String(resourceTypeFilterName), Values: []*string{&subnetId}} 230 tagsOutput, err := client.DescribeTags(&ec2.DescribeTagsInput{Filters: []*ec2.Filter{&subnetResourceTypeFilter, &subnetResourceIdFilter}}) 231 require.NoError(t, err) 232 233 tags := map[string]string{} 234 for _, tag := range tagsOutput.Tags { 235 tags[aws.StringValue(tag.Key)] = aws.StringValue(tag.Value) 236 } 237 238 return tags, nil 239 } 240 241 // IsPublicSubnet returns True if the subnet identified by the given id in the provided region is public. 242 func IsPublicSubnet(t testing.TestingT, subnetId string, region string) bool { 243 isPublic, err := IsPublicSubnetE(t, subnetId, region) 244 require.NoError(t, err) 245 return isPublic 246 } 247 248 // IsPublicSubnetE returns True if the subnet identified by the given id in the provided region is public. 249 func IsPublicSubnetE(t testing.TestingT, subnetId string, region string) (bool, error) { 250 subnetIdFilterName := "association.subnet-id" 251 252 subnetIdFilter := ec2.Filter{ 253 Name: &subnetIdFilterName, 254 Values: []*string{&subnetId}, 255 } 256 257 client, err := NewEc2ClientE(t, region) 258 if err != nil { 259 return false, err 260 } 261 262 rts, err := client.DescribeRouteTables(&ec2.DescribeRouteTablesInput{Filters: []*ec2.Filter{&subnetIdFilter}}) 263 if err != nil { 264 return false, err 265 } 266 267 if len(rts.RouteTables) == 0 { 268 // Subnets not explicitly associated with any route table are implicitly associated with the main route table 269 rts, err = getImplicitRouteTableForSubnetE(t, subnetId, region) 270 if err != nil { 271 return false, err 272 } 273 } 274 275 for _, rt := range rts.RouteTables { 276 for _, r := range rt.Routes { 277 if strings.HasPrefix(aws.StringValue(r.GatewayId), "igw-") { 278 return true, nil 279 } 280 } 281 } 282 283 return false, nil 284 } 285 286 func getImplicitRouteTableForSubnetE(t testing.TestingT, subnetId string, region string) (*ec2.DescribeRouteTablesOutput, error) { 287 mainRouteFilterName := "association.main" 288 mainRouteFilterValue := "true" 289 subnetFilterName := "subnet-id" 290 291 client, err := NewEc2ClientE(t, region) 292 if err != nil { 293 return nil, err 294 } 295 296 subnetFilter := ec2.Filter{ 297 Name: &subnetFilterName, 298 Values: []*string{&subnetId}, 299 } 300 subnetOutput, err := client.DescribeSubnets(&ec2.DescribeSubnetsInput{Filters: []*ec2.Filter{&subnetFilter}}) 301 if err != nil { 302 return nil, err 303 } 304 numSubnets := len(subnetOutput.Subnets) 305 if numSubnets != 1 { 306 return nil, fmt.Errorf("Expected to find one subnet with id %s but found %s", subnetId, strconv.Itoa(numSubnets)) 307 } 308 309 mainRouteFilter := ec2.Filter{ 310 Name: &mainRouteFilterName, 311 Values: []*string{&mainRouteFilterValue}, 312 } 313 vpcFilter := ec2.Filter{ 314 Name: aws.String(vpcIDFilterName), 315 Values: []*string{subnetOutput.Subnets[0].VpcId}, 316 } 317 return client.DescribeRouteTables(&ec2.DescribeRouteTablesInput{Filters: []*ec2.Filter{&mainRouteFilter, &vpcFilter}}) 318 } 319 320 // GetRandomPrivateCidrBlock gets a random CIDR block from the range of acceptable private IP addresses per RFC 1918 321 // (https://tools.ietf.org/html/rfc1918#section-3) 322 // The routingPrefix refers to the "/28" in 1.2.3.4/28. 323 // Note that, as written, this function will return a subset of all valid ranges. Since we will probably use this function 324 // mostly for generating random CIDR ranges for VPCs and Subnets, having comprehensive set coverage is not essential. 325 func GetRandomPrivateCidrBlock(routingPrefix int) string { 326 327 var o1, o2, o3, o4 int 328 329 switch routingPrefix { 330 case 32: 331 o1 = random.RandomInt([]int{10, 172, 192}) 332 333 switch o1 { 334 case 10: 335 o2 = random.Random(0, 255) 336 o3 = random.Random(0, 255) 337 o4 = random.Random(0, 255) 338 case 172: 339 o2 = random.Random(16, 31) 340 o3 = random.Random(0, 255) 341 o4 = random.Random(0, 255) 342 case 192: 343 o2 = 168 344 o3 = random.Random(0, 255) 345 o4 = random.Random(0, 255) 346 } 347 348 case 31, 30, 29, 28, 27, 26, 25: 349 fallthrough 350 case 24: 351 o1 = random.RandomInt([]int{10, 172, 192}) 352 353 switch o1 { 354 case 10: 355 o2 = random.Random(0, 255) 356 o3 = random.Random(0, 255) 357 o4 = 0 358 case 172: 359 o2 = 16 360 o3 = 0 361 o4 = 0 362 case 192: 363 o2 = 168 364 o3 = 0 365 o4 = 0 366 } 367 case 23, 22, 21, 20, 19: 368 fallthrough 369 case 18: 370 o1 = random.RandomInt([]int{10, 172, 192}) 371 372 switch o1 { 373 case 10: 374 o2 = 0 375 o3 = 0 376 o4 = 0 377 case 172: 378 o2 = 16 379 o3 = 0 380 o4 = 0 381 case 192: 382 o2 = 168 383 o3 = 0 384 o4 = 0 385 } 386 } 387 return fmt.Sprintf("%d.%d.%d.%d/%d", o1, o2, o3, o4, routingPrefix) 388 } 389 390 // GetFirstTwoOctets gets the first two octets from a CIDR block. 391 func GetFirstTwoOctets(cidrBlock string) string { 392 ipAddr := strings.Split(cidrBlock, "/")[0] 393 octets := strings.Split(ipAddr, ".") 394 return octets[0] + "." + octets[1] 395 }