github.com/paulmey/terraform@v0.5.2-0.20150519145237-046e9b4c884d/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/awslabs/aws-sdk-go/aws" 10 "github.com/awslabs/aws-sdk-go/service/ec2" 11 "github.com/hashicorp/terraform/helper/resource" 12 "github.com/hashicorp/terraform/helper/schema" 13 ) 14 15 func resourceAwsEip() *schema.Resource { 16 return &schema.Resource{ 17 Create: resourceAwsEipCreate, 18 Read: resourceAwsEipRead, 19 Update: resourceAwsEipUpdate, 20 Delete: resourceAwsEipDelete, 21 22 Schema: map[string]*schema.Schema{ 23 "vpc": &schema.Schema{ 24 Type: schema.TypeBool, 25 Optional: true, 26 ForceNew: true, 27 }, 28 29 "instance": &schema.Schema{ 30 Type: schema.TypeString, 31 Optional: true, 32 }, 33 34 "network_interface": &schema.Schema{ 35 Type: schema.TypeString, 36 Optional: true, 37 Computed: true, 38 ConflictsWith: []string{"instance"}, 39 }, 40 41 "allocation_id": &schema.Schema{ 42 Type: schema.TypeString, 43 Computed: true, 44 }, 45 46 "association_id": &schema.Schema{ 47 Type: schema.TypeString, 48 Computed: true, 49 }, 50 51 "domain": &schema.Schema{ 52 Type: schema.TypeString, 53 Computed: true, 54 }, 55 56 "public_ip": &schema.Schema{ 57 Type: schema.TypeString, 58 Computed: true, 59 }, 60 61 "private_ip": &schema.Schema{ 62 Type: schema.TypeString, 63 Computed: true, 64 }, 65 }, 66 } 67 } 68 69 func resourceAwsEipCreate(d *schema.ResourceData, meta interface{}) error { 70 ec2conn := meta.(*AWSClient).ec2conn 71 72 // By default, we're not in a VPC 73 domainOpt := "" 74 if v := d.Get("vpc"); v != nil && v.(bool) { 75 domainOpt = "vpc" 76 } 77 78 allocOpts := &ec2.AllocateAddressInput{ 79 Domain: aws.String(domainOpt), 80 } 81 82 log.Printf("[DEBUG] EIP create configuration: %#v", allocOpts) 83 allocResp, err := ec2conn.AllocateAddress(allocOpts) 84 if err != nil { 85 return fmt.Errorf("Error creating EIP: %s", err) 86 } 87 88 // The domain tells us if we're in a VPC or not 89 d.Set("domain", allocResp.Domain) 90 91 // Assign the eips (unique) allocation id for use later 92 // the EIP api has a conditional unique ID (really), so 93 // if we're in a VPC we need to save the ID as such, otherwise 94 // it defaults to using the public IP 95 log.Printf("[DEBUG] EIP Allocate: %#v", allocResp) 96 if d.Get("domain").(string) == "vpc" { 97 d.SetId(*allocResp.AllocationID) 98 } else { 99 d.SetId(*allocResp.PublicIP) 100 } 101 102 log.Printf("[INFO] EIP ID: %s (domain: %v)", d.Id(), *allocResp.Domain) 103 return resourceAwsEipUpdate(d, meta) 104 } 105 106 func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error { 107 ec2conn := meta.(*AWSClient).ec2conn 108 109 domain := resourceAwsEipDomain(d) 110 id := d.Id() 111 112 req := &ec2.DescribeAddressesInput{} 113 114 if domain == "vpc" { 115 req.AllocationIDs = []*string{aws.String(id)} 116 } else { 117 req.PublicIPs = []*string{aws.String(id)} 118 } 119 120 log.Printf( 121 "[DEBUG] EIP describe configuration: %#v (domain: %s)", 122 req, domain) 123 124 describeAddresses, err := ec2conn.DescribeAddresses(req) 125 if err != nil { 126 if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidAllocationID.NotFound" { 127 d.SetId("") 128 return nil 129 } 130 131 return fmt.Errorf("Error retrieving EIP: %s", err) 132 } 133 134 // Verify AWS returned our EIP 135 if len(describeAddresses.Addresses) != 1 || 136 (domain == "vpc" && *describeAddresses.Addresses[0].AllocationID != id) || 137 *describeAddresses.Addresses[0].PublicIP != id { 138 if err != nil { 139 return fmt.Errorf("Unable to find EIP: %#v", describeAddresses.Addresses) 140 } 141 } 142 143 address := describeAddresses.Addresses[0] 144 145 d.Set("association_id", address.AssociationID) 146 if address.InstanceID != nil { 147 d.Set("instance", address.InstanceID) 148 } 149 if address.NetworkInterfaceID != nil { 150 d.Set("network_interface", address.NetworkInterfaceID) 151 } 152 d.Set("private_ip", address.PrivateIPAddress) 153 d.Set("public_ip", address.PublicIP) 154 155 return nil 156 } 157 158 func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error { 159 ec2conn := meta.(*AWSClient).ec2conn 160 161 domain := resourceAwsEipDomain(d) 162 163 // Associate to instance or interface if specified 164 v_instance, ok_instance := d.GetOk("instance") 165 v_interface, ok_interface := d.GetOk("network_interface") 166 167 if ok_instance || ok_interface { 168 instanceId := v_instance.(string) 169 networkInterfaceId := v_interface.(string) 170 171 assocOpts := &ec2.AssociateAddressInput{ 172 InstanceID: aws.String(instanceId), 173 PublicIP: aws.String(d.Id()), 174 } 175 176 // more unique ID conditionals 177 if domain == "vpc" { 178 assocOpts = &ec2.AssociateAddressInput{ 179 NetworkInterfaceID: aws.String(networkInterfaceId), 180 InstanceID: aws.String(instanceId), 181 AllocationID: aws.String(d.Id()), 182 } 183 } 184 185 log.Printf("[DEBUG] EIP associate configuration: %#v (domain: %v)", assocOpts, domain) 186 _, err := ec2conn.AssociateAddress(assocOpts) 187 if err != nil { 188 // Prevent saving instance if association failed 189 // e.g. missing internet gateway in VPC 190 d.Set("instance", "") 191 d.Set("network_interface", "") 192 return fmt.Errorf("Failure associating EIP: %s", err) 193 } 194 } 195 196 return resourceAwsEipRead(d, meta) 197 } 198 199 func resourceAwsEipDelete(d *schema.ResourceData, meta interface{}) error { 200 ec2conn := meta.(*AWSClient).ec2conn 201 202 if err := resourceAwsEipRead(d, meta); err != nil { 203 return err 204 } 205 if d.Id() == "" { 206 // This might happen from the read 207 return nil 208 } 209 210 // If we are attached to an instance or interface, detach first. 211 if d.Get("instance").(string) != "" || d.Get("association_id").(string) != "" { 212 log.Printf("[DEBUG] Disassociating EIP: %s", d.Id()) 213 var err error 214 switch resourceAwsEipDomain(d) { 215 case "vpc": 216 _, err = ec2conn.DisassociateAddress(&ec2.DisassociateAddressInput{ 217 AssociationID: aws.String(d.Get("association_id").(string)), 218 }) 219 case "standard": 220 _, err = ec2conn.DisassociateAddress(&ec2.DisassociateAddressInput{ 221 PublicIP: aws.String(d.Get("public_ip").(string)), 222 }) 223 } 224 if err != nil { 225 return err 226 } 227 } 228 229 domain := resourceAwsEipDomain(d) 230 return resource.Retry(3*time.Minute, func() error { 231 var err error 232 switch domain { 233 case "vpc": 234 log.Printf( 235 "[DEBUG] EIP release (destroy) address allocation: %v", 236 d.Id()) 237 _, err = ec2conn.ReleaseAddress(&ec2.ReleaseAddressInput{ 238 AllocationID: aws.String(d.Id()), 239 }) 240 case "standard": 241 log.Printf("[DEBUG] EIP release (destroy) address: %v", d.Id()) 242 _, err = ec2conn.ReleaseAddress(&ec2.ReleaseAddressInput{ 243 PublicIP: aws.String(d.Id()), 244 }) 245 } 246 247 if err == nil { 248 return nil 249 } 250 if _, ok := err.(aws.APIError); !ok { 251 return resource.RetryError{Err: err} 252 } 253 254 return err 255 }) 256 } 257 258 func resourceAwsEipDomain(d *schema.ResourceData) string { 259 if v, ok := d.GetOk("domain"); ok { 260 return v.(string) 261 } else if strings.Contains(d.Id(), "eipalloc") { 262 // We have to do this for backwards compatibility since TF 0.1 263 // didn't have the "domain" computed attribute. 264 return "vpc" 265 } 266 267 return "standard" 268 }