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