github.com/andresvia/terraform@v0.6.15-0.20160412045437-d51c75946785/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 return nil 162 } 163 164 func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error { 165 ec2conn := meta.(*AWSClient).ec2conn 166 167 domain := resourceAwsEipDomain(d) 168 169 // Associate to instance or interface if specified 170 v_instance, ok_instance := d.GetOk("instance") 171 v_interface, ok_interface := d.GetOk("network_interface") 172 173 if ok_instance || ok_interface { 174 instanceId := v_instance.(string) 175 networkInterfaceId := v_interface.(string) 176 177 assocOpts := &ec2.AssociateAddressInput{ 178 InstanceId: aws.String(instanceId), 179 PublicIp: aws.String(d.Id()), 180 } 181 182 // more unique ID conditionals 183 if domain == "vpc" { 184 var privateIpAddress *string 185 if v := d.Get("private_ip").(string); v != "" { 186 privateIpAddress = aws.String(v) 187 } 188 assocOpts = &ec2.AssociateAddressInput{ 189 NetworkInterfaceId: aws.String(networkInterfaceId), 190 InstanceId: aws.String(instanceId), 191 AllocationId: aws.String(d.Id()), 192 PrivateIpAddress: privateIpAddress, 193 } 194 } 195 196 log.Printf("[DEBUG] EIP associate configuration: %#v (domain: %v)", assocOpts, domain) 197 _, err := ec2conn.AssociateAddress(assocOpts) 198 if err != nil { 199 // Prevent saving instance if association failed 200 // e.g. missing internet gateway in VPC 201 d.Set("instance", "") 202 d.Set("network_interface", "") 203 return fmt.Errorf("Failure associating EIP: %s", err) 204 } 205 } 206 207 return resourceAwsEipRead(d, meta) 208 } 209 210 func resourceAwsEipDelete(d *schema.ResourceData, meta interface{}) error { 211 ec2conn := meta.(*AWSClient).ec2conn 212 213 if err := resourceAwsEipRead(d, meta); err != nil { 214 return err 215 } 216 if d.Id() == "" { 217 // This might happen from the read 218 return nil 219 } 220 221 // If we are attached to an instance or interface, detach first. 222 if d.Get("instance").(string) != "" || d.Get("association_id").(string) != "" { 223 log.Printf("[DEBUG] Disassociating EIP: %s", d.Id()) 224 var err error 225 switch resourceAwsEipDomain(d) { 226 case "vpc": 227 _, err = ec2conn.DisassociateAddress(&ec2.DisassociateAddressInput{ 228 AssociationId: aws.String(d.Get("association_id").(string)), 229 }) 230 case "standard": 231 _, err = ec2conn.DisassociateAddress(&ec2.DisassociateAddressInput{ 232 PublicIp: aws.String(d.Get("public_ip").(string)), 233 }) 234 } 235 236 if err != nil { 237 // First check if the association ID is not found. If this 238 // is the case, then it was already disassociated somehow, 239 // and that is okay. The most commmon reason for this is that 240 // the instance or ENI it was attached it was destroyed. 241 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidAssociationID.NotFound" { 242 err = nil 243 } 244 } 245 246 if err != nil { 247 return err 248 } 249 } 250 251 domain := resourceAwsEipDomain(d) 252 return resource.Retry(3*time.Minute, func() *resource.RetryError { 253 var err error 254 switch domain { 255 case "vpc": 256 log.Printf( 257 "[DEBUG] EIP release (destroy) address allocation: %v", 258 d.Id()) 259 _, err = ec2conn.ReleaseAddress(&ec2.ReleaseAddressInput{ 260 AllocationId: aws.String(d.Id()), 261 }) 262 case "standard": 263 log.Printf("[DEBUG] EIP release (destroy) address: %v", d.Id()) 264 _, err = ec2conn.ReleaseAddress(&ec2.ReleaseAddressInput{ 265 PublicIp: aws.String(d.Id()), 266 }) 267 } 268 269 if err == nil { 270 return nil 271 } 272 if _, ok := err.(awserr.Error); !ok { 273 return resource.NonRetryableError(err) 274 } 275 276 return resource.RetryableError(err) 277 }) 278 } 279 280 func resourceAwsEipDomain(d *schema.ResourceData) string { 281 if v, ok := d.GetOk("domain"); ok { 282 return v.(string) 283 } else if strings.Contains(d.Id(), "eipalloc") { 284 // We have to do this for backwards compatibility since TF 0.1 285 // didn't have the "domain" computed attribute. 286 return "vpc" 287 } 288 289 return "standard" 290 }