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