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
This commit is contained in:
Paul Hinze 2016-04-07 12:15:00 -05:00
parent 7eb91755fd
commit 8380a7b03e
3 changed files with 86 additions and 3 deletions

View File

@ -61,6 +61,7 @@ func resourceAwsEip() *schema.Resource {
"private_ip": &schema.Schema{ "private_ip": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true,
Computed: true, Computed: true,
}, },
}, },
@ -180,10 +181,15 @@ func resourceAwsEipUpdate(d *schema.ResourceData, meta interface{}) error {
// more unique ID conditionals // more unique ID conditionals
if domain == "vpc" { if domain == "vpc" {
var privateIpAddress *string
if v := d.Get("private_ip").(string); v != "" {
privateIpAddress = aws.String(v)
}
assocOpts = &ec2.AssociateAddressInput{ assocOpts = &ec2.AssociateAddressInput{
NetworkInterfaceId: aws.String(networkInterfaceId), NetworkInterfaceId: aws.String(networkInterfaceId),
InstanceId: aws.String(instanceId), InstanceId: aws.String(instanceId),
AllocationId: aws.String(d.Id()), AllocationId: aws.String(d.Id()),
PrivateIpAddress: privateIpAddress,
} }
} }

View File

@ -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 { func testAccCheckAWSEIPDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn conn := testAccProvider.Meta().(*AWSClient).ec2conn
@ -136,7 +159,7 @@ func testAccCheckAWSEIPAttributes(conf *ec2.Address) resource.TestCheckFunc {
func testAccCheckAWSEIPAssociated(conf *ec2.Address) resource.TestCheckFunc { func testAccCheckAWSEIPAssociated(conf *ec2.Address) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
if *conf.AssociationId == "" { if conf.AssociationId == nil || *conf.AssociationId == "" {
return fmt.Errorf("empty association_id") return fmt.Errorf("empty association_id")
} }
@ -220,6 +243,7 @@ resource "aws_eip" "bar" {
instance = "${aws_instance.bar.id}" instance = "${aws_instance.bar.id}"
} }
` `
const testAccAWSEIPNetworkInterfaceConfig = ` const testAccAWSEIPNetworkInterfaceConfig = `
resource "aws_vpc" "bar" { resource "aws_vpc" "bar" {
cidr_block = "10.0.0.0/24" cidr_block = "10.0.0.0/24"
@ -242,3 +266,32 @@ resource "aws_eip" "bar" {
network_interface = "${aws_network_interface.bar.id}" 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"
}
`

View File

@ -12,6 +12,8 @@ Provides an Elastic IP resource.
## Example Usage ## Example Usage
Single EIP associated with an instance:
``` ```
resource "aws_eip" "lb" { resource "aws_eip" "lb" {
instance = "${aws_instance.web.id}" instance = "${aws_instance.web.id}"
@ -19,6 +21,25 @@ resource "aws_eip" "lb" {
} }
``` ```
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"
}
```
## Argument Reference ## Argument Reference
The following arguments are supported: The following arguments are supported:
@ -26,6 +47,9 @@ The following arguments are supported:
* `vpc` - (Optional) Boolean if the EIP is in a VPC or not. * `vpc` - (Optional) Boolean if the EIP is in a VPC or not.
* `instance` - (Optional) EC2 instance ID. * `instance` - (Optional) EC2 instance ID.
* `network_interface` - (Optional) Network interface ID to associate with. * `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, ~> **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 but not both. Including both will **not** return an error from the AWS API, but will