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