From 8380a7b03eec090f4c41c571531ede303ec26880 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Thu, 7 Apr 2016 12:15:00 -0500 Subject: [PATCH] provider/aws: Allow multiple EIPs to associate to single ENI When calling AssociateAddress, the PrivateIpAddress parameter must be used to select which private IP the EIP should associate with, otherwise the EIP always associates with the _first_ private IP. Without this parameter, multiple EIPs couldn't be assigned to a single ENI. Includes covering test and docs update. Fixes #2997 --- builtin/providers/aws/resource_aws_eip.go | 6 ++ .../providers/aws/resource_aws_eip_test.go | 55 ++++++++++++++++++- .../docs/providers/aws/r/eip.html.markdown | 28 +++++++++- 3 files changed, 86 insertions(+), 3 deletions(-) diff --git a/builtin/providers/aws/resource_aws_eip.go b/builtin/providers/aws/resource_aws_eip.go index b147b99caa..ee1aec8bc8 100644 --- a/builtin/providers/aws/resource_aws_eip.go +++ b/builtin/providers/aws/resource_aws_eip.go @@ -61,6 +61,7 @@ func resourceAwsEip() *schema.Resource { "private_ip": &schema.Schema{ Type: schema.TypeString, + Optional: true, Computed: true, }, }, @@ -180,10 +181,15 @@ func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error { // more unique ID conditionals if domain == "vpc" { + var privateIpAddress *string + if v := d.Get("private_ip").(string); v != "" { + privateIpAddress = aws.String(v) + } assocOpts = &ec2.AssociateAddressInput{ NetworkInterfaceId: aws.String(networkInterfaceId), InstanceId: aws.String(instanceId), AllocationId: aws.String(d.Id()), + PrivateIpAddress: privateIpAddress, } } diff --git a/builtin/providers/aws/resource_aws_eip_test.go b/builtin/providers/aws/resource_aws_eip_test.go index 56955a8c11..ef3e8113bd 100644 --- a/builtin/providers/aws/resource_aws_eip_test.go +++ b/builtin/providers/aws/resource_aws_eip_test.go @@ -78,6 +78,29 @@ func TestAccAWSEIP_network_interface(t *testing.T) { }) } +func TestAccAWSEIP_twoEIPsOneNetworkInterface(t *testing.T) { + var one, two ec2.Address + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEIPDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSEIPMultiNetworkInterfaceConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEIPExists("aws_eip.one", &one), + testAccCheckAWSEIPAttributes(&one), + testAccCheckAWSEIPAssociated(&one), + testAccCheckAWSEIPExists("aws_eip.two", &two), + testAccCheckAWSEIPAttributes(&two), + testAccCheckAWSEIPAssociated(&two), + ), + }, + }, + }) +} + func testAccCheckAWSEIPDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ec2conn @@ -136,7 +159,7 @@ func testAccCheckAWSEIPAttributes(conf *ec2.Address) resource.TestCheckFunc { func testAccCheckAWSEIPAssociated(conf *ec2.Address) resource.TestCheckFunc { return func(s *terraform.State) error { - if *conf.AssociationId == "" { + if conf.AssociationId == nil || *conf.AssociationId == "" { return fmt.Errorf("empty association_id") } @@ -220,6 +243,7 @@ resource "aws_eip" "bar" { instance = "${aws_instance.bar.id}" } ` + const testAccAWSEIPNetworkInterfaceConfig = ` resource "aws_vpc" "bar" { cidr_block = "10.0.0.0/24" @@ -242,3 +266,32 @@ resource "aws_eip" "bar" { network_interface = "${aws_network_interface.bar.id}" } ` + +const testAccAWSEIPMultiNetworkInterfaceConfig = ` +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", "10.0.0.11"] + security_groups = [ "${aws_vpc.bar.default_security_group_id}" ] +} +resource "aws_eip" "one" { + vpc = "true" + network_interface = "${aws_network_interface.bar.id}" + private_ip = "10.0.0.10" +} +resource "aws_eip" "two" { + vpc = "true" + network_interface = "${aws_network_interface.bar.id}" + private_ip = "10.0.0.11" +} +` diff --git a/website/source/docs/providers/aws/r/eip.html.markdown b/website/source/docs/providers/aws/r/eip.html.markdown index a4d01777a0..63f9ae5c0f 100644 --- a/website/source/docs/providers/aws/r/eip.html.markdown +++ b/website/source/docs/providers/aws/r/eip.html.markdown @@ -12,10 +12,31 @@ Provides an Elastic IP resource. ## Example Usage +Single EIP associated with an instance: + ``` resource "aws_eip" "lb" { - instance = "${aws_instance.web.id}" - vpc = true + instance = "${aws_instance.web.id}" + vpc = true +} +``` + +Muliple EIPs associated with a single network interface: + +``` +resource "aws_network_interface" "multi-ip" { + subnet_id = "${aws_subnet.main.id}" + private_ips = ["10.0.0.10", "10.0.0.11"] +} +resource "aws_eip" "one" { + vpc = true + network_interface = "${aws_network_interface.multi-ip.id}" + private_ip = "10.0.0.10" +} +resource "aws_eip" "two" { + vpc = true + network_interface = "${aws_network_interface.multi-ip.id}" + private_ip = "10.0.0.11" } ``` @@ -26,6 +47,9 @@ The following arguments are supported: * `vpc` - (Optional) Boolean if the EIP is in a VPC or not. * `instance` - (Optional) EC2 instance ID. * `network_interface` - (Optional) Network interface ID to associate with. +* `private_ip` - (Optional) The primary or secondary private IP address to + associate with the Elastic IP address. If no private IP address is specified, + the Elastic IP address is associated with the primary private IP address. ~> **NOTE:** You can specify either the `instance` ID or the `network_interface` ID, but not both. Including both will **not** return an error from the AWS API, but will