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 }