github.com/dougneal/terraform@v0.6.15-0.20170330092735-b6a3840768a4/builtin/providers/aws/resource_aws_vpc.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"time"
     7  
     8  	"github.com/aws/aws-sdk-go/aws"
     9  	"github.com/aws/aws-sdk-go/aws/awserr"
    10  	"github.com/aws/aws-sdk-go/service/ec2"
    11  	"github.com/hashicorp/terraform/helper/resource"
    12  	"github.com/hashicorp/terraform/helper/schema"
    13  )
    14  
    15  func resourceAwsVpc() *schema.Resource {
    16  	return &schema.Resource{
    17  		Create: resourceAwsVpcCreate,
    18  		Read:   resourceAwsVpcRead,
    19  		Update: resourceAwsVpcUpdate,
    20  		Delete: resourceAwsVpcDelete,
    21  		Importer: &schema.ResourceImporter{
    22  			State: resourceAwsVpcInstanceImport,
    23  		},
    24  
    25  		SchemaVersion: 1,
    26  		MigrateState:  resourceAwsVpcMigrateState,
    27  
    28  		Schema: map[string]*schema.Schema{
    29  			"cidr_block": {
    30  				Type:         schema.TypeString,
    31  				Required:     true,
    32  				ForceNew:     true,
    33  				ValidateFunc: validateCIDRNetworkAddress,
    34  			},
    35  
    36  			"instance_tenancy": {
    37  				Type:     schema.TypeString,
    38  				Optional: true,
    39  				ForceNew: true,
    40  				Computed: true,
    41  			},
    42  
    43  			"enable_dns_hostnames": {
    44  				Type:     schema.TypeBool,
    45  				Optional: true,
    46  				Computed: true,
    47  			},
    48  
    49  			"enable_dns_support": {
    50  				Type:     schema.TypeBool,
    51  				Optional: true,
    52  				Default:  true,
    53  			},
    54  
    55  			"enable_classiclink": {
    56  				Type:     schema.TypeBool,
    57  				Optional: true,
    58  				Computed: true,
    59  			},
    60  
    61  			"assign_generated_ipv6_cidr_block": {
    62  				Type:     schema.TypeBool,
    63  				ForceNew: true,
    64  				Optional: true,
    65  				Default:  false,
    66  			},
    67  
    68  			"main_route_table_id": {
    69  				Type:     schema.TypeString,
    70  				Computed: true,
    71  			},
    72  
    73  			"default_network_acl_id": {
    74  				Type:     schema.TypeString,
    75  				Computed: true,
    76  			},
    77  
    78  			"dhcp_options_id": {
    79  				Type:     schema.TypeString,
    80  				Computed: true,
    81  			},
    82  
    83  			"default_security_group_id": {
    84  				Type:     schema.TypeString,
    85  				Computed: true,
    86  			},
    87  
    88  			"default_route_table_id": {
    89  				Type:     schema.TypeString,
    90  				Computed: true,
    91  			},
    92  
    93  			"ipv6_association_id": {
    94  				Type:     schema.TypeString,
    95  				Computed: true,
    96  			},
    97  
    98  			"ipv6_cidr_block": {
    99  				Type:     schema.TypeString,
   100  				Computed: true,
   101  			},
   102  
   103  			"tags": tagsSchema(),
   104  		},
   105  	}
   106  }
   107  
   108  func resourceAwsVpcCreate(d *schema.ResourceData, meta interface{}) error {
   109  	conn := meta.(*AWSClient).ec2conn
   110  	instance_tenancy := "default"
   111  	if v, ok := d.GetOk("instance_tenancy"); ok {
   112  		instance_tenancy = v.(string)
   113  	}
   114  
   115  	// Create the VPC
   116  	createOpts := &ec2.CreateVpcInput{
   117  		CidrBlock:                   aws.String(d.Get("cidr_block").(string)),
   118  		InstanceTenancy:             aws.String(instance_tenancy),
   119  		AmazonProvidedIpv6CidrBlock: aws.Bool(d.Get("assign_generated_ipv6_cidr_block").(bool)),
   120  	}
   121  
   122  	log.Printf("[DEBUG] VPC create config: %#v", *createOpts)
   123  	vpcResp, err := conn.CreateVpc(createOpts)
   124  	if err != nil {
   125  		return fmt.Errorf("Error creating VPC: %s", err)
   126  	}
   127  
   128  	// Get the ID and store it
   129  	vpc := vpcResp.Vpc
   130  	d.SetId(*vpc.VpcId)
   131  	log.Printf("[INFO] VPC ID: %s", d.Id())
   132  
   133  	// Set partial mode and say that we setup the cidr block
   134  	d.Partial(true)
   135  	d.SetPartial("cidr_block")
   136  
   137  	// Wait for the VPC to become available
   138  	log.Printf(
   139  		"[DEBUG] Waiting for VPC (%s) to become available",
   140  		d.Id())
   141  	stateConf := &resource.StateChangeConf{
   142  		Pending: []string{"pending"},
   143  		Target:  []string{"available"},
   144  		Refresh: VPCStateRefreshFunc(conn, d.Id()),
   145  		Timeout: 10 * time.Minute,
   146  	}
   147  	if _, err := stateConf.WaitForState(); err != nil {
   148  		return fmt.Errorf(
   149  			"Error waiting for VPC (%s) to become available: %s",
   150  			d.Id(), err)
   151  	}
   152  
   153  	// Update our attributes and return
   154  	return resourceAwsVpcUpdate(d, meta)
   155  }
   156  
   157  func resourceAwsVpcRead(d *schema.ResourceData, meta interface{}) error {
   158  	conn := meta.(*AWSClient).ec2conn
   159  
   160  	// Refresh the VPC state
   161  	vpcRaw, _, err := VPCStateRefreshFunc(conn, d.Id())()
   162  	if err != nil {
   163  		return err
   164  	}
   165  	if vpcRaw == nil {
   166  		d.SetId("")
   167  		return nil
   168  	}
   169  
   170  	// VPC stuff
   171  	vpc := vpcRaw.(*ec2.Vpc)
   172  	vpcid := d.Id()
   173  	d.Set("cidr_block", vpc.CidrBlock)
   174  	d.Set("dhcp_options_id", vpc.DhcpOptionsId)
   175  	d.Set("instance_tenancy", vpc.InstanceTenancy)
   176  
   177  	// Tags
   178  	d.Set("tags", tagsToMap(vpc.Tags))
   179  
   180  	for _, a := range vpc.Ipv6CidrBlockAssociationSet {
   181  		if *a.Ipv6CidrBlockState.State == "associated" {
   182  			d.Set("assign_generated_ipv6_cidr_block", true)
   183  			d.Set("ipv6_association_id", a.AssociationId)
   184  			d.Set("ipv6_cidr_block", a.Ipv6CidrBlock)
   185  		} else {
   186  			d.Set("assign_generated_ipv6_cidr_block", false)
   187  			d.Set("ipv6_association_id", "") // we blank these out to remove old entries
   188  			d.Set("ipv6_cidr_block", "")
   189  		}
   190  	}
   191  
   192  	// Attributes
   193  	attribute := "enableDnsSupport"
   194  	DescribeAttrOpts := &ec2.DescribeVpcAttributeInput{
   195  		Attribute: aws.String(attribute),
   196  		VpcId:     aws.String(vpcid),
   197  	}
   198  	resp, err := conn.DescribeVpcAttribute(DescribeAttrOpts)
   199  	if err != nil {
   200  		return err
   201  	}
   202  	d.Set("enable_dns_support", *resp.EnableDnsSupport.Value)
   203  	attribute = "enableDnsHostnames"
   204  	DescribeAttrOpts = &ec2.DescribeVpcAttributeInput{
   205  		Attribute: &attribute,
   206  		VpcId:     &vpcid,
   207  	}
   208  	resp, err = conn.DescribeVpcAttribute(DescribeAttrOpts)
   209  	if err != nil {
   210  		return err
   211  	}
   212  	d.Set("enable_dns_hostnames", *resp.EnableDnsHostnames.Value)
   213  
   214  	DescribeClassiclinkOpts := &ec2.DescribeVpcClassicLinkInput{
   215  		VpcIds: []*string{&vpcid},
   216  	}
   217  
   218  	// Classic Link is only available in regions that support EC2 Classic
   219  	respClassiclink, err := conn.DescribeVpcClassicLink(DescribeClassiclinkOpts)
   220  	if err != nil {
   221  		if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "UnsupportedOperation" {
   222  			log.Printf("[WARN] VPC Classic Link is not supported in this region")
   223  		} else {
   224  			return err
   225  		}
   226  	} else {
   227  		classiclink_enabled := false
   228  		for _, v := range respClassiclink.Vpcs {
   229  			if *v.VpcId == vpcid {
   230  				if v.ClassicLinkEnabled != nil {
   231  					classiclink_enabled = *v.ClassicLinkEnabled
   232  				}
   233  				break
   234  			}
   235  		}
   236  		d.Set("enable_classiclink", classiclink_enabled)
   237  	}
   238  
   239  	// Get the main routing table for this VPC
   240  	// Really Ugly need to make this better - rmenn
   241  	filter1 := &ec2.Filter{
   242  		Name:   aws.String("association.main"),
   243  		Values: []*string{aws.String("true")},
   244  	}
   245  	filter2 := &ec2.Filter{
   246  		Name:   aws.String("vpc-id"),
   247  		Values: []*string{aws.String(d.Id())},
   248  	}
   249  	DescribeRouteOpts := &ec2.DescribeRouteTablesInput{
   250  		Filters: []*ec2.Filter{filter1, filter2},
   251  	}
   252  	routeResp, err := conn.DescribeRouteTables(DescribeRouteOpts)
   253  	if err != nil {
   254  		return err
   255  	}
   256  	if v := routeResp.RouteTables; len(v) > 0 {
   257  		d.Set("main_route_table_id", *v[0].RouteTableId)
   258  	}
   259  
   260  	if err := resourceAwsVpcSetDefaultNetworkAcl(conn, d); err != nil {
   261  		log.Printf("[WARN] Unable to set Default Network ACL: %s", err)
   262  	}
   263  	if err := resourceAwsVpcSetDefaultSecurityGroup(conn, d); err != nil {
   264  		log.Printf("[WARN] Unable to set Default Security Group: %s", err)
   265  	}
   266  	if err := resourceAwsVpcSetDefaultRouteTable(conn, d); err != nil {
   267  		log.Printf("[WARN] Unable to set Default Route Table: %s", err)
   268  	}
   269  
   270  	return nil
   271  }
   272  
   273  func resourceAwsVpcUpdate(d *schema.ResourceData, meta interface{}) error {
   274  	conn := meta.(*AWSClient).ec2conn
   275  
   276  	// Turn on partial mode
   277  	d.Partial(true)
   278  	vpcid := d.Id()
   279  	if d.HasChange("enable_dns_hostnames") {
   280  		val := d.Get("enable_dns_hostnames").(bool)
   281  		modifyOpts := &ec2.ModifyVpcAttributeInput{
   282  			VpcId: &vpcid,
   283  			EnableDnsHostnames: &ec2.AttributeBooleanValue{
   284  				Value: &val,
   285  			},
   286  		}
   287  
   288  		log.Printf(
   289  			"[INFO] Modifying enable_dns_hostnames vpc attribute for %s: %s",
   290  			d.Id(), modifyOpts)
   291  		if _, err := conn.ModifyVpcAttribute(modifyOpts); err != nil {
   292  			return err
   293  		}
   294  
   295  		d.SetPartial("enable_dns_hostnames")
   296  	}
   297  
   298  	_, hasEnableDnsSupportOption := d.GetOk("enable_dns_support")
   299  
   300  	if !hasEnableDnsSupportOption || d.HasChange("enable_dns_support") {
   301  		val := d.Get("enable_dns_support").(bool)
   302  		modifyOpts := &ec2.ModifyVpcAttributeInput{
   303  			VpcId: &vpcid,
   304  			EnableDnsSupport: &ec2.AttributeBooleanValue{
   305  				Value: &val,
   306  			},
   307  		}
   308  
   309  		log.Printf(
   310  			"[INFO] Modifying enable_dns_support vpc attribute for %s: %s",
   311  			d.Id(), modifyOpts)
   312  		if _, err := conn.ModifyVpcAttribute(modifyOpts); err != nil {
   313  			return err
   314  		}
   315  
   316  		d.SetPartial("enable_dns_support")
   317  	}
   318  
   319  	if d.HasChange("enable_classiclink") {
   320  		val := d.Get("enable_classiclink").(bool)
   321  
   322  		if val {
   323  			modifyOpts := &ec2.EnableVpcClassicLinkInput{
   324  				VpcId: &vpcid,
   325  			}
   326  			log.Printf(
   327  				"[INFO] Modifying enable_classiclink vpc attribute for %s: %#v",
   328  				d.Id(), modifyOpts)
   329  			if _, err := conn.EnableVpcClassicLink(modifyOpts); err != nil {
   330  				return err
   331  			}
   332  		} else {
   333  			modifyOpts := &ec2.DisableVpcClassicLinkInput{
   334  				VpcId: &vpcid,
   335  			}
   336  			log.Printf(
   337  				"[INFO] Modifying enable_classiclink vpc attribute for %s: %#v",
   338  				d.Id(), modifyOpts)
   339  			if _, err := conn.DisableVpcClassicLink(modifyOpts); err != nil {
   340  				return err
   341  			}
   342  		}
   343  
   344  		d.SetPartial("enable_classiclink")
   345  	}
   346  
   347  	if err := setTags(conn, d); err != nil {
   348  		return err
   349  	} else {
   350  		d.SetPartial("tags")
   351  	}
   352  
   353  	d.Partial(false)
   354  	return resourceAwsVpcRead(d, meta)
   355  }
   356  
   357  func resourceAwsVpcDelete(d *schema.ResourceData, meta interface{}) error {
   358  	conn := meta.(*AWSClient).ec2conn
   359  	vpcID := d.Id()
   360  	DeleteVpcOpts := &ec2.DeleteVpcInput{
   361  		VpcId: &vpcID,
   362  	}
   363  	log.Printf("[INFO] Deleting VPC: %s", d.Id())
   364  
   365  	return resource.Retry(5*time.Minute, func() *resource.RetryError {
   366  		_, err := conn.DeleteVpc(DeleteVpcOpts)
   367  		if err == nil {
   368  			return nil
   369  		}
   370  
   371  		ec2err, ok := err.(awserr.Error)
   372  		if !ok {
   373  			return resource.NonRetryableError(err)
   374  		}
   375  
   376  		switch ec2err.Code() {
   377  		case "InvalidVpcID.NotFound":
   378  			return nil
   379  		case "DependencyViolation":
   380  			return resource.RetryableError(err)
   381  		}
   382  
   383  		return resource.NonRetryableError(fmt.Errorf("Error deleting VPC: %s", err))
   384  	})
   385  }
   386  
   387  // VPCStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   388  // a VPC.
   389  func VPCStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
   390  	return func() (interface{}, string, error) {
   391  		DescribeVpcOpts := &ec2.DescribeVpcsInput{
   392  			VpcIds: []*string{aws.String(id)},
   393  		}
   394  		resp, err := conn.DescribeVpcs(DescribeVpcOpts)
   395  		if err != nil {
   396  			if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpcID.NotFound" {
   397  				resp = nil
   398  			} else {
   399  				log.Printf("Error on VPCStateRefresh: %s", err)
   400  				return nil, "", err
   401  			}
   402  		}
   403  
   404  		if resp == nil {
   405  			// Sometimes AWS just has consistency issues and doesn't see
   406  			// our instance yet. Return an empty state.
   407  			return nil, "", nil
   408  		}
   409  
   410  		vpc := resp.Vpcs[0]
   411  		return vpc, *vpc.State, nil
   412  	}
   413  }
   414  
   415  func resourceAwsVpcSetDefaultNetworkAcl(conn *ec2.EC2, d *schema.ResourceData) error {
   416  	filter1 := &ec2.Filter{
   417  		Name:   aws.String("default"),
   418  		Values: []*string{aws.String("true")},
   419  	}
   420  	filter2 := &ec2.Filter{
   421  		Name:   aws.String("vpc-id"),
   422  		Values: []*string{aws.String(d.Id())},
   423  	}
   424  	DescribeNetworkACLOpts := &ec2.DescribeNetworkAclsInput{
   425  		Filters: []*ec2.Filter{filter1, filter2},
   426  	}
   427  	networkAclResp, err := conn.DescribeNetworkAcls(DescribeNetworkACLOpts)
   428  
   429  	if err != nil {
   430  		return err
   431  	}
   432  	if v := networkAclResp.NetworkAcls; len(v) > 0 {
   433  		d.Set("default_network_acl_id", v[0].NetworkAclId)
   434  	}
   435  
   436  	return nil
   437  }
   438  
   439  func resourceAwsVpcSetDefaultSecurityGroup(conn *ec2.EC2, d *schema.ResourceData) error {
   440  	filter1 := &ec2.Filter{
   441  		Name:   aws.String("group-name"),
   442  		Values: []*string{aws.String("default")},
   443  	}
   444  	filter2 := &ec2.Filter{
   445  		Name:   aws.String("vpc-id"),
   446  		Values: []*string{aws.String(d.Id())},
   447  	}
   448  	DescribeSgOpts := &ec2.DescribeSecurityGroupsInput{
   449  		Filters: []*ec2.Filter{filter1, filter2},
   450  	}
   451  	securityGroupResp, err := conn.DescribeSecurityGroups(DescribeSgOpts)
   452  
   453  	if err != nil {
   454  		return err
   455  	}
   456  	if v := securityGroupResp.SecurityGroups; len(v) > 0 {
   457  		d.Set("default_security_group_id", v[0].GroupId)
   458  	}
   459  
   460  	return nil
   461  }
   462  
   463  func resourceAwsVpcSetDefaultRouteTable(conn *ec2.EC2, d *schema.ResourceData) error {
   464  	filter1 := &ec2.Filter{
   465  		Name:   aws.String("association.main"),
   466  		Values: []*string{aws.String("true")},
   467  	}
   468  	filter2 := &ec2.Filter{
   469  		Name:   aws.String("vpc-id"),
   470  		Values: []*string{aws.String(d.Id())},
   471  	}
   472  
   473  	findOpts := &ec2.DescribeRouteTablesInput{
   474  		Filters: []*ec2.Filter{filter1, filter2},
   475  	}
   476  
   477  	resp, err := conn.DescribeRouteTables(findOpts)
   478  	if err != nil {
   479  		return err
   480  	}
   481  
   482  	if len(resp.RouteTables) < 1 || resp.RouteTables[0] == nil {
   483  		return fmt.Errorf("Default Route table not found")
   484  	}
   485  
   486  	// There Can Be Only 1 ... Default Route Table
   487  	d.Set("default_route_table_id", resp.RouteTables[0].RouteTableId)
   488  
   489  	return nil
   490  }
   491  
   492  func resourceAwsVpcInstanceImport(
   493  	d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
   494  	d.Set("assign_generated_ipv6_cidr_block", false)
   495  	return []*schema.ResourceData{d}, nil
   496  }