github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/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  }