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