diff --git a/builtin/providers/aws/resource_aws_instance.go b/builtin/providers/aws/resource_aws_instance.go index e43a20b1042b..86e8ec21f8d4 100644 --- a/builtin/providers/aws/resource_aws_instance.go +++ b/builtin/providers/aws/resource_aws_instance.go @@ -79,10 +79,20 @@ func resourceAwsInstance() *schema.Resource { }, "private_ip": &schema.Schema{ - Type: schema.TypeString, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + Deprecated: "Please use the `private_ips` field instead", + }, + + "private_ips": &schema.Schema{ + Type: schema.TypeSet, Optional: true, ForceNew: true, Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, }, "source_dest_check": &schema.Schema{ @@ -491,6 +501,7 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { for _, ni := range instance.NetworkInterfaces { if *ni.Attachment.DeviceIndex == 0 { d.Set("subnet_id", ni.SubnetId) + d.Set("private_ips", flattenInstancePrivateIpAddresses(ni.PrivateIpAddresses)) } } } else { @@ -1049,7 +1060,24 @@ func buildAwsInstanceOpts( } } - if hasSubnet && associatePublicIPAddress { + private_ips := d.Get("private_ips").(*schema.Set).List() + if len(private_ips) != 0 { + ni := &ec2.InstanceNetworkInterfaceSpecification{ + AssociatePublicIpAddress: aws.Bool(associatePublicIPAddress), + DeviceIndex: aws.Int64(int64(0)), + SubnetId: aws.String(subnetID), + Groups: groups, + PrivateIpAddresses: expandPrivateIPAddresses(private_ips), + } + + if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 { + for _, v := range v.List() { + ni.Groups = append(ni.Groups, aws.String(v.(string))) + } + } + + opts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{ni} + } else if hasSubnet && associatePublicIPAddress { // If we have a non-default VPC / Subnet specified, we can flag // AssociatePublicIpAddress to get a Public IP assigned. By default these are not provided. // You cannot specify both SubnetId and the NetworkInterface.0.* parameters though, otherwise @@ -1148,3 +1176,13 @@ func iamInstanceProfileArnToName(ip *ec2.IamInstanceProfile) string { parts := strings.Split(*ip.Arn, "/") return parts[len(parts)-1] } + +//Flattens an array of private ip addresses into a []string, where the elements returned are the IP strings e.g. "192.168.0.0" +func flattenInstancePrivateIpAddresses(dtos []*ec2.InstancePrivateIpAddress) []string { + ips := make([]string, 0, len(dtos)) + for _, v := range dtos { + ip := *v.PrivateIpAddress + ips = append(ips, ip) + } + return ips +} diff --git a/builtin/providers/aws/resource_aws_instance_test.go b/builtin/providers/aws/resource_aws_instance_test.go index c19ddc0e43e8..220cde8b083a 100644 --- a/builtin/providers/aws/resource_aws_instance_test.go +++ b/builtin/providers/aws/resource_aws_instance_test.go @@ -452,6 +452,50 @@ func TestAccAWSInstance_privateIP(t *testing.T) { }) } +func TestAccAWSInstance_privateIPs(t *testing.T) { + var v ec2.Instance + + testCheckPrivateIPs := func() resource.TestCheckFunc { + return func(*terraform.State) error { + if l := len(v.NetworkInterfaces); l != 1 { + return fmt.Errorf("expected 1 eni, got %s", l) + } + + privateIps := flattenInstancePrivateIpAddresses(v.NetworkInterfaces[0].PrivateIpAddresses) + if l := len(privateIps); l != 2 { + return fmt.Errorf("expected 2 private IPs, got %s", l) + } + + ip1 := privateIps[0] + ip2 := privateIps[1] + if ip1 != "10.1.1.42" && ip2 != "10.1.1.42" { + return fmt.Errorf("expected private IP 10.1.1.42 to be set") + } + if ip1 != "10.1.1.43" && ip2 != "10.1.1.43" { + return fmt.Errorf("expected private IP 10.1.1.43 to be set") + } + + return nil + } + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_instance.foo", + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccInstanceConfigPrivateIPs, + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists("aws_instance.foo", &v), + testCheckPrivateIPs(), + ), + }, + }, + }) +} + func TestAccAWSInstance_associatePublicIPAndPrivateIP(t *testing.T) { var v ec2.Instance @@ -700,6 +744,31 @@ func driftTags(instance *ec2.Instance) resource.TestCheckFunc { } } +func TestFlattenInstancePrivateIpAddresses(t *testing.T) { + expanded := []*ec2.InstancePrivateIpAddress{ + &ec2.InstancePrivateIpAddress{PrivateIpAddress: aws.String("192.168.0.1")}, + &ec2.InstancePrivateIpAddress{PrivateIpAddress: aws.String("192.168.0.2")}, + } + + result := flattenInstancePrivateIpAddresses(expanded) + + if result == nil { + t.Fatal("result was nil") + } + + if len(result) != 2 { + t.Fatalf("expected result had %d elements, but got %d", 2, len(result)) + } + + if result[0] != "192.168.0.1" { + t.Fatalf("expected ip to be 192.168.0.1, but was %s", result[0]) + } + + if result[1] != "192.168.0.2" { + t.Fatalf("expected ip to be 192.168.0.2, but was %s", result[1]) + } +} + const testAccInstanceConfig_pre = ` resource "aws_security_group" "tf_test_foo" { name = "tf_test_foo" @@ -921,6 +990,24 @@ resource "aws_instance" "foo" { } ` +const testAccInstanceConfigPrivateIPs = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_subnet" "foo" { + cidr_block = "10.1.1.0/24" + vpc_id = "${aws_vpc.foo.id}" +} + +resource "aws_instance" "foo" { + ami = "ami-c5eabbf5" + instance_type = "t2.micro" + subnet_id = "${aws_subnet.foo.id}" + private_ips = ["10.1.1.42","10.1.1.43"] +} +` + const testAccInstanceConfigAssociatePublicIPAndPrivateIP = ` resource "aws_vpc" "foo" { cidr_block = "10.1.0.0/16" diff --git a/website/source/docs/providers/aws/r/instance.html.markdown b/website/source/docs/providers/aws/r/instance.html.markdown index c93e66c53af6..4f0bff035602 100644 --- a/website/source/docs/providers/aws/r/instance.html.markdown +++ b/website/source/docs/providers/aws/r/instance.html.markdown @@ -14,12 +14,12 @@ and deleted. Instances also support [provisioning](/docs/provisioners/index.html ## Example Usage ``` -# Create a new instance of the `ami-408c7f28` (Ubuntu 14.04) on an +# Create a new instance of the `ami-408c7f28` (Ubuntu 14.04) on an # t1.micro node with an AWS Tag naming it "HelloWorld" provider "aws" { region = "us-east-1" } - + resource "aws_instance" "web" { ami = "ami-408c7f28" instance_type = "t1.micro" @@ -41,9 +41,9 @@ The following arguments are supported: EBS-optimized. * `disable_api_termination` - (Optional) If true, enables [EC2 Instance Termination Protection](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/terminating-instances.html#Using_ChangingDisableAPITermination) -* `instance_initiated_shutdown_behavior` - (Optional) Shutdown behavior for the -instance. Amazon defaults this to `stop` for EBS-backed instances and -`terminate` for instance-store instances. Cannot be set on instance-store +* `instance_initiated_shutdown_behavior` - (Optional) Shutdown behavior for the +instance. Amazon defaults this to `stop` for EBS-backed instances and +`terminate` for instance-store instances. Cannot be set on instance-store instances. See [Shutdown Behavior](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/terminating-instances.html#Using_ChangingInstanceInitiatedShutdownBehavior) for more information. * `instance_type` - (Required) The type of instance to start * `key_name` - (Optional) The key name to use for the instance. @@ -52,9 +52,11 @@ instances. See [Shutdown Behavior](https://docs.aws.amazon.com/AWSEC2/latest/Use If you are within a non-default VPC, you'll need to use `vpc_security_group_ids` instead. * `vpc_security_group_ids` - (Optional) A list of security group IDs to associate with. * `subnet_id` - (Optional) The VPC Subnet ID to launch in. -* `associate_public_ip_address` - (Optional) Associate a public ip address with an instance in a VPC. Boolean value. -* `private_ip` - (Optional) Private IP address to associate with the - instance in a VPC. +* `associate_public_ip_address` - (Optional) Associate a public ip address with an instance in a VPC. Boolean value. +* `private_ip` - (Optional, Deprecated) Private IP address to associate with the + instance in a VPC. This + attribute is deprecated, please use the `private_ips` attribute instead. +* `private_ips` - (Optional) A list of private IP addresses to accociate with the instance's first network interface in a VPC. * `source_dest_check` - (Optional) Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs. Defaults true. * `user_data` - (Optional) The user data to provide when launching the instance. @@ -136,13 +138,14 @@ The following attributes are exported: * `availability_zone` - The availability zone of the instance. * `placement_group` - The placement group of the instance. * `key_name` - The key name of the instance -* `public_dns` - The public DNS name assigned to the instance. For EC2-VPC, this +* `public_dns` - The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC * `public_ip` - The public IP address assigned to the instance, if applicable. **NOTE**: If you are using an [`aws_eip`](/docs/providers/aws/r/eip.html) with your instance, you should refer to the EIP's address directly and not use `public_ip`, as this field will change after the EIP is attached. -* `private_dns` - The private DNS name assigned to the instance. Can only be - used inside the Amazon EC2, and only available if you've enabled DNS hostnames +* `private_dns` - The private DNS name assigned to the instance. Can only be + used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC * `private_ip` - The private IP address assigned to the instance +* `private_ips` - A list of private IP addresses assigned to the instance's first network interface. * `security_groups` - The associated security groups. * `vpc_security_group_ids` - The associated security groups in non-default VPC * `subnet_id` - The VPC subnet ID.