github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/aws/resource_aws_network_interface.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "math" 8 "strconv" 9 "time" 10 11 "github.com/aws/aws-sdk-go/aws" 12 "github.com/aws/aws-sdk-go/aws/awserr" 13 "github.com/aws/aws-sdk-go/service/ec2" 14 "github.com/hashicorp/terraform/helper/hashcode" 15 "github.com/hashicorp/terraform/helper/resource" 16 "github.com/hashicorp/terraform/helper/schema" 17 ) 18 19 func resourceAwsNetworkInterface() *schema.Resource { 20 return &schema.Resource{ 21 Create: resourceAwsNetworkInterfaceCreate, 22 Read: resourceAwsNetworkInterfaceRead, 23 Update: resourceAwsNetworkInterfaceUpdate, 24 Delete: resourceAwsNetworkInterfaceDelete, 25 Importer: &schema.ResourceImporter{ 26 State: schema.ImportStatePassthrough, 27 }, 28 29 Schema: map[string]*schema.Schema{ 30 31 "subnet_id": &schema.Schema{ 32 Type: schema.TypeString, 33 Required: true, 34 ForceNew: true, 35 }, 36 37 "private_ip": &schema.Schema{ 38 Type: schema.TypeString, 39 Optional: true, 40 Computed: true, 41 }, 42 43 "private_ips": &schema.Schema{ 44 Type: schema.TypeSet, 45 Optional: true, 46 Computed: true, 47 Elem: &schema.Schema{Type: schema.TypeString}, 48 Set: schema.HashString, 49 }, 50 51 "private_ips_count": &schema.Schema{ 52 Type: schema.TypeInt, 53 Optional: true, 54 Computed: true, 55 }, 56 57 "security_groups": &schema.Schema{ 58 Type: schema.TypeSet, 59 Optional: true, 60 Computed: true, 61 Elem: &schema.Schema{Type: schema.TypeString}, 62 Set: schema.HashString, 63 }, 64 65 "source_dest_check": &schema.Schema{ 66 Type: schema.TypeBool, 67 Optional: true, 68 Default: true, 69 }, 70 71 "description": &schema.Schema{ 72 Type: schema.TypeString, 73 Optional: true, 74 }, 75 76 "attachment": &schema.Schema{ 77 Type: schema.TypeSet, 78 Optional: true, 79 Computed: true, 80 Elem: &schema.Resource{ 81 Schema: map[string]*schema.Schema{ 82 "instance": &schema.Schema{ 83 Type: schema.TypeString, 84 Required: true, 85 }, 86 "device_index": &schema.Schema{ 87 Type: schema.TypeInt, 88 Required: true, 89 }, 90 "attachment_id": &schema.Schema{ 91 Type: schema.TypeString, 92 Computed: true, 93 }, 94 }, 95 }, 96 Set: resourceAwsEniAttachmentHash, 97 }, 98 99 "tags": tagsSchema(), 100 }, 101 } 102 } 103 104 func resourceAwsNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) error { 105 106 conn := meta.(*AWSClient).ec2conn 107 108 request := &ec2.CreateNetworkInterfaceInput{ 109 SubnetId: aws.String(d.Get("subnet_id").(string)), 110 } 111 112 security_groups := d.Get("security_groups").(*schema.Set).List() 113 if len(security_groups) != 0 { 114 request.Groups = expandStringList(security_groups) 115 } 116 117 private_ips := d.Get("private_ips").(*schema.Set).List() 118 if len(private_ips) != 0 { 119 request.PrivateIpAddresses = expandPrivateIPAddresses(private_ips) 120 } 121 122 if v, ok := d.GetOk("description"); ok { 123 request.Description = aws.String(v.(string)) 124 } 125 126 if v, ok := d.GetOk("private_ips_count"); ok { 127 request.SecondaryPrivateIpAddressCount = aws.Int64(int64(v.(int))) 128 } 129 130 log.Printf("[DEBUG] Creating network interface") 131 resp, err := conn.CreateNetworkInterface(request) 132 if err != nil { 133 return fmt.Errorf("Error creating ENI: %s", err) 134 } 135 136 d.SetId(*resp.NetworkInterface.NetworkInterfaceId) 137 log.Printf("[INFO] ENI ID: %s", d.Id()) 138 return resourceAwsNetworkInterfaceUpdate(d, meta) 139 } 140 141 func resourceAwsNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) error { 142 143 conn := meta.(*AWSClient).ec2conn 144 describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesInput{ 145 NetworkInterfaceIds: []*string{aws.String(d.Id())}, 146 } 147 describeResp, err := conn.DescribeNetworkInterfaces(describe_network_interfaces_request) 148 149 if err != nil { 150 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidNetworkInterfaceID.NotFound" { 151 // The ENI is gone now, so just remove it from the state 152 d.SetId("") 153 return nil 154 } 155 156 return fmt.Errorf("Error retrieving ENI: %s", err) 157 } 158 if len(describeResp.NetworkInterfaces) != 1 { 159 return fmt.Errorf("Unable to find ENI: %#v", describeResp.NetworkInterfaces) 160 } 161 162 eni := describeResp.NetworkInterfaces[0] 163 d.Set("subnet_id", eni.SubnetId) 164 d.Set("private_ip", eni.PrivateIpAddress) 165 d.Set("private_ips", flattenNetworkInterfacesPrivateIPAddresses(eni.PrivateIpAddresses)) 166 d.Set("security_groups", flattenGroupIdentifiers(eni.Groups)) 167 d.Set("source_dest_check", eni.SourceDestCheck) 168 169 if eni.Description != nil { 170 d.Set("description", eni.Description) 171 } 172 173 // Tags 174 d.Set("tags", tagsToMap(eni.TagSet)) 175 176 if eni.Attachment != nil { 177 attachment := []map[string]interface{}{flattenAttachment(eni.Attachment)} 178 d.Set("attachment", attachment) 179 } else { 180 d.Set("attachment", nil) 181 } 182 183 return nil 184 } 185 186 func networkInterfaceAttachmentRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { 187 return func() (interface{}, string, error) { 188 189 describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesInput{ 190 NetworkInterfaceIds: []*string{aws.String(id)}, 191 } 192 describeResp, err := conn.DescribeNetworkInterfaces(describe_network_interfaces_request) 193 194 if err != nil { 195 log.Printf("[ERROR] Could not find network interface %s. %s", id, err) 196 return nil, "", err 197 } 198 199 eni := describeResp.NetworkInterfaces[0] 200 hasAttachment := strconv.FormatBool(eni.Attachment != nil) 201 log.Printf("[DEBUG] ENI %s has attachment state %s", id, hasAttachment) 202 return eni, hasAttachment, nil 203 } 204 } 205 206 func resourceAwsNetworkInterfaceDetach(oa *schema.Set, meta interface{}, eniId string) error { 207 // if there was an old attachment, remove it 208 if oa != nil && len(oa.List()) > 0 { 209 old_attachment := oa.List()[0].(map[string]interface{}) 210 detach_request := &ec2.DetachNetworkInterfaceInput{ 211 AttachmentId: aws.String(old_attachment["attachment_id"].(string)), 212 Force: aws.Bool(true), 213 } 214 conn := meta.(*AWSClient).ec2conn 215 _, detach_err := conn.DetachNetworkInterface(detach_request) 216 if detach_err != nil { 217 if awsErr, _ := detach_err.(awserr.Error); awsErr.Code() != "InvalidAttachmentID.NotFound" { 218 return fmt.Errorf("Error detaching ENI: %s", detach_err) 219 } 220 } 221 222 log.Printf("[DEBUG] Waiting for ENI (%s) to become dettached", eniId) 223 stateConf := &resource.StateChangeConf{ 224 Pending: []string{"true"}, 225 Target: []string{"false"}, 226 Refresh: networkInterfaceAttachmentRefreshFunc(conn, eniId), 227 Timeout: 10 * time.Minute, 228 } 229 if _, err := stateConf.WaitForState(); err != nil { 230 return fmt.Errorf( 231 "Error waiting for ENI (%s) to become dettached: %s", eniId, err) 232 } 233 } 234 235 return nil 236 } 237 238 func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) error { 239 conn := meta.(*AWSClient).ec2conn 240 d.Partial(true) 241 242 if d.HasChange("attachment") { 243 oa, na := d.GetChange("attachment") 244 245 detach_err := resourceAwsNetworkInterfaceDetach(oa.(*schema.Set), meta, d.Id()) 246 if detach_err != nil { 247 return detach_err 248 } 249 250 // if there is a new attachment, attach it 251 if na != nil && len(na.(*schema.Set).List()) > 0 { 252 new_attachment := na.(*schema.Set).List()[0].(map[string]interface{}) 253 di := new_attachment["device_index"].(int) 254 attach_request := &ec2.AttachNetworkInterfaceInput{ 255 DeviceIndex: aws.Int64(int64(di)), 256 InstanceId: aws.String(new_attachment["instance"].(string)), 257 NetworkInterfaceId: aws.String(d.Id()), 258 } 259 _, attach_err := conn.AttachNetworkInterface(attach_request) 260 if attach_err != nil { 261 return fmt.Errorf("Error attaching ENI: %s", attach_err) 262 } 263 } 264 265 d.SetPartial("attachment") 266 } 267 268 if d.HasChange("private_ips") { 269 o, n := d.GetChange("private_ips") 270 if o == nil { 271 o = new(schema.Set) 272 } 273 if n == nil { 274 n = new(schema.Set) 275 } 276 277 os := o.(*schema.Set) 278 ns := n.(*schema.Set) 279 280 // Unassign old IP addresses 281 unassignIps := os.Difference(ns) 282 if unassignIps.Len() != 0 { 283 input := &ec2.UnassignPrivateIpAddressesInput{ 284 NetworkInterfaceId: aws.String(d.Id()), 285 PrivateIpAddresses: expandStringList(unassignIps.List()), 286 } 287 _, err := conn.UnassignPrivateIpAddresses(input) 288 if err != nil { 289 return fmt.Errorf("Failure to unassign Private IPs: %s", err) 290 } 291 } 292 293 // Assign new IP addresses 294 assignIps := ns.Difference(os) 295 if assignIps.Len() != 0 { 296 input := &ec2.AssignPrivateIpAddressesInput{ 297 NetworkInterfaceId: aws.String(d.Id()), 298 PrivateIpAddresses: expandStringList(assignIps.List()), 299 } 300 _, err := conn.AssignPrivateIpAddresses(input) 301 if err != nil { 302 return fmt.Errorf("Failure to assign Private IPs: %s", err) 303 } 304 } 305 306 d.SetPartial("private_ips") 307 } 308 309 request := &ec2.ModifyNetworkInterfaceAttributeInput{ 310 NetworkInterfaceId: aws.String(d.Id()), 311 SourceDestCheck: &ec2.AttributeBooleanValue{Value: aws.Bool(d.Get("source_dest_check").(bool))}, 312 } 313 314 _, err := conn.ModifyNetworkInterfaceAttribute(request) 315 if err != nil { 316 return fmt.Errorf("Failure updating ENI: %s", err) 317 } 318 319 d.SetPartial("source_dest_check") 320 321 if d.HasChange("private_ips_count") { 322 o, n := d.GetChange("private_ips_count") 323 private_ips := d.Get("private_ips").(*schema.Set).List() 324 private_ips_filtered := private_ips[:0] 325 primary_ip := d.Get("private_ip") 326 327 for _, ip := range private_ips { 328 if ip != primary_ip { 329 private_ips_filtered = append(private_ips_filtered, ip) 330 } 331 } 332 333 if o != nil && o != 0 && n != nil && n != len(private_ips_filtered) { 334 335 diff := n.(int) - o.(int) 336 337 // Surplus of IPs, add the diff 338 if diff > 0 { 339 input := &ec2.AssignPrivateIpAddressesInput{ 340 NetworkInterfaceId: aws.String(d.Id()), 341 SecondaryPrivateIpAddressCount: aws.Int64(int64(diff)), 342 } 343 _, err := conn.AssignPrivateIpAddresses(input) 344 if err != nil { 345 return fmt.Errorf("Failure to assign Private IPs: %s", err) 346 } 347 } 348 349 if diff < 0 { 350 input := &ec2.UnassignPrivateIpAddressesInput{ 351 NetworkInterfaceId: aws.String(d.Id()), 352 PrivateIpAddresses: expandStringList(private_ips_filtered[0:int(math.Abs(float64(diff)))]), 353 } 354 _, err := conn.UnassignPrivateIpAddresses(input) 355 if err != nil { 356 return fmt.Errorf("Failure to unassign Private IPs: %s", err) 357 } 358 } 359 360 d.SetPartial("private_ips_count") 361 } 362 } 363 364 if d.HasChange("security_groups") { 365 request := &ec2.ModifyNetworkInterfaceAttributeInput{ 366 NetworkInterfaceId: aws.String(d.Id()), 367 Groups: expandStringList(d.Get("security_groups").(*schema.Set).List()), 368 } 369 370 _, err := conn.ModifyNetworkInterfaceAttribute(request) 371 if err != nil { 372 return fmt.Errorf("Failure updating ENI: %s", err) 373 } 374 375 d.SetPartial("security_groups") 376 } 377 378 if d.HasChange("description") { 379 request := &ec2.ModifyNetworkInterfaceAttributeInput{ 380 NetworkInterfaceId: aws.String(d.Id()), 381 Description: &ec2.AttributeValue{Value: aws.String(d.Get("description").(string))}, 382 } 383 384 _, err := conn.ModifyNetworkInterfaceAttribute(request) 385 if err != nil { 386 return fmt.Errorf("Failure updating ENI: %s", err) 387 } 388 389 d.SetPartial("description") 390 } 391 392 if err := setTags(conn, d); err != nil { 393 return err 394 } else { 395 d.SetPartial("tags") 396 } 397 398 d.Partial(false) 399 400 return resourceAwsNetworkInterfaceRead(d, meta) 401 } 402 403 func resourceAwsNetworkInterfaceDelete(d *schema.ResourceData, meta interface{}) error { 404 conn := meta.(*AWSClient).ec2conn 405 406 log.Printf("[INFO] Deleting ENI: %s", d.Id()) 407 408 detach_err := resourceAwsNetworkInterfaceDetach(d.Get("attachment").(*schema.Set), meta, d.Id()) 409 if detach_err != nil { 410 return detach_err 411 } 412 413 deleteEniOpts := ec2.DeleteNetworkInterfaceInput{ 414 NetworkInterfaceId: aws.String(d.Id()), 415 } 416 if _, err := conn.DeleteNetworkInterface(&deleteEniOpts); err != nil { 417 return fmt.Errorf("Error deleting ENI: %s", err) 418 } 419 420 return nil 421 } 422 423 func resourceAwsEniAttachmentHash(v interface{}) int { 424 var buf bytes.Buffer 425 m := v.(map[string]interface{}) 426 buf.WriteString(fmt.Sprintf("%s-", m["instance"].(string))) 427 buf.WriteString(fmt.Sprintf("%d-", m["device_index"].(int))) 428 return hashcode.String(buf.String()) 429 }