github.com/mohanarpit/terraform@v0.6.16-0.20160909104007-291f29853544/builtin/providers/aws/resource_aws_eip.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/aws/aws-sdk-go/aws"
    10  	"github.com/aws/aws-sdk-go/aws/awserr"
    11  	"github.com/aws/aws-sdk-go/service/ec2"
    12  	"github.com/hashicorp/terraform/helper/resource"
    13  	"github.com/hashicorp/terraform/helper/schema"
    14  )
    15  
    16  func resourceAwsEip() *schema.Resource {
    17  	return &schema.Resource{
    18  		Create: resourceAwsEipCreate,
    19  		Read:   resourceAwsEipRead,
    20  		Update: resourceAwsEipUpdate,
    21  		Delete: resourceAwsEipDelete,
    22  		Importer: &schema.ResourceImporter{
    23  			State: schema.ImportStatePassthrough,
    24  		},
    25  
    26  		Schema: map[string]*schema.Schema{
    27  			"vpc": &schema.Schema{
    28  				Type:     schema.TypeBool,
    29  				Optional: true,
    30  				ForceNew: true,
    31  			},
    32  
    33  			"instance": &schema.Schema{
    34  				Type:     schema.TypeString,
    35  				Optional: true,
    36  				Computed: true,
    37  			},
    38  
    39  			"network_interface": &schema.Schema{
    40  				Type:     schema.TypeString,
    41  				Optional: true,
    42  				Computed: true,
    43  			},
    44  
    45  			"allocation_id": &schema.Schema{
    46  				Type:     schema.TypeString,
    47  				Computed: true,
    48  			},
    49  
    50  			"association_id": &schema.Schema{
    51  				Type:     schema.TypeString,
    52  				Computed: true,
    53  			},
    54  
    55  			"domain": &schema.Schema{
    56  				Type:     schema.TypeString,
    57  				Computed: true,
    58  			},
    59  
    60  			"public_ip": &schema.Schema{
    61  				Type:     schema.TypeString,
    62  				Computed: true,
    63  			},
    64  
    65  			"private_ip": &schema.Schema{
    66  				Type:     schema.TypeString,
    67  				Computed: true,
    68  			},
    69  
    70  			"associate_with_private_ip": &schema.Schema{
    71  				Type:     schema.TypeString,
    72  				Optional: true,
    73  			},
    74  		},
    75  	}
    76  }
    77  
    78  func resourceAwsEipCreate(d *schema.ResourceData, meta interface{}) error {
    79  	ec2conn := meta.(*AWSClient).ec2conn
    80  
    81  	// By default, we're not in a VPC
    82  	domainOpt := ""
    83  	if v := d.Get("vpc"); v != nil && v.(bool) {
    84  		domainOpt = "vpc"
    85  	}
    86  
    87  	allocOpts := &ec2.AllocateAddressInput{
    88  		Domain: aws.String(domainOpt),
    89  	}
    90  
    91  	log.Printf("[DEBUG] EIP create configuration: %#v", allocOpts)
    92  	allocResp, err := ec2conn.AllocateAddress(allocOpts)
    93  	if err != nil {
    94  		return fmt.Errorf("Error creating EIP: %s", err)
    95  	}
    96  
    97  	// The domain tells us if we're in a VPC or not
    98  	d.Set("domain", allocResp.Domain)
    99  
   100  	// Assign the eips (unique) allocation id for use later
   101  	// the EIP api has a conditional unique ID (really), so
   102  	// if we're in a VPC we need to save the ID as such, otherwise
   103  	// it defaults to using the public IP
   104  	log.Printf("[DEBUG] EIP Allocate: %#v", allocResp)
   105  	if d.Get("domain").(string) == "vpc" {
   106  		d.SetId(*allocResp.AllocationId)
   107  	} else {
   108  		d.SetId(*allocResp.PublicIp)
   109  	}
   110  
   111  	log.Printf("[INFO] EIP ID: %s (domain: %v)", d.Id(), *allocResp.Domain)
   112  	return resourceAwsEipUpdate(d, meta)
   113  }
   114  
   115  func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error {
   116  	ec2conn := meta.(*AWSClient).ec2conn
   117  
   118  	domain := resourceAwsEipDomain(d)
   119  	id := d.Id()
   120  
   121  	req := &ec2.DescribeAddressesInput{}
   122  
   123  	if domain == "vpc" {
   124  		req.AllocationIds = []*string{aws.String(id)}
   125  	} else {
   126  		req.PublicIps = []*string{aws.String(id)}
   127  	}
   128  
   129  	log.Printf(
   130  		"[DEBUG] EIP describe configuration: %#v (domain: %s)",
   131  		req, domain)
   132  
   133  	describeAddresses, err := ec2conn.DescribeAddresses(req)
   134  	if err != nil {
   135  		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidAllocationID.NotFound" {
   136  			d.SetId("")
   137  			return nil
   138  		}
   139  
   140  		return fmt.Errorf("Error retrieving EIP: %s", err)
   141  	}
   142  
   143  	// Verify AWS returned our EIP
   144  	if len(describeAddresses.Addresses) != 1 ||
   145  		domain == "vpc" && *describeAddresses.Addresses[0].AllocationId != id ||
   146  		*describeAddresses.Addresses[0].PublicIp != id {
   147  		if err != nil {
   148  			return fmt.Errorf("Unable to find EIP: %#v", describeAddresses.Addresses)
   149  		}
   150  	}
   151  
   152  	address := describeAddresses.Addresses[0]
   153  
   154  	d.Set("association_id", address.AssociationId)
   155  	if address.InstanceId != nil {
   156  		d.Set("instance", address.InstanceId)
   157  	} else {
   158  		d.Set("instance", "")
   159  	}
   160  	if address.NetworkInterfaceId != nil {
   161  		d.Set("network_interface", address.NetworkInterfaceId)
   162  	} else {
   163  		d.Set("network_interface", "")
   164  	}
   165  	d.Set("private_ip", address.PrivateIpAddress)
   166  	d.Set("public_ip", address.PublicIp)
   167  
   168  	// On import (domain never set, which it must've been if we created),
   169  	// set the 'vpc' attribute depending on if we're in a VPC.
   170  	if _, ok := d.GetOk("domain"); !ok {
   171  		d.Set("vpc", *address.Domain == "vpc")
   172  	}
   173  
   174  	d.Set("domain", address.Domain)
   175  
   176  	return nil
   177  }
   178  
   179  func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error {
   180  	ec2conn := meta.(*AWSClient).ec2conn
   181  
   182  	domain := resourceAwsEipDomain(d)
   183  
   184  	// Associate to instance or interface if specified
   185  	v_instance, ok_instance := d.GetOk("instance")
   186  	v_interface, ok_interface := d.GetOk("network_interface")
   187  
   188  	if ok_instance || ok_interface {
   189  		instanceId := v_instance.(string)
   190  		networkInterfaceId := v_interface.(string)
   191  
   192  		assocOpts := &ec2.AssociateAddressInput{
   193  			InstanceId: aws.String(instanceId),
   194  			PublicIp:   aws.String(d.Id()),
   195  		}
   196  
   197  		// more unique ID conditionals
   198  		if domain == "vpc" {
   199  			var privateIpAddress *string
   200  			if v := d.Get("associate_with_private_ip").(string); v != "" {
   201  				privateIpAddress = aws.String(v)
   202  			}
   203  			assocOpts = &ec2.AssociateAddressInput{
   204  				NetworkInterfaceId: aws.String(networkInterfaceId),
   205  				InstanceId:         aws.String(instanceId),
   206  				AllocationId:       aws.String(d.Id()),
   207  				PrivateIpAddress:   privateIpAddress,
   208  			}
   209  		}
   210  
   211  		log.Printf("[DEBUG] EIP associate configuration: %s (domain: %s)", assocOpts, domain)
   212  
   213  		err := resource.Retry(1*time.Minute, func() *resource.RetryError {
   214  			_, err := ec2conn.AssociateAddress(assocOpts)
   215  			if err != nil {
   216  				if awsErr, ok := err.(awserr.Error); ok {
   217  					if awsErr.Code() == "InvalidAllocationID.NotFound" {
   218  						return resource.RetryableError(awsErr)
   219  					}
   220  				}
   221  				return resource.NonRetryableError(err)
   222  			}
   223  			return nil
   224  		})
   225  		if err != nil {
   226  			// Prevent saving instance if association failed
   227  			// e.g. missing internet gateway in VPC
   228  			d.Set("instance", "")
   229  			d.Set("network_interface", "")
   230  			return fmt.Errorf("Failure associating EIP: %s", err)
   231  		}
   232  	}
   233  
   234  	return resourceAwsEipRead(d, meta)
   235  }
   236  
   237  func resourceAwsEipDelete(d *schema.ResourceData, meta interface{}) error {
   238  	ec2conn := meta.(*AWSClient).ec2conn
   239  
   240  	if err := resourceAwsEipRead(d, meta); err != nil {
   241  		return err
   242  	}
   243  	if d.Id() == "" {
   244  		// This might happen from the read
   245  		return nil
   246  	}
   247  
   248  	// If we are attached to an instance or interface, detach first.
   249  	if d.Get("instance").(string) != "" || d.Get("association_id").(string) != "" {
   250  		log.Printf("[DEBUG] Disassociating EIP: %s", d.Id())
   251  		var err error
   252  		switch resourceAwsEipDomain(d) {
   253  		case "vpc":
   254  			_, err = ec2conn.DisassociateAddress(&ec2.DisassociateAddressInput{
   255  				AssociationId: aws.String(d.Get("association_id").(string)),
   256  			})
   257  		case "standard":
   258  			_, err = ec2conn.DisassociateAddress(&ec2.DisassociateAddressInput{
   259  				PublicIp: aws.String(d.Get("public_ip").(string)),
   260  			})
   261  		}
   262  
   263  		if err != nil {
   264  			// First check if the association ID is not found. If this
   265  			// is the case, then it was already disassociated somehow,
   266  			// and that is okay. The most commmon reason for this is that
   267  			// the instance or ENI it was attached it was destroyed.
   268  			if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidAssociationID.NotFound" {
   269  				err = nil
   270  			}
   271  		}
   272  
   273  		if err != nil {
   274  			return err
   275  		}
   276  	}
   277  
   278  	domain := resourceAwsEipDomain(d)
   279  	return resource.Retry(3*time.Minute, func() *resource.RetryError {
   280  		var err error
   281  		switch domain {
   282  		case "vpc":
   283  			log.Printf(
   284  				"[DEBUG] EIP release (destroy) address allocation: %v",
   285  				d.Id())
   286  			_, err = ec2conn.ReleaseAddress(&ec2.ReleaseAddressInput{
   287  				AllocationId: aws.String(d.Id()),
   288  			})
   289  		case "standard":
   290  			log.Printf("[DEBUG] EIP release (destroy) address: %v", d.Id())
   291  			_, err = ec2conn.ReleaseAddress(&ec2.ReleaseAddressInput{
   292  				PublicIp: aws.String(d.Id()),
   293  			})
   294  		}
   295  
   296  		if err == nil {
   297  			return nil
   298  		}
   299  		if _, ok := err.(awserr.Error); !ok {
   300  			return resource.NonRetryableError(err)
   301  		}
   302  
   303  		return resource.RetryableError(err)
   304  	})
   305  }
   306  
   307  func resourceAwsEipDomain(d *schema.ResourceData) string {
   308  	if v, ok := d.GetOk("domain"); ok {
   309  		return v.(string)
   310  	} else if strings.Contains(d.Id(), "eipalloc") {
   311  		// We have to do this for backwards compatibility since TF 0.1
   312  		// didn't have the "domain" computed attribute.
   313  		return "vpc"
   314  	}
   315  
   316  	return "standard"
   317  }