Skip to content

Commit

Permalink
change private_ips to secondary_private_ips and enable update
Browse files Browse the repository at this point in the history
  • Loading branch information
anGie44 committed Jul 8, 2020
1 parent 4b09012 commit d9c3513
Show file tree
Hide file tree
Showing 6 changed files with 333 additions and 46 deletions.
15 changes: 15 additions & 0 deletions aws/data_source_aws_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ func dataSourceAwsInstance() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"secondary_private_ips": {
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"iam_instance_profile": {
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -436,6 +441,16 @@ func instanceDescriptionAttributes(d *schema.ResourceData, instance *ec2.Instanc
d.Set("subnet_id", ni.SubnetId)
d.Set("network_interface_id", ni.NetworkInterfaceId)
d.Set("associate_public_ip_address", ni.Association != nil)

secondaryIPs := make([]string, 0, len(ni.PrivateIpAddresses))
for _, ip := range ni.PrivateIpAddresses {
if !aws.BoolValue(ip.Primary) {
secondaryIPs = append(secondaryIPs, aws.StringValue(ip.PrivateIpAddress))
}
}
if err := d.Set("secondary_private_ips", secondaryIPs); err != nil {
return fmt.Errorf("error setting secondary_private_ips: %w", err)
}
}
}
} else {
Expand Down
36 changes: 36 additions & 0 deletions aws/data_source_aws_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,27 @@ func TestAccAWSInstanceDataSource_privateIP(t *testing.T) {
})
}

func TestAccAWSInstanceDataSource_secondaryPrivateIPs(t *testing.T) {
resourceName := "aws_instance.test"
datasourceName := "data.aws_instance.test"
rName := fmt.Sprintf("tf-testacc-instance-%s", acctest.RandStringFromCharSet(12, acctest.CharSetAlphaNum))

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccInstanceDataSourceConfig_secondaryPrivateIPs(rName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrPair(datasourceName, "ami", resourceName, "ami"),
resource.TestCheckResourceAttrPair(datasourceName, "instance_type", resourceName, "instance_type"),
resource.TestCheckResourceAttrPair(datasourceName, "secondary_private_ips", resourceName, "secondary_private_ips"),
),
},
},
})
}

func TestAccAWSInstanceDataSource_keyPair(t *testing.T) {
resourceName := "aws_instance.test"
datasourceName := "data.aws_instance.test"
Expand Down Expand Up @@ -665,6 +686,21 @@ data "aws_instance" "test" {
`)
}

func testAccInstanceDataSourceConfig_secondaryPrivateIPs(rName string) string {
return testAccLatestAmazonLinuxHvmEbsAmiConfig() + testAccAwsInstanceVpcConfig(rName, false) + fmt.Sprintf(`
resource "aws_instance" "test" {
ami = "${data.aws_ami.amzn-ami-minimal-hvm-ebs.id}"
instance_type = "t2.micro"
subnet_id = "${aws_subnet.test.id}"
secondary_private_ips = ["10.1.1.42"]
}
data "aws_instance" "test" {
instance_id = "${aws_instance.test.id}"
}
`)
}

func testAccInstanceDataSourceConfig_keyPair(rName string) string {
return testAccLatestAmazonLinuxHvmEbsAmiConfig() + fmt.Sprintf(`
resource "aws_key_pair" "test" {
Expand Down
130 changes: 93 additions & 37 deletions aws/resource_aws_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,19 +113,16 @@ func resourceAwsInstance() *schema.Resource {
validation.StringIsEmpty,
validation.IsIPv4Address,
),
ConflictsWith: []string{"private_ips"},
},

"private_ips": {
"secondary_private_ips": {
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.IsIPv4Address,
},
ConflictsWith: []string{"private_ip"},
},

"source_dest_check": {
Expand Down Expand Up @@ -219,7 +216,7 @@ func resourceAwsInstance() *schema.Resource {
},

"network_interface": {
ConflictsWith: []string{"associate_public_ip_address", "subnet_id", "private_ip", "private_ips", "vpc_security_group_ids", "security_groups", "ipv6_addresses", "ipv6_address_count", "source_dest_check"},
ConflictsWith: []string{"associate_public_ip_address", "subnet_id", "private_ip", "secondary_private_ips", "vpc_security_group_ids", "security_groups", "ipv6_addresses", "ipv6_address_count", "source_dest_check"},
Type: schema.TypeSet,
Optional: true,
Computed: true,
Expand Down Expand Up @@ -820,7 +817,7 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
}
}

var privateIPs []string
var secondaryPrivateIPs []string
var ipv6Addresses []string
if len(instance.NetworkInterfaces) > 0 {
var primaryNetworkInterface ec2.InstanceNetworkInterface
Expand Down Expand Up @@ -866,7 +863,9 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
d.Set("associate_public_ip_address", primaryNetworkInterface.Association != nil)

for _, address := range primaryNetworkInterface.PrivateIpAddresses {
privateIPs = append(privateIPs, aws.StringValue(address.PrivateIpAddress))
if !aws.BoolValue(address.Primary) {
secondaryPrivateIPs = append(secondaryPrivateIPs, aws.StringValue(address.PrivateIpAddress))
}
}

for _, address := range primaryNetworkInterface.Ipv6Addresses {
Expand All @@ -880,7 +879,7 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
d.Set("subnet_id", instance.SubnetId)
}

if err := d.Set("private_ips", privateIPs); err != nil {
if err := d.Set("secondary_private_ips", secondaryPrivateIPs); err != nil {
return fmt.Errorf("Error setting private_ips for AWS Instance (%s): %w", d.Id(), err)
}

Expand Down Expand Up @@ -1131,23 +1130,7 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
}
}

if d.HasChange("vpc_security_group_ids") && !d.IsNewResource() {
var groups []*string
if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 {
for _, v := range v.List() {
groups = append(groups, aws.String(v.(string)))
}
}

if len(groups) < 1 {
return fmt.Errorf("VPC-based instances require at least one security group to be attached.")
}
// If a user has multiple network interface attachments on the target EC2 instance, simply modifying the
// instance attributes via a `ModifyInstanceAttributes()` request would fail with the following error message:
// "There are multiple interfaces attached to instance 'i-XX'. Please specify an interface ID for the operation instead."
// Thus, we need to actually modify the primary network interface for the new security groups, as the primary
// network interface is where we modify/create security group assignments during Create.
log.Printf("[INFO] Modifying `vpc_security_group_ids` on Instance %q", d.Id())
if d.HasChanges("secondary_private_ips", "vpc_security_group_ids") && !d.IsNewResource() {
instance, err := resourceAwsInstanceFindByID(conn, d.Id())
if err != nil {
return fmt.Errorf("error retrieving instance %q: %w", d.Id(), err)
Expand All @@ -1159,18 +1142,78 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
}
}

if primaryInterface.NetworkInterfaceId == nil {
log.Print("[Error] Attempted to set vpc_security_group_ids on an instance without a primary network interface")
return fmt.Errorf(
"Failed to update vpc_security_group_ids on %q, which does not contain a primary network interface",
d.Id())
if d.HasChange("secondary_private_ips") {
if primaryInterface.NetworkInterfaceId == nil {
return fmt.Errorf("Failed to update secondary_private_ips on %q, which does not contain a primary network interface",
d.Id())
}
o, n := d.GetChange("secondary_private_ips")
if o == nil {
o = new(schema.Set)
}
if n == nil {
n = new(schema.Set)
}

os := o.(*schema.Set)
ns := n.(*schema.Set)

// Unassign old IP addresses
unassignIps := os.Difference(ns)
if unassignIps.Len() != 0 {
input := &ec2.UnassignPrivateIpAddressesInput{
NetworkInterfaceId: primaryInterface.NetworkInterfaceId,
PrivateIpAddresses: expandStringList(unassignIps.List()),
}
log.Printf("[INFO] Unassigning secondary_private_ips on Instance %q", d.Id())
_, err := conn.UnassignPrivateIpAddresses(input)
if err != nil {
return fmt.Errorf("Failure to unassign Secondary Private IPs: %w", err)
}
}

// Assign new IP addresses
assignIps := ns.Difference(os)
if assignIps.Len() != 0 {
input := &ec2.AssignPrivateIpAddressesInput{
NetworkInterfaceId: primaryInterface.NetworkInterfaceId,
PrivateIpAddresses: expandStringList(assignIps.List()),
}
log.Printf("[INFO] Assigning secondary_private_ips on Instance %q", d.Id())
_, err := conn.AssignPrivateIpAddresses(input)
if err != nil {
return fmt.Errorf("Failure to assign Secondary Private IPs: %w", err)
}
}
}

if _, err := conn.ModifyNetworkInterfaceAttribute(&ec2.ModifyNetworkInterfaceAttributeInput{
NetworkInterfaceId: primaryInterface.NetworkInterfaceId,
Groups: groups,
}); err != nil {
return err
if d.HasChange("vpc_security_group_ids") {
if primaryInterface.NetworkInterfaceId == nil {
return fmt.Errorf("Failed to update vpc_security_group_ids on %q, which does not contain a primary network interface",
d.Id())
}
var groups []*string
if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 {
for _, v := range v.List() {
groups = append(groups, aws.String(v.(string)))
}
}

if len(groups) < 1 {
return fmt.Errorf("VPC-based instances require at least one security group to be attached.")
}
// If a user has multiple network interface attachments on the target EC2 instance, simply modifying the
// instance attributes via a `ModifyInstanceAttributes()` request would fail with the following error message:
// "There are multiple interfaces attached to instance 'i-XX'. Please specify an interface ID for the operation instead."
// Thus, we need to actually modify the primary network interface for the new security groups, as the primary
// network interface is where we modify/create security group assignments during Create.
log.Printf("[INFO] Modifying `vpc_security_group_ids` on Instance %q", d.Id())
if _, err := conn.ModifyNetworkInterfaceAttribute(&ec2.ModifyNetworkInterfaceAttributeInput{
NetworkInterfaceId: primaryInterface.NetworkInterfaceId,
Groups: groups,
}); err != nil {
return err
}
}
}

Expand Down Expand Up @@ -1709,8 +1752,8 @@ func buildNetworkInterfaceOpts(d *schema.ResourceData, groups []*string, nInterf
ni.PrivateIpAddress = aws.String(v.(string))
}

if v, ok := d.GetOk("private_ips"); ok && v.(*schema.Set).Len() > 0 {
ni.PrivateIpAddresses = expandPrivateIPAddresses(v.(*schema.Set).List())
if v, ok := d.GetOk("secondary_private_ips"); ok && v.(*schema.Set).Len() > 0 {
ni.PrivateIpAddresses = expandSecondaryPrivateIPAddresses(v.(*schema.Set).List())
}

if v, ok := d.GetOk("ipv6_address_count"); ok {
Expand Down Expand Up @@ -2338,6 +2381,19 @@ func expandEc2InstanceMetadataOptions(l []interface{}) *ec2.InstanceMetadataOpti
return opts
}

//Expands an array of secondary Private IPs into a ec2 Private IP Address Spec
func expandSecondaryPrivateIPAddresses(ips []interface{}) []*ec2.PrivateIpAddressSpecification {
specs := make([]*ec2.PrivateIpAddressSpecification, 0, len(ips))
for _, v := range ips {
spec := &ec2.PrivateIpAddressSpecification{
PrivateIpAddress: aws.String(v.(string)),
}

specs = append(specs, spec)
}
return specs
}

func flattenEc2InstanceMetadataOptions(opts *ec2.InstanceMetadataOptionsResponse) []interface{} {
if opts == nil {
return nil
Expand Down
Loading

0 comments on commit d9c3513

Please sign in to comment.