github.com/paulmey/terraform@v0.5.2-0.20150519145237-046e9b4c884d/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/awslabs/aws-sdk-go/aws"
    10  	"github.com/awslabs/aws-sdk-go/service/ec2"
    11  	"github.com/hashicorp/terraform/helper/resource"
    12  	"github.com/hashicorp/terraform/helper/schema"
    13  )
    14  
    15  func resourceAwsEip() *schema.Resource {
    16  	return &schema.Resource{
    17  		Create: resourceAwsEipCreate,
    18  		Read:   resourceAwsEipRead,
    19  		Update: resourceAwsEipUpdate,
    20  		Delete: resourceAwsEipDelete,
    21  
    22  		Schema: map[string]*schema.Schema{
    23  			"vpc": &schema.Schema{
    24  				Type:     schema.TypeBool,
    25  				Optional: true,
    26  				ForceNew: true,
    27  			},
    28  
    29  			"instance": &schema.Schema{
    30  				Type:     schema.TypeString,
    31  				Optional: true,
    32  			},
    33  
    34  			"network_interface": &schema.Schema{
    35  				Type:          schema.TypeString,
    36  				Optional:      true,
    37  				Computed:      true,
    38  				ConflictsWith: []string{"instance"},
    39  			},
    40  
    41  			"allocation_id": &schema.Schema{
    42  				Type:     schema.TypeString,
    43  				Computed: true,
    44  			},
    45  
    46  			"association_id": &schema.Schema{
    47  				Type:     schema.TypeString,
    48  				Computed: true,
    49  			},
    50  
    51  			"domain": &schema.Schema{
    52  				Type:     schema.TypeString,
    53  				Computed: true,
    54  			},
    55  
    56  			"public_ip": &schema.Schema{
    57  				Type:     schema.TypeString,
    58  				Computed: true,
    59  			},
    60  
    61  			"private_ip": &schema.Schema{
    62  				Type:     schema.TypeString,
    63  				Computed: true,
    64  			},
    65  		},
    66  	}
    67  }
    68  
    69  func resourceAwsEipCreate(d *schema.ResourceData, meta interface{}) error {
    70  	ec2conn := meta.(*AWSClient).ec2conn
    71  
    72  	// By default, we're not in a VPC
    73  	domainOpt := ""
    74  	if v := d.Get("vpc"); v != nil && v.(bool) {
    75  		domainOpt = "vpc"
    76  	}
    77  
    78  	allocOpts := &ec2.AllocateAddressInput{
    79  		Domain: aws.String(domainOpt),
    80  	}
    81  
    82  	log.Printf("[DEBUG] EIP create configuration: %#v", allocOpts)
    83  	allocResp, err := ec2conn.AllocateAddress(allocOpts)
    84  	if err != nil {
    85  		return fmt.Errorf("Error creating EIP: %s", err)
    86  	}
    87  
    88  	// The domain tells us if we're in a VPC or not
    89  	d.Set("domain", allocResp.Domain)
    90  
    91  	// Assign the eips (unique) allocation id for use later
    92  	// the EIP api has a conditional unique ID (really), so
    93  	// if we're in a VPC we need to save the ID as such, otherwise
    94  	// it defaults to using the public IP
    95  	log.Printf("[DEBUG] EIP Allocate: %#v", allocResp)
    96  	if d.Get("domain").(string) == "vpc" {
    97  		d.SetId(*allocResp.AllocationID)
    98  	} else {
    99  		d.SetId(*allocResp.PublicIP)
   100  	}
   101  
   102  	log.Printf("[INFO] EIP ID: %s (domain: %v)", d.Id(), *allocResp.Domain)
   103  	return resourceAwsEipUpdate(d, meta)
   104  }
   105  
   106  func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error {
   107  	ec2conn := meta.(*AWSClient).ec2conn
   108  
   109  	domain := resourceAwsEipDomain(d)
   110  	id := d.Id()
   111  
   112  	req := &ec2.DescribeAddressesInput{}
   113  
   114  	if domain == "vpc" {
   115  		req.AllocationIDs = []*string{aws.String(id)}
   116  	} else {
   117  		req.PublicIPs = []*string{aws.String(id)}
   118  	}
   119  
   120  	log.Printf(
   121  		"[DEBUG] EIP describe configuration: %#v (domain: %s)",
   122  		req, domain)
   123  
   124  	describeAddresses, err := ec2conn.DescribeAddresses(req)
   125  	if err != nil {
   126  		if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidAllocationID.NotFound" {
   127  			d.SetId("")
   128  			return nil
   129  		}
   130  
   131  		return fmt.Errorf("Error retrieving EIP: %s", err)
   132  	}
   133  
   134  	// Verify AWS returned our EIP
   135  	if len(describeAddresses.Addresses) != 1 ||
   136  		(domain == "vpc" && *describeAddresses.Addresses[0].AllocationID != id) ||
   137  		*describeAddresses.Addresses[0].PublicIP != id {
   138  		if err != nil {
   139  			return fmt.Errorf("Unable to find EIP: %#v", describeAddresses.Addresses)
   140  		}
   141  	}
   142  
   143  	address := describeAddresses.Addresses[0]
   144  
   145  	d.Set("association_id", address.AssociationID)
   146  	if address.InstanceID != nil {
   147  		d.Set("instance", address.InstanceID)
   148  	}
   149  	if address.NetworkInterfaceID != nil {
   150  		d.Set("network_interface", address.NetworkInterfaceID)
   151  	}
   152  	d.Set("private_ip", address.PrivateIPAddress)
   153  	d.Set("public_ip", address.PublicIP)
   154  
   155  	return nil
   156  }
   157  
   158  func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error {
   159  	ec2conn := meta.(*AWSClient).ec2conn
   160  
   161  	domain := resourceAwsEipDomain(d)
   162  
   163  	// Associate to instance or interface if specified
   164  	v_instance, ok_instance := d.GetOk("instance")
   165  	v_interface, ok_interface := d.GetOk("network_interface")
   166  
   167  	if ok_instance || ok_interface {
   168  		instanceId := v_instance.(string)
   169  		networkInterfaceId := v_interface.(string)
   170  
   171  		assocOpts := &ec2.AssociateAddressInput{
   172  			InstanceID: aws.String(instanceId),
   173  			PublicIP:   aws.String(d.Id()),
   174  		}
   175  
   176  		// more unique ID conditionals
   177  		if domain == "vpc" {
   178  			assocOpts = &ec2.AssociateAddressInput{
   179  				NetworkInterfaceID: aws.String(networkInterfaceId),
   180  				InstanceID:         aws.String(instanceId),
   181  				AllocationID:       aws.String(d.Id()),
   182  			}
   183  		}
   184  
   185  		log.Printf("[DEBUG] EIP associate configuration: %#v (domain: %v)", assocOpts, domain)
   186  		_, err := ec2conn.AssociateAddress(assocOpts)
   187  		if err != nil {
   188  			// Prevent saving instance if association failed
   189  			// e.g. missing internet gateway in VPC
   190  			d.Set("instance", "")
   191  			d.Set("network_interface", "")
   192  			return fmt.Errorf("Failure associating EIP: %s", err)
   193  		}
   194  	}
   195  
   196  	return resourceAwsEipRead(d, meta)
   197  }
   198  
   199  func resourceAwsEipDelete(d *schema.ResourceData, meta interface{}) error {
   200  	ec2conn := meta.(*AWSClient).ec2conn
   201  
   202  	if err := resourceAwsEipRead(d, meta); err != nil {
   203  		return err
   204  	}
   205  	if d.Id() == "" {
   206  		// This might happen from the read
   207  		return nil
   208  	}
   209  
   210  	// If we are attached to an instance or interface, detach first.
   211  	if d.Get("instance").(string) != "" || d.Get("association_id").(string) != "" {
   212  		log.Printf("[DEBUG] Disassociating EIP: %s", d.Id())
   213  		var err error
   214  		switch resourceAwsEipDomain(d) {
   215  		case "vpc":
   216  			_, err = ec2conn.DisassociateAddress(&ec2.DisassociateAddressInput{
   217  				AssociationID: aws.String(d.Get("association_id").(string)),
   218  			})
   219  		case "standard":
   220  			_, err = ec2conn.DisassociateAddress(&ec2.DisassociateAddressInput{
   221  				PublicIP: aws.String(d.Get("public_ip").(string)),
   222  			})
   223  		}
   224  		if err != nil {
   225  			return err
   226  		}
   227  	}
   228  
   229  	domain := resourceAwsEipDomain(d)
   230  	return resource.Retry(3*time.Minute, func() error {
   231  		var err error
   232  		switch domain {
   233  		case "vpc":
   234  			log.Printf(
   235  				"[DEBUG] EIP release (destroy) address allocation: %v",
   236  				d.Id())
   237  			_, err = ec2conn.ReleaseAddress(&ec2.ReleaseAddressInput{
   238  				AllocationID: aws.String(d.Id()),
   239  			})
   240  		case "standard":
   241  			log.Printf("[DEBUG] EIP release (destroy) address: %v", d.Id())
   242  			_, err = ec2conn.ReleaseAddress(&ec2.ReleaseAddressInput{
   243  				PublicIP: aws.String(d.Id()),
   244  			})
   245  		}
   246  
   247  		if err == nil {
   248  			return nil
   249  		}
   250  		if _, ok := err.(aws.APIError); !ok {
   251  			return resource.RetryError{Err: err}
   252  		}
   253  
   254  		return err
   255  	})
   256  }
   257  
   258  func resourceAwsEipDomain(d *schema.ResourceData) string {
   259  	if v, ok := d.GetOk("domain"); ok {
   260  		return v.(string)
   261  	} else if strings.Contains(d.Id(), "eipalloc") {
   262  		// We have to do this for backwards compatibility since TF 0.1
   263  		// didn't have the "domain" computed attribute.
   264  		return "vpc"
   265  	}
   266  
   267  	return "standard"
   268  }