github.com/ricardclau/terraform@v0.6.17-0.20160519222547-283e3ae6b5a9/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: %#v (domain: %v)", assocOpts, domain) 212 _, err := ec2conn.AssociateAddress(assocOpts) 213 if err != nil { 214 // Prevent saving instance if association failed 215 // e.g. missing internet gateway in VPC 216 d.Set("instance", "") 217 d.Set("network_interface", "") 218 return fmt.Errorf("Failure associating EIP: %s", err) 219 } 220 } 221 222 return resourceAwsEipRead(d, meta) 223 } 224 225 func resourceAwsEipDelete(d *schema.ResourceData, meta interface{}) error { 226 ec2conn := meta.(*AWSClient).ec2conn 227 228 if err := resourceAwsEipRead(d, meta); err != nil { 229 return err 230 } 231 if d.Id() == "" { 232 // This might happen from the read 233 return nil 234 } 235 236 // If we are attached to an instance or interface, detach first. 237 if d.Get("instance").(string) != "" || d.Get("association_id").(string) != "" { 238 log.Printf("[DEBUG] Disassociating EIP: %s", d.Id()) 239 var err error 240 switch resourceAwsEipDomain(d) { 241 case "vpc": 242 _, err = ec2conn.DisassociateAddress(&ec2.DisassociateAddressInput{ 243 AssociationId: aws.String(d.Get("association_id").(string)), 244 }) 245 case "standard": 246 _, err = ec2conn.DisassociateAddress(&ec2.DisassociateAddressInput{ 247 PublicIp: aws.String(d.Get("public_ip").(string)), 248 }) 249 } 250 251 if err != nil { 252 // First check if the association ID is not found. If this 253 // is the case, then it was already disassociated somehow, 254 // and that is okay. The most commmon reason for this is that 255 // the instance or ENI it was attached it was destroyed. 256 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidAssociationID.NotFound" { 257 err = nil 258 } 259 } 260 261 if err != nil { 262 return err 263 } 264 } 265 266 domain := resourceAwsEipDomain(d) 267 return resource.Retry(3*time.Minute, func() *resource.RetryError { 268 var err error 269 switch domain { 270 case "vpc": 271 log.Printf( 272 "[DEBUG] EIP release (destroy) address allocation: %v", 273 d.Id()) 274 _, err = ec2conn.ReleaseAddress(&ec2.ReleaseAddressInput{ 275 AllocationId: aws.String(d.Id()), 276 }) 277 case "standard": 278 log.Printf("[DEBUG] EIP release (destroy) address: %v", d.Id()) 279 _, err = ec2conn.ReleaseAddress(&ec2.ReleaseAddressInput{ 280 PublicIp: aws.String(d.Id()), 281 }) 282 } 283 284 if err == nil { 285 return nil 286 } 287 if _, ok := err.(awserr.Error); !ok { 288 return resource.NonRetryableError(err) 289 } 290 291 return resource.RetryableError(err) 292 }) 293 } 294 295 func resourceAwsEipDomain(d *schema.ResourceData) string { 296 if v, ok := d.GetOk("domain"); ok { 297 return v.(string) 298 } else if strings.Contains(d.Id(), "eipalloc") { 299 // We have to do this for backwards compatibility since TF 0.1 300 // didn't have the "domain" computed attribute. 301 return "vpc" 302 } 303 304 return "standard" 305 }