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