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