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