github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/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  				Optional: true,
    64  				Default:  false,
    65  			},
    66  
    67  			"main_route_table_id": {
    68  				Type:     schema.TypeString,
    69  				Computed: true,
    70  			},
    71  
    72  			"default_network_acl_id": {
    73  				Type:     schema.TypeString,
    74  				Computed: true,
    75  			},
    76  
    77  			"dhcp_options_id": {
    78  				Type:     schema.TypeString,
    79  				Computed: true,
    80  			},
    81  
    82  			"default_security_group_id": {
    83  				Type:     schema.TypeString,
    84  				Computed: true,
    85  			},
    86  
    87  			"default_route_table_id": {
    88  				Type:     schema.TypeString,
    89  				Computed: true,
    90  			},
    91  
    92  			"ipv6_association_id": {
    93  				Type:     schema.TypeString,
    94  				Computed: true,
    95  			},
    96  
    97  			"ipv6_cidr_block": {
    98  				Type:     schema.TypeString,
    99  				Computed: true,
   100  			},
   101  
   102  			"tags": tagsSchema(),
   103  		},
   104  	}
   105  }
   106  
   107  func resourceAwsVpcCreate(d *schema.ResourceData, meta interface{}) error {
   108  	conn := meta.(*AWSClient).ec2conn
   109  	instance_tenancy := "default"
   110  	if v, ok := d.GetOk("instance_tenancy"); ok {
   111  		instance_tenancy = v.(string)
   112  	}
   113  
   114  	// Create the VPC
   115  	createOpts := &ec2.CreateVpcInput{
   116  		CidrBlock:                   aws.String(d.Get("cidr_block").(string)),
   117  		InstanceTenancy:             aws.String(instance_tenancy),
   118  		AmazonProvidedIpv6CidrBlock: aws.Bool(d.Get("assign_generated_ipv6_cidr_block").(bool)),
   119  	}
   120  
   121  	log.Printf("[DEBUG] VPC create config: %#v", *createOpts)
   122  	vpcResp, err := conn.CreateVpc(createOpts)
   123  	if err != nil {
   124  		return fmt.Errorf("Error creating VPC: %s", err)
   125  	}
   126  
   127  	// Get the ID and store it
   128  	vpc := vpcResp.Vpc
   129  	d.SetId(*vpc.VpcId)
   130  	log.Printf("[INFO] VPC ID: %s", d.Id())
   131  
   132  	// Set partial mode and say that we setup the cidr block
   133  	d.Partial(true)
   134  	d.SetPartial("cidr_block")
   135  
   136  	// Wait for the VPC to become available
   137  	log.Printf(
   138  		"[DEBUG] Waiting for VPC (%s) to become available",
   139  		d.Id())
   140  	stateConf := &resource.StateChangeConf{
   141  		Pending: []string{"pending"},
   142  		Target:  []string{"available"},
   143  		Refresh: VPCStateRefreshFunc(conn, d.Id()),
   144  		Timeout: 10 * time.Minute,
   145  	}
   146  	if _, err := stateConf.WaitForState(); err != nil {
   147  		return fmt.Errorf(
   148  			"Error waiting for VPC (%s) to become available: %s",
   149  			d.Id(), err)
   150  	}
   151  
   152  	// Update our attributes and return
   153  	return resourceAwsVpcUpdate(d, meta)
   154  }
   155  
   156  func resourceAwsVpcRead(d *schema.ResourceData, meta interface{}) error {
   157  	conn := meta.(*AWSClient).ec2conn
   158  
   159  	// Refresh the VPC state
   160  	vpcRaw, _, err := VPCStateRefreshFunc(conn, d.Id())()
   161  	if err != nil {
   162  		return err
   163  	}
   164  	if vpcRaw == nil {
   165  		d.SetId("")
   166  		return nil
   167  	}
   168  
   169  	// VPC stuff
   170  	vpc := vpcRaw.(*ec2.Vpc)
   171  	vpcid := d.Id()
   172  	d.Set("cidr_block", vpc.CidrBlock)
   173  	d.Set("dhcp_options_id", vpc.DhcpOptionsId)
   174  	d.Set("instance_tenancy", vpc.InstanceTenancy)
   175  
   176  	// Tags
   177  	d.Set("tags", tagsToMap(vpc.Tags))
   178  
   179  	for _, a := range vpc.Ipv6CidrBlockAssociationSet {
   180  		if *a.Ipv6CidrBlockState.State == "associated" { //we can only ever have 1 IPv6 block associated at once
   181  			d.Set("assign_generated_ipv6_cidr_block", true)
   182  			d.Set("ipv6_association_id", a.AssociationId)
   183  			d.Set("ipv6_cidr_block", a.Ipv6CidrBlock)
   184  		} else {
   185  			d.Set("assign_generated_ipv6_cidr_block", false)
   186  			d.Set("ipv6_association_id", "") // we blank these out to remove old entries
   187  			d.Set("ipv6_cidr_block", "")
   188  		}
   189  	}
   190  
   191  	// Attributes
   192  	attribute := "enableDnsSupport"
   193  	DescribeAttrOpts := &ec2.DescribeVpcAttributeInput{
   194  		Attribute: aws.String(attribute),
   195  		VpcId:     aws.String(vpcid),
   196  	}
   197  	resp, err := conn.DescribeVpcAttribute(DescribeAttrOpts)
   198  	if err != nil {
   199  		return err
   200  	}
   201  	d.Set("enable_dns_support", *resp.EnableDnsSupport.Value)
   202  	attribute = "enableDnsHostnames"
   203  	DescribeAttrOpts = &ec2.DescribeVpcAttributeInput{
   204  		Attribute: &attribute,
   205  		VpcId:     &vpcid,
   206  	}
   207  	resp, err = conn.DescribeVpcAttribute(DescribeAttrOpts)
   208  	if err != nil {
   209  		return err
   210  	}
   211  	d.Set("enable_dns_hostnames", *resp.EnableDnsHostnames.Value)
   212  
   213  	DescribeClassiclinkOpts := &ec2.DescribeVpcClassicLinkInput{
   214  		VpcIds: []*string{&vpcid},
   215  	}
   216  
   217  	// Classic Link is only available in regions that support EC2 Classic
   218  	respClassiclink, err := conn.DescribeVpcClassicLink(DescribeClassiclinkOpts)
   219  	if err != nil {
   220  		if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "UnsupportedOperation" {
   221  			log.Printf("[WARN] VPC Classic Link is not supported in this region")
   222  		} else {
   223  			return err
   224  		}
   225  	} else {
   226  		classiclink_enabled := false
   227  		for _, v := range respClassiclink.Vpcs {
   228  			if *v.VpcId == vpcid {
   229  				if v.ClassicLinkEnabled != nil {
   230  					classiclink_enabled = *v.ClassicLinkEnabled
   231  				}
   232  				break
   233  			}
   234  		}
   235  		d.Set("enable_classiclink", classiclink_enabled)
   236  	}
   237  
   238  	// Get the main routing table for this VPC
   239  	// Really Ugly need to make this better - rmenn
   240  	filter1 := &ec2.Filter{
   241  		Name:   aws.String("association.main"),
   242  		Values: []*string{aws.String("true")},
   243  	}
   244  	filter2 := &ec2.Filter{
   245  		Name:   aws.String("vpc-id"),
   246  		Values: []*string{aws.String(d.Id())},
   247  	}
   248  	DescribeRouteOpts := &ec2.DescribeRouteTablesInput{
   249  		Filters: []*ec2.Filter{filter1, filter2},
   250  	}
   251  	routeResp, err := conn.DescribeRouteTables(DescribeRouteOpts)
   252  	if err != nil {
   253  		return err
   254  	}
   255  	if v := routeResp.RouteTables; len(v) > 0 {
   256  		d.Set("main_route_table_id", *v[0].RouteTableId)
   257  	}
   258  
   259  	if err := resourceAwsVpcSetDefaultNetworkAcl(conn, d); err != nil {
   260  		log.Printf("[WARN] Unable to set Default Network ACL: %s", err)
   261  	}
   262  	if err := resourceAwsVpcSetDefaultSecurityGroup(conn, d); err != nil {
   263  		log.Printf("[WARN] Unable to set Default Security Group: %s", err)
   264  	}
   265  	if err := resourceAwsVpcSetDefaultRouteTable(conn, d); err != nil {
   266  		log.Printf("[WARN] Unable to set Default Route Table: %s", err)
   267  	}
   268  
   269  	return nil
   270  }
   271  
   272  func resourceAwsVpcUpdate(d *schema.ResourceData, meta interface{}) error {
   273  	conn := meta.(*AWSClient).ec2conn
   274  
   275  	// Turn on partial mode
   276  	d.Partial(true)
   277  	vpcid := d.Id()
   278  	if d.HasChange("enable_dns_hostnames") {
   279  		val := d.Get("enable_dns_hostnames").(bool)
   280  		modifyOpts := &ec2.ModifyVpcAttributeInput{
   281  			VpcId: &vpcid,
   282  			EnableDnsHostnames: &ec2.AttributeBooleanValue{
   283  				Value: &val,
   284  			},
   285  		}
   286  
   287  		log.Printf(
   288  			"[INFO] Modifying enable_dns_hostnames vpc attribute for %s: %s",
   289  			d.Id(), modifyOpts)
   290  		if _, err := conn.ModifyVpcAttribute(modifyOpts); err != nil {
   291  			return err
   292  		}
   293  
   294  		d.SetPartial("enable_dns_hostnames")
   295  	}
   296  
   297  	_, hasEnableDnsSupportOption := d.GetOk("enable_dns_support")
   298  
   299  	if !hasEnableDnsSupportOption || d.HasChange("enable_dns_support") {
   300  		val := d.Get("enable_dns_support").(bool)
   301  		modifyOpts := &ec2.ModifyVpcAttributeInput{
   302  			VpcId: &vpcid,
   303  			EnableDnsSupport: &ec2.AttributeBooleanValue{
   304  				Value: &val,
   305  			},
   306  		}
   307  
   308  		log.Printf(
   309  			"[INFO] Modifying enable_dns_support vpc attribute for %s: %s",
   310  			d.Id(), modifyOpts)
   311  		if _, err := conn.ModifyVpcAttribute(modifyOpts); err != nil {
   312  			return err
   313  		}
   314  
   315  		d.SetPartial("enable_dns_support")
   316  	}
   317  
   318  	if d.HasChange("enable_classiclink") {
   319  		val := d.Get("enable_classiclink").(bool)
   320  
   321  		if val {
   322  			modifyOpts := &ec2.EnableVpcClassicLinkInput{
   323  				VpcId: &vpcid,
   324  			}
   325  			log.Printf(
   326  				"[INFO] Modifying enable_classiclink vpc attribute for %s: %#v",
   327  				d.Id(), modifyOpts)
   328  			if _, err := conn.EnableVpcClassicLink(modifyOpts); err != nil {
   329  				return err
   330  			}
   331  		} else {
   332  			modifyOpts := &ec2.DisableVpcClassicLinkInput{
   333  				VpcId: &vpcid,
   334  			}
   335  			log.Printf(
   336  				"[INFO] Modifying enable_classiclink vpc attribute for %s: %#v",
   337  				d.Id(), modifyOpts)
   338  			if _, err := conn.DisableVpcClassicLink(modifyOpts); err != nil {
   339  				return err
   340  			}
   341  		}
   342  
   343  		d.SetPartial("enable_classiclink")
   344  	}
   345  
   346  	if d.HasChange("assign_generated_ipv6_cidr_block") && !d.IsNewResource() {
   347  		toAssign := d.Get("assign_generated_ipv6_cidr_block").(bool)
   348  
   349  		log.Printf("[INFO] Modifying assign_generated_ipv6_cidr_block to %#v", toAssign)
   350  
   351  		if toAssign {
   352  			modifyOpts := &ec2.AssociateVpcCidrBlockInput{
   353  				VpcId: &vpcid,
   354  				AmazonProvidedIpv6CidrBlock: aws.Bool(toAssign),
   355  			}
   356  			log.Printf("[INFO] Enabling assign_generated_ipv6_cidr_block vpc attribute for %s: %#v",
   357  				d.Id(), modifyOpts)
   358  			resp, err := conn.AssociateVpcCidrBlock(modifyOpts)
   359  			if err != nil {
   360  				return err
   361  			}
   362  
   363  			// Wait for the CIDR to become available
   364  			log.Printf(
   365  				"[DEBUG] Waiting for IPv6 CIDR (%s) to become associated",
   366  				d.Id())
   367  			stateConf := &resource.StateChangeConf{
   368  				Pending: []string{"associating", "disassociated"},
   369  				Target:  []string{"associated"},
   370  				Refresh: Ipv6CidrStateRefreshFunc(conn, d.Id(), *resp.Ipv6CidrBlockAssociation.AssociationId),
   371  				Timeout: 1 * time.Minute,
   372  			}
   373  			if _, err := stateConf.WaitForState(); err != nil {
   374  				return fmt.Errorf(
   375  					"Error waiting for IPv6 CIDR (%s) to become associated: %s",
   376  					d.Id(), err)
   377  			}
   378  		} else {
   379  			modifyOpts := &ec2.DisassociateVpcCidrBlockInput{
   380  				AssociationId: aws.String(d.Get("ipv6_association_id").(string)),
   381  			}
   382  			log.Printf("[INFO] Disabling assign_generated_ipv6_cidr_block vpc attribute for %s: %#v",
   383  				d.Id(), modifyOpts)
   384  			if _, err := conn.DisassociateVpcCidrBlock(modifyOpts); err != nil {
   385  				return err
   386  			}
   387  
   388  			// Wait for the CIDR to become available
   389  			log.Printf(
   390  				"[DEBUG] Waiting for IPv6 CIDR (%s) to become disassociated",
   391  				d.Id())
   392  			stateConf := &resource.StateChangeConf{
   393  				Pending: []string{"disassociating", "associated"},
   394  				Target:  []string{"disassociated"},
   395  				Refresh: Ipv6CidrStateRefreshFunc(conn, d.Id(), d.Get("ipv6_association_id").(string)),
   396  				Timeout: 1 * time.Minute,
   397  			}
   398  			if _, err := stateConf.WaitForState(); err != nil {
   399  				return fmt.Errorf(
   400  					"Error waiting for IPv6 CIDR (%s) to become disassociated: %s",
   401  					d.Id(), err)
   402  			}
   403  		}
   404  
   405  		d.SetPartial("assign_generated_ipv6_cidr_block")
   406  	}
   407  
   408  	if err := setTags(conn, d); err != nil {
   409  		return err
   410  	} else {
   411  		d.SetPartial("tags")
   412  	}
   413  
   414  	d.Partial(false)
   415  	return resourceAwsVpcRead(d, meta)
   416  }
   417  
   418  func resourceAwsVpcDelete(d *schema.ResourceData, meta interface{}) error {
   419  	conn := meta.(*AWSClient).ec2conn
   420  	vpcID := d.Id()
   421  	DeleteVpcOpts := &ec2.DeleteVpcInput{
   422  		VpcId: &vpcID,
   423  	}
   424  	log.Printf("[INFO] Deleting VPC: %s", d.Id())
   425  
   426  	return resource.Retry(5*time.Minute, func() *resource.RetryError {
   427  		_, err := conn.DeleteVpc(DeleteVpcOpts)
   428  		if err == nil {
   429  			return nil
   430  		}
   431  
   432  		ec2err, ok := err.(awserr.Error)
   433  		if !ok {
   434  			return resource.NonRetryableError(err)
   435  		}
   436  
   437  		switch ec2err.Code() {
   438  		case "InvalidVpcID.NotFound":
   439  			return nil
   440  		case "DependencyViolation":
   441  			return resource.RetryableError(err)
   442  		}
   443  
   444  		return resource.NonRetryableError(fmt.Errorf("Error deleting VPC: %s", err))
   445  	})
   446  }
   447  
   448  // VPCStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   449  // a VPC.
   450  func VPCStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
   451  	return func() (interface{}, string, error) {
   452  		DescribeVpcOpts := &ec2.DescribeVpcsInput{
   453  			VpcIds: []*string{aws.String(id)},
   454  		}
   455  		resp, err := conn.DescribeVpcs(DescribeVpcOpts)
   456  		if err != nil {
   457  			if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpcID.NotFound" {
   458  				resp = nil
   459  			} else {
   460  				log.Printf("Error on VPCStateRefresh: %s", err)
   461  				return nil, "", err
   462  			}
   463  		}
   464  
   465  		if resp == nil {
   466  			// Sometimes AWS just has consistency issues and doesn't see
   467  			// our instance yet. Return an empty state.
   468  			return nil, "", nil
   469  		}
   470  
   471  		vpc := resp.Vpcs[0]
   472  		return vpc, *vpc.State, nil
   473  	}
   474  }
   475  
   476  func Ipv6CidrStateRefreshFunc(conn *ec2.EC2, id string, associationId string) resource.StateRefreshFunc {
   477  	return func() (interface{}, string, error) {
   478  		describeVpcOpts := &ec2.DescribeVpcsInput{
   479  			VpcIds: []*string{aws.String(id)},
   480  		}
   481  		resp, err := conn.DescribeVpcs(describeVpcOpts)
   482  		if err != nil {
   483  			if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpcID.NotFound" {
   484  				resp = nil
   485  			} else {
   486  				log.Printf("Error on VPCStateRefresh: %s", err)
   487  				return nil, "", err
   488  			}
   489  		}
   490  
   491  		if resp == nil {
   492  			// Sometimes AWS just has consistency issues and doesn't see
   493  			// our instance yet. Return an empty state.
   494  			return nil, "", nil
   495  		}
   496  
   497  		if resp.Vpcs[0].Ipv6CidrBlockAssociationSet == nil {
   498  			return nil, "", nil
   499  		}
   500  
   501  		for _, association := range resp.Vpcs[0].Ipv6CidrBlockAssociationSet {
   502  			if *association.AssociationId == associationId {
   503  				return association, *association.Ipv6CidrBlockState.State, nil
   504  			}
   505  		}
   506  
   507  		return nil, "", nil
   508  	}
   509  }
   510  
   511  func resourceAwsVpcSetDefaultNetworkAcl(conn *ec2.EC2, d *schema.ResourceData) error {
   512  	filter1 := &ec2.Filter{
   513  		Name:   aws.String("default"),
   514  		Values: []*string{aws.String("true")},
   515  	}
   516  	filter2 := &ec2.Filter{
   517  		Name:   aws.String("vpc-id"),
   518  		Values: []*string{aws.String(d.Id())},
   519  	}
   520  	DescribeNetworkACLOpts := &ec2.DescribeNetworkAclsInput{
   521  		Filters: []*ec2.Filter{filter1, filter2},
   522  	}
   523  	networkAclResp, err := conn.DescribeNetworkAcls(DescribeNetworkACLOpts)
   524  
   525  	if err != nil {
   526  		return err
   527  	}
   528  	if v := networkAclResp.NetworkAcls; len(v) > 0 {
   529  		d.Set("default_network_acl_id", v[0].NetworkAclId)
   530  	}
   531  
   532  	return nil
   533  }
   534  
   535  func resourceAwsVpcSetDefaultSecurityGroup(conn *ec2.EC2, d *schema.ResourceData) error {
   536  	filter1 := &ec2.Filter{
   537  		Name:   aws.String("group-name"),
   538  		Values: []*string{aws.String("default")},
   539  	}
   540  	filter2 := &ec2.Filter{
   541  		Name:   aws.String("vpc-id"),
   542  		Values: []*string{aws.String(d.Id())},
   543  	}
   544  	DescribeSgOpts := &ec2.DescribeSecurityGroupsInput{
   545  		Filters: []*ec2.Filter{filter1, filter2},
   546  	}
   547  	securityGroupResp, err := conn.DescribeSecurityGroups(DescribeSgOpts)
   548  
   549  	if err != nil {
   550  		return err
   551  	}
   552  	if v := securityGroupResp.SecurityGroups; len(v) > 0 {
   553  		d.Set("default_security_group_id", v[0].GroupId)
   554  	}
   555  
   556  	return nil
   557  }
   558  
   559  func resourceAwsVpcSetDefaultRouteTable(conn *ec2.EC2, d *schema.ResourceData) error {
   560  	filter1 := &ec2.Filter{
   561  		Name:   aws.String("association.main"),
   562  		Values: []*string{aws.String("true")},
   563  	}
   564  	filter2 := &ec2.Filter{
   565  		Name:   aws.String("vpc-id"),
   566  		Values: []*string{aws.String(d.Id())},
   567  	}
   568  
   569  	findOpts := &ec2.DescribeRouteTablesInput{
   570  		Filters: []*ec2.Filter{filter1, filter2},
   571  	}
   572  
   573  	resp, err := conn.DescribeRouteTables(findOpts)
   574  	if err != nil {
   575  		return err
   576  	}
   577  
   578  	if len(resp.RouteTables) < 1 || resp.RouteTables[0] == nil {
   579  		return fmt.Errorf("Default Route table not found")
   580  	}
   581  
   582  	// There Can Be Only 1 ... Default Route Table
   583  	d.Set("default_route_table_id", resp.RouteTables[0].RouteTableId)
   584  
   585  	return nil
   586  }
   587  
   588  func resourceAwsVpcInstanceImport(
   589  	d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
   590  	d.Set("assign_generated_ipv6_cidr_block", false)
   591  	return []*schema.ResourceData{d}, nil
   592  }