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  }