github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/aws/resource_aws_subnet.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 resourceAwsSubnet() *schema.Resource {
    16  	return &schema.Resource{
    17  		Create: resourceAwsSubnetCreate,
    18  		Read:   resourceAwsSubnetRead,
    19  		Update: resourceAwsSubnetUpdate,
    20  		Delete: resourceAwsSubnetDelete,
    21  		Importer: &schema.ResourceImporter{
    22  			State: schema.ImportStatePassthrough,
    23  		},
    24  
    25  		SchemaVersion: 1,
    26  		MigrateState:  resourceAwsSubnetMigrateState,
    27  
    28  		Schema: map[string]*schema.Schema{
    29  			"vpc_id": {
    30  				Type:     schema.TypeString,
    31  				Required: true,
    32  				ForceNew: true,
    33  			},
    34  
    35  			"cidr_block": {
    36  				Type:     schema.TypeString,
    37  				Required: true,
    38  				ForceNew: true,
    39  			},
    40  
    41  			"ipv6_cidr_block": {
    42  				Type:     schema.TypeString,
    43  				Optional: true,
    44  			},
    45  
    46  			"availability_zone": {
    47  				Type:     schema.TypeString,
    48  				Optional: true,
    49  				Computed: true,
    50  				ForceNew: true,
    51  			},
    52  
    53  			"map_public_ip_on_launch": {
    54  				Type:     schema.TypeBool,
    55  				Optional: true,
    56  				Default:  false,
    57  			},
    58  
    59  			"assign_ipv6_address_on_creation": {
    60  				Type:     schema.TypeBool,
    61  				Optional: true,
    62  				Default:  false,
    63  			},
    64  
    65  			"ipv6_cidr_block_association_id": {
    66  				Type:     schema.TypeString,
    67  				Computed: true,
    68  			},
    69  
    70  			"tags": tagsSchema(),
    71  		},
    72  	}
    73  }
    74  
    75  func resourceAwsSubnetCreate(d *schema.ResourceData, meta interface{}) error {
    76  	conn := meta.(*AWSClient).ec2conn
    77  
    78  	createOpts := &ec2.CreateSubnetInput{
    79  		AvailabilityZone: aws.String(d.Get("availability_zone").(string)),
    80  		CidrBlock:        aws.String(d.Get("cidr_block").(string)),
    81  		VpcId:            aws.String(d.Get("vpc_id").(string)),
    82  	}
    83  
    84  	if v, ok := d.GetOk("ipv6_cidr_block"); ok {
    85  		createOpts.Ipv6CidrBlock = aws.String(v.(string))
    86  	}
    87  
    88  	var err error
    89  	resp, err := conn.CreateSubnet(createOpts)
    90  
    91  	if err != nil {
    92  		return fmt.Errorf("Error creating subnet: %s", err)
    93  	}
    94  
    95  	// Get the ID and store it
    96  	subnet := resp.Subnet
    97  	d.SetId(*subnet.SubnetId)
    98  	log.Printf("[INFO] Subnet ID: %s", *subnet.SubnetId)
    99  
   100  	// Wait for the Subnet to become available
   101  	log.Printf("[DEBUG] Waiting for subnet (%s) to become available", *subnet.SubnetId)
   102  	stateConf := &resource.StateChangeConf{
   103  		Pending: []string{"pending"},
   104  		Target:  []string{"available"},
   105  		Refresh: SubnetStateRefreshFunc(conn, *subnet.SubnetId),
   106  		Timeout: 10 * time.Minute,
   107  	}
   108  
   109  	_, err = stateConf.WaitForState()
   110  
   111  	if err != nil {
   112  		return fmt.Errorf(
   113  			"Error waiting for subnet (%s) to become ready: %s",
   114  			d.Id(), err)
   115  	}
   116  
   117  	return resourceAwsSubnetUpdate(d, meta)
   118  }
   119  
   120  func resourceAwsSubnetRead(d *schema.ResourceData, meta interface{}) error {
   121  	conn := meta.(*AWSClient).ec2conn
   122  
   123  	resp, err := conn.DescribeSubnets(&ec2.DescribeSubnetsInput{
   124  		SubnetIds: []*string{aws.String(d.Id())},
   125  	})
   126  
   127  	if err != nil {
   128  		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSubnetID.NotFound" {
   129  			// Update state to indicate the subnet no longer exists.
   130  			d.SetId("")
   131  			return nil
   132  		}
   133  		return err
   134  	}
   135  	if resp == nil {
   136  		return nil
   137  	}
   138  
   139  	subnet := resp.Subnets[0]
   140  
   141  	d.Set("vpc_id", subnet.VpcId)
   142  	d.Set("availability_zone", subnet.AvailabilityZone)
   143  	d.Set("cidr_block", subnet.CidrBlock)
   144  	d.Set("map_public_ip_on_launch", subnet.MapPublicIpOnLaunch)
   145  	d.Set("assign_ipv6_address_on_creation", subnet.AssignIpv6AddressOnCreation)
   146  	for _, a := range subnet.Ipv6CidrBlockAssociationSet {
   147  		if *a.Ipv6CidrBlockState.State == "associated" { //we can only ever have 1 IPv6 block associated at once
   148  			d.Set("ipv6_cidr_block_association_id", a.AssociationId)
   149  			d.Set("ipv6_cidr_block", a.Ipv6CidrBlock)
   150  			break
   151  		} else {
   152  			d.Set("ipv6_cidr_block_association_id", "") // we blank these out to remove old entries
   153  			d.Set("ipv6_cidr_block", "")
   154  		}
   155  	}
   156  	d.Set("tags", tagsToMap(subnet.Tags))
   157  
   158  	return nil
   159  }
   160  
   161  func resourceAwsSubnetUpdate(d *schema.ResourceData, meta interface{}) error {
   162  	conn := meta.(*AWSClient).ec2conn
   163  
   164  	d.Partial(true)
   165  
   166  	if err := setTags(conn, d); err != nil {
   167  		return err
   168  	} else {
   169  		d.SetPartial("tags")
   170  	}
   171  
   172  	if d.HasChange("assign_ipv6_address_on_creation") {
   173  		modifyOpts := &ec2.ModifySubnetAttributeInput{
   174  			SubnetId: aws.String(d.Id()),
   175  			AssignIpv6AddressOnCreation: &ec2.AttributeBooleanValue{
   176  				Value: aws.Bool(d.Get("assign_ipv6_address_on_creation").(bool)),
   177  			},
   178  		}
   179  
   180  		log.Printf("[DEBUG] Subnet modify attributes: %#v", modifyOpts)
   181  
   182  		_, err := conn.ModifySubnetAttribute(modifyOpts)
   183  
   184  		if err != nil {
   185  			return err
   186  		} else {
   187  			d.SetPartial("assign_ipv6_address_on_creation")
   188  		}
   189  	}
   190  
   191  	if d.HasChange("map_public_ip_on_launch") {
   192  		modifyOpts := &ec2.ModifySubnetAttributeInput{
   193  			SubnetId: aws.String(d.Id()),
   194  			MapPublicIpOnLaunch: &ec2.AttributeBooleanValue{
   195  				Value: aws.Bool(d.Get("map_public_ip_on_launch").(bool)),
   196  			},
   197  		}
   198  
   199  		log.Printf("[DEBUG] Subnet modify attributes: %#v", modifyOpts)
   200  
   201  		_, err := conn.ModifySubnetAttribute(modifyOpts)
   202  
   203  		if err != nil {
   204  			return err
   205  		} else {
   206  			d.SetPartial("map_public_ip_on_launch")
   207  		}
   208  	}
   209  
   210  	// We have to be careful here to not go through a change of association if this is a new resource
   211  	// A New resource here would denote that the Update func is called by the Create func
   212  	if d.HasChange("ipv6_cidr_block") && !d.IsNewResource() {
   213  		// We need to handle that we disassociate the IPv6 CIDR block before we try and associate the new one
   214  		// This could be an issue as, we could error out when we try and add the new one
   215  		// We may need to roll back the state and reattach the old one if this is the case
   216  
   217  		_, new := d.GetChange("ipv6_cidr_block")
   218  
   219  		//Firstly we have to disassociate the old IPv6 CIDR Block
   220  		disassociateOps := &ec2.DisassociateSubnetCidrBlockInput{
   221  			AssociationId: aws.String(d.Get("ipv6_cidr_block_association_id").(string)),
   222  		}
   223  
   224  		_, err := conn.DisassociateSubnetCidrBlock(disassociateOps)
   225  		if err != nil {
   226  			return err
   227  		}
   228  
   229  		// Wait for the CIDR to become disassociated
   230  		log.Printf(
   231  			"[DEBUG] Waiting for IPv6 CIDR (%s) to become disassociated",
   232  			d.Id())
   233  		stateConf := &resource.StateChangeConf{
   234  			Pending: []string{"disassociating", "associated"},
   235  			Target:  []string{"disassociated"},
   236  			Refresh: SubnetIpv6CidrStateRefreshFunc(conn, d.Id(), d.Get("ipv6_cidr_block_association_id").(string)),
   237  			Timeout: 1 * time.Minute,
   238  		}
   239  		if _, err := stateConf.WaitForState(); err != nil {
   240  			return fmt.Errorf(
   241  				"Error waiting for IPv6 CIDR (%s) to become disassociated: %s",
   242  				d.Id(), err)
   243  		}
   244  
   245  		//Now we need to try and associate the new CIDR block
   246  		associatesOpts := &ec2.AssociateSubnetCidrBlockInput{
   247  			SubnetId:      aws.String(d.Id()),
   248  			Ipv6CidrBlock: aws.String(new.(string)),
   249  		}
   250  
   251  		resp, err := conn.AssociateSubnetCidrBlock(associatesOpts)
   252  		if err != nil {
   253  			//The big question here is, do we want to try and reassociate the old one??
   254  			//If we have a failure here, then we may be in a situation that we have nothing associated
   255  			return err
   256  		}
   257  
   258  		// Wait for the CIDR to become associated
   259  		log.Printf(
   260  			"[DEBUG] Waiting for IPv6 CIDR (%s) to become associated",
   261  			d.Id())
   262  		stateConf = &resource.StateChangeConf{
   263  			Pending: []string{"associating", "disassociated"},
   264  			Target:  []string{"associated"},
   265  			Refresh: SubnetIpv6CidrStateRefreshFunc(conn, d.Id(), *resp.Ipv6CidrBlockAssociation.AssociationId),
   266  			Timeout: 1 * time.Minute,
   267  		}
   268  		if _, err := stateConf.WaitForState(); err != nil {
   269  			return fmt.Errorf(
   270  				"Error waiting for IPv6 CIDR (%s) to become associated: %s",
   271  				d.Id(), err)
   272  		}
   273  
   274  		d.SetPartial("ipv6_cidr_block")
   275  	}
   276  
   277  	d.Partial(false)
   278  
   279  	return resourceAwsSubnetRead(d, meta)
   280  }
   281  
   282  func resourceAwsSubnetDelete(d *schema.ResourceData, meta interface{}) error {
   283  	conn := meta.(*AWSClient).ec2conn
   284  
   285  	log.Printf("[INFO] Deleting subnet: %s", d.Id())
   286  	req := &ec2.DeleteSubnetInput{
   287  		SubnetId: aws.String(d.Id()),
   288  	}
   289  
   290  	wait := resource.StateChangeConf{
   291  		Pending:    []string{"pending"},
   292  		Target:     []string{"destroyed"},
   293  		Timeout:    10 * time.Minute,
   294  		MinTimeout: 1 * time.Second,
   295  		Refresh: func() (interface{}, string, error) {
   296  			_, err := conn.DeleteSubnet(req)
   297  			if err != nil {
   298  				if apiErr, ok := err.(awserr.Error); ok {
   299  					if apiErr.Code() == "DependencyViolation" {
   300  						// There is some pending operation, so just retry
   301  						// in a bit.
   302  						return 42, "pending", nil
   303  					}
   304  
   305  					if apiErr.Code() == "InvalidSubnetID.NotFound" {
   306  						return 42, "destroyed", nil
   307  					}
   308  				}
   309  
   310  				return 42, "failure", err
   311  			}
   312  
   313  			return 42, "destroyed", nil
   314  		},
   315  	}
   316  
   317  	if _, err := wait.WaitForState(); err != nil {
   318  		return fmt.Errorf("Error deleting subnet: %s", err)
   319  	}
   320  
   321  	return nil
   322  }
   323  
   324  // SubnetStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch a Subnet.
   325  func SubnetStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
   326  	return func() (interface{}, string, error) {
   327  		resp, err := conn.DescribeSubnets(&ec2.DescribeSubnetsInput{
   328  			SubnetIds: []*string{aws.String(id)},
   329  		})
   330  		if err != nil {
   331  			if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSubnetID.NotFound" {
   332  				resp = nil
   333  			} else {
   334  				log.Printf("Error on SubnetStateRefresh: %s", err)
   335  				return nil, "", err
   336  			}
   337  		}
   338  
   339  		if resp == nil {
   340  			// Sometimes AWS just has consistency issues and doesn't see
   341  			// our instance yet. Return an empty state.
   342  			return nil, "", nil
   343  		}
   344  
   345  		subnet := resp.Subnets[0]
   346  		return subnet, *subnet.State, nil
   347  	}
   348  }
   349  
   350  func SubnetIpv6CidrStateRefreshFunc(conn *ec2.EC2, id string, associationId string) resource.StateRefreshFunc {
   351  	return func() (interface{}, string, error) {
   352  		opts := &ec2.DescribeSubnetsInput{
   353  			SubnetIds: []*string{aws.String(id)},
   354  		}
   355  		resp, err := conn.DescribeSubnets(opts)
   356  		if err != nil {
   357  			if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSubnetID.NotFound" {
   358  				resp = nil
   359  			} else {
   360  				log.Printf("Error on SubnetIpv6CidrStateRefreshFunc: %s", err)
   361  				return nil, "", err
   362  			}
   363  		}
   364  
   365  		if resp == nil {
   366  			// Sometimes AWS just has consistency issues and doesn't see
   367  			// our instance yet. Return an empty state.
   368  			return nil, "", nil
   369  		}
   370  
   371  		if resp.Subnets[0].Ipv6CidrBlockAssociationSet == nil {
   372  			return nil, "", nil
   373  		}
   374  
   375  		for _, association := range resp.Subnets[0].Ipv6CidrBlockAssociationSet {
   376  			if *association.AssociationId == associationId {
   377  				return association, *association.Ipv6CidrBlockState.State, nil
   378  			}
   379  		}
   380  
   381  		return nil, "", nil
   382  	}
   383  }