diff --git a/builtin/providers/aws/resource_aws_eip.go b/builtin/providers/aws/resource_aws_eip.go index d8e802704..619310f92 100644 --- a/builtin/providers/aws/resource_aws_eip.go +++ b/builtin/providers/aws/resource_aws_eip.go @@ -31,6 +31,12 @@ func resourceAwsEip() *schema.Resource { Optional: true, }, + "network_interface": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"instance"}, + }, + "allocation_id": &schema.Schema{ Type: schema.TypeString, Computed: true, @@ -102,22 +108,18 @@ func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error { domain := resourceAwsEipDomain(d) id := d.Id() - var assocIds []*string - var publicIps []*string + req := &ec2.DescribeAddressesInput{} + if domain == "vpc" { - assocIds = []*string{aws.String(id)} + req.AllocationIDs = []*string{aws.String(id)} } else { - publicIps = []*string{aws.String(id)} + req.PublicIPs = []*string{aws.String(id)} } log.Printf( - "[DEBUG] EIP describe configuration: %#v, %#v (domain: %s)", - assocIds, publicIps, domain) + "[DEBUG] EIP describe configuration: %#v (domain: %s)", + req, domain) - req := &ec2.DescribeAddressesInput{ - AllocationIDs: assocIds, - PublicIPs: publicIps, - } describeAddresses, err := ec2conn.DescribeAddresses(req) if err != nil { if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidAllocationID.NotFound" { @@ -143,6 +145,9 @@ func resourceAwsEipRead(d *schema.ResourceData, meta interface{}) error { if address.InstanceID != nil { d.Set("instance", address.InstanceID) } + if address.NetworkInterfaceID != nil { + d.Set("network_interface", address.NetworkInterfaceID) + } d.Set("private_ip", address.PrivateIPAddress) d.Set("public_ip", address.PublicIP) @@ -154,9 +159,13 @@ func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error { domain := resourceAwsEipDomain(d) - // Only register with an instance if we have one - if v, ok := d.GetOk("instance"); ok { - instanceId := v.(string) + // Associate to instance or interface if specified + v_instance, ok_instance := d.GetOk("instance") + v_interface, ok_interface := d.GetOk("network_interface") + + if ok_instance || ok_interface { + instanceId := v_instance.(string) + networkInterfaceId := v_interface.(string) assocOpts := &ec2.AssociateAddressInput{ InstanceID: aws.String(instanceId), @@ -166,9 +175,9 @@ func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error { // more unique ID conditionals if domain == "vpc" { assocOpts = &ec2.AssociateAddressInput{ - InstanceID: aws.String(instanceId), - AllocationID: aws.String(d.Id()), - PublicIP: aws.String(""), + NetworkInterfaceID: aws.String(networkInterfaceId), + InstanceID: aws.String(instanceId), + AllocationID: aws.String(d.Id()), } } @@ -178,7 +187,8 @@ func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error { // Prevent saving instance if association failed // e.g. missing internet gateway in VPC d.Set("instance", "") - return fmt.Errorf("Failure associating instances: %s", err) + d.Set("network_interface", "") + return fmt.Errorf("Failure associating EIP: %s", err) } } @@ -196,8 +206,8 @@ func resourceAwsEipDelete(d *schema.ResourceData, meta interface{}) error { return nil } - // If we are attached to an instance, detach first. - if d.Get("instance").(string) != "" { + // If we are attached to an instance or interface, detach first. + if d.Get("instance").(string) != "" || d.Get("association_id").(string) != "" { log.Printf("[DEBUG] Disassociating EIP: %s", d.Id()) var err error switch resourceAwsEipDomain(d) { diff --git a/builtin/providers/aws/resource_aws_eip_test.go b/builtin/providers/aws/resource_aws_eip_test.go index 199dc3046..b87794131 100644 --- a/builtin/providers/aws/resource_aws_eip_test.go +++ b/builtin/providers/aws/resource_aws_eip_test.go @@ -57,6 +57,26 @@ func TestAccAWSEIP_instance(t *testing.T) { }) } +func TestAccAWSEIP_network_interface(t *testing.T) { + var conf ec2.Address + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEIPDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSEIPNetworkInterfaceConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEIPExists("aws_eip.bar", &conf), + testAccCheckAWSEIPAttributes(&conf), + testAccCheckAWSEIPAssociated(&conf), + ), + }, + }, + }) +} + func testAccCheckAWSEIPDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ec2conn @@ -101,6 +121,16 @@ func testAccCheckAWSEIPAttributes(conf *ec2.Address) resource.TestCheckFunc { } } +func testAccCheckAWSEIPAssociated(conf *ec2.Address) resource.TestCheckFunc { + return func(s *terraform.State) error { + if *conf.AssociationID == "" { + return fmt.Errorf("empty association_id") + } + + return nil + } +} + func testAccCheckAWSEIPExists(n string, res *ec2.Address) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -177,3 +207,25 @@ resource "aws_eip" "bar" { instance = "${aws_instance.bar.id}" } ` +const testAccAWSEIPNetworkInterfaceConfig = ` +resource "aws_vpc" "bar" { + cidr_block = "10.0.0.0/24" +} +resource "aws_internet_gateway" "bar" { + vpc_id = "${aws_vpc.bar.id}" +} +resource "aws_subnet" "bar" { + vpc_id = "${aws_vpc.bar.id}" + availability_zone = "us-west-2a" + cidr_block = "10.0.0.0/24" +} +resource "aws_network_interface" "bar" { + subnet_id = "${aws_subnet.bar.id}" + private_ips = ["10.0.0.10"] + security_groups = [ "${aws_vpc.bar.default_security_group_id}" ] +} +resource "aws_eip" "bar" { + vpc = "true" + network_interface = "${aws_network_interface.bar.id}" +} +`