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