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