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