From 0026f9e9b93245104e9fadbb9db2b255d2576efe Mon Sep 17 00:00:00 2001 From: axeman Date: Mon, 11 Oct 2021 13:08:38 -0400 Subject: [PATCH 01/28] ignore cross account ENIs and FSx Tag --- aws/data_source_aws_route_table.go | 46 +++++++++++++++++++++++-- aws/resource_aws_route_table.go | 54 ++++++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 5 deletions(-) diff --git a/aws/data_source_aws_route_table.go b/aws/data_source_aws_route_table.go index 27fdfb9dd09..31894021d41 100644 --- a/aws/data_source_aws_route_table.go +++ b/aws/data_source_aws_route_table.go @@ -228,7 +228,7 @@ func dataSourceAwsRouteTableRead(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("error setting tags: %w", err) } - if err := d.Set("routes", dataSourceRoutesRead(rt.Routes)); err != nil { + if err := d.Set("routes", dataSourceRoutesRead(rt.Routes, meta)); err != nil { return err } @@ -239,7 +239,7 @@ func dataSourceAwsRouteTableRead(d *schema.ResourceData, meta interface{}) error return nil } -func dataSourceRoutesRead(ec2Routes []*ec2.Route) []map[string]interface{} { +func dataSourceRoutesRead(ec2Routes []*ec2.Route, meta interface{}) []map[string]interface{} { routes := make([]map[string]interface{}, 0, len(ec2Routes)) // Loop through the routes and add them to the set for _, r := range ec2Routes { @@ -257,6 +257,48 @@ func dataSourceRoutesRead(ec2Routes []*ec2.Route) []map[string]interface{} { continue } + if r.NetworkInterfaceId != nil { + + conn := meta.(*AWSClient).ec2conn + + describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesInput{ + NetworkInterfaceIds: []*string{r.NetworkInterfaceId}, + } + + describeResp, err := conn.DescribeNetworkInterfaces(describe_network_interfaces_request) + + if err != nil { + if isAWSErr(err, "InvalidNetworkInterfaceID.NotFound", "") { + log.Printf("Network Interface %s not found", err) + } else { + log.Printf("Error occured checking network inteface for route: %s", err) + } + } + + if len(describeResp.NetworkInterfaces) != 1 { + log.Printf("Unable to find ENI: %s", describeResp.NetworkInterfaces) + } else { + + eni := describeResp.NetworkInterfaces[0] + + if eni.Attachment != nil { + + owner := aws.StringValue(eni.OwnerId) + iowner := aws.StringValue(eni.Attachment.InstanceOwnerId) + + log.Printf("[DEBUG] ENI owner: %s, ENI Instane Owner %s", owner, iowner) + + if iowner != "" && iowner != owner { + //Skipping cross account ENI for AWS services + log.Printf("Found Cross Account ENI: %s. Skipping", aws.StringValue(describeResp.NetworkInterfaces[0].NetworkInterfaceId)) + log.Printf("[DEBUG] Cross Account ENI Details: \n %s", describeResp.NetworkInterfaces[0]) + continue + } + } + } + + } + m := make(map[string]interface{}) if r.DestinationCidrBlock != nil { diff --git a/aws/resource_aws_route_table.go b/aws/resource_aws_route_table.go index 93c560d3230..ac06b957586 100644 --- a/aws/resource_aws_route_table.go +++ b/aws/resource_aws_route_table.go @@ -242,11 +242,17 @@ func resourceAwsRouteTableRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("error setting propagating_vgws: %w", err) } - if err := d.Set("route", flattenEc2Routes(routeTable.Routes)); err != nil { + if err := d.Set("route", flattenEc2Routes(routeTable.Routes,meta)); err != nil { return fmt.Errorf("error setting route: %w", err) } - tags := keyvaluetags.Ec2KeyValueTags(routeTable.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig) + //Ignore the FSx service tag + var mtag map[string]string + mtag = make(map[string]string) + mtag["AmazonFSx"] = "ManagedByAmazonFSx" + fsxtag := keyvaluetags.New(mtag) + + tags := keyvaluetags.Ec2KeyValueTags(routeTable.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Ignore(fsxtag) //lintignore:AWSR002 if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { @@ -851,7 +857,7 @@ func flattenEc2Route(apiObject *ec2.Route) map[string]interface{} { return tfMap } -func flattenEc2Routes(apiObjects []*ec2.Route) []interface{} { +func flattenEc2Routes(apiObjects []*ec2.Route,meta interface{}) []interface{} { if len(apiObjects) == 0 { return nil } @@ -877,6 +883,48 @@ func flattenEc2Routes(apiObjects []*ec2.Route) []interface{} { continue } + if apiObject.NetworkInterfaceId != nil { + + conn := meta.(*AWSClient).ec2conn + + describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesInput{ + NetworkInterfaceIds: []*string{apiObject.NetworkInterfaceId}, + } + + describeResp, err := conn.DescribeNetworkInterfaces(describe_network_interfaces_request) + + if err != nil { + if isAWSErr(err, "InvalidNetworkInterfaceID.NotFound", "") { + log.Printf("Network Interface %s not found" , err) + } else { + log.Printf("Error occured checking network inteface for route: %s", err) + } + } + + if len(describeResp.NetworkInterfaces) != 1 { + log.Printf("Unable to find ENI: %s", describeResp.NetworkInterfaces) + } else { + + eni := describeResp.NetworkInterfaces[0] + + if eni.Attachment != nil { + + owner := aws.StringValue(eni.OwnerId) + iowner := aws.StringValue(eni.Attachment.InstanceOwnerId) + + log.Printf("[DEBUG] ENI owner: %s, ENI Instane Owner %s",owner,iowner) + + if iowner != "" && iowner != owner { + //Skipping cross account ENI for AWS services + log.Printf("Found Cross Account ENI: %s. Skipping",aws.StringValue(describeResp.NetworkInterfaces[0].NetworkInterfaceId)) + log.Printf("[DEBUG] Cross Account ENI Details: \n %s",describeResp.NetworkInterfaces[0]) + continue + } + } + } + + } + tfList = append(tfList, flattenEc2Route(apiObject)) } From 50c946dac7d8ebc1790afcb6c31ca37490fc03e8 Mon Sep 17 00:00:00 2001 From: axeman Date: Tue, 12 Oct 2021 18:59:58 -0400 Subject: [PATCH 02/28] ignore cross account ENIs and FSx Tag --- aws/resource_aws_route_table.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/aws/resource_aws_route_table.go b/aws/resource_aws_route_table.go index ac06b957586..9eee3f09e60 100644 --- a/aws/resource_aws_route_table.go +++ b/aws/resource_aws_route_table.go @@ -242,16 +242,16 @@ func resourceAwsRouteTableRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("error setting propagating_vgws: %w", err) } - if err := d.Set("route", flattenEc2Routes(routeTable.Routes,meta)); err != nil { + if err := d.Set("route", flattenEc2Routes(routeTable.Routes, meta)); err != nil { return fmt.Errorf("error setting route: %w", err) } //Ignore the FSx service tag var mtag map[string]string - mtag = make(map[string]string) + mtag = make(map[string]string) mtag["AmazonFSx"] = "ManagedByAmazonFSx" fsxtag := keyvaluetags.New(mtag) - + tags := keyvaluetags.Ec2KeyValueTags(routeTable.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Ignore(fsxtag) //lintignore:AWSR002 @@ -857,7 +857,7 @@ func flattenEc2Route(apiObject *ec2.Route) map[string]interface{} { return tfMap } -func flattenEc2Routes(apiObjects []*ec2.Route,meta interface{}) []interface{} { +func flattenEc2Routes(apiObjects []*ec2.Route, meta interface{}) []interface{} { if len(apiObjects) == 0 { return nil } @@ -884,9 +884,9 @@ func flattenEc2Routes(apiObjects []*ec2.Route,meta interface{}) []interface{} { } if apiObject.NetworkInterfaceId != nil { - + conn := meta.(*AWSClient).ec2conn - + describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesInput{ NetworkInterfaceIds: []*string{apiObject.NetworkInterfaceId}, } @@ -895,30 +895,30 @@ func flattenEc2Routes(apiObjects []*ec2.Route,meta interface{}) []interface{} { if err != nil { if isAWSErr(err, "InvalidNetworkInterfaceID.NotFound", "") { - log.Printf("Network Interface %s not found" , err) + log.Printf("Network Interface %s not found", err) } else { log.Printf("Error occured checking network inteface for route: %s", err) } - } - + } + if len(describeResp.NetworkInterfaces) != 1 { log.Printf("Unable to find ENI: %s", describeResp.NetworkInterfaces) } else { eni := describeResp.NetworkInterfaces[0] - + if eni.Attachment != nil { owner := aws.StringValue(eni.OwnerId) iowner := aws.StringValue(eni.Attachment.InstanceOwnerId) - log.Printf("[DEBUG] ENI owner: %s, ENI Instane Owner %s",owner,iowner) + log.Printf("[DEBUG] ENI owner: %s, ENI Instane Owner %s", owner, iowner) if iowner != "" && iowner != owner { - //Skipping cross account ENI for AWS services - log.Printf("Found Cross Account ENI: %s. Skipping",aws.StringValue(describeResp.NetworkInterfaces[0].NetworkInterfaceId)) - log.Printf("[DEBUG] Cross Account ENI Details: \n %s",describeResp.NetworkInterfaces[0]) - continue + //Skipping cross account ENI for AWS services + log.Printf("Found Cross Account ENI: %s. Skipping", aws.StringValue(describeResp.NetworkInterfaces[0].NetworkInterfaceId)) + log.Printf("[DEBUG] Cross Account ENI Details: \n %s", describeResp.NetworkInterfaces[0]) + continue } } } From e57cb054327a453c6347330a3dacdba7e6eaaf58 Mon Sep 17 00:00:00 2001 From: axeman Date: Wed, 13 Oct 2021 12:15:35 -0400 Subject: [PATCH 03/28] added change log --- .changelog/21265.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/21265.txt diff --git a/.changelog/21265.txt b/.changelog/21265.txt new file mode 100644 index 00000000000..8408b443528 --- /dev/null +++ b/.changelog/21265.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_route_table: Remove cross account ENIs managed by services like FSX for ONTAP from terraform managed routes to avoid deletion. +``` \ No newline at end of file From 970b3f24156a86a3f04dacf338bdeebabc21ea19 Mon Sep 17 00:00:00 2001 From: axeman Date: Wed, 13 Oct 2021 17:34:11 -0400 Subject: [PATCH 04/28] added changelog --- .changelog/21265.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/21265.txt b/.changelog/21265.txt index 8408b443528..1e39655eab7 100644 --- a/.changelog/21265.txt +++ b/.changelog/21265.txt @@ -1,3 +1,3 @@ ```release-note:bug -resource/aws_route_table: Remove cross account ENIs managed by services like FSX for ONTAP from terraform managed routes to avoid deletion. +resource/aws_route_table: Remove cross account ENIs managed by services like FSX for ONTAP from terraform managed routes to avoid deletion. ``` \ No newline at end of file From 1abdf63912d175d1d5c8277b47ea964a95a68871 Mon Sep 17 00:00:00 2001 From: axeman Date: Fri, 15 Oct 2021 23:45:03 -0400 Subject: [PATCH 05/28] added test for FSx route update --- aws/resource_aws_route_table_test.go | 125 +++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/aws/resource_aws_route_table_test.go b/aws/resource_aws_route_table_test.go index f377447270a..47214cf61f1 100644 --- a/aws/resource_aws_route_table_test.go +++ b/aws/resource_aws_route_table_test.go @@ -1152,6 +1152,41 @@ func TestAccAWSRouteTable_PrefixList_To_InternetGateway(t *testing.T) { }) } +func TestAccAWSRouteTable_FSxRoute_And_Tag(t *testing.T) { + var routeTable ec2.RouteTable + resourceName := "aws_route_table.test" + ngwResourceName := "aws_nat_gateway.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + destinationCidr := "10.2.0.0/16" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, ec2.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckRouteTableDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRouteTableFSxRouteAndTag(rName, destinationCidr), + Check: resource.ComposeTestCheckFunc( + testAccCheckRouteTableExists(resourceName, &routeTable), + testAccCheckAWSRouteTableNumberOfRoutes(&routeTable, 3), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`route-table/.+$`)), + testAccCheckResourceAttrAccountID(resourceName, "owner_id"), + resource.TestCheckResourceAttr(resourceName, "route.#", "1"), + testAccCheckAWSRouteTableRoute(resourceName, "cidr_block", destinationCidr, "nat_gateway_id", ngwResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCheckRouteTableExists(n string, v *ec2.RouteTable) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -2399,3 +2434,93 @@ resource "aws_route_table" "test" { } `, rName) } + +func testAccAWSRouteTableFSxRouteAndTag(rName, destinationCidr string) string { + return composeConfig( + testAccAvailableAZsNoOptInDefaultExcludeConfig(), + fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + cidr_block = "10.1.1.0/24" + vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[0] + map_public_ip_on_launch = true + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test2" { + cidr_block = "10.1.2.0/24" + vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[1] + map_public_ip_on_launch = true + + tags = { + Name = %[1]q + } + } + +resource "aws_internet_gateway" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_eip" "test" { + vpc = true + + tags = { + Name = %[1]q + } +} + +resource "aws_nat_gateway" "test" { + allocation_id = aws_eip.test.id + subnet_id = aws_subnet.test.id + + tags = { + Name = %[1]q + } + + depends_on = [aws_internet_gateway.test] +} + +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id + + route { + cidr_block = %[2]q + nat_gateway_id = aws_nat_gateway.test.id + } + + tags = { + Name = %[1]q + } +} + +resource "aws_fsx_ontap_file_system" "test" { + storage_capacity = 1024 + subnet_ids = [aws_subnet.test.id, aws_subnet.test2.id,] + deployment_type = "MULTI_AZ_1" + throughput_capacity = 512 + preferred_subnet_id = aws_subnet.test.id + route_table_ids = [aws_route_table.test.id] + depends_on = [ + aws_route_table.test, + aws_subnet.test, + aws_subnet.test2 + ] +} +`, rName, destinationCidr)) +} From 3c8c3d3defc50fba4d3743ca6201578e685484ed Mon Sep 17 00:00:00 2001 From: axeman Date: Sat, 16 Oct 2021 23:28:18 -0400 Subject: [PATCH 06/28] ignore cross account ENIs and FSx Tag updated to new schema --- internal/service/ec2/route_table.go | 53 +++++++- .../service/ec2/route_table_data_source.go | 54 +++++++- .../ec2/route_table_data_source_test.go | 122 +++++++++++++++++ internal/service/ec2/route_table_test.go | 125 ++++++++++++++++++ 4 files changed, 348 insertions(+), 6 deletions(-) diff --git a/internal/service/ec2/route_table.go b/internal/service/ec2/route_table.go index 0fb45f4a8dc..7359c7b1b63 100644 --- a/internal/service/ec2/route_table.go +++ b/internal/service/ec2/route_table.go @@ -240,11 +240,16 @@ func resourceRouteTableRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("error setting propagating_vgws: %w", err) } - if err := d.Set("route", flattenEc2Routes(routeTable.Routes)); err != nil { + if err := d.Set("route", flattenEc2Routes(routeTable.Routes, meta)); err != nil { return fmt.Errorf("error setting route: %w", err) } - tags := KeyValueTags(routeTable.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + //Ignore the FSx service tag + mTag := make(map[string]string) + mTag["AmazonFSx"] = "ManagedByAmazonFSx" + fsxTag := tftags.New(mTag) + + tags := KeyValueTags(routeTable.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Ignore(fsxTag) //lintignore:AWSR002 if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { @@ -849,7 +854,7 @@ func flattenEc2Route(apiObject *ec2.Route) map[string]interface{} { return tfMap } -func flattenEc2Routes(apiObjects []*ec2.Route) []interface{} { +func flattenEc2Routes(apiObjects []*ec2.Route, meta interface{}) []interface{} { if len(apiObjects) == 0 { return nil } @@ -875,6 +880,48 @@ func flattenEc2Routes(apiObjects []*ec2.Route) []interface{} { continue } + if apiObject.NetworkInterfaceId != nil { + + conn := meta.(*conns.AWSClient).EC2Conn + + describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesInput{ + NetworkInterfaceIds: []*string{apiObject.NetworkInterfaceId}, + } + + describeResp, err := conn.DescribeNetworkInterfaces(describe_network_interfaces_request) + + if err != nil { + if tfawserr.ErrMessageContains(err, "InvalidNetworkInterfaceID.NotFound", "") { + log.Printf("Network Interface %s not found", err) + } else { + log.Printf("Error occurred checking network inteface for route: %s", err) + } + } + + if len(describeResp.NetworkInterfaces) != 1 { + log.Printf("Unable to find ENI: %s", describeResp.NetworkInterfaces) + } else { + + eni := describeResp.NetworkInterfaces[0] + + if eni.Attachment != nil { + + owner := aws.StringValue(eni.OwnerId) + iowner := aws.StringValue(eni.Attachment.InstanceOwnerId) + + log.Printf("[DEBUG] ENI owner: %s, ENI Instane Owner %s", owner, iowner) + + if iowner != "" && iowner != owner { + //Skipping cross account ENI for AWS services + log.Printf("Found Cross Account ENI: %s. Skipping", aws.StringValue(describeResp.NetworkInterfaces[0].NetworkInterfaceId)) + log.Printf("[DEBUG] Cross Account ENI Details: \n %s", describeResp.NetworkInterfaces[0]) + continue + } + } + } + + } + tfList = append(tfList, flattenEc2Route(apiObject)) } diff --git a/internal/service/ec2/route_table_data_source.go b/internal/service/ec2/route_table_data_source.go index c4197e2cdf8..dbdf46a7c75 100644 --- a/internal/service/ec2/route_table_data_source.go +++ b/internal/service/ec2/route_table_data_source.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" @@ -225,11 +226,16 @@ func dataSourceRouteTableRead(d *schema.ResourceData, meta interface{}) error { d.Set("route_table_id", rt.RouteTableId) d.Set("vpc_id", rt.VpcId) - if err := d.Set("tags", KeyValueTags(rt.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + //Ignore the FSx service tag + mTag := make(map[string]string) + mTag["AmazonFSx"] = "ManagedByAmazonFSx" + fsxTag := tftags.New(mTag) + + if err := d.Set("tags", KeyValueTags(rt.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Ignore(fsxTag).Map()); err != nil { return fmt.Errorf("error setting tags: %w", err) } - if err := d.Set("routes", dataSourceRoutesRead(rt.Routes)); err != nil { + if err := d.Set("routes", dataSourceRoutesRead(rt.Routes, meta)); err != nil { return err } @@ -240,7 +246,7 @@ func dataSourceRouteTableRead(d *schema.ResourceData, meta interface{}) error { return nil } -func dataSourceRoutesRead(ec2Routes []*ec2.Route) []map[string]interface{} { +func dataSourceRoutesRead(ec2Routes []*ec2.Route, meta interface{}) []map[string]interface{} { routes := make([]map[string]interface{}, 0, len(ec2Routes)) // Loop through the routes and add them to the set for _, r := range ec2Routes { @@ -258,6 +264,48 @@ func dataSourceRoutesRead(ec2Routes []*ec2.Route) []map[string]interface{} { continue } + if r.NetworkInterfaceId != nil { + + conn := meta.(*conns.AWSClient).EC2Conn + + describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesInput{ + NetworkInterfaceIds: []*string{r.NetworkInterfaceId}, + } + + describeResp, err := conn.DescribeNetworkInterfaces(describe_network_interfaces_request) + + if err != nil { + if tfawserr.ErrMessageContains(err, "InvalidNetworkInterfaceID.NotFound", "") { + log.Printf("Network Interface %s not found", err) + } else { + log.Printf("Error occurred checking network inteface for route: %s", err) + } + } + + if len(describeResp.NetworkInterfaces) != 1 { + log.Printf("Unable to find ENI: %s", describeResp.NetworkInterfaces) + } else { + + eni := describeResp.NetworkInterfaces[0] + + if eni.Attachment != nil { + + owner := aws.StringValue(eni.OwnerId) + iowner := aws.StringValue(eni.Attachment.InstanceOwnerId) + + log.Printf("[DEBUG] ENI owner: %s, ENI Instane Owner %s", owner, iowner) + + if iowner != "" && iowner != owner { + //Skipping cross account ENI for AWS services + log.Printf("Found Cross Account ENI: %s. Skipping", aws.StringValue(describeResp.NetworkInterfaces[0].NetworkInterfaceId)) + log.Printf("[DEBUG] Cross Account ENI Details: \n %s", describeResp.NetworkInterfaces[0]) + continue + } + } + } + + } + m := make(map[string]interface{}) if r.DestinationCidrBlock != nil { diff --git a/internal/service/ec2/route_table_data_source_test.go b/internal/service/ec2/route_table_data_source_test.go index 60a19a014ac..1879a59c19e 100644 --- a/internal/service/ec2/route_table_data_source_test.go +++ b/internal/service/ec2/route_table_data_source_test.go @@ -118,6 +118,38 @@ func TestAccEC2RouteTableDataSource_main(t *testing.T) { }) } +func TestAccEC2RouteTableDataSource_fsxRouteAndTag(t *testing.T) { + datasourceName := "data.aws_route_table.test" + snResourceName := "aws_subnet.test" + snResource2Name := "aws_subnet.test2" + rtResourceName := "aws_route_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + destinationCidr := "10.2.0.0/16" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckRouteTableDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRouteTableFSxRouteAndTagDataSourceConfig(rName, destinationCidr), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(datasourceName, "route_table_id", rtResourceName, "id"), + acctest.MatchResourceAttrRegionalARN(datasourceName, "arn", "ec2", regexp.MustCompile(`route-table/.+$`)), + acctest.CheckResourceAttrAccountID(datasourceName, "owner_id"), + resource.TestCheckResourceAttr(datasourceName, "routes.#", "1"), + resource.TestCheckResourceAttr(datasourceName, "associations.#", "2"), + acctest.CheckListHasSomeElementAttrPair(datasourceName, "associations", "subnet_id", snResourceName, "id"), + acctest.CheckListHasSomeElementAttrPair(datasourceName, "associations", "subnet_id", snResource2Name, "id"), + resource.TestCheckResourceAttr(datasourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(datasourceName, "tags.Name", rName), + ), + }, + }, + }) +} + func testAccRouteTableBasicDataSourceConfig(rName string) string { return fmt.Sprintf(` resource "aws_vpc" "test" { @@ -223,3 +255,93 @@ data "aws_route_table" "test" { } `, rName) } + +func testAccRouteTableFSxRouteAndTagDataSourceConfig(rName, destinationCidr string) string { + return acctest.ConfigCompose( + acctest.ConfigAvailableAZsNoOptInDefaultExclude(), + fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + cidr_block = "10.1.1.0/24" + vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[0] + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test2" { + cidr_block = "10.1.2.0/24" + vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[1] + + tags = { + Name = %[1]q + } +} + +resource "aws_internet_gateway" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id + + route { + cidr_block = %[2]q + gateway_id = aws_internet_gateway.test.id + } + + tags = { + Name = %[1]q + } +} + +resource "aws_route_table_association" "a" { + subnet_id = aws_subnet.test.id + route_table_id = aws_route_table.test.id +} + +resource "aws_route_table_association" "b" { + subnet_id = aws_subnet.test2.id + route_table_id = aws_route_table.test.id +} + +resource "aws_fsx_ontap_file_system" "test" { + storage_capacity = 1024 + subnet_ids = [aws_subnet.test.id, aws_subnet.test2.id,] + deployment_type = "MULTI_AZ_1" + throughput_capacity = 512 + preferred_subnet_id = aws_subnet.test.id + route_table_ids = [aws_route_table.test.id] + depends_on = [ + aws_route_table.test, + aws_subnet.test, + aws_subnet.test2 + ] +} + +data "aws_route_table" "test" { + route_table_id = aws_route_table.test.id + + depends_on = [ + aws_route_table.test, + aws_route_table_association.a, + aws_route_table_association.b, + aws_fsx_ontap_file_system.test + ] +} +`, rName, destinationCidr)) +} diff --git a/internal/service/ec2/route_table_test.go b/internal/service/ec2/route_table_test.go index 64515a25375..c4512d50b6e 100644 --- a/internal/service/ec2/route_table_test.go +++ b/internal/service/ec2/route_table_test.go @@ -1025,6 +1025,41 @@ func TestAccEC2RouteTable_prefixListToInternetGateway(t *testing.T) { }) } +func TestAccEC2RouteTable_fsxRouteAndTag(t *testing.T) { + var routeTable ec2.RouteTable + resourceName := "aws_route_table.test" + ngwResourceName := "aws_nat_gateway.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + destinationCidr := "10.2.0.0/16" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckRouteTableDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRouteTableFSxRouteAndTag(rName, destinationCidr), + Check: resource.ComposeTestCheckFunc( + testAccCheckRouteTableExists(resourceName, &routeTable), + testAccCheckRouteTableNumberOfRoutes(&routeTable, 3), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`route-table/.+$`)), + acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), + resource.TestCheckResourceAttr(resourceName, "route.#", "1"), + testAccCheckRouteTableRoute(resourceName, "cidr_block", destinationCidr, "nat_gateway_id", ngwResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCheckRouteTableExists(n string, v *ec2.RouteTable) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -2272,3 +2307,93 @@ resource "aws_route_table" "test" { } `, rName) } + +func testAccRouteTableFSxRouteAndTag(rName, destinationCidr string) string { + return acctest.ConfigCompose( + acctest.ConfigAvailableAZsNoOptInDefaultExclude(), + fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.1.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + cidr_block = "10.1.1.0/24" + vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[0] + map_public_ip_on_launch = true + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test2" { + cidr_block = "10.1.2.0/24" + vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[1] + map_public_ip_on_launch = true + + tags = { + Name = %[1]q + } + } + +resource "aws_internet_gateway" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_eip" "test" { + vpc = true + + tags = { + Name = %[1]q + } +} + +resource "aws_nat_gateway" "test" { + allocation_id = aws_eip.test.id + subnet_id = aws_subnet.test.id + + tags = { + Name = %[1]q + } + + depends_on = [aws_internet_gateway.test] +} + +resource "aws_route_table" "test" { + vpc_id = aws_vpc.test.id + + route { + cidr_block = %[2]q + nat_gateway_id = aws_nat_gateway.test.id + } + + tags = { + Name = %[1]q + } +} + +resource "aws_fsx_ontap_file_system" "test" { + storage_capacity = 1024 + subnet_ids = [aws_subnet.test.id, aws_subnet.test2.id,] + deployment_type = "MULTI_AZ_1" + throughput_capacity = 512 + preferred_subnet_id = aws_subnet.test.id + route_table_ids = [aws_route_table.test.id] + depends_on = [ + aws_route_table.test, + aws_subnet.test, + aws_subnet.test2 + ] +} +`, rName, destinationCidr)) +} From 441d25489340e6aafe59485995bec26db22b1290 Mon Sep 17 00:00:00 2001 From: axeman Date: Fri, 22 Oct 2021 16:15:18 -0400 Subject: [PATCH 07/28] update FSx tag using Ignore --- internal/service/ec2/route_table.go | 8 ++------ internal/service/ec2/route_table_data_source.go | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/internal/service/ec2/route_table.go b/internal/service/ec2/route_table.go index 7359c7b1b63..0b047221d67 100644 --- a/internal/service/ec2/route_table.go +++ b/internal/service/ec2/route_table.go @@ -244,12 +244,8 @@ func resourceRouteTableRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("error setting route: %w", err) } - //Ignore the FSx service tag - mTag := make(map[string]string) - mTag["AmazonFSx"] = "ManagedByAmazonFSx" - fsxTag := tftags.New(mTag) - - tags := KeyValueTags(routeTable.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Ignore(fsxTag) + //Ignore the AmazonFSx service tag in addition to standard ignores + tags := KeyValueTags(routeTable.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Ignore(tftags.New([]string{"AmazonFSx"})) //lintignore:AWSR002 if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { diff --git a/internal/service/ec2/route_table_data_source.go b/internal/service/ec2/route_table_data_source.go index dbdf46a7c75..3f9de008a0c 100644 --- a/internal/service/ec2/route_table_data_source.go +++ b/internal/service/ec2/route_table_data_source.go @@ -226,12 +226,8 @@ func dataSourceRouteTableRead(d *schema.ResourceData, meta interface{}) error { d.Set("route_table_id", rt.RouteTableId) d.Set("vpc_id", rt.VpcId) - //Ignore the FSx service tag - mTag := make(map[string]string) - mTag["AmazonFSx"] = "ManagedByAmazonFSx" - fsxTag := tftags.New(mTag) - - if err := d.Set("tags", KeyValueTags(rt.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Ignore(fsxTag).Map()); err != nil { + //Ignore the AmazonFSx service tag in addition to standard ignores + if err := d.Set("tags", KeyValueTags(rt.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Ignore(tftags.New([]string{"AmazonFSx"})).Map()); err != nil { return fmt.Errorf("error setting tags: %w", err) } From 1735ce538358d7cc6f86d78bd77a228c8144c301 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 26 Oct 2021 15:02:43 -0400 Subject: [PATCH 08/28] Return NotFoundErrors from FindNetworkInterfaceByID/FindNetworkInterfaceSecurityGroup. Acceptance tests output: % make testacc PKG_NAME=internal/service/ec2 TESTARGS='-run=TestAccEC2NetworkInterfaceSgAttachment_' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/ec2/... -v -count 1 -parallel 20 -run=TestAccEC2NetworkInterfaceSgAttachment_ -timeout 180m === RUN TestAccEC2NetworkInterfaceSgAttachment_SG_basic === PAUSE TestAccEC2NetworkInterfaceSgAttachment_SG_basic === RUN TestAccEC2NetworkInterfaceSgAttachment_SG_disappears === PAUSE TestAccEC2NetworkInterfaceSgAttachment_SG_disappears === RUN TestAccEC2NetworkInterfaceSgAttachment_SG_instance === PAUSE TestAccEC2NetworkInterfaceSgAttachment_SG_instance === RUN TestAccEC2NetworkInterfaceSgAttachment_SG_dataSource === PAUSE TestAccEC2NetworkInterfaceSgAttachment_SG_dataSource === RUN TestAccEC2NetworkInterfaceSgAttachment_SG_multiple === PAUSE TestAccEC2NetworkInterfaceSgAttachment_SG_multiple === CONT TestAccEC2NetworkInterfaceSgAttachment_SG_basic === CONT TestAccEC2NetworkInterfaceSgAttachment_SG_dataSource === CONT TestAccEC2NetworkInterfaceSgAttachment_SG_instance === CONT TestAccEC2NetworkInterfaceSgAttachment_SG_multiple === CONT TestAccEC2NetworkInterfaceSgAttachment_SG_disappears --- PASS: TestAccEC2NetworkInterfaceSgAttachment_SG_disappears (66.50s) --- PASS: TestAccEC2NetworkInterfaceSgAttachment_SG_basic (67.15s) --- PASS: TestAccEC2NetworkInterfaceSgAttachment_SG_multiple (70.67s) --- PASS: TestAccEC2NetworkInterfaceSgAttachment_SG_instance (112.76s) --- PASS: TestAccEC2NetworkInterfaceSgAttachment_SG_dataSource (123.06s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/ec2 128.016s --- internal/service/ec2/find.go | 45 ++++++++++-------- .../ec2/network_interface_sg_attachment.go | 47 +++---------------- .../network_interface_sg_attachment_test.go | 27 ++++------- 3 files changed, 40 insertions(+), 79 deletions(-) diff --git a/internal/service/ec2/find.go b/internal/service/ec2/find.go index efdeb4e6760..8257af9589b 100644 --- a/internal/service/ec2/find.go +++ b/internal/service/ec2/find.go @@ -236,7 +236,6 @@ func FindNetworkACLEntry(conn *ec2.EC2, networkAclID string, egress bool, ruleNu return nil, nil } -// FindNetworkInterfaceByID looks up a NetworkInterface by ID. When not found, returns nil and potentially an API error. func FindNetworkInterfaceByID(conn *ec2.EC2, id string) (*ec2.NetworkInterface, error) { input := &ec2.DescribeNetworkInterfacesInput{ NetworkInterfaceIds: aws.StringSlice([]string{id}), @@ -244,6 +243,13 @@ func FindNetworkInterfaceByID(conn *ec2.EC2, id string) (*ec2.NetworkInterface, output, err := conn.DescribeNetworkInterfaces(input) + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidNetworkInterfaceIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + if err != nil { return nil, err } @@ -252,43 +258,42 @@ func FindNetworkInterfaceByID(conn *ec2.EC2, id string) (*ec2.NetworkInterface, return nil, nil } - for _, networkInterface := range output.NetworkInterfaces { - if networkInterface == nil { - continue - } + if output == nil || len(output.NetworkInterfaces) == 0 || output.NetworkInterfaces[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } - if aws.StringValue(networkInterface.NetworkInterfaceId) != id { - continue - } + if count := len(output.NetworkInterfaces); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } - return networkInterface, nil + networkInterface := output.NetworkInterfaces[0] + + // Eventual consistency check. + if aws.StringValue(networkInterface.NetworkInterfaceId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } } - return nil, nil + return networkInterface, nil } -// FindNetworkInterfaceSecurityGroup returns the associated GroupIdentifier if found func FindNetworkInterfaceSecurityGroup(conn *ec2.EC2, networkInterfaceID string, securityGroupID string) (*ec2.GroupIdentifier, error) { - var result *ec2.GroupIdentifier - networkInterface, err := FindNetworkInterfaceByID(conn, networkInterfaceID) if err != nil { return nil, err } - if networkInterface == nil { - return nil, nil - } - for _, groupIdentifier := range networkInterface.Groups { if aws.StringValue(groupIdentifier.GroupId) == securityGroupID { - result = groupIdentifier - break + return groupIdentifier, nil } } - return result, err + return nil, &resource.NotFoundError{ + LastError: fmt.Errorf("Network Interface (%s) Security Group (%s) not found", networkInterfaceID, securityGroupID), + } } // FindMainRouteTableAssociationByID returns the main route table association corresponding to the specified identifier. diff --git a/internal/service/ec2/network_interface_sg_attachment.go b/internal/service/ec2/network_interface_sg_attachment.go index c80bf96c4df..fa704c6e532 100644 --- a/internal/service/ec2/network_interface_sg_attachment.go +++ b/internal/service/ec2/network_interface_sg_attachment.go @@ -8,7 +8,6 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/aws-sdk-go-base/tfawserr" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -88,35 +87,11 @@ func resourceNetworkInterfaceSGAttachmentRead(d *schema.ResourceData, meta inter conn := meta.(*conns.AWSClient).EC2Conn - var groupIdentifier *ec2.GroupIdentifier + outputRaw, err := tfresource.RetryWhenNewResourceNotFound(PropagationTimeout, func() (interface{}, error) { + return FindNetworkInterfaceSecurityGroup(conn, interfaceID, sgID) + }, d.IsNewResource()) - err := resource.Retry(PropagationTimeout, func() *resource.RetryError { - var err error - - groupIdentifier, err = FindNetworkInterfaceSecurityGroup(conn, interfaceID, sgID) - - if d.IsNewResource() && tfawserr.ErrCodeEquals(err, ErrCodeInvalidNetworkInterfaceIDNotFound) { - return resource.RetryableError(err) - } - - if err != nil { - return resource.NonRetryableError(err) - } - - if d.IsNewResource() && groupIdentifier == nil { - return resource.RetryableError(&resource.NotFoundError{ - LastError: fmt.Errorf("EC2 Network Interface Security Group Attachment (%s) not found", d.Id()), - }) - } - - return nil - }) - - if tfresource.TimedOut(err) { - groupIdentifier, err = FindNetworkInterfaceSecurityGroup(conn, interfaceID, sgID) - } - - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, ErrCodeInvalidNetworkInterfaceIDNotFound) { + if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] EC2 Network Interface Security Group Attachment (%s) not found, removing from state", d.Id()) d.SetId("") return nil @@ -126,15 +101,7 @@ func resourceNetworkInterfaceSGAttachmentRead(d *schema.ResourceData, meta inter return fmt.Errorf("error reading EC2 Network Interface Security Group Attachment (%s): %w", d.Id(), err) } - if groupIdentifier == nil { - if d.IsNewResource() { - return fmt.Errorf("error reading EC2 Network Interface Security Group Attachment (%s): not found after creation", d.Id()) - } - - log.Printf("[WARN] EC2 Network Interface Security Group Attachment (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } + groupIdentifier := outputRaw.(*ec2.GroupIdentifier) d.Set("network_interface_id", interfaceID) d.Set("security_group_id", groupIdentifier.GroupId) @@ -156,7 +123,7 @@ func resourceNetworkInterfaceSGAttachmentDelete(d *schema.ResourceData, meta int iface, err := FindNetworkInterfaceByID(conn, interfaceID) - if tfawserr.ErrMessageContains(err, "InvalidNetworkInterfaceID.NotFound", "") { + if tfresource.NotFound(err) { return nil } @@ -188,7 +155,7 @@ func delSGFromENI(conn *ec2.EC2, sgID string, iface *ec2.NetworkInterface) error _, err := conn.ModifyNetworkInterfaceAttribute(params) - if tfawserr.ErrMessageContains(err, "InvalidNetworkInterfaceID.NotFound", "") { + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidNetworkInterfaceIDNotFound) { return nil } diff --git a/internal/service/ec2/network_interface_sg_attachment_test.go b/internal/service/ec2/network_interface_sg_attachment_test.go index 34f91ad4160..445b79e8ec9 100644 --- a/internal/service/ec2/network_interface_sg_attachment_test.go +++ b/internal/service/ec2/network_interface_sg_attachment_test.go @@ -5,13 +5,13 @@ import ( "testing" "github.com/aws/aws-sdk-go/service/ec2" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccEC2NetworkInterfaceSgAttachment_SG_basic(t *testing.T) { @@ -155,21 +155,15 @@ func testAccCheckNetworkInterfaceSGAttachmentExists(resourceName string) resourc } if rs.Primary.ID == "" { - return fmt.Errorf("No resource ID set: %s", resourceName) + return fmt.Errorf("No EC2 Network Interface Security Group Attachment ID is set: %s", resourceName) } conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - networkInterfaceID := rs.Primary.Attributes["network_interface_id"] - securityGroupID := rs.Primary.Attributes["security_group_id"] - groupIdentifier, err := tfec2.FindNetworkInterfaceSecurityGroup(conn, networkInterfaceID, securityGroupID) + _, err := tfec2.FindNetworkInterfaceSecurityGroup(conn, rs.Primary.Attributes["network_interface_id"], rs.Primary.Attributes["security_group_id"]) if err != nil { - return fmt.Errorf("error reading EC2 Network Interface Security Group Attachment (%s): %s", rs.Primary.ID, err) - } - - if groupIdentifier == nil { - return fmt.Errorf("Security Group ID (%s) not attached to ENI (%s)", securityGroupID, networkInterfaceID) + return err } return nil @@ -184,22 +178,17 @@ func testAccCheckNetworkInterfaceSGAttachmentDestroy(s *terraform.State) error { continue } - networkInterfaceID := rs.Primary.Attributes["network_interface_id"] - securityGroupID := rs.Primary.Attributes["security_group_id"] + _, err := tfec2.FindNetworkInterfaceSecurityGroup(conn, rs.Primary.Attributes["network_interface_id"], rs.Primary.Attributes["security_group_id"]) - groupIdentifier, err := tfec2.FindNetworkInterfaceSecurityGroup(conn, networkInterfaceID, securityGroupID) - - if tfawserr.ErrCodeEquals(err, tfec2.ErrCodeInvalidNetworkInterfaceIDNotFound) { + if tfresource.NotFound(err) { continue } if err != nil { - return fmt.Errorf("error reading EC2 Network Interface Security Group Attachment (%s): %s", rs.Primary.ID, err) + return err } - if groupIdentifier != nil { - return fmt.Errorf("Security Group ID (%s) still attached to ENI (%s)", securityGroupID, networkInterfaceID) - } + return fmt.Errorf("EC2 Network Interface Security Group Attachment %s still exists", rs.Primary.ID) } return nil From 627c4b978e2738ac9d6a5349d0c419198206b1f7 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 26 Oct 2021 15:25:42 -0400 Subject: [PATCH 09/28] Use 'FindNetworkInterfaceByID' when skipping cross-account ENIs for AWS services. --- internal/service/ec2/route_table.go | 48 ++++------------- .../service/ec2/route_table_data_source.go | 53 ++++--------------- 2 files changed, 20 insertions(+), 81 deletions(-) diff --git a/internal/service/ec2/route_table.go b/internal/service/ec2/route_table.go index 0b047221d67..66d0fc30bd4 100644 --- a/internal/service/ec2/route_table.go +++ b/internal/service/ec2/route_table.go @@ -240,7 +240,7 @@ func resourceRouteTableRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("error setting propagating_vgws: %w", err) } - if err := d.Set("route", flattenEc2Routes(routeTable.Routes, meta)); err != nil { + if err := d.Set("route", flattenEc2Routes(conn, routeTable.Routes)); err != nil { return fmt.Errorf("error setting route: %w", err) } @@ -850,7 +850,7 @@ func flattenEc2Route(apiObject *ec2.Route) map[string]interface{} { return tfMap } -func flattenEc2Routes(apiObjects []*ec2.Route, meta interface{}) []interface{} { +func flattenEc2Routes(conn *ec2.EC2, apiObjects []*ec2.Route) []interface{} { if len(apiObjects) == 0 { return nil } @@ -876,46 +876,16 @@ func flattenEc2Routes(apiObjects []*ec2.Route, meta interface{}) []interface{} { continue } - if apiObject.NetworkInterfaceId != nil { + // Skip cross-account ENIs for AWS services. + if networkInterfaceID := aws.StringValue(apiObject.NetworkInterfaceId); networkInterfaceID != "" { + networkInterface, err := FindNetworkInterfaceByID(conn, networkInterfaceID) - conn := meta.(*conns.AWSClient).EC2Conn - - describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesInput{ - NetworkInterfaceIds: []*string{apiObject.NetworkInterfaceId}, - } - - describeResp, err := conn.DescribeNetworkInterfaces(describe_network_interfaces_request) - - if err != nil { - if tfawserr.ErrMessageContains(err, "InvalidNetworkInterfaceID.NotFound", "") { - log.Printf("Network Interface %s not found", err) - } else { - log.Printf("Error occurred checking network inteface for route: %s", err) - } - } - - if len(describeResp.NetworkInterfaces) != 1 { - log.Printf("Unable to find ENI: %s", describeResp.NetworkInterfaces) - } else { - - eni := describeResp.NetworkInterfaces[0] - - if eni.Attachment != nil { - - owner := aws.StringValue(eni.OwnerId) - iowner := aws.StringValue(eni.Attachment.InstanceOwnerId) - - log.Printf("[DEBUG] ENI owner: %s, ENI Instane Owner %s", owner, iowner) - - if iowner != "" && iowner != owner { - //Skipping cross account ENI for AWS services - log.Printf("Found Cross Account ENI: %s. Skipping", aws.StringValue(describeResp.NetworkInterfaces[0].NetworkInterfaceId)) - log.Printf("[DEBUG] Cross Account ENI Details: \n %s", describeResp.NetworkInterfaces[0]) - continue - } + if err == nil && networkInterface.Attachment != nil { + if ownerID, instanceOwnerID := aws.StringValue(networkInterface.OwnerId), aws.StringValue(networkInterface.Attachment.InstanceOwnerId); ownerID != "" && instanceOwnerID != ownerID { + log.Printf("[DEBUG] Skip cross-account ENI (%s)", networkInterfaceID) + continue } } - } tfList = append(tfList, flattenEc2Route(apiObject)) diff --git a/internal/service/ec2/route_table_data_source.go b/internal/service/ec2/route_table_data_source.go index 3f9de008a0c..7a0be6d5dd5 100644 --- a/internal/service/ec2/route_table_data_source.go +++ b/internal/service/ec2/route_table_data_source.go @@ -8,7 +8,6 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/ec2" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" @@ -231,7 +230,7 @@ func dataSourceRouteTableRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("error setting tags: %w", err) } - if err := d.Set("routes", dataSourceRoutesRead(rt.Routes, meta)); err != nil { + if err := d.Set("routes", dataSourceRoutesRead(conn, rt.Routes)); err != nil { return err } @@ -242,15 +241,15 @@ func dataSourceRouteTableRead(d *schema.ResourceData, meta interface{}) error { return nil } -func dataSourceRoutesRead(ec2Routes []*ec2.Route, meta interface{}) []map[string]interface{} { +func dataSourceRoutesRead(conn *ec2.EC2, ec2Routes []*ec2.Route) []map[string]interface{} { routes := make([]map[string]interface{}, 0, len(ec2Routes)) // Loop through the routes and add them to the set for _, r := range ec2Routes { - if r.GatewayId != nil && *r.GatewayId == "local" { + if aws.StringValue(r.GatewayId) == "local" { continue } - if r.Origin != nil && *r.Origin == "EnableVgwRoutePropagation" { + if aws.StringValue(r.Origin) == ec2.RouteOriginEnableVgwRoutePropagation { continue } @@ -260,46 +259,16 @@ func dataSourceRoutesRead(ec2Routes []*ec2.Route, meta interface{}) []map[string continue } - if r.NetworkInterfaceId != nil { - - conn := meta.(*conns.AWSClient).EC2Conn - - describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesInput{ - NetworkInterfaceIds: []*string{r.NetworkInterfaceId}, - } + // Skip cross-account ENIs for AWS services. + if networkInterfaceID := aws.StringValue(r.NetworkInterfaceId); networkInterfaceID != "" { + networkInterface, err := FindNetworkInterfaceByID(conn, networkInterfaceID) - describeResp, err := conn.DescribeNetworkInterfaces(describe_network_interfaces_request) - - if err != nil { - if tfawserr.ErrMessageContains(err, "InvalidNetworkInterfaceID.NotFound", "") { - log.Printf("Network Interface %s not found", err) - } else { - log.Printf("Error occurred checking network inteface for route: %s", err) + if err == nil && networkInterface.Attachment != nil { + if ownerID, instanceOwnerID := aws.StringValue(networkInterface.OwnerId), aws.StringValue(networkInterface.Attachment.InstanceOwnerId); ownerID != "" && instanceOwnerID != ownerID { + log.Printf("[DEBUG] Skip cross-account ENI (%s)", networkInterfaceID) + continue } } - - if len(describeResp.NetworkInterfaces) != 1 { - log.Printf("Unable to find ENI: %s", describeResp.NetworkInterfaces) - } else { - - eni := describeResp.NetworkInterfaces[0] - - if eni.Attachment != nil { - - owner := aws.StringValue(eni.OwnerId) - iowner := aws.StringValue(eni.Attachment.InstanceOwnerId) - - log.Printf("[DEBUG] ENI owner: %s, ENI Instane Owner %s", owner, iowner) - - if iowner != "" && iowner != owner { - //Skipping cross account ENI for AWS services - log.Printf("Found Cross Account ENI: %s. Skipping", aws.StringValue(describeResp.NetworkInterfaces[0].NetworkInterfaceId)) - log.Printf("[DEBUG] Cross Account ENI Details: \n %s", describeResp.NetworkInterfaces[0]) - continue - } - } - } - } m := make(map[string]interface{}) From 133ff03961dfe680373039ea29b6c22d19428966 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 26 Oct 2021 16:18:06 -0400 Subject: [PATCH 10/28] Remove FSx ONTAP route tests from aws_route_table. Rely on aws_fsx_ontap_file_system tests. --- .../ec2/route_table_data_source_test.go | 122 ----------------- internal/service/ec2/route_table_test.go | 125 ------------------ 2 files changed, 247 deletions(-) diff --git a/internal/service/ec2/route_table_data_source_test.go b/internal/service/ec2/route_table_data_source_test.go index 1879a59c19e..60a19a014ac 100644 --- a/internal/service/ec2/route_table_data_source_test.go +++ b/internal/service/ec2/route_table_data_source_test.go @@ -118,38 +118,6 @@ func TestAccEC2RouteTableDataSource_main(t *testing.T) { }) } -func TestAccEC2RouteTableDataSource_fsxRouteAndTag(t *testing.T) { - datasourceName := "data.aws_route_table.test" - snResourceName := "aws_subnet.test" - snResource2Name := "aws_subnet.test2" - rtResourceName := "aws_route_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - destinationCidr := "10.2.0.0/16" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), - Providers: acctest.Providers, - CheckDestroy: testAccCheckRouteTableDestroy, - Steps: []resource.TestStep{ - { - Config: testAccRouteTableFSxRouteAndTagDataSourceConfig(rName, destinationCidr), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrPair(datasourceName, "route_table_id", rtResourceName, "id"), - acctest.MatchResourceAttrRegionalARN(datasourceName, "arn", "ec2", regexp.MustCompile(`route-table/.+$`)), - acctest.CheckResourceAttrAccountID(datasourceName, "owner_id"), - resource.TestCheckResourceAttr(datasourceName, "routes.#", "1"), - resource.TestCheckResourceAttr(datasourceName, "associations.#", "2"), - acctest.CheckListHasSomeElementAttrPair(datasourceName, "associations", "subnet_id", snResourceName, "id"), - acctest.CheckListHasSomeElementAttrPair(datasourceName, "associations", "subnet_id", snResource2Name, "id"), - resource.TestCheckResourceAttr(datasourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(datasourceName, "tags.Name", rName), - ), - }, - }, - }) -} - func testAccRouteTableBasicDataSourceConfig(rName string) string { return fmt.Sprintf(` resource "aws_vpc" "test" { @@ -255,93 +223,3 @@ data "aws_route_table" "test" { } `, rName) } - -func testAccRouteTableFSxRouteAndTagDataSourceConfig(rName, destinationCidr string) string { - return acctest.ConfigCompose( - acctest.ConfigAvailableAZsNoOptInDefaultExclude(), - fmt.Sprintf(` -resource "aws_vpc" "test" { - cidr_block = "10.1.0.0/16" - - tags = { - Name = %[1]q - } -} - -resource "aws_subnet" "test" { - cidr_block = "10.1.1.0/24" - vpc_id = aws_vpc.test.id - availability_zone = data.aws_availability_zones.available.names[0] - - tags = { - Name = %[1]q - } -} - -resource "aws_subnet" "test2" { - cidr_block = "10.1.2.0/24" - vpc_id = aws_vpc.test.id - availability_zone = data.aws_availability_zones.available.names[1] - - tags = { - Name = %[1]q - } -} - -resource "aws_internet_gateway" "test" { - vpc_id = aws_vpc.test.id - - tags = { - Name = %[1]q - } -} - -resource "aws_route_table" "test" { - vpc_id = aws_vpc.test.id - - route { - cidr_block = %[2]q - gateway_id = aws_internet_gateway.test.id - } - - tags = { - Name = %[1]q - } -} - -resource "aws_route_table_association" "a" { - subnet_id = aws_subnet.test.id - route_table_id = aws_route_table.test.id -} - -resource "aws_route_table_association" "b" { - subnet_id = aws_subnet.test2.id - route_table_id = aws_route_table.test.id -} - -resource "aws_fsx_ontap_file_system" "test" { - storage_capacity = 1024 - subnet_ids = [aws_subnet.test.id, aws_subnet.test2.id,] - deployment_type = "MULTI_AZ_1" - throughput_capacity = 512 - preferred_subnet_id = aws_subnet.test.id - route_table_ids = [aws_route_table.test.id] - depends_on = [ - aws_route_table.test, - aws_subnet.test, - aws_subnet.test2 - ] -} - -data "aws_route_table" "test" { - route_table_id = aws_route_table.test.id - - depends_on = [ - aws_route_table.test, - aws_route_table_association.a, - aws_route_table_association.b, - aws_fsx_ontap_file_system.test - ] -} -`, rName, destinationCidr)) -} diff --git a/internal/service/ec2/route_table_test.go b/internal/service/ec2/route_table_test.go index c4512d50b6e..64515a25375 100644 --- a/internal/service/ec2/route_table_test.go +++ b/internal/service/ec2/route_table_test.go @@ -1025,41 +1025,6 @@ func TestAccEC2RouteTable_prefixListToInternetGateway(t *testing.T) { }) } -func TestAccEC2RouteTable_fsxRouteAndTag(t *testing.T) { - var routeTable ec2.RouteTable - resourceName := "aws_route_table.test" - ngwResourceName := "aws_nat_gateway.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - destinationCidr := "10.2.0.0/16" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), - Providers: acctest.Providers, - CheckDestroy: testAccCheckRouteTableDestroy, - Steps: []resource.TestStep{ - { - Config: testAccRouteTableFSxRouteAndTag(rName, destinationCidr), - Check: resource.ComposeTestCheckFunc( - testAccCheckRouteTableExists(resourceName, &routeTable), - testAccCheckRouteTableNumberOfRoutes(&routeTable, 3), - acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`route-table/.+$`)), - acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), - resource.TestCheckResourceAttr(resourceName, "route.#", "1"), - testAccCheckRouteTableRoute(resourceName, "cidr_block", destinationCidr, "nat_gateway_id", ngwResourceName, "id"), - resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - func testAccCheckRouteTableExists(n string, v *ec2.RouteTable) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -2307,93 +2272,3 @@ resource "aws_route_table" "test" { } `, rName) } - -func testAccRouteTableFSxRouteAndTag(rName, destinationCidr string) string { - return acctest.ConfigCompose( - acctest.ConfigAvailableAZsNoOptInDefaultExclude(), - fmt.Sprintf(` -resource "aws_vpc" "test" { - cidr_block = "10.1.0.0/16" - - tags = { - Name = %[1]q - } -} - -resource "aws_subnet" "test" { - cidr_block = "10.1.1.0/24" - vpc_id = aws_vpc.test.id - availability_zone = data.aws_availability_zones.available.names[0] - map_public_ip_on_launch = true - - tags = { - Name = %[1]q - } -} - -resource "aws_subnet" "test2" { - cidr_block = "10.1.2.0/24" - vpc_id = aws_vpc.test.id - availability_zone = data.aws_availability_zones.available.names[1] - map_public_ip_on_launch = true - - tags = { - Name = %[1]q - } - } - -resource "aws_internet_gateway" "test" { - vpc_id = aws_vpc.test.id - - tags = { - Name = %[1]q - } -} - -resource "aws_eip" "test" { - vpc = true - - tags = { - Name = %[1]q - } -} - -resource "aws_nat_gateway" "test" { - allocation_id = aws_eip.test.id - subnet_id = aws_subnet.test.id - - tags = { - Name = %[1]q - } - - depends_on = [aws_internet_gateway.test] -} - -resource "aws_route_table" "test" { - vpc_id = aws_vpc.test.id - - route { - cidr_block = %[2]q - nat_gateway_id = aws_nat_gateway.test.id - } - - tags = { - Name = %[1]q - } -} - -resource "aws_fsx_ontap_file_system" "test" { - storage_capacity = 1024 - subnet_ids = [aws_subnet.test.id, aws_subnet.test2.id,] - deployment_type = "MULTI_AZ_1" - throughput_capacity = 512 - preferred_subnet_id = aws_subnet.test.id - route_table_ids = [aws_route_table.test.id] - depends_on = [ - aws_route_table.test, - aws_subnet.test, - aws_subnet.test2 - ] -} -`, rName, destinationCidr)) -} From 1d36b918a379745890eae406d50920ffb4a36507 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 26 Oct 2021 17:15:22 -0400 Subject: [PATCH 11/28] r/aws_fsx_ontap_file_system: Test aws_route_table cross-account ENI logic. --- .../service/fsx/ontap_file_system_test.go | 225 ++++++++++++------ 1 file changed, 157 insertions(+), 68 deletions(-) diff --git a/internal/service/fsx/ontap_file_system_test.go b/internal/service/fsx/ontap_file_system_test.go index d20d6923073..f9cc113504a 100644 --- a/internal/service/fsx/ontap_file_system_test.go +++ b/internal/service/fsx/ontap_file_system_test.go @@ -19,6 +19,7 @@ import ( func TestAccFSxOntapFileSystem_basic(t *testing.T) { var filesystem fsx.FileSystem resourceName := "aws_fsx_ontap_file_system.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(fsx.EndpointsID, t) }, @@ -27,7 +28,7 @@ func TestAccFSxOntapFileSystem_basic(t *testing.T) { CheckDestroy: testAccCheckFsxOntapFileSystemDestroy, Steps: []resource.TestStep{ { - Config: testAccOntapFileSystemBasicConfig(), + Config: testAccOntapFileSystemBasicConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckFsxOntapFileSystemExists(resourceName, &filesystem), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "fsx", regexp.MustCompile(`file-system/fs-.+`)), @@ -72,7 +73,8 @@ func TestAccFSxOntapFileSystem_basic(t *testing.T) { func TestAccFSxOntapFileSystem_fsxAdminPassword(t *testing.T) { var filesystem1, filesystem2 fsx.FileSystem resourceName := "aws_fsx_ontap_file_system.test" - pass := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + pass1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) pass2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ @@ -82,10 +84,10 @@ func TestAccFSxOntapFileSystem_fsxAdminPassword(t *testing.T) { CheckDestroy: testAccCheckFsxOntapFileSystemDestroy, Steps: []resource.TestStep{ { - Config: testAccOntapFileSystemFsxAdminPasswordConfig(pass), + Config: testAccOntapFileSystemFsxAdminPasswordConfig(rName, pass1), Check: resource.ComposeTestCheckFunc( testAccCheckFsxOntapFileSystemExists(resourceName, &filesystem1), - resource.TestCheckResourceAttr(resourceName, "fsx_admin_password", pass), + resource.TestCheckResourceAttr(resourceName, "fsx_admin_password", pass1), ), }, { @@ -95,7 +97,7 @@ func TestAccFSxOntapFileSystem_fsxAdminPassword(t *testing.T) { ImportStateVerifyIgnore: []string{"security_group_ids", "fsx_admin_password"}, }, { - Config: testAccOntapFileSystemFsxAdminPasswordConfig(pass2), + Config: testAccOntapFileSystemFsxAdminPasswordConfig(rName, pass2), Check: resource.ComposeTestCheckFunc( testAccCheckFsxOntapFileSystemExists(resourceName, &filesystem2), testAccCheckFsxOntapFileSystemNotRecreated(&filesystem1, &filesystem2), @@ -109,6 +111,7 @@ func TestAccFSxOntapFileSystem_fsxAdminPassword(t *testing.T) { func TestAccFSxOntapFileSystem_endpointIPAddressRange(t *testing.T) { var filesystem fsx.FileSystem resourceName := "aws_fsx_ontap_file_system.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(fsx.EndpointsID, t) }, @@ -117,7 +120,7 @@ func TestAccFSxOntapFileSystem_endpointIPAddressRange(t *testing.T) { CheckDestroy: testAccCheckFsxOntapFileSystemDestroy, Steps: []resource.TestStep{ { - Config: testAccOntapFileSystemEndpointIPAddressRangeConfig(), + Config: testAccOntapFileSystemEndpointIPAddressRangeConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckFsxOntapFileSystemExists(resourceName, &filesystem), resource.TestCheckResourceAttr(resourceName, "endpoint_ip_address_range", "198.19.255.0/24"), @@ -136,6 +139,7 @@ func TestAccFSxOntapFileSystem_endpointIPAddressRange(t *testing.T) { func TestAccFSxOntapFileSystem_diskIops(t *testing.T) { var filesystem fsx.FileSystem resourceName := "aws_fsx_ontap_file_system.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(fsx.EndpointsID, t) }, @@ -144,7 +148,7 @@ func TestAccFSxOntapFileSystem_diskIops(t *testing.T) { CheckDestroy: testAccCheckFsxOntapFileSystemDestroy, Steps: []resource.TestStep{ { - Config: testAccOntapFileSystemDiskIopsConfigurationConfig(), + Config: testAccOntapFileSystemDiskIopsConfigurationConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckFsxOntapFileSystemExists(resourceName, &filesystem), resource.TestCheckResourceAttr(resourceName, "disk_iops_configuration.#", "1"), @@ -165,6 +169,7 @@ func TestAccFSxOntapFileSystem_diskIops(t *testing.T) { func TestAccFSxOntapFileSystem_disappears(t *testing.T) { var filesystem fsx.FileSystem resourceName := "aws_fsx_ontap_file_system.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(fsx.EndpointsID, t) }, @@ -173,7 +178,7 @@ func TestAccFSxOntapFileSystem_disappears(t *testing.T) { CheckDestroy: testAccCheckFsxOntapFileSystemDestroy, Steps: []resource.TestStep{ { - Config: testAccOntapFileSystemBasicConfig(), + Config: testAccOntapFileSystemBasicConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckFsxOntapFileSystemExists(resourceName, &filesystem), acctest.CheckResourceDisappears(acctest.Provider, tffsx.ResourceOntapFileSystem(), resourceName), @@ -187,6 +192,7 @@ func TestAccFSxOntapFileSystem_disappears(t *testing.T) { func TestAccFSxOntapFileSystem_securityGroupIDs(t *testing.T) { var filesystem1, filesystem2 fsx.FileSystem resourceName := "aws_fsx_ontap_file_system.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(fsx.EndpointsID, t) }, @@ -195,7 +201,7 @@ func TestAccFSxOntapFileSystem_securityGroupIDs(t *testing.T) { CheckDestroy: testAccCheckFsxOntapFileSystemDestroy, Steps: []resource.TestStep{ { - Config: testAccOntapFileSystemSecurityGroupIds1Config(), + Config: testAccOntapFileSystemSecurityGroupIds1Config(rName), Check: resource.ComposeTestCheckFunc( testAccCheckFsxOntapFileSystemExists(resourceName, &filesystem1), resource.TestCheckResourceAttr(resourceName, "security_group_ids.#", "1"), @@ -208,7 +214,7 @@ func TestAccFSxOntapFileSystem_securityGroupIDs(t *testing.T) { ImportStateVerifyIgnore: []string{"security_group_ids"}, }, { - Config: testAccOntapFileSystemSecurityGroupIds2Config(), + Config: testAccOntapFileSystemSecurityGroupIds2Config(rName), Check: resource.ComposeTestCheckFunc( testAccCheckFsxOntapFileSystemExists(resourceName, &filesystem2), testAccCheckFsxOntapFileSystemRecreated(&filesystem1, &filesystem2), @@ -222,6 +228,7 @@ func TestAccFSxOntapFileSystem_securityGroupIDs(t *testing.T) { func TestAccFSxOntapFileSystem_routeTableIDs(t *testing.T) { var filesystem1 fsx.FileSystem resourceName := "aws_fsx_ontap_file_system.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(fsx.EndpointsID, t) }, @@ -230,7 +237,7 @@ func TestAccFSxOntapFileSystem_routeTableIDs(t *testing.T) { CheckDestroy: testAccCheckFsxOntapFileSystemDestroy, Steps: []resource.TestStep{ { - Config: testAccOntapFileSystemRouteTableConfig(), + Config: testAccOntapFileSystemRouteTableConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckFsxOntapFileSystemExists(resourceName, &filesystem1), resource.TestCheckResourceAttr(resourceName, "route_table_ids.#", "1"), @@ -250,6 +257,7 @@ func TestAccFSxOntapFileSystem_routeTableIDs(t *testing.T) { func TestAccFSxOntapFileSystem_tags(t *testing.T) { var filesystem1, filesystem2, filesystem3 fsx.FileSystem resourceName := "aws_fsx_ontap_file_system.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(fsx.EndpointsID, t) }, @@ -258,7 +266,7 @@ func TestAccFSxOntapFileSystem_tags(t *testing.T) { CheckDestroy: testAccCheckFsxOntapFileSystemDestroy, Steps: []resource.TestStep{ { - Config: testAccOntapFileSystemTags1Config("key1", "value1"), + Config: testAccOntapFileSystemTags1Config(rName, "key1", "value1"), Check: resource.ComposeTestCheckFunc( testAccCheckFsxOntapFileSystemExists(resourceName, &filesystem1), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), @@ -272,7 +280,7 @@ func TestAccFSxOntapFileSystem_tags(t *testing.T) { ImportStateVerifyIgnore: []string{"security_group_ids"}, }, { - Config: testAccOntapFileSystemTags2Config("key1", "value1updated", "key2", "value2"), + Config: testAccOntapFileSystemTags2Config(rName, "key1", "value1updated", "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckFsxOntapFileSystemExists(resourceName, &filesystem2), testAccCheckFsxOntapFileSystemNotRecreated(&filesystem1, &filesystem2), @@ -282,7 +290,7 @@ func TestAccFSxOntapFileSystem_tags(t *testing.T) { ), }, { - Config: testAccOntapFileSystemTags1Config("key2", "value2"), + Config: testAccOntapFileSystemTags1Config(rName, "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckFsxOntapFileSystemExists(resourceName, &filesystem3), testAccCheckFsxOntapFileSystemNotRecreated(&filesystem2, &filesystem3), @@ -297,6 +305,7 @@ func TestAccFSxOntapFileSystem_tags(t *testing.T) { func TestAccFSxOntapFileSystem_weeklyMaintenanceStartTime(t *testing.T) { var filesystem1, filesystem2 fsx.FileSystem resourceName := "aws_fsx_ontap_file_system.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(fsx.EndpointsID, t) }, @@ -305,7 +314,7 @@ func TestAccFSxOntapFileSystem_weeklyMaintenanceStartTime(t *testing.T) { CheckDestroy: testAccCheckFsxOntapFileSystemDestroy, Steps: []resource.TestStep{ { - Config: testAccOntapFileSystemWeeklyMaintenanceStartTimeConfig("1:01:01"), + Config: testAccOntapFileSystemWeeklyMaintenanceStartTimeConfig(rName, "1:01:01"), Check: resource.ComposeTestCheckFunc( testAccCheckFsxOntapFileSystemExists(resourceName, &filesystem1), resource.TestCheckResourceAttr(resourceName, "weekly_maintenance_start_time", "1:01:01"), @@ -318,7 +327,7 @@ func TestAccFSxOntapFileSystem_weeklyMaintenanceStartTime(t *testing.T) { ImportStateVerifyIgnore: []string{"security_group_ids"}, }, { - Config: testAccOntapFileSystemWeeklyMaintenanceStartTimeConfig("2:02:02"), + Config: testAccOntapFileSystemWeeklyMaintenanceStartTimeConfig(rName, "2:02:02"), Check: resource.ComposeTestCheckFunc( testAccCheckFsxOntapFileSystemExists(resourceName, &filesystem2), testAccCheckFsxOntapFileSystemNotRecreated(&filesystem1, &filesystem2), @@ -332,6 +341,7 @@ func TestAccFSxOntapFileSystem_weeklyMaintenanceStartTime(t *testing.T) { func TestAccFSxOntapFileSystem_automaticBackupRetentionDays(t *testing.T) { var filesystem1, filesystem2 fsx.FileSystem resourceName := "aws_fsx_ontap_file_system.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(fsx.EndpointsID, t) }, @@ -340,7 +350,7 @@ func TestAccFSxOntapFileSystem_automaticBackupRetentionDays(t *testing.T) { CheckDestroy: testAccCheckFsxOntapFileSystemDestroy, Steps: []resource.TestStep{ { - Config: testAccOntapFileSystemAutomaticBackupRetentionDaysConfig(90), + Config: testAccOntapFileSystemAutomaticBackupRetentionDaysConfig(rName, 90), Check: resource.ComposeTestCheckFunc( testAccCheckFsxOntapFileSystemExists(resourceName, &filesystem1), resource.TestCheckResourceAttr(resourceName, "automatic_backup_retention_days", "90"), @@ -353,7 +363,7 @@ func TestAccFSxOntapFileSystem_automaticBackupRetentionDays(t *testing.T) { ImportStateVerifyIgnore: []string{"security_group_ids"}, }, { - Config: testAccOntapFileSystemAutomaticBackupRetentionDaysConfig(0), + Config: testAccOntapFileSystemAutomaticBackupRetentionDaysConfig(rName, 0), Check: resource.ComposeTestCheckFunc( testAccCheckFsxOntapFileSystemExists(resourceName, &filesystem2), testAccCheckFsxOntapFileSystemNotRecreated(&filesystem1, &filesystem2), @@ -361,7 +371,7 @@ func TestAccFSxOntapFileSystem_automaticBackupRetentionDays(t *testing.T) { ), }, { - Config: testAccOntapFileSystemAutomaticBackupRetentionDaysConfig(1), + Config: testAccOntapFileSystemAutomaticBackupRetentionDaysConfig(rName, 1), Check: resource.ComposeTestCheckFunc( testAccCheckFsxOntapFileSystemExists(resourceName, &filesystem1), resource.TestCheckResourceAttr(resourceName, "automatic_backup_retention_days", "1"), @@ -374,6 +384,7 @@ func TestAccFSxOntapFileSystem_automaticBackupRetentionDays(t *testing.T) { func TestAccFSxOntapFileSystem_kmsKeyID(t *testing.T) { var filesystem fsx.FileSystem resourceName := "aws_fsx_ontap_file_system.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(fsx.EndpointsID, t) }, @@ -382,7 +393,7 @@ func TestAccFSxOntapFileSystem_kmsKeyID(t *testing.T) { CheckDestroy: testAccCheckFsxOntapFileSystemDestroy, Steps: []resource.TestStep{ { - Config: testAccOntapFileSystemKMSKeyIDConfig(), + Config: testAccOntapFileSystemKMSKeyIDConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckFsxOntapFileSystemExists(resourceName, &filesystem), resource.TestCheckResourceAttrPair(resourceName, "kms_key_id", "aws_kms_key.test", "arn"), @@ -401,6 +412,7 @@ func TestAccFSxOntapFileSystem_kmsKeyID(t *testing.T) { func TestAccFSxOntapFileSystem_dailyAutomaticBackupStartTime(t *testing.T) { var filesystem1, filesystem2 fsx.FileSystem resourceName := "aws_fsx_ontap_file_system.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(fsx.EndpointsID, t) }, @@ -409,7 +421,7 @@ func TestAccFSxOntapFileSystem_dailyAutomaticBackupStartTime(t *testing.T) { CheckDestroy: testAccCheckFsxLustreFileSystemDestroy, Steps: []resource.TestStep{ { - Config: testAccOntapFileSystemDailyAutomaticBackupStartTimeConfig("01:01"), + Config: testAccOntapFileSystemDailyAutomaticBackupStartTimeConfig(rName, "01:01"), Check: resource.ComposeTestCheckFunc( testAccCheckFsxOntapFileSystemExists(resourceName, &filesystem1), resource.TestCheckResourceAttr(resourceName, "daily_automatic_backup_start_time", "01:01"), @@ -422,7 +434,7 @@ func TestAccFSxOntapFileSystem_dailyAutomaticBackupStartTime(t *testing.T) { ImportStateVerifyIgnore: []string{"security_group_ids"}, }, { - Config: testAccOntapFileSystemDailyAutomaticBackupStartTimeConfig("02:02"), + Config: testAccOntapFileSystemDailyAutomaticBackupStartTimeConfig(rName, "02:02"), Check: resource.ComposeTestCheckFunc( testAccCheckFsxOntapFileSystemExists(resourceName, &filesystem2), testAccCheckFsxOntapFileSystemNotRecreated(&filesystem1, &filesystem2), @@ -497,30 +509,42 @@ func testAccCheckFsxOntapFileSystemRecreated(i, j *fsx.FileSystem) resource.Test } } -func testAccOntapFileSystemBaseConfig() string { - return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), ` +func testAccOntapFileSystemBaseConfig(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` data "aws_partition" "current" {} resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } } resource "aws_subnet" "test1" { vpc_id = aws_vpc.test.id cidr_block = "10.0.1.0/24" availability_zone = data.aws_availability_zones.available.names[0] + + tags = { + Name = %[1]q + } } resource "aws_subnet" "test2" { vpc_id = aws_vpc.test.id cidr_block = "10.0.2.0/24" availability_zone = data.aws_availability_zones.available.names[1] + + tags = { + Name = %[1]q + } } -`) +`, rName)) } -func testAccOntapFileSystemBasicConfig() string { - return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(), ` +func testAccOntapFileSystemBasicConfig(rName string) string { + return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(rName), ` resource "aws_fsx_ontap_file_system" "test" { storage_capacity = 1024 subnet_ids = [aws_subnet.test1.id, aws_subnet.test2.id] @@ -531,21 +555,25 @@ resource "aws_fsx_ontap_file_system" "test" { `) } -func testAccOntapFileSystemFsxAdminPasswordConfig(pass string) string { - return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(), fmt.Sprintf(` +func testAccOntapFileSystemFsxAdminPasswordConfig(rName, pass string) string { + return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(rName), fmt.Sprintf(` resource "aws_fsx_ontap_file_system" "test" { storage_capacity = 1024 subnet_ids = [aws_subnet.test1.id, aws_subnet.test2.id] deployment_type = "MULTI_AZ_1" throughput_capacity = 512 preferred_subnet_id = aws_subnet.test1.id - fsx_admin_password = %[1]q + fsx_admin_password = %[2]q + + tags = { + Name = %[1]q + } } -`, pass)) +`, rName, pass)) } -func testAccOntapFileSystemEndpointIPAddressRangeConfig() string { - return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(), ` +func testAccOntapFileSystemEndpointIPAddressRangeConfig(rName string) string { + return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(rName), fmt.Sprintf(` resource "aws_fsx_ontap_file_system" "test" { storage_capacity = 1024 subnet_ids = [aws_subnet.test1.id, aws_subnet.test2.id] @@ -553,12 +581,16 @@ resource "aws_fsx_ontap_file_system" "test" { throughput_capacity = 512 preferred_subnet_id = aws_subnet.test1.id endpoint_ip_address_range = "198.19.255.0/24" + + tags = { + Name = %[1]q + } } -`) +`, rName)) } -func testAccOntapFileSystemDiskIopsConfigurationConfig() string { - return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(), ` +func testAccOntapFileSystemDiskIopsConfigurationConfig(rName string) string { + return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(rName), fmt.Sprintf(` resource "aws_fsx_ontap_file_system" "test" { storage_capacity = 1024 subnet_ids = [aws_subnet.test1.id, aws_subnet.test2.id] @@ -570,17 +602,34 @@ resource "aws_fsx_ontap_file_system" "test" { mode = "USER_PROVISIONED" iops = 3072 } + + tags = { + Name = %[1]q + } } -`) +`, rName)) +} + +func testAccOntapFileSystemRouteTableConfig(rName string) string { + return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(rName), fmt.Sprintf(` +resource "aws_internet_gateway" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } -func testAccOntapFileSystemRouteTableConfig() string { - return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(), ` resource "aws_route_table" "test" { vpc_id = aws_vpc.test.id - lifecycle { - ignore_changes = [tags, tags_all] + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.test.id + } + + tags = { + Name = %[1]q } } @@ -591,12 +640,16 @@ resource "aws_fsx_ontap_file_system" "test" { throughput_capacity = 512 preferred_subnet_id = aws_subnet.test1.id route_table_ids = [aws_route_table.test.id] + + tags = { + Name = %[1]q + } } -`) +`, rName)) } -func testAccOntapFileSystemSecurityGroupIds1Config() string { - return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(), ` +func testAccOntapFileSystemSecurityGroupIds1Config(rName string) string { + return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(rName), fmt.Sprintf(` resource "aws_security_group" "test1" { description = "security group for FSx testing" vpc_id = aws_vpc.test.id @@ -614,6 +667,10 @@ resource "aws_security_group" "test1" { protocol = "-1" to_port = 0 } + + tags = { + Name = %[1]q + } } resource "aws_fsx_ontap_file_system" "test" { @@ -623,12 +680,16 @@ resource "aws_fsx_ontap_file_system" "test" { deployment_type = "MULTI_AZ_1" throughput_capacity = 512 preferred_subnet_id = aws_subnet.test1.id + + tags = { + Name = %[1]q + } } -`) +`, rName)) } -func testAccOntapFileSystemSecurityGroupIds2Config() string { - return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(), ` +func testAccOntapFileSystemSecurityGroupIds2Config(rName string) string { + return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(rName), fmt.Sprintf(` resource "aws_security_group" "test1" { description = "security group for FSx testing" vpc_id = aws_vpc.test.id @@ -646,6 +707,10 @@ resource "aws_security_group" "test1" { protocol = "-1" to_port = 0 } + + tags = { + Name = %[1]q + } } resource "aws_security_group" "test2" { @@ -665,6 +730,10 @@ resource "aws_security_group" "test2" { protocol = "-1" to_port = 0 } + + tags = { + Name = %[1]q + } } resource "aws_fsx_ontap_file_system" "test" { @@ -674,12 +743,16 @@ resource "aws_fsx_ontap_file_system" "test" { deployment_type = "MULTI_AZ_1" throughput_capacity = 512 preferred_subnet_id = aws_subnet.test1.id + + tags = { + Name = %[1]q + } } -`) +`, rName)) } -func testAccOntapFileSystemTags1Config(tagKey1, tagValue1 string) string { - return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(), fmt.Sprintf(` +func testAccOntapFileSystemTags1Config(rName, tagKey1, tagValue1 string) string { + return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(rName), fmt.Sprintf(` resource "aws_fsx_ontap_file_system" "test" { storage_capacity = 1024 subnet_ids = [aws_subnet.test1.id, aws_subnet.test2.id] @@ -694,8 +767,8 @@ resource "aws_fsx_ontap_file_system" "test" { `, tagKey1, tagValue1)) } -func testAccOntapFileSystemTags2Config(tagKey1, tagValue1, tagKey2, tagValue2 string) string { - return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(), fmt.Sprintf(` +func testAccOntapFileSystemTags2Config(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(rName), fmt.Sprintf(` resource "aws_fsx_ontap_file_system" "test" { storage_capacity = 1024 subnet_ids = [aws_subnet.test1.id, aws_subnet.test2.id] @@ -711,50 +784,62 @@ resource "aws_fsx_ontap_file_system" "test" { `, tagKey1, tagValue1, tagKey2, tagValue2)) } -func testAccOntapFileSystemWeeklyMaintenanceStartTimeConfig(weeklyMaintenanceStartTime string) string { - return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(), fmt.Sprintf(` +func testAccOntapFileSystemWeeklyMaintenanceStartTimeConfig(rName, weeklyMaintenanceStartTime string) string { + return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(rName), fmt.Sprintf(` resource "aws_fsx_ontap_file_system" "test" { storage_capacity = 1024 subnet_ids = [aws_subnet.test1.id, aws_subnet.test2.id] deployment_type = "MULTI_AZ_1" throughput_capacity = 512 preferred_subnet_id = aws_subnet.test1.id - weekly_maintenance_start_time = %[1]q + weekly_maintenance_start_time = %[2]q + + tags = { + Name = %[1]q + } } -`, weeklyMaintenanceStartTime)) +`, rName, weeklyMaintenanceStartTime)) } -func testAccOntapFileSystemDailyAutomaticBackupStartTimeConfig(dailyAutomaticBackupStartTime string) string { - return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(), fmt.Sprintf(` +func testAccOntapFileSystemDailyAutomaticBackupStartTimeConfig(rName, dailyAutomaticBackupStartTime string) string { + return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(rName), fmt.Sprintf(` resource "aws_fsx_ontap_file_system" "test" { storage_capacity = 1024 subnet_ids = [aws_subnet.test1.id, aws_subnet.test2.id] deployment_type = "MULTI_AZ_1" throughput_capacity = 512 preferred_subnet_id = aws_subnet.test1.id - daily_automatic_backup_start_time = %[1]q + daily_automatic_backup_start_time = %[2]q automatic_backup_retention_days = 1 + + tags = { + Name = %[1]q + } } -`, dailyAutomaticBackupStartTime)) +`, rName, dailyAutomaticBackupStartTime)) } -func testAccOntapFileSystemAutomaticBackupRetentionDaysConfig(retention int) string { - return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(), fmt.Sprintf(` +func testAccOntapFileSystemAutomaticBackupRetentionDaysConfig(rName string, retention int) string { + return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(rName), fmt.Sprintf(` resource "aws_fsx_ontap_file_system" "test" { storage_capacity = 1024 subnet_ids = [aws_subnet.test1.id, aws_subnet.test2.id] deployment_type = "MULTI_AZ_1" throughput_capacity = 512 preferred_subnet_id = aws_subnet.test1.id - automatic_backup_retention_days = %[1]d + automatic_backup_retention_days = %[2]d + + tags = { + Name = %[1]q + } } -`, retention)) +`, rName, retention)) } -func testAccOntapFileSystemKMSKeyIDConfig() string { - return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(), ` +func testAccOntapFileSystemKMSKeyIDConfig(rName string) string { + return acctest.ConfigCompose(testAccOntapFileSystemBaseConfig(rName), fmt.Sprintf(` resource "aws_kms_key" "test" { - description = "FSx KMS Testing key" + description = %[1]q deletion_window_in_days = 7 } @@ -765,6 +850,10 @@ resource "aws_fsx_ontap_file_system" "test" { throughput_capacity = 512 preferred_subnet_id = aws_subnet.test1.id kms_key_id = aws_kms_key.test.arn + + tags = { + Name = %[1]q + } } -`) +`, rName)) } From c81688d78e53c4611b84672d853dcf849cd3aa8d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 26 Oct 2021 17:15:55 -0400 Subject: [PATCH 12/28] r/aws_network_interface: Alphabetize attributes. --- internal/service/ec2/network_interface.go | 134 +++++++++------------- 1 file changed, 55 insertions(+), 79 deletions(-) diff --git a/internal/service/ec2/network_interface.go b/internal/service/ec2/network_interface.go index e936636034d..a5e4d934514 100644 --- a/internal/service/ec2/network_interface.go +++ b/internal/service/ec2/network_interface.go @@ -1,7 +1,6 @@ package ec2 import ( - "bytes" "fmt" "log" "math" @@ -15,7 +14,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/flex" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -33,114 +31,100 @@ func ResourceNetworkInterface() *schema.Resource { }, Schema: map[string]*schema.Schema{ - - "subnet_id": { + "attachment": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "device_index": { + Type: schema.TypeInt, + Required: true, + }, + "attachment_id": { + Type: schema.TypeString, + Computed: true, + }, + "instance": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "description": { Type: schema.TypeString, - Required: true, - ForceNew: true, + Optional: true, + }, + "interface_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(ec2.NetworkInterfaceCreationType_Values(), false), + }, + "ipv6_address_count": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ConflictsWith: []string{"ipv6_addresses"}, + }, + "ipv6_addresses": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.IsIPv6Address, + }, + ConflictsWith: []string{"ipv6_address_count"}, }, - "mac_address": { Type: schema.TypeString, Computed: true, }, - - "private_ip": { + "outpost_arn": { Type: schema.TypeString, - Optional: true, Computed: true, }, - "private_dns_name": { Type: schema.TypeString, Computed: true, }, - + "private_ip": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, "private_ips": { Type: schema.TypeSet, Optional: true, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "private_ips_count": { Type: schema.TypeInt, Optional: true, Computed: true, }, - "security_groups": { Type: schema.TypeSet, Optional: true, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "source_dest_check": { Type: schema.TypeBool, Optional: true, Default: true, }, - - "outpost_arn": { - Type: schema.TypeString, - Computed: true, - }, - - "description": { + "subnet_id": { Type: schema.TypeString, - Optional: true, - }, - - "attachment": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "instance": { - Type: schema.TypeString, - Required: true, - }, - "device_index": { - Type: schema.TypeInt, - Required: true, - }, - "attachment_id": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - Set: resourceEniAttachmentHash, + Required: true, + ForceNew: true, }, - "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), - "ipv6_address_count": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - ConflictsWith: []string{"ipv6_addresses"}, - }, - "ipv6_addresses": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.IsIPv6Address, - }, - ConflictsWith: []string{"ipv6_address_count"}, - }, - - "interface_type": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice(ec2.NetworkInterfaceCreationType_Values(), false), - }, }, CustomizeDiff: verify.SetTagsDiff, @@ -600,14 +584,6 @@ func resourceNetworkInterfaceDelete(d *schema.ResourceData, meta interface{}) er return nil } -func resourceEniAttachmentHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s-", m["instance"].(string))) - buf.WriteString(fmt.Sprintf("%d-", m["device_index"].(int))) - return create.StringHashcode(buf.String()) -} - func deleteNetworkInterface(conn *ec2.EC2, eniId string) error { _, err := conn.DeleteNetworkInterface(&ec2.DeleteNetworkInterfaceInput{ NetworkInterfaceId: aws.String(eniId), From 82bfdfb352264714165b18b6324b8f3548af47e1 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 26 Oct 2021 17:33:05 -0400 Subject: [PATCH 13/28] Tweak CHANGELOG entry. --- .changelog/21265.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changelog/21265.txt b/.changelog/21265.txt index 1e39655eab7..e0642b8076f 100644 --- a/.changelog/21265.txt +++ b/.changelog/21265.txt @@ -1,3 +1,3 @@ -```release-note:bug -resource/aws_route_table: Remove cross account ENIs managed by services like FSX for ONTAP from terraform managed routes to avoid deletion. +```release-note:bug-fix +resource/aws_route_table: Remove cross-account ENIs managed by AWS services like FSX for ONTAP from Terraform-managed routes to avoid diffs. ``` \ No newline at end of file From 54b24fe4a38a880c0719629babc9619733dc344e Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 26 Oct 2021 17:38:43 -0400 Subject: [PATCH 14/28] r/aws_network_interface: Add 'arn' and 'owner_id` attributes. d/aws_network_interface: Add 'arn' attribute. Acceptance test output: % make testacc TESTARGS='-run=TestAccEC2NetworkInterface_ENI_basic\|TestAccEC2NetworkInterfaceDataSource_basic' PKG_NAME=internal/service/ec2 ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/ec2/... -v -count 1 -parallel 20 -run=TestAccEC2NetworkInterface_ENI_basic\|TestAccEC2NetworkInterfaceDataSource_basic -timeout 180m === RUN TestAccEC2NetworkInterfaceDataSource_basic === PAUSE TestAccEC2NetworkInterfaceDataSource_basic === RUN TestAccEC2NetworkInterface_ENI_basic === PAUSE TestAccEC2NetworkInterface_ENI_basic === CONT TestAccEC2NetworkInterfaceDataSource_basic === CONT TestAccEC2NetworkInterface_ENI_basic --- PASS: TestAccEC2NetworkInterfaceDataSource_basic (62.12s) --- PASS: TestAccEC2NetworkInterface_ENI_basic (73.25s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/ec2 76.478s --- .changelog/21265.txt | 10 ++++- internal/service/ec2/network_interface.go | 40 ++++++++++++------- .../ec2/network_interface_data_source.go | 16 +++++++- .../ec2/network_interface_data_source_test.go | 2 + .../service/ec2/network_interface_test.go | 3 ++ .../docs/d/network_interface.html.markdown | 1 + website/docs/r/network_interface.markdown | 2 + 7 files changed, 58 insertions(+), 16 deletions(-) diff --git a/.changelog/21265.txt b/.changelog/21265.txt index e0642b8076f..61304ac3878 100644 --- a/.changelog/21265.txt +++ b/.changelog/21265.txt @@ -1,3 +1,11 @@ ```release-note:bug-fix -resource/aws_route_table: Remove cross-account ENIs managed by AWS services like FSX for ONTAP from Terraform-managed routes to avoid diffs. +resource/aws_route_table: Remove cross-account ENIs managed by AWS services like FSX for ONTAP from Terraform-managed routes to avoid diffs +``` + +```release-note:enhancement +resource/aws_network_interface: Add `arn` and `owner_id` attributes +``` + +```release-note:enhancement +data-source/aws_network_interface: Add `arn` attribute ``` \ No newline at end of file diff --git a/internal/service/ec2/network_interface.go b/internal/service/ec2/network_interface.go index a5e4d934514..7c70e4dea7b 100644 --- a/internal/service/ec2/network_interface.go +++ b/internal/service/ec2/network_interface.go @@ -8,6 +8,7 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -31,6 +32,10 @@ func ResourceNetworkInterface() *schema.Resource { }, Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, "attachment": { Type: schema.TypeSet, Optional: true, @@ -87,6 +92,10 @@ func ResourceNetworkInterface() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "owner_id": { + Type: schema.TypeString, + Computed: true, + }, "private_dns_name": { Type: schema.TypeString, Computed: true, @@ -248,6 +257,17 @@ func resourceNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("error setting attachment: %s", err) } + ownerID := aws.StringValue(eni.OwnerId) + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Service: ec2.ServiceName, + Region: meta.(*conns.AWSClient).Region, + AccountID: ownerID, + Resource: fmt.Sprintf("network-interface/%s", d.Id()), + }.String() + d.Set("arn", arn) + d.Set("owner_id", ownerID) + d.Set("interface_type", eni.InterfaceType) d.Set("description", eni.Description) d.Set("private_dns_name", eni.PrivateDnsName) @@ -568,33 +588,25 @@ func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) er func resourceNetworkInterfaceDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - log.Printf("[INFO] Deleting ENI: %s", d.Id()) - if err := resourceNetworkInterfaceDetach(d.Get("attachment").(*schema.Set), meta, d.Id()); err != nil { return err } - deleteEniOpts := ec2.DeleteNetworkInterfaceInput{ - NetworkInterfaceId: aws.String(d.Id()), - } - if _, err := conn.DeleteNetworkInterface(&deleteEniOpts); err != nil { - return fmt.Errorf("Error deleting ENI: %s", err) - } - - return nil + return deleteNetworkInterface(conn, d.Id()) } -func deleteNetworkInterface(conn *ec2.EC2, eniId string) error { +func deleteNetworkInterface(conn *ec2.EC2, networkInterfaceID string) error { + log.Printf("[INFO] Deleting EC2 Network Interface: %s", networkInterfaceID) _, err := conn.DeleteNetworkInterface(&ec2.DeleteNetworkInterfaceInput{ - NetworkInterfaceId: aws.String(eniId), + NetworkInterfaceId: aws.String(networkInterfaceID), }) - if tfawserr.ErrMessageContains(err, "InvalidNetworkInterfaceID.NotFound", "") { + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidNetworkInterfaceIDNotFound) { return nil } if err != nil { - return fmt.Errorf("error deleting ENI (%s): %s", eniId, err) + return fmt.Errorf("error deleting EC2 Network Interface (%s): %w", networkInterfaceID, err) } return nil diff --git a/internal/service/ec2/network_interface_data_source.go b/internal/service/ec2/network_interface_data_source.go index e481eac6911..5a48cf7b082 100644 --- a/internal/service/ec2/network_interface_data_source.go +++ b/internal/service/ec2/network_interface_data_source.go @@ -5,6 +5,7 @@ import ( "log" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -15,6 +16,10 @@ func DataSourceNetworkInterface() *schema.Resource { return &schema.Resource{ Read: dataSourceNetworkInterfaceRead, Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, "id": { Type: schema.TypeString, Optional: true, @@ -175,6 +180,16 @@ func dataSourceNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) er eni := resp.NetworkInterfaces[0] d.SetId(aws.StringValue(eni.NetworkInterfaceId)) + ownerID := aws.StringValue(eni.OwnerId) + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Service: ec2.ServiceName, + Region: meta.(*conns.AWSClient).Region, + AccountID: ownerID, + Resource: fmt.Sprintf("network-interface/%s", d.Id()), + }.String() + d.Set("arn", arn) + d.Set("owner_id", ownerID) if eni.Association != nil { d.Set("association", flattenNetworkInterfaceAssociation(eni.Association)) } @@ -188,7 +203,6 @@ func dataSourceNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) er d.Set("interface_type", eni.InterfaceType) d.Set("ipv6_addresses", flattenNetworkInterfaceIPv6Address(eni.Ipv6Addresses)) d.Set("mac_address", eni.MacAddress) - d.Set("owner_id", eni.OwnerId) d.Set("private_dns_name", eni.PrivateDnsName) d.Set("private_ip", eni.PrivateIpAddress) d.Set("private_ips", FlattenNetworkInterfacesPrivateIPAddresses(eni.PrivateIpAddresses)) diff --git a/internal/service/ec2/network_interface_data_source_test.go b/internal/service/ec2/network_interface_data_source_test.go index fc97a3bcb55..ca8c3e5f459 100644 --- a/internal/service/ec2/network_interface_data_source_test.go +++ b/internal/service/ec2/network_interface_data_source_test.go @@ -33,6 +33,8 @@ func TestAccEC2NetworkInterfaceDataSource_basic(t *testing.T) { resource.TestCheckResourceAttrPair(datasourceName, "subnet_id", resourceName, "subnet_id"), resource.TestCheckResourceAttr(datasourceName, "outpost_arn", ""), resource.TestCheckResourceAttrSet(datasourceName, "vpc_id"), + resource.TestCheckResourceAttrPair(datasourceName, "arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(datasourceName, "owner_id", resourceName, "owner_id"), ), }, }, diff --git a/internal/service/ec2/network_interface_test.go b/internal/service/ec2/network_interface_test.go index 8fb758ca131..b4d963eea56 100644 --- a/internal/service/ec2/network_interface_test.go +++ b/internal/service/ec2/network_interface_test.go @@ -2,6 +2,7 @@ package ec2_test import ( "fmt" + "regexp" "testing" "github.com/aws/aws-sdk-go/aws" @@ -31,6 +32,7 @@ func TestAccEC2NetworkInterface_ENI_basic(t *testing.T) { Config: testAccENIConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckENIExists(resourceName, &conf), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`network-interface/.+$`)), resource.TestCheckResourceAttr(resourceName, "attachment.#", "0"), resource.TestCheckResourceAttr(resourceName, "description", ""), resource.TestCheckResourceAttr(resourceName, "interface_type", "interface"), @@ -38,6 +40,7 @@ func TestAccEC2NetworkInterface_ENI_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "ipv6_addresses.#", "0"), resource.TestCheckResourceAttrSet(resourceName, "mac_address"), resource.TestCheckResourceAttr(resourceName, "outpost_arn", ""), + acctest.CheckResourceAttrAccountID(resourceName, "owner_id"), acctest.CheckResourceAttrPrivateDNSName(resourceName, "private_dns_name", &conf.PrivateIpAddress), resource.TestCheckResourceAttrSet(resourceName, "private_ip"), resource.TestCheckResourceAttr(resourceName, "private_ips.#", "1"), diff --git a/website/docs/d/network_interface.html.markdown b/website/docs/d/network_interface.html.markdown index ae4021b51a9..63400e8521b 100644 --- a/website/docs/d/network_interface.html.markdown +++ b/website/docs/d/network_interface.html.markdown @@ -31,6 +31,7 @@ See the [Network Interface](/docs/providers/aws/r/network_interface.html) for de Additionally, the following attributes are exported: +* `arn` - The ARN of the network interface. * `association` - The association information for an Elastic IP address (IPv4) associated with the network interface. See supported fields below. * `availability_zone` - The Availability Zone. * `description` - Description of the network interface. diff --git a/website/docs/r/network_interface.markdown b/website/docs/r/network_interface.markdown index 3f484db0f01..791ec902569 100644 --- a/website/docs/r/network_interface.markdown +++ b/website/docs/r/network_interface.markdown @@ -53,8 +53,10 @@ The `attachment` block supports: In addition to all arguments above, the following attributes are exported: +* `arn` - The ARN of the network interface. * `id` - The ID of the network interface. * `mac_address` - The MAC address of the network interface. +* `owner_id` - The AWS account ID of the owner of the network interface. * `private_dns_name` - The private DNS name of the network interface (IPv4). * `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block). From cb30abe39bcccb18a9c81f0440314a0014142667 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 27 Oct 2021 12:36:43 -0400 Subject: [PATCH 15/28] r/aws_network_interface: Move wait functionality. --- internal/service/ec2/enum.go | 5 + internal/service/ec2/network_interface.go | 149 +++++++++------------- internal/service/ec2/security_group.go | 22 +--- internal/service/ec2/status.go | 16 +++ internal/service/ec2/wait.go | 41 ++++++ 5 files changed, 122 insertions(+), 111 deletions(-) diff --git a/internal/service/ec2/enum.go b/internal/service/ec2/enum.go index e3f02a9fae3..54c2eba389c 100644 --- a/internal/service/ec2/enum.go +++ b/internal/service/ec2/enum.go @@ -35,3 +35,8 @@ const ( EBSSnapshotImportStateConverting = "converting" EBSSnapshotImportStateCompleted = "completed" ) + +// See https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateNetworkInterface.html#API_CreateNetworkInterface_Example_2_Response +const ( + NetworkInterfaceStatusPending = "pending" +) diff --git a/internal/service/ec2/network_interface.go b/internal/service/ec2/network_interface.go index 7c70e4dea7b..278a9876edd 100644 --- a/internal/service/ec2/network_interface.go +++ b/internal/service/ec2/network_interface.go @@ -42,14 +42,14 @@ func ResourceNetworkInterface() *schema.Resource { Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "device_index": { - Type: schema.TypeInt, - Required: true, - }, "attachment_id": { Type: schema.TypeString, Computed: true, }, + "device_index": { + Type: schema.TypeInt, + Required: true, + }, "instance": { Type: schema.TypeString, Required: true, @@ -141,80 +141,77 @@ func ResourceNetworkInterface() *schema.Resource { } func resourceNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*conns.AWSClient).EC2Conn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - request := &ec2.CreateNetworkInterfaceInput{ + input := &ec2.CreateNetworkInterfaceInput{ SubnetId: aws.String(d.Get("subnet_id").(string)), TagSpecifications: ec2TagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeNetworkInterface), } - if v, ok := d.GetOk("security_groups"); ok && v.(*schema.Set).Len() > 0 { - request.Groups = flex.ExpandStringSet(v.(*schema.Set)) + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) } - if v, ok := d.GetOk("private_ips"); ok && v.(*schema.Set).Len() > 0 { - request.PrivateIpAddresses = ExpandPrivateIPAddresses(v.(*schema.Set).List()) + if v, ok := d.GetOk("interface_type"); ok { + input.InterfaceType = aws.String(v.(string)) } - if v, ok := d.GetOk("description"); ok { - request.Description = aws.String(v.(string)) + if v, ok := d.GetOk("ipv6_address_count"); ok { + input.Ipv6AddressCount = aws.Int64(int64(v.(int))) } - if v, ok := d.GetOk("private_ips_count"); ok { - request.SecondaryPrivateIpAddressCount = aws.Int64(int64(v.(int))) + if v, ok := d.GetOk("ipv6_addresses"); ok && v.(*schema.Set).Len() > 0 { + input.Ipv6Addresses = expandIP6Addresses(v.(*schema.Set).List()) } - if v, ok := d.GetOk("ipv6_address_count"); ok { - request.Ipv6AddressCount = aws.Int64(int64(v.(int))) + if v, ok := d.GetOk("private_ips"); ok && v.(*schema.Set).Len() > 0 { + input.PrivateIpAddresses = ExpandPrivateIPAddresses(v.(*schema.Set).List()) } - if v, ok := d.GetOk("ipv6_addresses"); ok && v.(*schema.Set).Len() > 0 { - request.Ipv6Addresses = expandIP6Addresses(v.(*schema.Set).List()) + if v, ok := d.GetOk("private_ips_count"); ok { + input.SecondaryPrivateIpAddressCount = aws.Int64(int64(v.(int))) } - if v, ok := d.GetOk("interface_type"); ok { - request.InterfaceType = aws.String(v.(string)) + if v, ok := d.GetOk("security_groups"); ok && v.(*schema.Set).Len() > 0 { + input.Groups = flex.ExpandStringSet(v.(*schema.Set)) } - log.Printf("[DEBUG] Creating network interface") - resp, err := conn.CreateNetworkInterface(request) + log.Printf("[DEBUG] Creating EC2 Network Interface: %s", input) + output, err := conn.CreateNetworkInterface(input) + if err != nil { - return fmt.Errorf("Error creating ENI: %s", err) + return fmt.Errorf("error creating EC2 Network Interface: %w", err) } - d.SetId(aws.StringValue(resp.NetworkInterface.NetworkInterfaceId)) + d.SetId(aws.StringValue(output.NetworkInterface.NetworkInterfaceId)) - if err := waitForNetworkInterfaceCreation(conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { - return fmt.Errorf("error waiting for Network Interface (%s) creation: %s", d.Id(), err) + if _, err := WaitNetworkInterfaceCreated(conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return fmt.Errorf("error waiting for EC2 Network Interface (%s) create: %w", d.Id(), err) } //Default value is enabled if !d.Get("source_dest_check").(bool) { - request := &ec2.ModifyNetworkInterfaceAttributeInput{ + input := &ec2.ModifyNetworkInterfaceAttributeInput{ NetworkInterfaceId: aws.String(d.Id()), SourceDestCheck: &ec2.AttributeBooleanValue{Value: aws.Bool(false)}, } - _, err := conn.ModifyNetworkInterfaceAttribute(request) + _, err := conn.ModifyNetworkInterfaceAttribute(input) + if err != nil { - return fmt.Errorf("Failure updating SourceDestCheck: %s", err) + return fmt.Errorf("error setting EC2 Network Interface (%s) SourceDestCheck: %w", d.Id(), err) } } if v, ok := d.GetOk("attachment"); ok && v.(*schema.Set).Len() > 0 { attachment := v.(*schema.Set).List()[0].(map[string]interface{}) - di := attachment["device_index"].(int) - attachReq := &ec2.AttachNetworkInterfaceInput{ - DeviceIndex: aws.Int64(int64(di)), - InstanceId: aws.String(attachment["instance"].(string)), - NetworkInterfaceId: aws.String(d.Id()), - } - _, err := conn.AttachNetworkInterface(attachReq) + + err := attachNetworkInterface(conn, d.Id(), attachment["instance"].(string), attachment["device_index"].(int)) + if err != nil { - return fmt.Errorf("Error attaching ENI: %s", err) + return err } } @@ -371,17 +368,13 @@ func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) er } // if there is a new attachment, attach it - if na != nil && len(na.(*schema.Set).List()) > 0 { - new_attachment := na.(*schema.Set).List()[0].(map[string]interface{}) - di := new_attachment["device_index"].(int) - attach_request := &ec2.AttachNetworkInterfaceInput{ - DeviceIndex: aws.Int64(int64(di)), - InstanceId: aws.String(new_attachment["instance"].(string)), - NetworkInterfaceId: aws.String(d.Id()), - } - _, attach_err := conn.AttachNetworkInterface(attach_request) - if attach_err != nil { - return fmt.Errorf("Error attaching ENI: %s", attach_err) + if na != nil && na.(*schema.Set).Len() > 0 { + attachment := na.(*schema.Set).List()[0].(map[string]interface{}) + + err := attachNetworkInterface(conn, d.Id(), attachment["instance"].(string), attachment["device_index"].(int)) + + if err != nil { + return err } } } @@ -595,6 +588,23 @@ func resourceNetworkInterfaceDelete(d *schema.ResourceData, meta interface{}) er return deleteNetworkInterface(conn, d.Id()) } +func attachNetworkInterface(conn *ec2.EC2, networkInterfaceID, instanceID string, deviceIndex int) error { + input := &ec2.AttachNetworkInterfaceInput{ + DeviceIndex: aws.Int64(int64(deviceIndex)), + InstanceId: aws.String(instanceID), + NetworkInterfaceId: aws.String(networkInterfaceID), + } + + log.Printf("[INFO] Attaching EC2 Network Interface: %s", input) + _, err := conn.AttachNetworkInterface(input) + + if err != nil { + return fmt.Errorf("error attaching EC2 Network Interface (%s/%s): %w", networkInterfaceID, instanceID, err) + } + + return nil +} + func deleteNetworkInterface(conn *ec2.EC2, networkInterfaceID string) error { log.Printf("[INFO] Deleting EC2 Network Interface: %s", networkInterfaceID) _, err := conn.DeleteNetworkInterface(&ec2.DeleteNetworkInterfaceInput{ @@ -697,46 +707,3 @@ func networkInterfaceAttachmentStateRefresh(conn *ec2.EC2, eniId string) resourc } } } - -func networkInterfaceStateRefresh(conn *ec2.EC2, eniId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - resp, err := conn.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ - NetworkInterfaceIds: aws.StringSlice([]string{eniId}), - }) - - if tfawserr.ErrMessageContains(err, "InvalidNetworkInterfaceID.NotFound", "") { - return nil, "", nil - } - - if err != nil { - return nil, "", fmt.Errorf("error describing ENI (%s): %s", eniId, err) - } - - n := len(resp.NetworkInterfaces) - switch n { - case 0: - return nil, "", nil - - case 1: - eni := resp.NetworkInterfaces[0] - return eni, aws.StringValue(eni.Status), nil - - default: - return nil, "", fmt.Errorf("found %d ENIs for %s, expected 1", n, eniId) - } - } -} - -func waitForNetworkInterfaceCreation(conn *ec2.EC2, id string, timeout time.Duration) error { - stateConf := &resource.StateChangeConf{ - Pending: []string{"pending"}, - Target: []string{ec2.NetworkInterfaceStatusAvailable}, - Refresh: networkInterfaceStateRefresh(conn, id), - Timeout: timeout, - Delay: 30 * time.Second, - } - - _, err := stateConf.WaitForState() - - return err -} diff --git a/internal/service/ec2/security_group.go b/internal/service/ec2/security_group.go index b450fe3ed60..5a54a2bda42 100644 --- a/internal/service/ec2/security_group.go +++ b/internal/service/ec2/security_group.go @@ -1367,25 +1367,7 @@ func deleteLingeringLambdaENIs(conn *ec2.EC2, filterName, resourceId string, tim eniId := aws.StringValue(eni.NetworkInterfaceId) if eni.Attachment != nil && aws.StringValue(eni.Attachment.InstanceOwnerId) == "amazon-aws" { - // Hyperplane attached ENI. - // Wait for it to be moved into a removable state. - stateConf := &resource.StateChangeConf{ - Pending: []string{ - ec2.NetworkInterfaceStatusInUse, - }, - Target: []string{ - ec2.NetworkInterfaceStatusAvailable, - }, - Refresh: networkInterfaceStateRefresh(conn, eniId), - Timeout: timeout, - Delay: 10 * time.Second, - MinTimeout: 10 * time.Second, - // Handle EC2 ENI eventual consistency. It can take up to 3 minutes. - ContinuousTargetOccurence: 18, - NotFoundChecks: 1, - } - - eniRaw, err := stateConf.WaitForState() + networkInterface, err := WaitNetworkInterfaceAvailableAfterUse(conn, eniId, timeout) if tfresource.NotFound(err) { continue @@ -1395,7 +1377,7 @@ func deleteLingeringLambdaENIs(conn *ec2.EC2, filterName, resourceId string, tim return fmt.Errorf("error waiting for Lambda V2N ENI (%s) to become available for detachment: %w", eniId, err) } - eni = eniRaw.(*ec2.NetworkInterface) + eni = networkInterface } err = detachNetworkInterface(conn, eni, timeout) diff --git a/internal/service/ec2/status.go b/internal/service/ec2/status.go index 8228641f6c1..917d875fed2 100644 --- a/internal/service/ec2/status.go +++ b/internal/service/ec2/status.go @@ -511,6 +511,22 @@ func StatusManagedPrefixListState(conn *ec2.EC2, id string) resource.StateRefres } } +func StatusNetworkInterfaceStatus(conn *ec2.EC2, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindNetworkInterfaceByID(conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.Status), nil + } +} + func StatusPlacementGroupState(conn *ec2.EC2, name string) resource.StateRefreshFunc { return func() (interface{}, string, error) { output, err := FindPlacementGroupByName(conn, name) diff --git a/internal/service/ec2/wait.go b/internal/service/ec2/wait.go index f6fd500d227..7ba71b2daa1 100644 --- a/internal/service/ec2/wait.go +++ b/internal/service/ec2/wait.go @@ -756,6 +756,47 @@ func WaitManagedPrefixListDeleted(conn *ec2.EC2, id string) (*ec2.ManagedPrefixL return nil, err } +// Hyperplane attached ENI. +// Wait for it to be moved into a removable state. +func WaitNetworkInterfaceAvailableAfterUse(conn *ec2.EC2, id string, timeout time.Duration) (*ec2.NetworkInterface, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.NetworkInterfaceStatusInUse}, + Target: []string{ec2.NetworkInterfaceStatusAvailable}, + Timeout: timeout, + Refresh: StatusNetworkInterfaceStatus(conn, id), + Delay: 10 * time.Second, + MinTimeout: 10 * time.Second, + // Handle EC2 ENI eventual consistency. It can take up to 3 minutes. + ContinuousTargetOccurence: 18, + NotFoundChecks: 1, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.NetworkInterface); ok { + return output, err + } + + return nil, err +} + +func WaitNetworkInterfaceCreated(conn *ec2.EC2, id string, timeout time.Duration) (*ec2.NetworkInterface, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{NetworkInterfaceStatusPending}, + Target: []string{ec2.NetworkInterfaceStatusAvailable}, + Timeout: timeout, + Refresh: StatusNetworkInterfaceStatus(conn, id), + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.NetworkInterface); ok { + return output, err + } + + return nil, err +} + const ( PlacementGroupCreatedTimeout = 5 * time.Minute PlacementGroupDeletedTimeout = 5 * time.Minute From 4cdfe3e6fea2a79aca7f6600c8ef9990241e58e2 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 27 Oct 2021 15:03:50 -0400 Subject: [PATCH 16/28] r/aws_network_interface: Retry Read for new resource. Acceptance test output: % make testacc PKG_NAME=internal/service/ec2 TESTARGS='-run=TestAccEC2NetworkInterface_' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/ec2/... -v -count 1 -parallel 20 -run=TestAccEC2NetworkInterface_ -timeout 180m === RUN TestAccEC2NetworkInterface_ENI_basic === PAUSE TestAccEC2NetworkInterface_ENI_basic === RUN TestAccEC2NetworkInterface_ENI_ipv6 === PAUSE TestAccEC2NetworkInterface_ENI_ipv6 === RUN TestAccEC2NetworkInterface_ENI_tags === PAUSE TestAccEC2NetworkInterface_ENI_tags === RUN TestAccEC2NetworkInterface_ENI_ipv6Count === PAUSE TestAccEC2NetworkInterface_ENI_ipv6Count === RUN TestAccEC2NetworkInterface_ENI_disappears === PAUSE TestAccEC2NetworkInterface_ENI_disappears === RUN TestAccEC2NetworkInterface_ENI_description === PAUSE TestAccEC2NetworkInterface_ENI_description === RUN TestAccEC2NetworkInterface_ENI_attachment === PAUSE TestAccEC2NetworkInterface_ENI_attachment === RUN TestAccEC2NetworkInterface_ENI_ignoreExternalAttachment === PAUSE TestAccEC2NetworkInterface_ENI_ignoreExternalAttachment === RUN TestAccEC2NetworkInterface_ENI_sourceDestCheck === PAUSE TestAccEC2NetworkInterface_ENI_sourceDestCheck === RUN TestAccEC2NetworkInterface_ENI_privateIPsCount === PAUSE TestAccEC2NetworkInterface_ENI_privateIPsCount === RUN TestAccEC2NetworkInterface_ENIInterfaceType_efa === PAUSE TestAccEC2NetworkInterface_ENIInterfaceType_efa === CONT TestAccEC2NetworkInterface_ENI_basic === CONT TestAccEC2NetworkInterface_ENI_attachment === CONT TestAccEC2NetworkInterface_ENI_sourceDestCheck === CONT TestAccEC2NetworkInterface_ENIInterfaceType_efa === CONT TestAccEC2NetworkInterface_ENI_disappears === CONT TestAccEC2NetworkInterface_ENI_privateIPsCount === CONT TestAccEC2NetworkInterface_ENI_ipv6Count === CONT TestAccEC2NetworkInterface_ENI_description === CONT TestAccEC2NetworkInterface_ENI_ignoreExternalAttachment === CONT TestAccEC2NetworkInterface_ENI_tags === CONT TestAccEC2NetworkInterface_ENI_ipv6 --- PASS: TestAccEC2NetworkInterface_ENI_disappears (47.72s) --- PASS: TestAccEC2NetworkInterface_ENI_basic (58.38s) --- PASS: TestAccEC2NetworkInterface_ENIInterfaceType_efa (60.51s) --- PASS: TestAccEC2NetworkInterface_ENI_description (81.99s) --- PASS: TestAccEC2NetworkInterface_ENI_tags (101.62s) --- PASS: TestAccEC2NetworkInterface_ENI_ipv6 (105.45s) --- PASS: TestAccEC2NetworkInterface_ENI_sourceDestCheck (107.26s) --- PASS: TestAccEC2NetworkInterface_ENI_ipv6Count (125.96s) --- PASS: TestAccEC2NetworkInterface_ENI_privateIPsCount (132.52s) --- PASS: TestAccEC2NetworkInterface_ENI_ignoreExternalAttachment (145.22s) --- PASS: TestAccEC2NetworkInterface_ENI_attachment (162.14s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/ec2 165.460s --- internal/service/ec2/network_interface.go | 53 +++++++++---------- .../service/ec2/network_interface_test.go | 35 +++++------- 2 files changed, 39 insertions(+), 49 deletions(-) diff --git a/internal/service/ec2/network_interface.go b/internal/service/ec2/network_interface.go index 278a9876edd..bedcb5b0b5a 100644 --- a/internal/service/ec2/network_interface.go +++ b/internal/service/ec2/network_interface.go @@ -223,26 +223,21 @@ func resourceNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) erro defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesInput{ - NetworkInterfaceIds: []*string{aws.String(d.Id())}, + outputRaw, err := tfresource.RetryWhenNewResourceNotFound(1*time.Minute, func() (interface{}, error) { + return FindNetworkInterfaceByID(conn, d.Id()) + }, d.IsNewResource()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] EC2 Network Interface (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil } - describeResp, err := conn.DescribeNetworkInterfaces(describe_network_interfaces_request) if err != nil { - if tfawserr.ErrMessageContains(err, "InvalidNetworkInterfaceID.NotFound", "") { - // The ENI is gone now, so just remove it from the state - log.Printf("[WARN] EC2 Network Interface (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } - - return fmt.Errorf("Error retrieving ENI: %s", err) - } - if len(describeResp.NetworkInterfaces) != 1 { - return fmt.Errorf("Unable to find ENI: %#v", describeResp.NetworkInterfaces) + return fmt.Errorf("error reading EC2 Network Interface (%s): %w", d.Id(), err) } - eni := describeResp.NetworkInterfaces[0] + eni := outputRaw.(*ec2.NetworkInterface) attachment := []map[string]interface{}{} @@ -251,7 +246,7 @@ func resourceNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) erro } if err := d.Set("attachment", attachment); err != nil { - return fmt.Errorf("error setting attachment: %s", err) + return fmt.Errorf("error setting attachment: %w", err) } ownerID := aws.StringValue(eni.OwnerId) @@ -263,32 +258,34 @@ func resourceNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) erro Resource: fmt.Sprintf("network-interface/%s", d.Id()), }.String() d.Set("arn", arn) - d.Set("owner_id", ownerID) - d.Set("interface_type", eni.InterfaceType) d.Set("description", eni.Description) - d.Set("private_dns_name", eni.PrivateDnsName) + d.Set("interface_type", eni.InterfaceType) + + d.Set("ipv6_address_count", len(eni.Ipv6Addresses)) + + if err := d.Set("ipv6_addresses", flattenNetworkInterfaceIPv6Address(eni.Ipv6Addresses)); err != nil { + return fmt.Errorf("error setting ipv6_addresses: %w", err) + } + d.Set("mac_address", eni.MacAddress) - d.Set("private_ip", eni.PrivateIpAddress) d.Set("outpost_arn", eni.OutpostArn) + d.Set("owner_id", ownerID) + d.Set("private_dns_name", eni.PrivateDnsName) + d.Set("private_ip", eni.PrivateIpAddress) if err := d.Set("private_ips", FlattenNetworkInterfacesPrivateIPAddresses(eni.PrivateIpAddresses)); err != nil { - return fmt.Errorf("error setting private_ips: %s", err) + return fmt.Errorf("error setting private_ips: %w", err) } d.Set("private_ips_count", len(eni.PrivateIpAddresses)-1) if err := d.Set("security_groups", FlattenGroupIdentifiers(eni.Groups)); err != nil { - return fmt.Errorf("error setting security_groups: %s", err) + return fmt.Errorf("error setting security_groups: %w", err) } d.Set("source_dest_check", eni.SourceDestCheck) d.Set("subnet_id", eni.SubnetId) - d.Set("ipv6_address_count", len(eni.Ipv6Addresses)) - - if err := d.Set("ipv6_addresses", flattenNetworkInterfaceIPv6Address(eni.Ipv6Addresses)); err != nil { - return fmt.Errorf("error setting ipv6 addresses: %s", err) - } tags := KeyValueTags(eni.TagSet).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) @@ -571,7 +568,7 @@ func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) er o, n := d.GetChange("tags_all") if err := UpdateTags(conn, d.Id(), o, n); err != nil { - return fmt.Errorf("error updating EC2 Network Interface (%s) tags: %s", d.Id(), err) + return fmt.Errorf("error updating EC2 Network Interface (%s) tags: %w", d.Id(), err) } } diff --git a/internal/service/ec2/network_interface_test.go b/internal/service/ec2/network_interface_test.go index b4d963eea56..5e4e8428f1e 100644 --- a/internal/service/ec2/network_interface_test.go +++ b/internal/service/ec2/network_interface_test.go @@ -7,13 +7,13 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccEC2NetworkInterface_ENI_basic(t *testing.T) { @@ -478,7 +478,7 @@ func TestAccEC2NetworkInterface_ENIInterfaceType_efa(t *testing.T) { }) } -func testAccCheckENIExists(n string, res *ec2.NetworkInterface) resource.TestCheckFunc { +func testAccCheckENIExists(n string, v *ec2.NetworkInterface) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -486,49 +486,42 @@ func testAccCheckENIExists(n string, res *ec2.NetworkInterface) resource.TestChe } if rs.Primary.ID == "" { - return fmt.Errorf("No ENI ID is set") + return fmt.Errorf("No EC2 Network Interface ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - input := &ec2.DescribeNetworkInterfacesInput{ - NetworkInterfaceIds: []*string{aws.String(rs.Primary.ID)}, - } - describeResp, err := conn.DescribeNetworkInterfaces(input) + + output, err := tfec2.FindNetworkInterfaceByID(conn, rs.Primary.ID) if err != nil { return err } - if len(describeResp.NetworkInterfaces) != 1 || - *describeResp.NetworkInterfaces[0].NetworkInterfaceId != rs.Primary.ID { - return fmt.Errorf("ENI not found") - } - - *res = *describeResp.NetworkInterfaces[0] + *v = *output return nil } } func testAccCheckENIDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn + for _, rs := range s.RootModule().Resources { if rs.Type != "aws_network_interface" { continue } - conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn - input := &ec2.DescribeNetworkInterfacesInput{ - NetworkInterfaceIds: []*string{aws.String(rs.Primary.ID)}, + _, err := tfec2.FindNetworkInterfaceByID(conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue } - _, err := conn.DescribeNetworkInterfaces(input) if err != nil { - if tfawserr.ErrMessageContains(err, "InvalidNetworkInterfaceID.NotFound", "") { - return nil - } - return err } + + return fmt.Errorf("EC2 Network Interface %s still exists", rs.Primary.ID) } return nil From 512434184623aa913997e513137bec0f8ba1b0ad Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 27 Oct 2021 17:45:13 -0400 Subject: [PATCH 17/28] r/aws_network_interface: Start to consolidate attach/detach code. Acceptance test output: % make testacc PKG_NAME=internal/service/ec2 TESTARGS='-run=TestAccEC2NetworkInterface_' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/ec2/... -v -count 1 -parallel 20 -run=TestAccEC2NetworkInterface_ -timeout 180m === RUN TestAccEC2NetworkInterface_ENI_basic === PAUSE TestAccEC2NetworkInterface_ENI_basic === RUN TestAccEC2NetworkInterface_ENI_ipv6 === PAUSE TestAccEC2NetworkInterface_ENI_ipv6 === RUN TestAccEC2NetworkInterface_ENI_tags === PAUSE TestAccEC2NetworkInterface_ENI_tags === RUN TestAccEC2NetworkInterface_ENI_ipv6Count === PAUSE TestAccEC2NetworkInterface_ENI_ipv6Count === RUN TestAccEC2NetworkInterface_ENI_disappears === PAUSE TestAccEC2NetworkInterface_ENI_disappears === RUN TestAccEC2NetworkInterface_ENI_description === PAUSE TestAccEC2NetworkInterface_ENI_description === RUN TestAccEC2NetworkInterface_ENI_attachment === PAUSE TestAccEC2NetworkInterface_ENI_attachment === RUN TestAccEC2NetworkInterface_ENI_ignoreExternalAttachment === PAUSE TestAccEC2NetworkInterface_ENI_ignoreExternalAttachment === RUN TestAccEC2NetworkInterface_ENI_sourceDestCheck === PAUSE TestAccEC2NetworkInterface_ENI_sourceDestCheck === RUN TestAccEC2NetworkInterface_ENI_privateIPsCount === PAUSE TestAccEC2NetworkInterface_ENI_privateIPsCount === RUN TestAccEC2NetworkInterface_ENIInterfaceType_efa === PAUSE TestAccEC2NetworkInterface_ENIInterfaceType_efa === CONT TestAccEC2NetworkInterface_ENI_basic === CONT TestAccEC2NetworkInterface_ENI_attachment === CONT TestAccEC2NetworkInterface_ENI_privateIPsCount === CONT TestAccEC2NetworkInterface_ENI_ignoreExternalAttachment === CONT TestAccEC2NetworkInterface_ENI_ipv6Count === CONT TestAccEC2NetworkInterface_ENI_description === CONT TestAccEC2NetworkInterface_ENI_sourceDestCheck === CONT TestAccEC2NetworkInterface_ENI_disappears === CONT TestAccEC2NetworkInterface_ENI_tags === CONT TestAccEC2NetworkInterface_ENI_ipv6 === CONT TestAccEC2NetworkInterface_ENIInterfaceType_efa --- PASS: TestAccEC2NetworkInterface_ENI_disappears (50.79s) --- PASS: TestAccEC2NetworkInterface_ENI_basic (58.37s) --- PASS: TestAccEC2NetworkInterface_ENIInterfaceType_efa (68.21s) --- PASS: TestAccEC2NetworkInterface_ENI_description (92.56s) --- PASS: TestAccEC2NetworkInterface_ENI_sourceDestCheck (111.39s) --- PASS: TestAccEC2NetworkInterface_ENI_ipv6 (113.78s) --- PASS: TestAccEC2NetworkInterface_ENI_tags (115.93s) --- PASS: TestAccEC2NetworkInterface_ENI_ipv6Count (137.20s) --- PASS: TestAccEC2NetworkInterface_ENI_privateIPsCount (142.19s) --- PASS: TestAccEC2NetworkInterface_ENI_ignoreExternalAttachment (311.93s) --- PASS: TestAccEC2NetworkInterface_ENI_attachment (344.06s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/ec2 349.531s --- internal/service/ec2/errors.go | 1 + internal/service/ec2/find.go | 40 +++++- internal/service/ec2/network_interface.go | 145 ++++++---------------- internal/service/ec2/security_group.go | 8 +- internal/service/ec2/status.go | 16 +++ internal/service/ec2/wait.go | 38 +++++- 6 files changed, 130 insertions(+), 118 deletions(-) diff --git a/internal/service/ec2/errors.go b/internal/service/ec2/errors.go index a79527a9e4c..27d403b933b 100644 --- a/internal/service/ec2/errors.go +++ b/internal/service/ec2/errors.go @@ -12,6 +12,7 @@ import ( const ( ErrCodeGatewayNotAttached = "Gateway.NotAttached" ErrCodeInvalidAssociationIDNotFound = "InvalidAssociationID.NotFound" + ErrCodeInvalidAttachmentIDNotFound = "InvalidAttachmentID.NotFound" ErrCodeInvalidParameter = "InvalidParameter" ErrCodeInvalidParameterException = "InvalidParameterException" ErrCodeInvalidParameterValue = "InvalidParameterValue" diff --git a/internal/service/ec2/find.go b/internal/service/ec2/find.go index 8257af9589b..8eea42ca87b 100644 --- a/internal/service/ec2/find.go +++ b/internal/service/ec2/find.go @@ -236,11 +236,7 @@ func FindNetworkACLEntry(conn *ec2.EC2, networkAclID string, egress bool, ruleNu return nil, nil } -func FindNetworkInterfaceByID(conn *ec2.EC2, id string) (*ec2.NetworkInterface, error) { - input := &ec2.DescribeNetworkInterfacesInput{ - NetworkInterfaceIds: aws.StringSlice([]string{id}), - } - +func FindNetworkInterface(conn *ec2.EC2, input *ec2.DescribeNetworkInterfacesInput) (*ec2.NetworkInterface, error) { output, err := conn.DescribeNetworkInterfaces(input) if tfawserr.ErrCodeEquals(err, ErrCodeInvalidNetworkInterfaceIDNotFound) { @@ -266,7 +262,19 @@ func FindNetworkInterfaceByID(conn *ec2.EC2, id string) (*ec2.NetworkInterface, return nil, tfresource.NewTooManyResultsError(count, input) } - networkInterface := output.NetworkInterfaces[0] + return output.NetworkInterfaces[0], nil +} + +func FindNetworkInterfaceByID(conn *ec2.EC2, id string) (*ec2.NetworkInterface, error) { + input := &ec2.DescribeNetworkInterfacesInput{ + NetworkInterfaceIds: aws.StringSlice([]string{id}), + } + + networkInterface, err := FindNetworkInterface(conn, input) + + if err != nil { + return nil, err + } // Eventual consistency check. if aws.StringValue(networkInterface.NetworkInterfaceId) != id { @@ -278,6 +286,26 @@ func FindNetworkInterfaceByID(conn *ec2.EC2, id string) (*ec2.NetworkInterface, return networkInterface, nil } +func FindNetworkInterfaceAttachmentByID(conn *ec2.EC2, id string) (*ec2.NetworkInterfaceAttachment, error) { + input := &ec2.DescribeNetworkInterfacesInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "attachment.attachment-id": id, + }), + } + + networkInterface, err := FindNetworkInterface(conn, input) + + if err != nil { + return nil, err + } + + if networkInterface.Attachment == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return networkInterface.Attachment, nil +} + func FindNetworkInterfaceSecurityGroup(conn *ec2.EC2, networkInterfaceID string, securityGroupID string) (*ec2.GroupIdentifier, error) { networkInterface, err := FindNetworkInterfaceByID(conn, networkInterfaceID) diff --git a/internal/service/ec2/network_interface.go b/internal/service/ec2/network_interface.go index bedcb5b0b5a..13d2ca510e7 100644 --- a/internal/service/ec2/network_interface.go +++ b/internal/service/ec2/network_interface.go @@ -208,7 +208,7 @@ func resourceNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) er if v, ok := d.GetOk("attachment"); ok && v.(*schema.Set).Len() > 0 { attachment := v.(*schema.Set).List()[0].(map[string]interface{}) - err := attachNetworkInterface(conn, d.Id(), attachment["instance"].(string), attachment["device_index"].(int)) + err := attachNetworkInterface(conn, d.Id(), attachment["instance"].(string), attachment["device_index"].(int), 5*time.Minute) if err != nil { return err @@ -321,54 +321,27 @@ func networkInterfaceAttachmentRefreshFunc(conn *ec2.EC2, id string) resource.St } } -func resourceNetworkInterfaceDetach(oa *schema.Set, meta interface{}, eniId string) error { - // if there was an old attachment, remove it - if oa != nil && len(oa.List()) > 0 { - old_attachment := oa.List()[0].(map[string]interface{}) - detach_request := &ec2.DetachNetworkInterfaceInput{ - AttachmentId: aws.String(old_attachment["attachment_id"].(string)), - Force: aws.Bool(true), - } - conn := meta.(*conns.AWSClient).EC2Conn - _, detach_err := conn.DetachNetworkInterface(detach_request) - if detach_err != nil { - if !tfawserr.ErrMessageContains(detach_err, "InvalidAttachmentID.NotFound", "") { - return fmt.Errorf("Error detaching ENI: %s", detach_err) - } - } - - log.Printf("[DEBUG] Waiting for ENI (%s) to become detached", eniId) - stateConf := &resource.StateChangeConf{ - Pending: []string{"true"}, - Target: []string{"false"}, - Refresh: networkInterfaceAttachmentRefreshFunc(conn, eniId), - Timeout: 10 * time.Minute, - } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf( - "Error waiting for ENI (%s) to become detached: %s", eniId, err) - } - } - - return nil -} - func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn if d.HasChange("attachment") { oa, na := d.GetChange("attachment") - detach_err := resourceNetworkInterfaceDetach(oa.(*schema.Set), meta, d.Id()) - if detach_err != nil { - return detach_err + if oa != nil && oa.(*schema.Set).Len() > 0 { + attachment := oa.(*schema.Set).List()[0].(map[string]interface{}) + + err := detachNetworkInterface(conn, d.Id(), attachment["attachment_id"].(string), 10*time.Minute) + + if err != nil { + return err + } } // if there is a new attachment, attach it if na != nil && na.(*schema.Set).Len() > 0 { attachment := na.(*schema.Set).List()[0].(map[string]interface{}) - err := attachNetworkInterface(conn, d.Id(), attachment["instance"].(string), attachment["device_index"].(int)) + err := attachNetworkInterface(conn, d.Id(), attachment["instance"].(string), attachment["device_index"].(int), 5*time.Minute) if err != nil { return err @@ -578,14 +551,20 @@ func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) er func resourceNetworkInterfaceDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - if err := resourceNetworkInterfaceDetach(d.Get("attachment").(*schema.Set), meta, d.Id()); err != nil { - return err + if v, ok := d.GetOk("attachment"); ok && v.(*schema.Set).Len() > 0 { + attachment := v.(*schema.Set).List()[0].(map[string]interface{}) + + err := detachNetworkInterface(conn, d.Id(), attachment["attachment_id"].(string), 10*time.Minute) + + if err != nil { + return err + } } return deleteNetworkInterface(conn, d.Id()) } -func attachNetworkInterface(conn *ec2.EC2, networkInterfaceID, instanceID string, deviceIndex int) error { +func attachNetworkInterface(conn *ec2.EC2, networkInterfaceID, instanceID string, deviceIndex int, timeout time.Duration) error { input := &ec2.AttachNetworkInterfaceInput{ DeviceIndex: aws.Int64(int64(deviceIndex)), InstanceId: aws.String(instanceID), @@ -593,12 +572,20 @@ func attachNetworkInterface(conn *ec2.EC2, networkInterfaceID, instanceID string } log.Printf("[INFO] Attaching EC2 Network Interface: %s", input) - _, err := conn.AttachNetworkInterface(input) + output, err := conn.AttachNetworkInterface(input) if err != nil { return fmt.Errorf("error attaching EC2 Network Interface (%s/%s): %w", networkInterfaceID, instanceID, err) } + attachmentID := aws.StringValue(output.AttachmentId) + + _, err = WaitNetworkInterfaceAttached(conn, attachmentID, timeout) + + if err != nil { + return fmt.Errorf("error waiting for EC2 Network Interface (%s/%s) attach: %w", networkInterfaceID, attachmentID, err) + } + return nil } @@ -619,88 +606,32 @@ func deleteNetworkInterface(conn *ec2.EC2, networkInterfaceID string) error { return nil } -func detachNetworkInterface(conn *ec2.EC2, eni *ec2.NetworkInterface, timeout time.Duration) error { - if eni == nil { - return nil +func detachNetworkInterface(conn *ec2.EC2, networkInterfaceID, attachmentID string, timeout time.Duration) error { + input := &ec2.DetachNetworkInterfaceInput{ + AttachmentId: aws.String(attachmentID), + Force: aws.Bool(true), } - eniId := aws.StringValue(eni.NetworkInterfaceId) - if eni.Attachment == nil { - log.Printf("[DEBUG] ENI %s is already detached", eniId) - return nil - } + log.Printf("[INFO] Detaching EC2 Network Interface: %s", input) + _, err := conn.DetachNetworkInterface(input) - _, err := conn.DetachNetworkInterface(&ec2.DetachNetworkInterfaceInput{ - AttachmentId: eni.Attachment.AttachmentId, - Force: aws.Bool(true), - }) - - if tfawserr.ErrMessageContains(err, "InvalidAttachmentID.NotFound", "") { + if tfawserr.ErrCodeEquals(err, ErrCodeInvalidAttachmentIDNotFound) { return nil } if err != nil { - return fmt.Errorf("error detaching ENI (%s): %s", eniId, err) - } - - stateConf := &resource.StateChangeConf{ - Pending: []string{ - ec2.AttachmentStatusAttaching, - ec2.AttachmentStatusAttached, - ec2.AttachmentStatusDetaching, - }, - Target: []string{ - ec2.AttachmentStatusDetached, - }, - Refresh: networkInterfaceAttachmentStateRefresh(conn, eniId), - Timeout: timeout, - Delay: 10 * time.Second, - MinTimeout: 5 * time.Second, - NotFoundChecks: 1, + return fmt.Errorf("error detaching EC2 Network Interface (%s/%s): %w", networkInterfaceID, attachmentID, err) } - log.Printf("[DEBUG] Waiting for ENI (%s) to become detached", eniId) - _, err = stateConf.WaitForState() + _, err = WaitNetworkInterfaceDetached(conn, attachmentID, timeout) if tfresource.NotFound(err) { return nil } if err != nil { - return fmt.Errorf("error waiting for ENI (%s) to become detached: %s", eniId, err) + return fmt.Errorf("error waiting for EC2 Network Interface (%s/%s) detach: %w", networkInterfaceID, attachmentID, err) } return nil } - -func networkInterfaceAttachmentStateRefresh(conn *ec2.EC2, eniId string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - resp, err := conn.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ - NetworkInterfaceIds: aws.StringSlice([]string{eniId}), - }) - - if tfawserr.ErrMessageContains(err, "InvalidNetworkInterfaceID.NotFound", "") { - return nil, ec2.AttachmentStatusDetached, nil - } - - if err != nil { - return nil, "", fmt.Errorf("error describing ENI (%s): %s", eniId, err) - } - - n := len(resp.NetworkInterfaces) - switch n { - case 0: - return nil, ec2.AttachmentStatusDetached, nil - - case 1: - attachment := resp.NetworkInterfaces[0].Attachment - if attachment == nil { - return nil, ec2.AttachmentStatusDetached, nil - } - return attachment, aws.StringValue(attachment.Status), nil - - default: - return nil, "", fmt.Errorf("found %d ENIs for %s, expected 1", n, eniId) - } - } -} diff --git a/internal/service/ec2/security_group.go b/internal/service/ec2/security_group.go index 5a54a2bda42..2786b893436 100644 --- a/internal/service/ec2/security_group.go +++ b/internal/service/ec2/security_group.go @@ -1380,10 +1380,12 @@ func deleteLingeringLambdaENIs(conn *ec2.EC2, filterName, resourceId string, tim eni = networkInterface } - err = detachNetworkInterface(conn, eni, timeout) + if eni.Attachment != nil { + err = detachNetworkInterface(conn, eniId, aws.StringValue(eni.Attachment.AttachmentId), timeout) - if err != nil { - return fmt.Errorf("error detaching Lambda ENI (%s): %w", eniId, err) + if err != nil { + return fmt.Errorf("error detaching Lambda ENI (%s): %w", eniId, err) + } } err = deleteNetworkInterface(conn, eniId) diff --git a/internal/service/ec2/status.go b/internal/service/ec2/status.go index 917d875fed2..987fba88de9 100644 --- a/internal/service/ec2/status.go +++ b/internal/service/ec2/status.go @@ -527,6 +527,22 @@ func StatusNetworkInterfaceStatus(conn *ec2.EC2, id string) resource.StateRefres } } +func StatusNetworkInterfaceAttachmentStatus(conn *ec2.EC2, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindNetworkInterfaceAttachmentByID(conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.Status), nil + } +} + func StatusPlacementGroupState(conn *ec2.EC2, name string) resource.StateRefreshFunc { return func() (interface{}, string, error) { output, err := FindPlacementGroupByName(conn, name) diff --git a/internal/service/ec2/wait.go b/internal/service/ec2/wait.go index 7ba71b2daa1..95262871250 100644 --- a/internal/service/ec2/wait.go +++ b/internal/service/ec2/wait.go @@ -756,9 +756,26 @@ func WaitManagedPrefixListDeleted(conn *ec2.EC2, id string) (*ec2.ManagedPrefixL return nil, err } -// Hyperplane attached ENI. -// Wait for it to be moved into a removable state. +func WaitNetworkInterfaceAttached(conn *ec2.EC2, id string, timeout time.Duration) (*ec2.NetworkInterfaceAttachment, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.AttachmentStatusAttaching}, + Target: []string{ec2.AttachmentStatusAttached}, + Timeout: timeout, + Refresh: StatusNetworkInterfaceAttachmentStatus(conn, id), + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.NetworkInterfaceAttachment); ok { + return output, err + } + + return nil, err +} + func WaitNetworkInterfaceAvailableAfterUse(conn *ec2.EC2, id string, timeout time.Duration) (*ec2.NetworkInterface, error) { + // Hyperplane attached ENI. + // Wait for it to be moved into a removable state. stateConf := &resource.StateChangeConf{ Pending: []string{ec2.NetworkInterfaceStatusInUse}, Target: []string{ec2.NetworkInterfaceStatusAvailable}, @@ -797,6 +814,23 @@ func WaitNetworkInterfaceCreated(conn *ec2.EC2, id string, timeout time.Duration return nil, err } +func WaitNetworkInterfaceDetached(conn *ec2.EC2, id string, timeout time.Duration) (*ec2.NetworkInterfaceAttachment, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.AttachmentStatusDetaching}, + Target: []string{ec2.AttachmentStatusDetached}, + Timeout: timeout, + Refresh: StatusNetworkInterfaceAttachmentStatus(conn, id), + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.NetworkInterfaceAttachment); ok { + return output, err + } + + return nil, err +} + const ( PlacementGroupCreatedTimeout = 5 * time.Minute PlacementGroupDeletedTimeout = 5 * time.Minute From 47f79382e3035343dda573db79839400b8fbd1d4 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 2 Nov 2021 10:17:51 -0400 Subject: [PATCH 18/28] r/aws_network_interface_attachment: Use consolidated attach/detach code. Acceptance test output: % make testacc PKG_NAME=internal/service/ec2 TESTARGS='-run=TestAccEC2NetworkInterfaceAttachment_' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/ec2/... -v -count 1 -parallel 20 -run=TestAccEC2NetworkInterfaceAttachment_ -timeout 180m === RUN TestAccEC2NetworkInterfaceAttachment_basic === PAUSE TestAccEC2NetworkInterfaceAttachment_basic === CONT TestAccEC2NetworkInterfaceAttachment_basic --- PASS: TestAccEC2NetworkInterfaceAttachment_basic (325.33s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/ec2 329.262s --- internal/service/ec2/network_interface.go | 12 +- .../ec2/network_interface_attachment.go | 121 ++++-------------- .../ec2/network_interface_attachment_test.go | 79 +++++------- 3 files changed, 65 insertions(+), 147 deletions(-) diff --git a/internal/service/ec2/network_interface.go b/internal/service/ec2/network_interface.go index 13d2ca510e7..9d5c835673d 100644 --- a/internal/service/ec2/network_interface.go +++ b/internal/service/ec2/network_interface.go @@ -208,7 +208,7 @@ func resourceNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) er if v, ok := d.GetOk("attachment"); ok && v.(*schema.Set).Len() > 0 { attachment := v.(*schema.Set).List()[0].(map[string]interface{}) - err := attachNetworkInterface(conn, d.Id(), attachment["instance"].(string), attachment["device_index"].(int), 5*time.Minute) + _, err := attachNetworkInterface(conn, d.Id(), attachment["instance"].(string), attachment["device_index"].(int), 5*time.Minute) if err != nil { return err @@ -341,7 +341,7 @@ func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) er if na != nil && na.(*schema.Set).Len() > 0 { attachment := na.(*schema.Set).List()[0].(map[string]interface{}) - err := attachNetworkInterface(conn, d.Id(), attachment["instance"].(string), attachment["device_index"].(int), 5*time.Minute) + _, err := attachNetworkInterface(conn, d.Id(), attachment["instance"].(string), attachment["device_index"].(int), 5*time.Minute) if err != nil { return err @@ -564,7 +564,7 @@ func resourceNetworkInterfaceDelete(d *schema.ResourceData, meta interface{}) er return deleteNetworkInterface(conn, d.Id()) } -func attachNetworkInterface(conn *ec2.EC2, networkInterfaceID, instanceID string, deviceIndex int, timeout time.Duration) error { +func attachNetworkInterface(conn *ec2.EC2, networkInterfaceID, instanceID string, deviceIndex int, timeout time.Duration) (string, error) { input := &ec2.AttachNetworkInterfaceInput{ DeviceIndex: aws.Int64(int64(deviceIndex)), InstanceId: aws.String(instanceID), @@ -575,7 +575,7 @@ func attachNetworkInterface(conn *ec2.EC2, networkInterfaceID, instanceID string output, err := conn.AttachNetworkInterface(input) if err != nil { - return fmt.Errorf("error attaching EC2 Network Interface (%s/%s): %w", networkInterfaceID, instanceID, err) + return "", fmt.Errorf("error attaching EC2 Network Interface (%s/%s): %w", networkInterfaceID, instanceID, err) } attachmentID := aws.StringValue(output.AttachmentId) @@ -583,10 +583,10 @@ func attachNetworkInterface(conn *ec2.EC2, networkInterfaceID, instanceID string _, err = WaitNetworkInterfaceAttached(conn, attachmentID, timeout) if err != nil { - return fmt.Errorf("error waiting for EC2 Network Interface (%s/%s) attach: %w", networkInterfaceID, attachmentID, err) + return attachmentID, fmt.Errorf("error waiting for EC2 Network Interface (%s/%s) attach: %w", networkInterfaceID, attachmentID, err) } - return nil + return attachmentID, nil } func deleteNetworkInterface(conn *ec2.EC2, networkInterfaceID string) error { diff --git a/internal/service/ec2/network_interface_attachment.go b/internal/service/ec2/network_interface_attachment.go index 94b179bef62..95156fffaf5 100644 --- a/internal/service/ec2/network_interface_attachment.go +++ b/internal/service/ec2/network_interface_attachment.go @@ -5,12 +5,9 @@ import ( "log" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/service/ec2" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceNetworkInterfaceAttachment() *schema.Resource { @@ -20,29 +17,25 @@ func ResourceNetworkInterfaceAttachment() *schema.Resource { Delete: resourceNetworkInterfaceAttachmentDelete, Schema: map[string]*schema.Schema{ + "attachment_id": { + Type: schema.TypeString, + Computed: true, + }, "device_index": { Type: schema.TypeInt, Required: true, ForceNew: true, }, - "instance_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "network_interface_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - - "attachment_id": { - Type: schema.TypeString, - Computed: true, - }, - "status": { Type: schema.TypeString, Computed: true, @@ -54,81 +47,44 @@ func ResourceNetworkInterfaceAttachment() *schema.Resource { func resourceNetworkInterfaceAttachmentCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - device_index := d.Get("device_index").(int) - instance_id := d.Get("instance_id").(string) - network_interface_id := d.Get("network_interface_id").(string) + attachmentID, err := attachNetworkInterface( + conn, + d.Get("network_interface_id").(string), + d.Get("instance_id").(string), + d.Get("device_index").(int), + 5*time.Minute, + ) - opts := &ec2.AttachNetworkInterfaceInput{ - DeviceIndex: aws.Int64(int64(device_index)), - InstanceId: aws.String(instance_id), - NetworkInterfaceId: aws.String(network_interface_id), + if attachmentID != "" { + d.SetId(attachmentID) } - log.Printf("[DEBUG] Attaching network interface (%s) to instance (%s)", network_interface_id, instance_id) - resp, err := conn.AttachNetworkInterface(opts) if err != nil { - if awsErr, ok := err.(awserr.Error); ok { - return fmt.Errorf("Error attaching network interface (%s) to instance (%s), message: \"%s\", code: \"%s\"", - network_interface_id, instance_id, awsErr.Message(), awsErr.Code()) - } return err } - stateConf := &resource.StateChangeConf{ - Pending: []string{"false"}, - Target: []string{"true"}, - Refresh: networkInterfaceAttachmentRefreshFunc(conn, network_interface_id), - Timeout: 5 * time.Minute, - Delay: 10 * time.Second, - MinTimeout: 3 * time.Second, - } - - _, err = stateConf.WaitForState() - if err != nil { - return fmt.Errorf( - "Error waiting for Volume (%s) to attach to Instance: %s, error: %s", network_interface_id, instance_id, err) - } - - d.SetId(aws.StringValue(resp.AttachmentId)) return resourceNetworkInterfaceAttachmentRead(d, meta) } func resourceNetworkInterfaceAttachmentRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - interfaceId := d.Get("network_interface_id").(string) + attachment, err := FindNetworkInterfaceAttachmentByID(conn, d.Id()) - req := &ec2.DescribeNetworkInterfacesInput{ - NetworkInterfaceIds: []*string{aws.String(interfaceId)}, + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] EC2 Network Interface Attachment (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil } - resp, err := conn.DescribeNetworkInterfaces(req) if err != nil { - if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidNetworkInterfaceID.NotFound" { - // The ENI is gone now, so just remove the attachment from the state - d.SetId("") - return nil - } - - return fmt.Errorf("Error retrieving ENI: %s", err) - } - if len(resp.NetworkInterfaces) != 1 { - return fmt.Errorf("Unable to find ENI (%s): %#v", interfaceId, resp.NetworkInterfaces) + return fmt.Errorf("error reading EC2 Network Interface Attachment (%s): %w", d.Id(), err) } - eni := resp.NetworkInterfaces[0] - - if eni.Attachment == nil { - // Interface is no longer attached, remove from state - d.SetId("") - return nil - } - - d.Set("attachment_id", eni.Attachment.AttachmentId) - d.Set("device_index", eni.Attachment.DeviceIndex) - d.Set("instance_id", eni.Attachment.InstanceId) - d.Set("network_interface_id", eni.NetworkInterfaceId) - d.Set("status", eni.Attachment.Status) + d.Set("attachment_id", attachment.AttachmentId) + d.Set("device_index", attachment.DeviceIndex) + d.Set("instance_id", attachment.InstanceId) + d.Set("status", attachment.Status) return nil } @@ -136,32 +92,5 @@ func resourceNetworkInterfaceAttachmentRead(d *schema.ResourceData, meta interfa func resourceNetworkInterfaceAttachmentDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - interfaceId := d.Get("network_interface_id").(string) - - detach_request := &ec2.DetachNetworkInterfaceInput{ - AttachmentId: aws.String(d.Id()), - Force: aws.Bool(true), - } - - _, detach_err := conn.DetachNetworkInterface(detach_request) - if detach_err != nil { - if awsErr, _ := detach_err.(awserr.Error); awsErr.Code() != "InvalidAttachmentID.NotFound" { - return fmt.Errorf("Error detaching ENI: %s", detach_err) - } - } - - log.Printf("[DEBUG] Waiting for ENI (%s) to become detached", interfaceId) - stateConf := &resource.StateChangeConf{ - Pending: []string{"true"}, - Target: []string{"false"}, - Refresh: networkInterfaceAttachmentRefreshFunc(conn, interfaceId), - Timeout: 10 * time.Minute, - } - - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf( - "Error waiting for ENI (%s) to become detached: %s", interfaceId, err) - } - - return nil + return detachNetworkInterface(conn, d.Get("network_interface_id").(string), d.Id(), 10*time.Minute) } diff --git a/internal/service/ec2/network_interface_attachment_test.go b/internal/service/ec2/network_interface_attachment_test.go index ccb9e96d614..b6198053dde 100644 --- a/internal/service/ec2/network_interface_attachment_test.go +++ b/internal/service/ec2/network_interface_attachment_test.go @@ -12,7 +12,8 @@ import ( func TestAccEC2NetworkInterfaceAttachment_basic(t *testing.T) { var conf ec2.NetworkInterface - rInt := sdkacctest.RandInt() + resourceName := "aws_network_interface_attachment.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -21,58 +22,47 @@ func TestAccEC2NetworkInterfaceAttachment_basic(t *testing.T) { CheckDestroy: testAccCheckENIDestroy, Steps: []resource.TestStep{ { - Config: testAccNetworkInterfaceAttachmentConfig_basic(rInt), + Config: testAccNetworkInterfaceAttachmentConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckENIExists("aws_network_interface.bar", &conf), - resource.TestCheckResourceAttr( - "aws_network_interface_attachment.test", "device_index", "1"), - resource.TestCheckResourceAttrSet( - "aws_network_interface_attachment.test", "instance_id"), - resource.TestCheckResourceAttrSet( - "aws_network_interface_attachment.test", "network_interface_id"), - resource.TestCheckResourceAttrSet( - "aws_network_interface_attachment.test", "attachment_id"), - resource.TestCheckResourceAttrSet( - "aws_network_interface_attachment.test", "status"), + testAccCheckENIExists("aws_network_interface.test", &conf), + resource.TestCheckResourceAttrSet(resourceName, "attachment_id"), + resource.TestCheckResourceAttr(resourceName, "device_index", "1"), + resource.TestCheckResourceAttrSet(resourceName, "instance_id"), + resource.TestCheckResourceAttrSet(resourceName, "network_interface_id"), + resource.TestCheckResourceAttrSet(resourceName, "status"), ), }, }, }) } -func testAccNetworkInterfaceAttachmentConfig_basic(rInt int) string { - return acctest.ConfigLatestAmazonLinuxHvmEbsAmi() + fmt.Sprintf(` -resource "aws_vpc" "foo" { +func testAccNetworkInterfaceAttachmentConfig(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigLatestAmazonLinuxHvmEbsAmi(), + acctest.AvailableEC2InstanceTypeForRegion("t3.micro", "t2.micro"), + acctest.ConfigAvailableAZsNoOptIn(), + fmt.Sprintf(` +resource "aws_vpc" "test" { cidr_block = "172.16.0.0/16" tags = { - Name = "terraform-testacc-network-iface-attachment-basic" + Name = %[1]q } } -data "aws_availability_zones" "available" { - state = "available" - - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] - } -} - -resource "aws_subnet" "foo" { - vpc_id = aws_vpc.foo.id +resource "aws_subnet" "test" { + vpc_id = aws_vpc.test.id cidr_block = "172.16.10.0/24" availability_zone = data.aws_availability_zones.available.names[0] tags = { - Name = "tf-acc-network-iface-attachment-basic" + Name = %[1]q } } -resource "aws_security_group" "foo" { - vpc_id = aws_vpc.foo.id - description = "foo" - name = "foo-%d" +resource "aws_security_group" "test" { + vpc_id = aws_vpc.test.id + name = %[1]q egress { from_port = 0 @@ -82,31 +72,30 @@ resource "aws_security_group" "foo" { } } -resource "aws_network_interface" "bar" { - subnet_id = aws_subnet.foo.id +resource "aws_network_interface" "test" { + subnet_id = aws_subnet.test.id private_ips = ["172.16.10.100"] - security_groups = [aws_security_group.foo.id] - description = "Managed by Terraform" + security_groups = [aws_security_group.test.id] tags = { - Name = "bar_interface" + Name = %[1]q } } -resource "aws_instance" "foo" { +resource "aws_instance" "test" { ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id - instance_type = "t2.micro" - subnet_id = aws_subnet.foo.id + instance_type = data.aws_ec2_instance_type_offering.available.instance_type + subnet_id = aws_subnet.test.id tags = { - Name = "foo-%d" + Name = %[1]q } } resource "aws_network_interface_attachment" "test" { device_index = 1 - instance_id = aws_instance.foo.id - network_interface_id = aws_network_interface.bar.id + instance_id = aws_instance.test.id + network_interface_id = aws_network_interface.test.id } -`, rInt, rInt) +`, rName)) } From 8f8b316f72fc7e0760632de4ae508101278b5fa1 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 2 Nov 2021 10:24:39 -0400 Subject: [PATCH 19/28] Add constants for network interface attach/detach timeouts. --- internal/service/ec2/network_interface.go | 30 +++---------------- .../ec2/network_interface_attachment.go | 5 ++-- internal/service/ec2/wait.go | 5 ++++ 3 files changed, 11 insertions(+), 29 deletions(-) diff --git a/internal/service/ec2/network_interface.go b/internal/service/ec2/network_interface.go index 9d5c835673d..a03aeda3193 100644 --- a/internal/service/ec2/network_interface.go +++ b/internal/service/ec2/network_interface.go @@ -4,14 +4,12 @@ import ( "fmt" "log" "math" - "strconv" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/aws-sdk-go-base/tfawserr" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -208,7 +206,7 @@ func resourceNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) er if v, ok := d.GetOk("attachment"); ok && v.(*schema.Set).Len() > 0 { attachment := v.(*schema.Set).List()[0].(map[string]interface{}) - _, err := attachNetworkInterface(conn, d.Id(), attachment["instance"].(string), attachment["device_index"].(int), 5*time.Minute) + _, err := attachNetworkInterface(conn, d.Id(), attachment["instance"].(string), attachment["device_index"].(int), networkInterfaceAttachedTimeout) if err != nil { return err @@ -301,26 +299,6 @@ func resourceNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) erro return nil } -func networkInterfaceAttachmentRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - - describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesInput{ - NetworkInterfaceIds: []*string{aws.String(id)}, - } - describeResp, err := conn.DescribeNetworkInterfaces(describe_network_interfaces_request) - - if err != nil { - log.Printf("[ERROR] Could not find network interface %s. %s", id, err) - return nil, "", err - } - - eni := describeResp.NetworkInterfaces[0] - hasAttachment := strconv.FormatBool(eni.Attachment != nil) - log.Printf("[DEBUG] ENI %s has attachment state %s", id, hasAttachment) - return eni, hasAttachment, nil - } -} - func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn @@ -330,7 +308,7 @@ func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) er if oa != nil && oa.(*schema.Set).Len() > 0 { attachment := oa.(*schema.Set).List()[0].(map[string]interface{}) - err := detachNetworkInterface(conn, d.Id(), attachment["attachment_id"].(string), 10*time.Minute) + err := detachNetworkInterface(conn, d.Id(), attachment["attachment_id"].(string), networkInterfaceDetachedTimeout) if err != nil { return err @@ -341,7 +319,7 @@ func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) er if na != nil && na.(*schema.Set).Len() > 0 { attachment := na.(*schema.Set).List()[0].(map[string]interface{}) - _, err := attachNetworkInterface(conn, d.Id(), attachment["instance"].(string), attachment["device_index"].(int), 5*time.Minute) + _, err := attachNetworkInterface(conn, d.Id(), attachment["instance"].(string), attachment["device_index"].(int), networkInterfaceAttachedTimeout) if err != nil { return err @@ -554,7 +532,7 @@ func resourceNetworkInterfaceDelete(d *schema.ResourceData, meta interface{}) er if v, ok := d.GetOk("attachment"); ok && v.(*schema.Set).Len() > 0 { attachment := v.(*schema.Set).List()[0].(map[string]interface{}) - err := detachNetworkInterface(conn, d.Id(), attachment["attachment_id"].(string), 10*time.Minute) + err := detachNetworkInterface(conn, d.Id(), attachment["attachment_id"].(string), networkInterfaceDetachedTimeout) if err != nil { return err diff --git a/internal/service/ec2/network_interface_attachment.go b/internal/service/ec2/network_interface_attachment.go index 95156fffaf5..4009c3f55fa 100644 --- a/internal/service/ec2/network_interface_attachment.go +++ b/internal/service/ec2/network_interface_attachment.go @@ -3,7 +3,6 @@ package ec2 import ( "fmt" "log" - "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -52,7 +51,7 @@ func resourceNetworkInterfaceAttachmentCreate(d *schema.ResourceData, meta inter d.Get("network_interface_id").(string), d.Get("instance_id").(string), d.Get("device_index").(int), - 5*time.Minute, + networkInterfaceAttachedTimeout, ) if attachmentID != "" { @@ -92,5 +91,5 @@ func resourceNetworkInterfaceAttachmentRead(d *schema.ResourceData, meta interfa func resourceNetworkInterfaceAttachmentDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - return detachNetworkInterface(conn, d.Get("network_interface_id").(string), d.Id(), 10*time.Minute) + return detachNetworkInterface(conn, d.Get("network_interface_id").(string), d.Id(), networkInterfaceDetachedTimeout) } diff --git a/internal/service/ec2/wait.go b/internal/service/ec2/wait.go index 95262871250..541cf5b2cc8 100644 --- a/internal/service/ec2/wait.go +++ b/internal/service/ec2/wait.go @@ -756,6 +756,11 @@ func WaitManagedPrefixListDeleted(conn *ec2.EC2, id string) (*ec2.ManagedPrefixL return nil, err } +const ( + networkInterfaceAttachedTimeout = 5 * time.Minute + networkInterfaceDetachedTimeout = 10 * time.Minute +) + func WaitNetworkInterfaceAttached(conn *ec2.EC2, id string, timeout time.Duration) (*ec2.NetworkInterfaceAttachment, error) { stateConf := &resource.StateChangeConf{ Pending: []string{ec2.AttachmentStatusAttaching}, From a1735a9d2edf8b034f18a5d82701f37182b15ed5 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 2 Nov 2021 11:04:05 -0400 Subject: [PATCH 20/28] r/aws_network_interface_sg_attachment: Simplify. Acceptance test output: % make testacc PKG_NAME=internal/service/ec2 TESTARGS='-run=TestAccEC2NetworkInterfaceSgAttachment_' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/ec2/... -v -count 1 -parallel 20 -run=TestAccEC2NetworkInterfaceSgAttachment_ -timeout 180m === RUN TestAccEC2NetworkInterfaceSgAttachment_SG_basic === PAUSE TestAccEC2NetworkInterfaceSgAttachment_SG_basic === RUN TestAccEC2NetworkInterfaceSgAttachment_SG_disappears === PAUSE TestAccEC2NetworkInterfaceSgAttachment_SG_disappears === RUN TestAccEC2NetworkInterfaceSgAttachment_SG_instance === PAUSE TestAccEC2NetworkInterfaceSgAttachment_SG_instance === RUN TestAccEC2NetworkInterfaceSgAttachment_SG_multiple === PAUSE TestAccEC2NetworkInterfaceSgAttachment_SG_multiple === CONT TestAccEC2NetworkInterfaceSgAttachment_SG_basic === CONT TestAccEC2NetworkInterfaceSgAttachment_SG_multiple === CONT TestAccEC2NetworkInterfaceSgAttachment_SG_instance === CONT TestAccEC2NetworkInterfaceSgAttachment_SG_disappears --- PASS: TestAccEC2NetworkInterfaceSgAttachment_SG_basic (29.52s) --- PASS: TestAccEC2NetworkInterfaceSgAttachment_SG_disappears (29.61s) --- PASS: TestAccEC2NetworkInterfaceSgAttachment_SG_multiple (41.81s) --- PASS: TestAccEC2NetworkInterfaceSgAttachment_SG_instance (129.43s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/ec2 132.286s --- .../ec2/network_interface_sg_attachment.go | 112 +++++++------- .../network_interface_sg_attachment_test.go | 146 +++++++----------- 2 files changed, 114 insertions(+), 144 deletions(-) diff --git a/internal/service/ec2/network_interface_sg_attachment.go b/internal/service/ec2/network_interface_sg_attachment.go index fa704c6e532..2ec81056195 100644 --- a/internal/service/ec2/network_interface_sg_attachment.go +++ b/internal/service/ec2/network_interface_sg_attachment.go @@ -3,7 +3,6 @@ package ec2 import ( "fmt" "log" - "reflect" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" @@ -18,13 +17,14 @@ func ResourceNetworkInterfaceSGAttachment() *schema.Resource { Create: resourceNetworkInterfaceSGAttachmentCreate, Read: resourceNetworkInterfaceSGAttachmentRead, Delete: resourceNetworkInterfaceSGAttachmentDelete, + Schema: map[string]*schema.Schema{ - "security_group_id": { + "network_interface_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "network_interface_id": { + "security_group_id": { Type: schema.TypeString, Required: true, ForceNew: true, @@ -34,130 +34,130 @@ func ResourceNetworkInterfaceSGAttachment() *schema.Resource { } func resourceNetworkInterfaceSGAttachmentCreate(d *schema.ResourceData, meta interface{}) error { - mk := "network_interface_sg_attachment_" + d.Get("network_interface_id").(string) - conns.GlobalMutexKV.Lock(mk) - defer conns.GlobalMutexKV.Unlock(mk) + conn := meta.(*conns.AWSClient).EC2Conn + networkInterfaceID := d.Get("network_interface_id").(string) sgID := d.Get("security_group_id").(string) - interfaceID := d.Get("network_interface_id").(string) - - conn := meta.(*conns.AWSClient).EC2Conn + mutexKey := "network_interface_sg_attachment_" + networkInterfaceID + conns.GlobalMutexKV.Lock(mutexKey) + defer conns.GlobalMutexKV.Unlock(mutexKey) - iface, err := FindNetworkInterfaceByID(conn, interfaceID) + eni, err := FindNetworkInterfaceByID(conn, networkInterfaceID) if err != nil { - return fmt.Errorf("error reading EC2 Network Interface (%s): %w", interfaceID, err) + return fmt.Errorf("error reading EC2 Network Interface (%s): %w", networkInterfaceID, err) } groupIDs := []string{sgID} - for _, group := range iface.Groups { + for _, group := range eni.Groups { if group == nil { continue } - if aws.StringValue(group.GroupId) == sgID { - return fmt.Errorf("EC2 Security Group (%s) already attached to EC2 Network Interface ID: %s", sgID, interfaceID) + groupID := aws.StringValue(group.GroupId) + + if groupID == sgID { + return fmt.Errorf("EC2 Security Group (%s) already attached to EC2 Network Interface (%s)", sgID, networkInterfaceID) } - groupIDs = append(groupIDs, aws.StringValue(group.GroupId)) + groupIDs = append(groupIDs, groupID) } - params := &ec2.ModifyNetworkInterfaceAttributeInput{ - NetworkInterfaceId: iface.NetworkInterfaceId, + input := &ec2.ModifyNetworkInterfaceAttributeInput{ + NetworkInterfaceId: aws.String(networkInterfaceID), Groups: aws.StringSlice(groupIDs), } - _, err = conn.ModifyNetworkInterfaceAttribute(params) + log.Printf("[INFO] Modifying EC2 Network Interface: %s", input) + _, err = conn.ModifyNetworkInterfaceAttribute(input) if err != nil { - return fmt.Errorf("error modifying EC2 Network Interface (%s): %w", interfaceID, err) + return fmt.Errorf("error modifying EC2 Network Interface (%s): %w", networkInterfaceID, err) } - d.SetId(fmt.Sprintf("%s_%s", sgID, interfaceID)) + d.SetId(fmt.Sprintf("%s_%s", sgID, networkInterfaceID)) return resourceNetworkInterfaceSGAttachmentRead(d, meta) } func resourceNetworkInterfaceSGAttachmentRead(d *schema.ResourceData, meta interface{}) error { - sgID := d.Get("security_group_id").(string) - interfaceID := d.Get("network_interface_id").(string) - - log.Printf("[DEBUG] Checking association of security group %s to network interface ID %s", sgID, interfaceID) - conn := meta.(*conns.AWSClient).EC2Conn + networkInterfaceID := d.Get("network_interface_id").(string) + sgID := d.Get("security_group_id").(string) outputRaw, err := tfresource.RetryWhenNewResourceNotFound(PropagationTimeout, func() (interface{}, error) { - return FindNetworkInterfaceSecurityGroup(conn, interfaceID, sgID) + return FindNetworkInterfaceSecurityGroup(conn, networkInterfaceID, sgID) }, d.IsNewResource()) if !d.IsNewResource() && tfresource.NotFound(err) { - log.Printf("[WARN] EC2 Network Interface Security Group Attachment (%s) not found, removing from state", d.Id()) + log.Printf("[WARN] EC2 Network Interface (%s) Security Group (%s) Attachment not found, removing from state", networkInterfaceID, sgID) d.SetId("") return nil } if err != nil { - return fmt.Errorf("error reading EC2 Network Interface Security Group Attachment (%s): %w", d.Id(), err) + return fmt.Errorf("error reading EC2 Network Interface (%s) Security Group (%s) Attachment: %w", networkInterfaceID, sgID, err) } groupIdentifier := outputRaw.(*ec2.GroupIdentifier) - d.Set("network_interface_id", interfaceID) + d.Set("network_interface_id", networkInterfaceID) d.Set("security_group_id", groupIdentifier.GroupId) return nil } func resourceNetworkInterfaceSGAttachmentDelete(d *schema.ResourceData, meta interface{}) error { - mk := "network_interface_sg_attachment_" + d.Get("network_interface_id").(string) - conns.GlobalMutexKV.Lock(mk) - defer conns.GlobalMutexKV.Unlock(mk) + conn := meta.(*conns.AWSClient).EC2Conn + networkInterfaceID := d.Get("network_interface_id").(string) sgID := d.Get("security_group_id").(string) - interfaceID := d.Get("network_interface_id").(string) + mutexKey := "network_interface_sg_attachment_" + networkInterfaceID + conns.GlobalMutexKV.Lock(mutexKey) + defer conns.GlobalMutexKV.Unlock(mutexKey) - log.Printf("[DEBUG] Removing security group %s from interface ID %s", sgID, interfaceID) - - conn := meta.(*conns.AWSClient).EC2Conn - - iface, err := FindNetworkInterfaceByID(conn, interfaceID) + eni, err := FindNetworkInterfaceByID(conn, networkInterfaceID) if tfresource.NotFound(err) { return nil } if err != nil { - return err + return fmt.Errorf("error reading EC2 Network Interface (%s): %w", networkInterfaceID, err) } - return delSGFromENI(conn, sgID, iface) -} + groupIDs := []string{} -func delSGFromENI(conn *ec2.EC2, sgID string, iface *ec2.NetworkInterface) error { - old := iface.Groups - var new []*string - for _, v := range iface.Groups { - if *v.GroupId == sgID { + for _, group := range eni.Groups { + if group == nil { continue } - new = append(new, v.GroupId) - } - if reflect.DeepEqual(old, new) { - // The interface already didn't have the security group, nothing to do - return nil + + groupID := aws.StringValue(group.GroupId) + + if groupID == sgID { + continue + } + + groupIDs = append(groupIDs, groupID) } - params := &ec2.ModifyNetworkInterfaceAttributeInput{ - NetworkInterfaceId: iface.NetworkInterfaceId, - Groups: new, + input := &ec2.ModifyNetworkInterfaceAttributeInput{ + NetworkInterfaceId: aws.String(networkInterfaceID), + Groups: aws.StringSlice(groupIDs), } - _, err := conn.ModifyNetworkInterfaceAttribute(params) + log.Printf("[INFO] Modifying EC2 Network Interface: %s", input) + _, err = conn.ModifyNetworkInterfaceAttribute(input) if tfawserr.ErrCodeEquals(err, ErrCodeInvalidNetworkInterfaceIDNotFound) { return nil } - return err + if err != nil { + return fmt.Errorf("error modifying EC2 Network Interface (%s): %w", networkInterfaceID, err) + } + + return nil } diff --git a/internal/service/ec2/network_interface_sg_attachment_test.go b/internal/service/ec2/network_interface_sg_attachment_test.go index 445b79e8ec9..43c83772325 100644 --- a/internal/service/ec2/network_interface_sg_attachment_test.go +++ b/internal/service/ec2/network_interface_sg_attachment_test.go @@ -84,30 +84,6 @@ func TestAccEC2NetworkInterfaceSgAttachment_SG_instance(t *testing.T) { }) } -func TestAccEC2NetworkInterfaceSgAttachment_SG_dataSource(t *testing.T) { - instanceDataSourceName := "data.aws_instance.test" - securityGroupResourceName := "aws_security_group.test" - resourceName := "aws_network_interface_sg_attachment.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), - Providers: acctest.Providers, - CheckDestroy: testAccCheckNetworkInterfaceSGAttachmentDestroy, - Steps: []resource.TestStep{ - { - Config: testAccNetworkInterfaceSGAttachmentViaDataSourceConfig(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckNetworkInterfaceSGAttachmentExists(resourceName), - resource.TestCheckResourceAttrPair(resourceName, "network_interface_id", instanceDataSourceName, "network_interface_id"), - resource.TestCheckResourceAttrPair(resourceName, "security_group_id", securityGroupResourceName, "id"), - ), - }, - }, - }) -} - func TestAccEC2NetworkInterfaceSgAttachment_SG_multiple(t *testing.T) { networkInterfaceResourceName := "aws_network_interface.test" securityGroupResourceName1 := "aws_security_group.test.0" @@ -200,7 +176,7 @@ resource "aws_vpc" "test" { cidr_block = "172.16.0.0/16" tags = { - Name = %q + Name = %[1]q } } @@ -209,20 +185,24 @@ resource "aws_subnet" "test" { vpc_id = aws_vpc.test.id tags = { - Name = %q + Name = %[1]q } } resource "aws_security_group" "test" { - name = %q + name = %[1]q vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } resource "aws_network_interface" "test" { subnet_id = aws_subnet.test.id tags = { - Name = %q + Name = %[1]q } } @@ -230,106 +210,96 @@ resource "aws_network_interface_sg_attachment" "test" { network_interface_id = aws_network_interface.test.id security_group_id = aws_security_group.test.id } -`, rName, rName, rName, rName) +`, rName) } func testAccNetworkInterfaceSGAttachmentViaInstanceConfig(rName string) string { - return fmt.Sprintf(` -data "aws_ami" "ami" { - most_recent = true + return acctest.ConfigCompose( + acctest.ConfigLatestAmazonLinuxHvmEbsAmi(), + acctest.AvailableEC2InstanceTypeForRegion("t3.micro", "t2.micro"), + acctest.ConfigAvailableAZsNoOptIn(), + fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "172.16.0.0/16" - filter { - name = "name" - values = ["amzn-ami-hvm-*"] + tags = { + Name = %[1]q } - - owners = ["amazon"] } -resource "aws_instance" "test" { - instance_type = "t2.micro" - ami = data.aws_ami.ami.id +resource "aws_subnet" "test" { + cidr_block = "172.16.10.0/24" + vpc_id = aws_vpc.test.id + + availability_zone = data.aws_availability_zones.available.names[0] tags = { - Name = %q + Name = %[1]q } } resource "aws_security_group" "test" { - name = %q -} - -resource "aws_network_interface_sg_attachment" "test" { - network_interface_id = aws_instance.test.primary_network_interface_id - security_group_id = aws_security_group.test.id -} -`, rName, rName) -} - -func testAccNetworkInterfaceSGAttachmentViaDataSourceConfig(rName string) string { - return fmt.Sprintf(` -data "aws_ami" "ami" { - most_recent = true + name = %[1]q + vpc_id = aws_vpc.test.id - filter { - name = "name" - values = ["amzn-ami-hvm-*"] + tags = { + Name = %[1]q } - - owners = ["amazon"] } resource "aws_instance" "test" { - instance_type = "t2.micro" - ami = data.aws_ami.ami.id - - tags = { - Name = %q + ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id + instance_type = data.aws_ec2_instance_type_offering.available.instance_type + subnet_id = aws_subnet.test.id + + tags = { + Name = %[1]q + } } -} - -data "aws_instance" "test" { - instance_id = aws_instance.test.id -} - -resource "aws_security_group" "test" { - name = %q -} resource "aws_network_interface_sg_attachment" "test" { + network_interface_id = aws_instance.test.primary_network_interface_id security_group_id = aws_security_group.test.id - network_interface_id = data.aws_instance.test.network_interface_id } -`, rName, rName) +`, rName)) } func testAccNetworkInterfaceSGAttachmentMultipleConfig(rName string) string { return fmt.Sprintf(` -data "aws_availability_zones" "available" { - state = "available" +resource "aws_vpc" "test" { + cidr_block = "172.16.0.0/16" - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] + tags = { + Name = %[1]q } } -data "aws_subnet" "test" { - availability_zone = data.aws_availability_zones.available.names[0] - default_for_az = "true" +resource "aws_subnet" "test" { + cidr_block = "172.16.10.0/24" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } resource "aws_network_interface" "test" { - subnet_id = data.aws_subnet.test.id + subnet_id = aws_subnet.test.id tags = { - Name = %q + Name = %[1]q } } resource "aws_security_group" "test" { count = 4 - name = "%s-${count.index}" + + name = "%[1]s-${count.index}" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } resource "aws_network_interface_sg_attachment" "test" { @@ -337,5 +307,5 @@ resource "aws_network_interface_sg_attachment" "test" { network_interface_id = aws_network_interface.test.id security_group_id = aws_security_group.test.*.id[count.index] } -`, rName, rName) +`, rName) } From 5ab1e7eb1fc0a8f9b2a59d3eef07e3e011a94f7a Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 2 Nov 2021 11:13:08 -0400 Subject: [PATCH 21/28] d/aws_network_interface: Use 'FindNetworkInterface'." Acceptance test output: % make testacc PKG_NAME=internal/service/ec2 TESTARGS='-run=TestAccEC2NetworkInterfaceDataSource_' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/ec2/... -v -count 1 -parallel 20 -run=TestAccEC2NetworkInterfaceDataSource_ -timeout 180m === RUN TestAccEC2NetworkInterfaceDataSource_basic === PAUSE TestAccEC2NetworkInterfaceDataSource_basic === RUN TestAccEC2NetworkInterfaceDataSource_filters === PAUSE TestAccEC2NetworkInterfaceDataSource_filters === RUN TestAccEC2NetworkInterfaceDataSource_carrierIPAssociation === PAUSE TestAccEC2NetworkInterfaceDataSource_carrierIPAssociation === RUN TestAccEC2NetworkInterfaceDataSource_publicIPAssociation === PAUSE TestAccEC2NetworkInterfaceDataSource_publicIPAssociation === CONT TestAccEC2NetworkInterfaceDataSource_basic === CONT TestAccEC2NetworkInterfaceDataSource_publicIPAssociation === CONT TestAccEC2NetworkInterfaceDataSource_carrierIPAssociation === CONT TestAccEC2NetworkInterfaceDataSource_filters --- PASS: TestAccEC2NetworkInterfaceDataSource_filters (32.03s) --- PASS: TestAccEC2NetworkInterfaceDataSource_basic (32.63s) --- PASS: TestAccEC2NetworkInterfaceDataSource_carrierIPAssociation (36.03s) --- PASS: TestAccEC2NetworkInterfaceDataSource_publicIPAssociation (42.38s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/ec2 45.285s --- .../ec2/network_interface_data_source.go | 59 ++++++++----------- .../ec2/network_interface_data_source_test.go | 8 +++ 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/internal/service/ec2/network_interface_data_source.go b/internal/service/ec2/network_interface_data_source.go index 5a48cf7b082..28af76cc10c 100644 --- a/internal/service/ec2/network_interface_data_source.go +++ b/internal/service/ec2/network_interface_data_source.go @@ -2,7 +2,6 @@ package ec2 import ( "fmt" - "log" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" @@ -20,12 +19,6 @@ func DataSourceNetworkInterface() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "id": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "filter": DataSourceFiltersSchema(), "association": { Type: schema.TypeList, Computed: true, @@ -94,10 +87,11 @@ func DataSourceNetworkInterface() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "security_groups": { - Type: schema.TypeSet, + "filter": DataSourceFiltersSchema(), + "id": { + Type: schema.TypeString, + Optional: true, Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, }, "interface_type": { Type: schema.TypeString, @@ -112,6 +106,10 @@ func DataSourceNetworkInterface() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "outpost_arn": { + Type: schema.TypeString, + Computed: true, + }, "owner_id": { Type: schema.TypeString, Computed: true, @@ -133,19 +131,20 @@ func DataSourceNetworkInterface() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "subnet_id": { - Type: schema.TypeString, + "security_groups": { + Type: schema.TypeSet, Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, }, - "outpost_arn": { + "subnet_id": { Type: schema.TypeString, Computed: true, }, + "tags": tftags.TagsSchemaComputed(), "vpc_id": { Type: schema.TypeString, Computed: true, }, - "tags": tftags.TagsSchemaComputed(), }, } } @@ -155,30 +154,21 @@ func dataSourceNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) er ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig input := &ec2.DescribeNetworkInterfacesInput{} - if v, ok := d.GetOk("id"); ok { - input.NetworkInterfaceIds = []*string{aws.String(v.(string))} - } if v, ok := d.GetOk("filter"); ok { input.Filters = BuildFiltersDataSource(v.(*schema.Set)) } - log.Printf("[DEBUG] Reading Network Interface: %s", input) - resp, err := conn.DescribeNetworkInterfaces(input) - if err != nil { - return err + if v, ok := d.GetOk("id"); ok { + input.NetworkInterfaceIds = []*string{aws.String(v.(string))} } - if resp == nil || len(resp.NetworkInterfaces) == 0 { - return fmt.Errorf("no matching network interface found") - } + eni, err := FindNetworkInterface(conn, input) - if len(resp.NetworkInterfaces) > 1 { - return fmt.Errorf("Your query returned more than one result. Please try a more specific search criteria") + if err != nil { + return fmt.Errorf("error reading EC2 Network Interface: %w", err) } - eni := resp.NetworkInterfaces[0] - d.SetId(aws.StringValue(eni.NetworkInterfaceId)) ownerID := aws.StringValue(eni.OwnerId) arn := arn.ARN{ @@ -189,13 +179,15 @@ func dataSourceNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) er Resource: fmt.Sprintf("network-interface/%s", d.Id()), }.String() d.Set("arn", arn) - d.Set("owner_id", ownerID) if eni.Association != nil { - d.Set("association", flattenNetworkInterfaceAssociation(eni.Association)) + if err := d.Set("association", flattenNetworkInterfaceAssociation(eni.Association)); err != nil { + return fmt.Errorf("error setting association: %w", err) + } } if eni.Attachment != nil { - attachment := []interface{}{FlattenAttachment(eni.Attachment)} - d.Set("attachment", attachment) + if err := d.Set("attachment", []interface{}{FlattenAttachment(eni.Attachment)}); err != nil { + return fmt.Errorf("error setting attachment: %w", err) + } } d.Set("availability_zone", eni.AvailabilityZone) d.Set("description", eni.Description) @@ -203,12 +195,13 @@ func dataSourceNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) er d.Set("interface_type", eni.InterfaceType) d.Set("ipv6_addresses", flattenNetworkInterfaceIPv6Address(eni.Ipv6Addresses)) d.Set("mac_address", eni.MacAddress) + d.Set("outpost_arn", eni.OutpostArn) + d.Set("owner_id", ownerID) d.Set("private_dns_name", eni.PrivateDnsName) d.Set("private_ip", eni.PrivateIpAddress) d.Set("private_ips", FlattenNetworkInterfacesPrivateIPAddresses(eni.PrivateIpAddresses)) d.Set("requester_id", eni.RequesterId) d.Set("subnet_id", eni.SubnetId) - d.Set("outpost_arn", eni.OutpostArn) d.Set("vpc_id", eni.VpcId) if err := d.Set("tags", KeyValueTags(eni.TagSet).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { diff --git a/internal/service/ec2/network_interface_data_source_test.go b/internal/service/ec2/network_interface_data_source_test.go index ca8c3e5f459..701129bf427 100644 --- a/internal/service/ec2/network_interface_data_source_test.go +++ b/internal/service/ec2/network_interface_data_source_test.go @@ -183,6 +183,10 @@ resource "aws_subnet" "test" { resource "aws_security_group" "test" { name = %[1]q vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } resource "aws_network_interface" "test" { @@ -232,6 +236,10 @@ resource "aws_subnet" "test" { resource "aws_security_group" "test" { name = %[1]q vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } } resource "aws_network_interface" "test" { From acc3281f77ea46479508458968bfcc43906b381d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 2 Nov 2021 11:26:08 -0400 Subject: [PATCH 22/28] d/aws_network_interfaces: Use pagination. Acceptance test output: % make testacc PKG_NAME=internal/service/ec2 TESTARGS='-run=TestAccEC2NetworkInterfacesDataSource_' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/ec2/... -v -count 1 -parallel 20 -run=TestAccEC2NetworkInterfacesDataSource_ -timeout 180m === RUN TestAccEC2NetworkInterfacesDataSource_filter === PAUSE TestAccEC2NetworkInterfacesDataSource_filter === RUN TestAccEC2NetworkInterfacesDataSource_tags === PAUSE TestAccEC2NetworkInterfacesDataSource_tags === CONT TestAccEC2NetworkInterfacesDataSource_filter === CONT TestAccEC2NetworkInterfacesDataSource_tags --- PASS: TestAccEC2NetworkInterfacesDataSource_tags (29.56s) --- PASS: TestAccEC2NetworkInterfacesDataSource_filter (29.84s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/ec2 34.858s --- .../ec2/network_interfaces_data_source.go | 56 +++++++++---------- .../network_interfaces_data_source_test.go | 34 ++++++----- 2 files changed, 47 insertions(+), 43 deletions(-) diff --git a/internal/service/ec2/network_interfaces_data_source.go b/internal/service/ec2/network_interfaces_data_source.go index a0f76ca9f05..0e0cfbdfdaf 100644 --- a/internal/service/ec2/network_interfaces_data_source.go +++ b/internal/service/ec2/network_interfaces_data_source.go @@ -3,7 +3,6 @@ package ec2 import ( "errors" "fmt" - "log" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" @@ -16,17 +15,14 @@ func DataSourceNetworkInterfaces() *schema.Resource { return &schema.Resource{ Read: dataSourceNetworkInterfacesRead, Schema: map[string]*schema.Schema{ - "filter": CustomFiltersSchema(), - - "tags": tftags.TagsSchemaComputed(), - "ids": { Type: schema.TypeSet, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, Set: schema.HashString, }, + "tags": tftags.TagsSchemaComputed(), }, } } @@ -34,47 +30,49 @@ func DataSourceNetworkInterfaces() *schema.Resource { func dataSourceNetworkInterfacesRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).EC2Conn - req := &ec2.DescribeNetworkInterfacesInput{} - - filters, filtersOk := d.GetOk("filter") - tags, tagsOk := d.GetOk("tags") + input := &ec2.DescribeNetworkInterfacesInput{} - if tagsOk { - req.Filters = BuildTagFilterList( - Tags(tftags.New(tags.(map[string]interface{}))), + if v, ok := d.GetOk("tags"); ok { + input.Filters = BuildTagFilterList( + Tags(tftags.New(v.(map[string]interface{}))), ) } - if filtersOk { - req.Filters = append(req.Filters, BuildCustomFilterList( - filters.(*schema.Set), + if v, ok := d.GetOk("filter"); ok { + input.Filters = append(input.Filters, BuildCustomFilterList( + v.(*schema.Set), )...) } - if len(req.Filters) == 0 { - req.Filters = nil + if len(input.Filters) == 0 { + input.Filters = nil } - log.Printf("[DEBUG] DescribeNetworkInterfaces %s\n", req) - resp, err := conn.DescribeNetworkInterfaces(req) + networkInterfaceIDs := []string{} + err := conn.DescribeNetworkInterfacesPages(input, func(page *ec2.DescribeNetworkInterfacesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, networkInterface := range page.NetworkInterfaces { + networkInterfaceIDs = append(networkInterfaceIDs, aws.StringValue(networkInterface.NetworkInterfaceId)) + } + + return !lastPage + }) + if err != nil { - return err + return fmt.Errorf("error reading EC2 Network Interfaces: %w", err) } - if resp == nil || len(resp.NetworkInterfaces) == 0 { + if len(networkInterfaceIDs) == 0 { return errors.New("no matching network interfaces found") } - networkInterfaces := make([]string, 0) - - for _, networkInterface := range resp.NetworkInterfaces { - networkInterfaces = append(networkInterfaces, aws.StringValue(networkInterface.NetworkInterfaceId)) - } - d.SetId(meta.(*conns.AWSClient).Region) - if err := d.Set("ids", networkInterfaces); err != nil { - return fmt.Errorf("Error setting network interfaces ids: %w", err) + if err := d.Set("ids", networkInterfaceIDs); err != nil { + return fmt.Errorf("error setting ids: %w", err) } return nil diff --git a/internal/service/ec2/network_interfaces_data_source_test.go b/internal/service/ec2/network_interfaces_data_source_test.go index beb3d846fb4..81f7759999d 100644 --- a/internal/service/ec2/network_interfaces_data_source_test.go +++ b/internal/service/ec2/network_interfaces_data_source_test.go @@ -11,7 +11,8 @@ import ( ) func TestAccEC2NetworkInterfacesDataSource_filter(t *testing.T) { - rName := sdkacctest.RandString(5) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), @@ -29,7 +30,8 @@ func TestAccEC2NetworkInterfacesDataSource_filter(t *testing.T) { } func TestAccEC2NetworkInterfacesDataSource_tags(t *testing.T) { - rName := sdkacctest.RandString(5) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), @@ -52,7 +54,7 @@ resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" tags = { - Name = "terraform-testacc-eni-data-source-basic-%s" + Name = %[1]q } } @@ -61,41 +63,45 @@ resource "aws_subnet" "test" { vpc_id = aws_vpc.test.id tags = { - Name = "terraform-testacc-eni-data-source-basic-%s" + Name = %[1]q } } -resource "aws_network_interface" "test" { +resource "aws_network_interface" "test1" { subnet_id = aws_subnet.test.id + + tags = { + Name = "%[1]s-1" + } } -resource "aws_network_interface" "test1" { +resource "aws_network_interface" "test2" { subnet_id = aws_subnet.test.id tags = { - Name = aws_vpc.test.tags.Name + Name = "%[1]s-2" } } -`, rName, rName) +`, rName) } func testAccNetworkInterfacesDataSourceConfig_Filter(rName string) string { - return testAccNetworkInterfacesDataSourceConfig_Base(rName) + ` + return acctest.ConfigCompose(testAccNetworkInterfacesDataSourceConfig_Base(rName), ` data "aws_network_interfaces" "test" { filter { name = "subnet-id" - values = [aws_network_interface.test.subnet_id, aws_network_interface.test1.subnet_id] + values = [aws_network_interface.test1.subnet_id, aws_network_interface.test2.subnet_id] } } -` +`) } func testAccNetworkInterfacesDataSourceConfig_Tags(rName string) string { - return testAccNetworkInterfacesDataSourceConfig_Base(rName) + ` + return acctest.ConfigCompose(testAccNetworkInterfacesDataSourceConfig_Base(rName), ` data "aws_network_interfaces" "test" { tags = { - Name = aws_network_interface.test1.tags.Name + Name = aws_network_interface.test2.tags.Name } } -` +`) } From daa7b8e493fa9721c1aa037af43fdd581071be90 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 2 Nov 2021 11:53:05 -0400 Subject: [PATCH 23/28] r/aws_network_interface: Better error messages. Acceptance test output: % make testacc PKG_NAME=internal/service/ec2 TESTARGS='-run=TestAccEC2NetworkInterface_' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/ec2/... -v -count 1 -parallel 20 -run=TestAccEC2NetworkInterface_ -timeout 180m === RUN TestAccEC2NetworkInterface_ENI_basic === PAUSE TestAccEC2NetworkInterface_ENI_basic === RUN TestAccEC2NetworkInterface_ENI_ipv6 === PAUSE TestAccEC2NetworkInterface_ENI_ipv6 === RUN TestAccEC2NetworkInterface_ENI_tags === PAUSE TestAccEC2NetworkInterface_ENI_tags === RUN TestAccEC2NetworkInterface_ENI_ipv6Count === PAUSE TestAccEC2NetworkInterface_ENI_ipv6Count === RUN TestAccEC2NetworkInterface_ENI_disappears === PAUSE TestAccEC2NetworkInterface_ENI_disappears === RUN TestAccEC2NetworkInterface_ENI_description === PAUSE TestAccEC2NetworkInterface_ENI_description === RUN TestAccEC2NetworkInterface_ENI_attachment === PAUSE TestAccEC2NetworkInterface_ENI_attachment === RUN TestAccEC2NetworkInterface_ENI_ignoreExternalAttachment === PAUSE TestAccEC2NetworkInterface_ENI_ignoreExternalAttachment === RUN TestAccEC2NetworkInterface_ENI_sourceDestCheck === PAUSE TestAccEC2NetworkInterface_ENI_sourceDestCheck === RUN TestAccEC2NetworkInterface_ENI_privateIPsCount === PAUSE TestAccEC2NetworkInterface_ENI_privateIPsCount === RUN TestAccEC2NetworkInterface_ENIInterfaceType_efa === PAUSE TestAccEC2NetworkInterface_ENIInterfaceType_efa === CONT TestAccEC2NetworkInterface_ENI_basic === CONT TestAccEC2NetworkInterface_ENI_attachment === CONT TestAccEC2NetworkInterface_ENIInterfaceType_efa === CONT TestAccEC2NetworkInterface_ENI_ipv6 === CONT TestAccEC2NetworkInterface_ENI_sourceDestCheck === CONT TestAccEC2NetworkInterface_ENI_privateIPsCount === CONT TestAccEC2NetworkInterface_ENI_ipv6Count === CONT TestAccEC2NetworkInterface_ENI_description === CONT TestAccEC2NetworkInterface_ENI_disappears === CONT TestAccEC2NetworkInterface_ENI_ignoreExternalAttachment === CONT TestAccEC2NetworkInterface_ENI_tags --- PASS: TestAccEC2NetworkInterface_ENI_disappears (47.48s) --- PASS: TestAccEC2NetworkInterface_ENI_basic (48.34s) --- PASS: TestAccEC2NetworkInterface_ENIInterfaceType_efa (52.39s) --- PASS: TestAccEC2NetworkInterface_ENI_description (81.72s) --- PASS: TestAccEC2NetworkInterface_ENI_ipv6 (92.79s) --- PASS: TestAccEC2NetworkInterface_ENI_tags (94.86s) --- PASS: TestAccEC2NetworkInterface_ENI_sourceDestCheck (99.54s) --- PASS: TestAccEC2NetworkInterface_ENI_ipv6Count (114.31s) --- PASS: TestAccEC2NetworkInterface_ENI_privateIPsCount (119.80s) --- PASS: TestAccEC2NetworkInterface_ENI_ignoreExternalAttachment (273.63s) --- PASS: TestAccEC2NetworkInterface_ENI_attachment (344.90s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/ec2 348.007s --- internal/service/ec2/network_interface.go | 182 ++++++++++++---------- 1 file changed, 100 insertions(+), 82 deletions(-) diff --git a/internal/service/ec2/network_interface.go b/internal/service/ec2/network_interface.go index a03aeda3193..592047abb2d 100644 --- a/internal/service/ec2/network_interface.go +++ b/internal/service/ec2/network_interface.go @@ -189,17 +189,18 @@ func resourceNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("error waiting for EC2 Network Interface (%s) create: %w", d.Id(), err) } - //Default value is enabled + // Default value is enabled. if !d.Get("source_dest_check").(bool) { input := &ec2.ModifyNetworkInterfaceAttributeInput{ NetworkInterfaceId: aws.String(d.Id()), SourceDestCheck: &ec2.AttributeBooleanValue{Value: aws.Bool(false)}, } + log.Printf("[INFO] Modifying EC2 Network Interface: %s", input) _, err := conn.ModifyNetworkInterfaceAttribute(input) if err != nil { - return fmt.Errorf("error setting EC2 Network Interface (%s) SourceDestCheck: %w", d.Id(), err) + return fmt.Errorf("error modifying EC2 Network Interface (%s) SourceDestCheck: %w", d.Id(), err) } } @@ -315,7 +316,6 @@ func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) er } } - // if there is a new attachment, attach it if na != nil && na.(*schema.Set).Len() > 0 { attachment := na.(*schema.Set).List()[0].(map[string]interface{}) @@ -339,29 +339,76 @@ func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) er os := o.(*schema.Set) ns := n.(*schema.Set) - // Unassign old IP addresses - unassignIps := os.Difference(ns) - if unassignIps.Len() != 0 { + // Unassign old IP addresses. + unassignIPs := os.Difference(ns) + if unassignIPs.Len() != 0 { input := &ec2.UnassignPrivateIpAddressesInput{ NetworkInterfaceId: aws.String(d.Id()), - PrivateIpAddresses: flex.ExpandStringSet(unassignIps), + PrivateIpAddresses: flex.ExpandStringSet(unassignIPs), } + + log.Printf("[INFO] Unassigning private IPv4 addresses: %s", input) _, err := conn.UnassignPrivateIpAddresses(input) + if err != nil { - return fmt.Errorf("Failure to unassign Private IPs: %s", err) + return fmt.Errorf("error unassigning EC2 Network Interface (%s) private IPv4 addresses: %w", d.Id(), err) } } - // Assign new IP addresses - assignIps := ns.Difference(os) - if assignIps.Len() != 0 { + // Assign new IP addresses. + assignIPs := ns.Difference(os) + if assignIPs.Len() != 0 { input := &ec2.AssignPrivateIpAddressesInput{ NetworkInterfaceId: aws.String(d.Id()), - PrivateIpAddresses: flex.ExpandStringSet(assignIps), + PrivateIpAddresses: flex.ExpandStringSet(assignIPs), } + + log.Printf("[INFO] Assigning private IPv4 addresses: %s", input) _, err := conn.AssignPrivateIpAddresses(input) + if err != nil { - return fmt.Errorf("Failure to assign Private IPs: %s", err) + return fmt.Errorf("error assigning EC2 Network Interface (%s) private IPv4 addresses: %w", d.Id(), err) + } + } + } + + if d.HasChange("private_ips_count") { + o, n := d.GetChange("private_ips_count") + privateIPs := d.Get("private_ips").(*schema.Set).List() + privateIPsFiltered := privateIPs[:0] + primaryIP := d.Get("private_ip") + + for _, ip := range privateIPs { + if ip != primaryIP { + privateIPsFiltered = append(privateIPsFiltered, ip) + } + } + + if o != nil && n != nil && n != len(privateIPsFiltered) { + if diff := n.(int) - o.(int); diff > 0 { + input := &ec2.AssignPrivateIpAddressesInput{ + NetworkInterfaceId: aws.String(d.Id()), + SecondaryPrivateIpAddressCount: aws.Int64(int64(diff)), + } + + log.Printf("[INFO] Assigning private IPv4 addresses: %s", input) + _, err := conn.AssignPrivateIpAddresses(input) + + if err != nil { + return fmt.Errorf("error assigning EC2 Network Interface (%s) private IPv4 addresses: %w", d.Id(), err) + } + } else if diff < 0 { + input := &ec2.UnassignPrivateIpAddressesInput{ + NetworkInterfaceId: aws.String(d.Id()), + PrivateIpAddresses: flex.ExpandStringList(privateIPsFiltered[0:int(math.Abs(float64(diff)))]), + } + + log.Printf("[INFO] Unassigning private IPv4 addresses: %s", input) + _, err := conn.UnassignPrivateIpAddresses(input) + + if err != nil { + return fmt.Errorf("error unassigning EC2 Network Interface (%s) private IPv4 addresses: %w", d.Id(), err) + } } } } @@ -378,29 +425,35 @@ func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) er os := o.(*schema.Set) ns := n.(*schema.Set) - // Unassign old IPV6 addresses - unassignIps := os.Difference(ns) - if unassignIps.Len() != 0 { + // Unassign old IPV6 addresses. + unassignIPs := os.Difference(ns) + if unassignIPs.Len() != 0 { input := &ec2.UnassignIpv6AddressesInput{ NetworkInterfaceId: aws.String(d.Id()), - Ipv6Addresses: flex.ExpandStringSet(unassignIps), + Ipv6Addresses: flex.ExpandStringSet(unassignIPs), } + + log.Printf("[INFO] Unassigning IPv6 addresses: %s", input) _, err := conn.UnassignIpv6Addresses(input) + if err != nil { - return fmt.Errorf("failure to unassign IPV6 Addresses: %s", err) + return fmt.Errorf("error unassigning EC2 Network Interface (%s) IPv6 addresses: %w", d.Id(), err) } } - // Assign new IPV6 addresses - assignIps := ns.Difference(os) - if assignIps.Len() != 0 { + // Assign new IPV6 addresses, + assignIPs := ns.Difference(os) + if assignIPs.Len() != 0 { input := &ec2.AssignIpv6AddressesInput{ NetworkInterfaceId: aws.String(d.Id()), - Ipv6Addresses: flex.ExpandStringSet(assignIps), + Ipv6Addresses: flex.ExpandStringSet(assignIPs), } + + log.Printf("[INFO] Assigning IPv6 addresses: %s", input) _, err := conn.AssignIpv6Addresses(input) + if err != nil { - return fmt.Errorf("Failure to assign IPV6 Addresses: %s", err) + return fmt.Errorf("error assigning EC2 Network Interface (%s) IPv6 addresses: %w", d.Id(), err) } } } @@ -410,27 +463,27 @@ func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) er ipv6Addresses := d.Get("ipv6_addresses").(*schema.Set).List() if o != nil && n != nil && n != len(ipv6Addresses) { - - diff := n.(int) - o.(int) - - // Surplus of IPs, add the diff - if diff > 0 { + if diff := n.(int) - o.(int); diff > 0 { input := &ec2.AssignIpv6AddressesInput{ NetworkInterfaceId: aws.String(d.Id()), Ipv6AddressCount: aws.Int64(int64(diff)), } + + log.Printf("[INFO] Assigning IPv6 addresses: %s", input) _, err := conn.AssignIpv6Addresses(input) + if err != nil { - return fmt.Errorf("failure to assign IPV6 Addresses: %s", err) + return fmt.Errorf("error assigning EC2 Network Interface (%s) IPv6 addresses: %w", d.Id(), err) } - } - - if diff < 0 { + } else if diff < 0 { input := &ec2.UnassignIpv6AddressesInput{ NetworkInterfaceId: aws.String(d.Id()), Ipv6Addresses: flex.ExpandStringList(ipv6Addresses[0:int(math.Abs(float64(diff)))]), } + + log.Printf("[INFO] Unassigning IPv6 addresses: %s", input) _, err := conn.UnassignIpv6Addresses(input) + if err != nil { return fmt.Errorf("failure to unassign IPV6 Addresses: %s", err) } @@ -439,79 +492,44 @@ func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) er } if d.HasChange("source_dest_check") { - request := &ec2.ModifyNetworkInterfaceAttributeInput{ + input := &ec2.ModifyNetworkInterfaceAttributeInput{ NetworkInterfaceId: aws.String(d.Id()), SourceDestCheck: &ec2.AttributeBooleanValue{Value: aws.Bool(d.Get("source_dest_check").(bool))}, } - _, err := conn.ModifyNetworkInterfaceAttribute(request) - if err != nil { - return fmt.Errorf("failure updating Source Dest Check on ENI: %s", err) - } - } - - if d.HasChange("private_ips_count") { - o, n := d.GetChange("private_ips_count") - privateIPs := d.Get("private_ips").(*schema.Set).List() - privateIPsFiltered := privateIPs[:0] - primaryIP := d.Get("private_ip") - - for _, ip := range privateIPs { - if ip != primaryIP { - privateIPsFiltered = append(privateIPsFiltered, ip) - } - } - - if o != nil && n != nil && n != len(privateIPsFiltered) { - - diff := n.(int) - o.(int) - - // Surplus of IPs, add the diff - if diff > 0 { - input := &ec2.AssignPrivateIpAddressesInput{ - NetworkInterfaceId: aws.String(d.Id()), - SecondaryPrivateIpAddressCount: aws.Int64(int64(diff)), - } - _, err := conn.AssignPrivateIpAddresses(input) - if err != nil { - return fmt.Errorf("Failure to assign Private IPs: %s", err) - } - } + log.Printf("[INFO] Modifying EC2 Network Interface: %s", input) + _, err := conn.ModifyNetworkInterfaceAttribute(input) - if diff < 0 { - input := &ec2.UnassignPrivateIpAddressesInput{ - NetworkInterfaceId: aws.String(d.Id()), - PrivateIpAddresses: flex.ExpandStringList(privateIPsFiltered[0:int(math.Abs(float64(diff)))]), - } - _, err := conn.UnassignPrivateIpAddresses(input) - if err != nil { - return fmt.Errorf("Failure to unassign Private IPs: %s", err) - } - } + if err != nil { + return fmt.Errorf("error modifying EC2 Network Interface (%s) SourceDestCheck: %w", d.Id(), err) } } if d.HasChange("security_groups") { - request := &ec2.ModifyNetworkInterfaceAttributeInput{ + input := &ec2.ModifyNetworkInterfaceAttributeInput{ NetworkInterfaceId: aws.String(d.Id()), Groups: flex.ExpandStringSet(d.Get("security_groups").(*schema.Set)), } - _, err := conn.ModifyNetworkInterfaceAttribute(request) + log.Printf("[INFO] Modifying EC2 Network Interface: %s", input) + _, err := conn.ModifyNetworkInterfaceAttribute(input) + if err != nil { - return fmt.Errorf("Failure updating ENI: %s", err) + return fmt.Errorf("error modifying EC2 Network Interface (%s) Groups: %w", d.Id(), err) } } if d.HasChange("description") { - request := &ec2.ModifyNetworkInterfaceAttributeInput{ + input := &ec2.ModifyNetworkInterfaceAttributeInput{ NetworkInterfaceId: aws.String(d.Id()), Description: &ec2.AttributeValue{Value: aws.String(d.Get("description").(string))}, } - _, err := conn.ModifyNetworkInterfaceAttribute(request) + log.Printf("[INFO] Modifying EC2 Network Interface: %s", input) + _, err := conn.ModifyNetworkInterfaceAttribute(input) + if err != nil { - return fmt.Errorf("Failure updating ENI: %s", err) + return fmt.Errorf("error modifying EC2 Network Interface (%s) Description: %w", d.Id(), err) } } From 3e2f231ecab457de120476bda6763e5da17fcf5c Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 2 Nov 2021 13:31:03 -0400 Subject: [PATCH 24/28] r/aws_network_interface: Add 'ipv4_prefix', 'ipv4_prefix_count', 'ipv6_prefix' and 'ipv6_prefix_count' arguments. --- .changelog/21265.txt | 4 + internal/service/ec2/network_interface.go | 379 +++++++++++++++++- .../service/ec2/network_interface_test.go | 63 +++ website/docs/r/network_interface.markdown | 4 + 4 files changed, 446 insertions(+), 4 deletions(-) diff --git a/.changelog/21265.txt b/.changelog/21265.txt index 61304ac3878..7195b497ab0 100644 --- a/.changelog/21265.txt +++ b/.changelog/21265.txt @@ -6,6 +6,10 @@ resource/aws_route_table: Remove cross-account ENIs managed by AWS services like resource/aws_network_interface: Add `arn` and `owner_id` attributes ``` +```release-note:enhancement +resource/aws_network_interface: Add `ipv4_prefix`, `ipv4_prefix_count`, `ipv6_prefix` and `ipv6_prefix_count` arguments +``` + ```release-note:enhancement data-source/aws_network_interface: Add `arn` attribute ``` \ No newline at end of file diff --git a/internal/service/ec2/network_interface.go b/internal/service/ec2/network_interface.go index 592047abb2d..aad4ce0a625 100644 --- a/internal/service/ec2/network_interface.go +++ b/internal/service/ec2/network_interface.go @@ -3,7 +3,6 @@ package ec2 import ( "fmt" "log" - "math" "time" "github.com/aws/aws-sdk-go/aws" @@ -66,6 +65,28 @@ func ResourceNetworkInterface() *schema.Resource { ForceNew: true, ValidateFunc: validation.StringInSlice(ec2.NetworkInterfaceCreationType_Values(), false), }, + "ipv4_prefix": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv4_prefix": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: verify.ValidIPv4CIDRNetworkAddress, + }, + }, + }, + ConflictsWith: []string{"ipv4_prefix_count"}, + }, + "ipv4_prefix_count": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ConflictsWith: []string{"ipv4_prefix"}, + }, "ipv6_address_count": { Type: schema.TypeInt, Optional: true, @@ -82,6 +103,28 @@ func ResourceNetworkInterface() *schema.Resource { }, ConflictsWith: []string{"ipv6_address_count"}, }, + "ipv6_prefix": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ipv6_prefix": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: verify.ValidIPv6CIDRNetworkAddress, + }, + }, + }, + ConflictsWith: []string{"ipv6_prefix_count"}, + }, + "ipv6_prefix_count": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ConflictsWith: []string{"ipv6_prefix"}, + }, "mac_address": { Type: schema.TypeString, Computed: true, @@ -156,6 +199,14 @@ func resourceNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) er input.InterfaceType = aws.String(v.(string)) } + if v, ok := d.GetOk("ipv4_prefix"); ok && v.(*schema.Set).Len() > 0 { + input.Ipv4Prefixes = expandIpv4PrefixSpecificationRequests(v.(*schema.Set).List()) + } + + if v, ok := d.GetOk("ipv4_prefix_count"); ok { + input.Ipv4PrefixCount = aws.Int64(int64(v.(int))) + } + if v, ok := d.GetOk("ipv6_address_count"); ok { input.Ipv6AddressCount = aws.Int64(int64(v.(int))) } @@ -164,6 +215,14 @@ func resourceNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) er input.Ipv6Addresses = expandIP6Addresses(v.(*schema.Set).List()) } + if v, ok := d.GetOk("ipv6_prefix"); ok && v.(*schema.Set).Len() > 0 { + input.Ipv6Prefixes = expandIpv6PrefixSpecificationRequests(v.(*schema.Set).List()) + } + + if v, ok := d.GetOk("ipv6_prefix_count"); ok { + input.Ipv6PrefixCount = aws.Int64(int64(v.(int))) + } + if v, ok := d.GetOk("private_ips"); ok && v.(*schema.Set).Len() > 0 { input.PrivateIpAddresses = ExpandPrivateIPAddresses(v.(*schema.Set).List()) } @@ -261,12 +320,24 @@ func resourceNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) erro d.Set("description", eni.Description) d.Set("interface_type", eni.InterfaceType) + if err := d.Set("ipv4_prefix", flattenIpv4PrefixSpecifications(eni.Ipv4Prefixes)); err != nil { + return fmt.Errorf("error setting ipv4_prefix: %w", err) + } + + d.Set("ipv4_prefix_count", len(eni.Ipv4Prefixes)) + d.Set("ipv6_address_count", len(eni.Ipv6Addresses)) if err := d.Set("ipv6_addresses", flattenNetworkInterfaceIPv6Address(eni.Ipv6Addresses)); err != nil { return fmt.Errorf("error setting ipv6_addresses: %w", err) } + if err := d.Set("ipv6_prefix", flattenIpv6PrefixSpecifications(eni.Ipv6Prefixes)); err != nil { + return fmt.Errorf("error setting ipv6_prefix: %w", err) + } + + d.Set("ipv6_prefix_count", len(eni.Ipv6Prefixes)) + d.Set("mac_address", eni.MacAddress) d.Set("outpost_arn", eni.OutpostArn) d.Set("owner_id", ownerID) @@ -400,7 +471,40 @@ func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) er } else if diff < 0 { input := &ec2.UnassignPrivateIpAddressesInput{ NetworkInterfaceId: aws.String(d.Id()), - PrivateIpAddresses: flex.ExpandStringList(privateIPsFiltered[0:int(math.Abs(float64(diff)))]), + PrivateIpAddresses: flex.ExpandStringList(privateIPsFiltered[0:-diff]), + } + + log.Printf("[INFO] Unassigning private IPv4 addresses: %s", input) + _, err := conn.UnassignPrivateIpAddresses(input) + + if err != nil { + return fmt.Errorf("error unassigning EC2 Network Interface (%s) private IPv4 addresses: %w", d.Id(), err) + } + } + } + } + + if d.HasChange("ipv4_prefix_count") { + o, n := d.GetChange("ipv4_prefix_count") + ipv4Prefixes := d.Get("ipv4_prefix").(*schema.Set).List() + + if o != nil && n != nil && n != len(ipv4Prefixes) { + if diff := n.(int) - o.(int); diff > 0 { + input := &ec2.AssignPrivateIpAddressesInput{ + NetworkInterfaceId: aws.String(d.Id()), + Ipv4PrefixCount: aws.Int64(int64(diff)), + } + + log.Printf("[INFO] Assigning private IPv4 addresses: %s", input) + _, err := conn.AssignPrivateIpAddresses(input) + + if err != nil { + return fmt.Errorf("error assigning EC2 Network Interface (%s) private IPv4 addresses: %w", d.Id(), err) + } + } else if diff < 0 { + input := &ec2.UnassignPrivateIpAddressesInput{ + NetworkInterfaceId: aws.String(d.Id()), + Ipv4Prefixes: flex.ExpandStringList(ipv4Prefixes[0:-diff]), } log.Printf("[INFO] Unassigning private IPv4 addresses: %s", input) @@ -413,6 +517,51 @@ func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) er } } + if d.HasChange("ipv4_prefix") { + o, n := d.GetChange("ipv4_prefix") + if o == nil { + o = new(schema.Set) + } + if n == nil { + n = new(schema.Set) + } + + os := o.(*schema.Set) + ns := n.(*schema.Set) + + // Unassign old IPV4 prefixes. + unassignPrefixes := os.Difference(ns) + if unassignPrefixes.Len() != 0 { + input := &ec2.UnassignPrivateIpAddressesInput{ + NetworkInterfaceId: aws.String(d.Id()), + Ipv4Prefixes: flex.ExpandStringSet(unassignPrefixes), + } + + log.Printf("[INFO] Unassigning private IPv4 addresses: %s", input) + _, err := conn.UnassignPrivateIpAddresses(input) + + if err != nil { + return fmt.Errorf("error unassigning EC2 Network Interface (%s) private IPv4 addresses: %w", d.Id(), err) + } + } + + // Assign new IPV4 prefixes, + assignPrefixes := ns.Difference(os) + if assignPrefixes.Len() != 0 { + input := &ec2.AssignPrivateIpAddressesInput{ + NetworkInterfaceId: aws.String(d.Id()), + Ipv4Prefixes: flex.ExpandStringSet(assignPrefixes), + } + + log.Printf("[INFO] Assigning private IPv4 addresses: %s", input) + _, err := conn.AssignPrivateIpAddresses(input) + + if err != nil { + return fmt.Errorf("error assigning EC2 Network Interface (%s) private IPv4 addresses: %w", d.Id(), err) + } + } + } + if d.HasChange("ipv6_addresses") { o, n := d.GetChange("ipv6_addresses") if o == nil { @@ -478,14 +627,92 @@ func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) er } else if diff < 0 { input := &ec2.UnassignIpv6AddressesInput{ NetworkInterfaceId: aws.String(d.Id()), - Ipv6Addresses: flex.ExpandStringList(ipv6Addresses[0:int(math.Abs(float64(diff)))]), + Ipv6Addresses: flex.ExpandStringList(ipv6Addresses[0:-diff]), } log.Printf("[INFO] Unassigning IPv6 addresses: %s", input) _, err := conn.UnassignIpv6Addresses(input) if err != nil { - return fmt.Errorf("failure to unassign IPV6 Addresses: %s", err) + return fmt.Errorf("error unassigning EC2 Network Interface (%s) IPv6 addresses: %w", d.Id(), err) + } + } + } + } + + if d.HasChange("ipv6_prefix") { + o, n := d.GetChange("ipv6_prefix") + if o == nil { + o = new(schema.Set) + } + if n == nil { + n = new(schema.Set) + } + + os := o.(*schema.Set) + ns := n.(*schema.Set) + + // Unassign old IPV6 prefixes. + unassignPrefixes := os.Difference(ns) + if unassignPrefixes.Len() != 0 { + input := &ec2.UnassignIpv6AddressesInput{ + NetworkInterfaceId: aws.String(d.Id()), + Ipv6Prefixes: flex.ExpandStringSet(unassignPrefixes), + } + + log.Printf("[INFO] Unassigning IPv6 addresses: %s", input) + _, err := conn.UnassignIpv6Addresses(input) + + if err != nil { + return fmt.Errorf("error unassigning EC2 Network Interface (%s) IPv6 addresses: %w", d.Id(), err) + } + } + + // Assign new IPV6 prefixes, + assignPrefixes := ns.Difference(os) + if assignPrefixes.Len() != 0 { + input := &ec2.AssignIpv6AddressesInput{ + NetworkInterfaceId: aws.String(d.Id()), + Ipv6Prefixes: flex.ExpandStringSet(assignPrefixes), + } + + log.Printf("[INFO] Assigning IPv6 addresses: %s", input) + _, err := conn.AssignIpv6Addresses(input) + + if err != nil { + return fmt.Errorf("error assigning EC2 Network Interface (%s) IPv6 addresses: %w", d.Id(), err) + } + } + } + + if d.HasChange("ipv6_prefix_count") { + o, n := d.GetChange("ipv6_prefix_count") + ipv6Prefixes := d.Get("ipv6_prefix").(*schema.Set).List() + + if o != nil && n != nil && n != len(ipv6Prefixes) { + if diff := n.(int) - o.(int); diff > 0 { + input := &ec2.AssignIpv6AddressesInput{ + NetworkInterfaceId: aws.String(d.Id()), + Ipv6PrefixCount: aws.Int64(int64(diff)), + } + + log.Printf("[INFO] Assigning IPv6 addresses: %s", input) + _, err := conn.AssignIpv6Addresses(input) + + if err != nil { + return fmt.Errorf("error assigning EC2 Network Interface (%s) IPv6 addresses: %w", d.Id(), err) + } + } else if diff < 0 { + input := &ec2.UnassignIpv6AddressesInput{ + NetworkInterfaceId: aws.String(d.Id()), + Ipv6Prefixes: flex.ExpandStringList(ipv6Prefixes[0:-diff]), + } + + log.Printf("[INFO] Unassigning IPv6 addresses: %s", input) + _, err := conn.UnassignIpv6Addresses(input) + + if err != nil { + return fmt.Errorf("error unassigning EC2 Network Interface (%s) IPv6 addresses: %w", d.Id(), err) } } } @@ -631,3 +858,147 @@ func detachNetworkInterface(conn *ec2.EC2, networkInterfaceID, attachmentID stri return nil } + +func expandIpv4PrefixSpecificationRequest(tfMap map[string]interface{}) *ec2.Ipv4PrefixSpecificationRequest { + if tfMap == nil { + return nil + } + + apiObject := &ec2.Ipv4PrefixSpecificationRequest{} + + if v, ok := tfMap["ipv4_prefix"].(string); ok && v != "" { + apiObject.Ipv4Prefix = aws.String(v) + } + + return apiObject +} + +func expandIpv4PrefixSpecificationRequests(tfList []interface{}) []*ec2.Ipv4PrefixSpecificationRequest { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*ec2.Ipv4PrefixSpecificationRequest + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandIpv4PrefixSpecificationRequest(tfMap) + + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func expandIpv6PrefixSpecificationRequest(tfMap map[string]interface{}) *ec2.Ipv6PrefixSpecificationRequest { + if tfMap == nil { + return nil + } + + apiObject := &ec2.Ipv6PrefixSpecificationRequest{} + + if v, ok := tfMap["ipv6_prefix"].(string); ok && v != "" { + apiObject.Ipv6Prefix = aws.String(v) + } + + return apiObject +} + +func expandIpv6PrefixSpecificationRequests(tfList []interface{}) []*ec2.Ipv6PrefixSpecificationRequest { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*ec2.Ipv6PrefixSpecificationRequest + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandIpv6PrefixSpecificationRequest(tfMap) + + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func flattenIpv4PrefixSpecification(apiObject *ec2.Ipv4PrefixSpecification) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.Ipv4Prefix; v != nil { + tfMap["ipv4_prefix"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenIpv4PrefixSpecifications(apiObjects []*ec2.Ipv4PrefixSpecification) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenIpv4PrefixSpecification(apiObject)) + } + + return tfList +} + +func flattenIpv6PrefixSpecification(apiObject *ec2.Ipv6PrefixSpecification) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.Ipv6Prefix; v != nil { + tfMap["ipv6_prefix"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenIpv6PrefixSpecifications(apiObjects []*ec2.Ipv6PrefixSpecification) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenIpv6PrefixSpecification(apiObject)) + } + + return tfList +} diff --git a/internal/service/ec2/network_interface_test.go b/internal/service/ec2/network_interface_test.go index 5e4e8428f1e..c192fd2c15e 100644 --- a/internal/service/ec2/network_interface_test.go +++ b/internal/service/ec2/network_interface_test.go @@ -478,6 +478,54 @@ func TestAccEC2NetworkInterface_ENIInterfaceType_efa(t *testing.T) { }) } +func TestAccEC2NetworkInterface_ENI_ipv6PrefixCount(t *testing.T) { + var conf ec2.NetworkInterface + resourceName := "aws_network_interface.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckENIDestroy, + Steps: []resource.TestStep{ + { + Config: testAccENIIPV6PrefixCountConfig(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckENIExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "ipv6_prefix_count", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccENIIPV6PrefixCountConfig(rName, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckENIExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "ipv6_prefix_count", "2"), + ), + }, + { + Config: testAccENIIPV6PrefixCountConfig(rName, 0), + Check: resource.ComposeTestCheckFunc( + testAccCheckENIExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "ipv6_prefix_count", "0"), + ), + }, + { + Config: testAccENIIPV6PrefixCountConfig(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckENIExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "ipv6_prefix_count", "1"), + ), + }, + }, + }) +} + func testAccCheckENIExists(n string, v *ec2.NetworkInterface) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -852,3 +900,18 @@ resource "aws_network_interface" "test" { } `, rName, interfaceType)) } + +func testAccENIIPV6PrefixCountConfig(rName string, ipv6PrefixCount int) string { + return acctest.ConfigCompose(testAccENIIPV6BaseConfig(rName) + fmt.Sprintf(` +resource "aws_network_interface" "test" { + subnet_id = aws_subnet.test.id + private_ips = ["172.16.10.100"] + ipv6_prefix_count = %[2]d + security_groups = [aws_security_group.test.id] + + tags = { + Name = %[1]q + } +} +`, rName, ipv6PrefixCount)) +} diff --git a/website/docs/r/network_interface.markdown b/website/docs/r/network_interface.markdown index 791ec902569..e48ddbc6472 100644 --- a/website/docs/r/network_interface.markdown +++ b/website/docs/r/network_interface.markdown @@ -38,6 +38,10 @@ The following arguments are supported: * `security_groups` - (Optional) List of security group IDs to assign to the ENI. * `attachment` - (Optional) Block to define the attachment of the ENI. Documented below. * `source_dest_check` - (Optional) Whether to enable source destination checking for the ENI. Default true. +* `ipv4_prefix` - (Optional) One or more IPv4 prefixes assigned to the network interface. Documented below. +* `ipv4_prefix_count` - (Optional) The number of IPv4 prefixes that AWS automatically assigns to the network interface. +* `ipv6_prefix` - (Optional) One or more IPv6 prefixes assigned to the network interface. Documented below. +* `ipv6_prefix_count` - (Optional) The number of IPv6 prefixes that AWS automatically assigns to the network interface. -> **NOTE:** Changing `interface_type` will cause the resource to be destroyed and re-created. From 52e7b77e1aac933e987bb1e54f81c63d0a54ec5f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 2 Nov 2021 15:28:24 -0400 Subject: [PATCH 25/28] r/aws_network_interface: 'ipv4_prefix' -> 'ipv4_prefixes' (and the same fo IPv6). --- internal/service/ec2/flex.go | 45 ---- internal/service/ec2/network_interface.go | 164 ++++++++------ .../service/ec2/network_interface_test.go | 208 ++++++++++++++++++ website/docs/r/network_interface.markdown | 4 +- 4 files changed, 308 insertions(+), 113 deletions(-) diff --git a/internal/service/ec2/flex.go b/internal/service/ec2/flex.go index b100a5aaea0..043111415e1 100644 --- a/internal/service/ec2/flex.go +++ b/internal/service/ec2/flex.go @@ -53,18 +53,6 @@ type GroupIdentifier struct { Description *string } -func expandIP6Addresses(ips []interface{}) []*ec2.InstanceIpv6Address { - dtos := make([]*ec2.InstanceIpv6Address, 0, len(ips)) - for _, v := range ips { - ipv6Address := &ec2.InstanceIpv6Address{ - Ipv6Address: aws.String(v.(string)), - } - - dtos = append(dtos, ipv6Address) - } - return dtos -} - // Takes the result of flatmap.Expand for an array of ingress/egress security // group rules and returns EC2 API compatible objects. This function will error // if it finds invalid permissions input, namely a protocol of "-1" with either @@ -203,39 +191,6 @@ func flattenNetworkInterfaceAssociation(a *ec2.NetworkInterfaceAssociation) []in return []interface{}{tfMap} } -func flattenNetworkInterfaceIPv6Address(niia []*ec2.NetworkInterfaceIpv6Address) []string { - ips := make([]string, 0, len(niia)) - for _, v := range niia { - ips = append(ips, *v.Ipv6Address) - } - return ips -} - -//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 FlattenNetworkInterfacesPrivateIPAddresses(dtos []*ec2.NetworkInterfacePrivateIpAddress) []string { - ips := make([]string, 0, len(dtos)) - for _, v := range dtos { - ip := *v.PrivateIpAddress - ips = append(ips, ip) - } - return ips -} - -//Expands an array of IPs into a ec2 Private IP Address Spec -func ExpandPrivateIPAddresses(ips []interface{}) []*ec2.PrivateIpAddressSpecification { - dtos := make([]*ec2.PrivateIpAddressSpecification, 0, len(ips)) - for i, v := range ips { - new_private_ip := &ec2.PrivateIpAddressSpecification{ - PrivateIpAddress: aws.String(v.(string)), - } - - new_private_ip.Primary = aws.Bool(i == 0) - - dtos = append(dtos, new_private_ip) - } - return dtos -} - // Flattens an array of UserSecurityGroups into a []*GroupIdentifier func FlattenSecurityGroups(list []*ec2.UserIdGroupPair, ownerId *string) []*GroupIdentifier { result := make([]*GroupIdentifier, 0, len(list)) diff --git a/internal/service/ec2/network_interface.go b/internal/service/ec2/network_interface.go index aad4ce0a625..6ad969f828d 100644 --- a/internal/service/ec2/network_interface.go +++ b/internal/service/ec2/network_interface.go @@ -65,19 +65,13 @@ func ResourceNetworkInterface() *schema.Resource { ForceNew: true, ValidateFunc: validation.StringInSlice(ec2.NetworkInterfaceCreationType_Values(), false), }, - "ipv4_prefix": { + "ipv4_prefixes": { Type: schema.TypeSet, Optional: true, Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "ipv4_prefix": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: verify.ValidIPv4CIDRNetworkAddress, - }, - }, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: verify.ValidIPv4CIDRNetworkAddress, }, ConflictsWith: []string{"ipv4_prefix_count"}, }, @@ -85,7 +79,7 @@ func ResourceNetworkInterface() *schema.Resource { Type: schema.TypeInt, Optional: true, Computed: true, - ConflictsWith: []string{"ipv4_prefix"}, + ConflictsWith: []string{"ipv4_prefixes"}, }, "ipv6_address_count": { Type: schema.TypeInt, @@ -103,19 +97,13 @@ func ResourceNetworkInterface() *schema.Resource { }, ConflictsWith: []string{"ipv6_address_count"}, }, - "ipv6_prefix": { + "ipv6_prefixes": { Type: schema.TypeSet, Optional: true, Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "ipv6_prefix": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: verify.ValidIPv6CIDRNetworkAddress, - }, - }, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: verify.ValidIPv6CIDRNetworkAddress, }, ConflictsWith: []string{"ipv6_prefix_count"}, }, @@ -123,7 +111,7 @@ func ResourceNetworkInterface() *schema.Resource { Type: schema.TypeInt, Optional: true, Computed: true, - ConflictsWith: []string{"ipv6_prefix"}, + ConflictsWith: []string{"ipv6_prefixes"}, }, "mac_address": { Type: schema.TypeString, @@ -187,8 +175,7 @@ func resourceNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) er tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) input := &ec2.CreateNetworkInterfaceInput{ - SubnetId: aws.String(d.Get("subnet_id").(string)), - TagSpecifications: ec2TagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeNetworkInterface), + SubnetId: aws.String(d.Get("subnet_id").(string)), } if v, ok := d.GetOk("description"); ok { @@ -199,7 +186,7 @@ func resourceNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) er input.InterfaceType = aws.String(v.(string)) } - if v, ok := d.GetOk("ipv4_prefix"); ok && v.(*schema.Set).Len() > 0 { + if v, ok := d.GetOk("ipv4_prefixes"); ok && v.(*schema.Set).Len() > 0 { input.Ipv4Prefixes = expandIpv4PrefixSpecificationRequests(v.(*schema.Set).List()) } @@ -215,7 +202,7 @@ func resourceNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) er input.Ipv6Addresses = expandIP6Addresses(v.(*schema.Set).List()) } - if v, ok := d.GetOk("ipv6_prefix"); ok && v.(*schema.Set).Len() > 0 { + if v, ok := d.GetOk("ipv6_prefixes"); ok && v.(*schema.Set).Len() > 0 { input.Ipv6Prefixes = expandIpv6PrefixSpecificationRequests(v.(*schema.Set).List()) } @@ -235,6 +222,10 @@ func resourceNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) er input.Groups = flex.ExpandStringSet(v.(*schema.Set)) } + if len(tags) > 0 { + input.TagSpecifications = ec2TagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeNetworkInterface) + } + log.Printf("[DEBUG] Creating EC2 Network Interface: %s", input) output, err := conn.CreateNetworkInterface(input) @@ -320,8 +311,8 @@ func resourceNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) erro d.Set("description", eni.Description) d.Set("interface_type", eni.InterfaceType) - if err := d.Set("ipv4_prefix", flattenIpv4PrefixSpecifications(eni.Ipv4Prefixes)); err != nil { - return fmt.Errorf("error setting ipv4_prefix: %w", err) + if err := d.Set("ipv4_prefixes", flattenIpv4PrefixSpecifications(eni.Ipv4Prefixes)); err != nil { + return fmt.Errorf("error setting ipv4_prefixes: %w", err) } d.Set("ipv4_prefix_count", len(eni.Ipv4Prefixes)) @@ -332,8 +323,8 @@ func resourceNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("error setting ipv6_addresses: %w", err) } - if err := d.Set("ipv6_prefix", flattenIpv6PrefixSpecifications(eni.Ipv6Prefixes)); err != nil { - return fmt.Errorf("error setting ipv6_prefix: %w", err) + if err := d.Set("ipv6_prefixes", flattenIpv6PrefixSpecifications(eni.Ipv6Prefixes)); err != nil { + return fmt.Errorf("error setting ipv6_prefixes: %w", err) } d.Set("ipv6_prefix_count", len(eni.Ipv6Prefixes)) @@ -486,7 +477,7 @@ func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) er if d.HasChange("ipv4_prefix_count") { o, n := d.GetChange("ipv4_prefix_count") - ipv4Prefixes := d.Get("ipv4_prefix").(*schema.Set).List() + ipv4Prefixes := d.Get("ipv4_prefixes").(*schema.Set).List() if o != nil && n != nil && n != len(ipv4Prefixes) { if diff := n.(int) - o.(int); diff > 0 { @@ -517,8 +508,8 @@ func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) er } } - if d.HasChange("ipv4_prefix") { - o, n := d.GetChange("ipv4_prefix") + if d.HasChange("ipv4_prefixes") { + o, n := d.GetChange("ipv4_prefixes") if o == nil { o = new(schema.Set) } @@ -640,8 +631,8 @@ func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) er } } - if d.HasChange("ipv6_prefix") { - o, n := d.GetChange("ipv6_prefix") + if d.HasChange("ipv6_prefixes") { + o, n := d.GetChange("ipv6_prefixes") if o == nil { o = new(schema.Set) } @@ -687,7 +678,7 @@ func resourceNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) er if d.HasChange("ipv6_prefix_count") { o, n := d.GetChange("ipv6_prefix_count") - ipv6Prefixes := d.Get("ipv6_prefix").(*schema.Set).List() + ipv6Prefixes := d.Get("ipv6_prefixes").(*schema.Set).List() if o != nil && n != nil && n != len(ipv6Prefixes) { if diff := n.(int) - o.(int); diff > 0 { @@ -859,15 +850,58 @@ func detachNetworkInterface(conn *ec2.EC2, networkInterfaceID, attachmentID stri return nil } -func expandIpv4PrefixSpecificationRequest(tfMap map[string]interface{}) *ec2.Ipv4PrefixSpecificationRequest { - if tfMap == nil { - return nil +//Expands an array of IPs into a ec2 Private IP Address Spec +func ExpandPrivateIPAddresses(ips []interface{}) []*ec2.PrivateIpAddressSpecification { + dtos := make([]*ec2.PrivateIpAddressSpecification, 0, len(ips)) + for i, v := range ips { + new_private_ip := &ec2.PrivateIpAddressSpecification{ + PrivateIpAddress: aws.String(v.(string)), + } + + new_private_ip.Primary = aws.Bool(i == 0) + + dtos = append(dtos, new_private_ip) } + return dtos +} + +func expandIP6Addresses(ips []interface{}) []*ec2.InstanceIpv6Address { + dtos := make([]*ec2.InstanceIpv6Address, 0, len(ips)) + for _, v := range ips { + ipv6Address := &ec2.InstanceIpv6Address{ + Ipv6Address: aws.String(v.(string)), + } - apiObject := &ec2.Ipv4PrefixSpecificationRequest{} + dtos = append(dtos, ipv6Address) + } + return dtos +} - if v, ok := tfMap["ipv4_prefix"].(string); ok && v != "" { - apiObject.Ipv4Prefix = aws.String(v) +//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 FlattenNetworkInterfacesPrivateIPAddresses(dtos []*ec2.NetworkInterfacePrivateIpAddress) []string { + ips := make([]string, 0, len(dtos)) + for _, v := range dtos { + ip := *v.PrivateIpAddress + ips = append(ips, ip) + } + return ips +} + +func flattenNetworkInterfaceIPv6Address(niia []*ec2.NetworkInterfaceIpv6Address) []string { + ips := make([]string, 0, len(niia)) + for _, v := range niia { + ips = append(ips, *v.Ipv6Address) + } + return ips +} + +func expandIpv4PrefixSpecificationRequest(tfString string) *ec2.Ipv4PrefixSpecificationRequest { + if tfString == "" { + return nil + } + + apiObject := &ec2.Ipv4PrefixSpecificationRequest{ + Ipv4Prefix: aws.String(tfString), } return apiObject @@ -881,13 +915,13 @@ func expandIpv4PrefixSpecificationRequests(tfList []interface{}) []*ec2.Ipv4Pref var apiObjects []*ec2.Ipv4PrefixSpecificationRequest for _, tfMapRaw := range tfList { - tfMap, ok := tfMapRaw.(map[string]interface{}) + tfString, ok := tfMapRaw.(string) if !ok { continue } - apiObject := expandIpv4PrefixSpecificationRequest(tfMap) + apiObject := expandIpv4PrefixSpecificationRequest(tfString) if apiObject == nil { continue @@ -899,15 +933,13 @@ func expandIpv4PrefixSpecificationRequests(tfList []interface{}) []*ec2.Ipv4Pref return apiObjects } -func expandIpv6PrefixSpecificationRequest(tfMap map[string]interface{}) *ec2.Ipv6PrefixSpecificationRequest { - if tfMap == nil { +func expandIpv6PrefixSpecificationRequest(tfString string) *ec2.Ipv6PrefixSpecificationRequest { + if tfString == "" { return nil } - apiObject := &ec2.Ipv6PrefixSpecificationRequest{} - - if v, ok := tfMap["ipv6_prefix"].(string); ok && v != "" { - apiObject.Ipv6Prefix = aws.String(v) + apiObject := &ec2.Ipv6PrefixSpecificationRequest{ + Ipv6Prefix: aws.String(tfString), } return apiObject @@ -921,13 +953,13 @@ func expandIpv6PrefixSpecificationRequests(tfList []interface{}) []*ec2.Ipv6Pref var apiObjects []*ec2.Ipv6PrefixSpecificationRequest for _, tfMapRaw := range tfList { - tfMap, ok := tfMapRaw.(map[string]interface{}) + tfString, ok := tfMapRaw.(string) if !ok { continue } - apiObject := expandIpv6PrefixSpecificationRequest(tfMap) + apiObject := expandIpv6PrefixSpecificationRequest(tfString) if apiObject == nil { continue @@ -939,26 +971,26 @@ func expandIpv6PrefixSpecificationRequests(tfList []interface{}) []*ec2.Ipv6Pref return apiObjects } -func flattenIpv4PrefixSpecification(apiObject *ec2.Ipv4PrefixSpecification) map[string]interface{} { +func flattenIpv4PrefixSpecification(apiObject *ec2.Ipv4PrefixSpecification) string { if apiObject == nil { - return nil + return "" } - tfMap := map[string]interface{}{} + tfString := "" if v := apiObject.Ipv4Prefix; v != nil { - tfMap["ipv4_prefix"] = aws.StringValue(v) + tfString = aws.StringValue(v) } - return tfMap + return tfString } -func flattenIpv4PrefixSpecifications(apiObjects []*ec2.Ipv4PrefixSpecification) []interface{} { +func flattenIpv4PrefixSpecifications(apiObjects []*ec2.Ipv4PrefixSpecification) []string { if len(apiObjects) == 0 { return nil } - var tfList []interface{} + var tfList []string for _, apiObject := range apiObjects { if apiObject == nil { @@ -971,26 +1003,26 @@ func flattenIpv4PrefixSpecifications(apiObjects []*ec2.Ipv4PrefixSpecification) return tfList } -func flattenIpv6PrefixSpecification(apiObject *ec2.Ipv6PrefixSpecification) map[string]interface{} { +func flattenIpv6PrefixSpecification(apiObject *ec2.Ipv6PrefixSpecification) string { if apiObject == nil { - return nil + return "" } - tfMap := map[string]interface{}{} + tfString := "" if v := apiObject.Ipv6Prefix; v != nil { - tfMap["ipv6_prefix"] = aws.StringValue(v) + tfString = aws.StringValue(v) } - return tfMap + return tfString } -func flattenIpv6PrefixSpecifications(apiObjects []*ec2.Ipv6PrefixSpecification) []interface{} { +func flattenIpv6PrefixSpecifications(apiObjects []*ec2.Ipv6PrefixSpecification) []string { if len(apiObjects) == 0 { return nil } - var tfList []interface{} + var tfList []string for _, apiObject := range apiObjects { if apiObject == nil { diff --git a/internal/service/ec2/network_interface_test.go b/internal/service/ec2/network_interface_test.go index c192fd2c15e..93cb2456389 100644 --- a/internal/service/ec2/network_interface_test.go +++ b/internal/service/ec2/network_interface_test.go @@ -478,6 +478,142 @@ func TestAccEC2NetworkInterface_ENIInterfaceType_efa(t *testing.T) { }) } +func TestAccEC2NetworkInterface_ENI_ipv4Prefix(t *testing.T) { + var conf ec2.NetworkInterface + resourceName := "aws_network_interface.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckENIDestroy, + Steps: []resource.TestStep{ + { + Config: testAccENIIPV4PrefixConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckENIExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "ipv4_prefix_count", "1"), + resource.TestCheckResourceAttr(resourceName, "ipv4_prefixes.#", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccENIIPV4PrefixMultipleConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckENIExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "ipv4_prefix_count", "2"), + resource.TestCheckResourceAttr(resourceName, "ipv4_prefixes.#", "2"), + ), + }, + { + Config: testAccENIIPV4PrefixConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckENIExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "ipv4_prefix_count", "1"), + resource.TestCheckResourceAttr(resourceName, "ipv4_prefixes.#", "1"), + ), + }, + }, + }) +} + +func TestAccEC2NetworkInterface_ENI_ipv4PrefixCount(t *testing.T) { + var conf ec2.NetworkInterface + resourceName := "aws_network_interface.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckENIDestroy, + Steps: []resource.TestStep{ + { + Config: testAccENIIPV4PrefixCountConfig(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckENIExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "ipv4_prefix_count", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccENIIPV4PrefixCountConfig(rName, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckENIExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "ipv4_prefix_count", "2"), + ), + }, + { + Config: testAccENIIPV4PrefixCountConfig(rName, 0), + Check: resource.ComposeTestCheckFunc( + testAccCheckENIExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "ipv4_prefix_count", "0"), + ), + }, + { + Config: testAccENIIPV4PrefixCountConfig(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckENIExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "ipv4_prefix_count", "1"), + ), + }, + }, + }) +} + +func TestAccEC2NetworkInterface_ENI_ipv6Prefix(t *testing.T) { + var conf ec2.NetworkInterface + resourceName := "aws_network_interface.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckENIDestroy, + Steps: []resource.TestStep{ + { + Config: testAccENIIPV6PrefixConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckENIExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "ipv6_prefix_count", "1"), + resource.TestCheckResourceAttr(resourceName, "ipv6_prefixes.#", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccENIIPV6PrefixMultipleConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckENIExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "ipv6_prefix_count", "2"), + resource.TestCheckResourceAttr(resourceName, "ipv6_prefixes.#", "2"), + ), + }, + { + Config: testAccENIIPV6PrefixConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckENIExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "ipv6_prefix_count", "1"), + resource.TestCheckResourceAttr(resourceName, "ipv6_prefixes.#", "1"), + ), + }, + }, + }) +} + func TestAccEC2NetworkInterface_ENI_ipv6PrefixCount(t *testing.T) { var conf ec2.NetworkInterface resourceName := "aws_network_interface.test" @@ -901,6 +1037,78 @@ resource "aws_network_interface" "test" { `, rName, interfaceType)) } +func testAccENIIPV4PrefixConfig(rName string) string { + return acctest.ConfigCompose(testAccENIIPV4BaseConfig(rName), fmt.Sprintf(` +resource "aws_network_interface" "test" { + subnet_id = aws_subnet.test.id + ipv4_prefixes = ["172.16.10.16/28"] + security_groups = [aws_security_group.test.id] + + tags = { + Name = %[1]q + } +} +`, rName)) +} + +func testAccENIIPV4PrefixMultipleConfig(rName string) string { + return acctest.ConfigCompose(testAccENIIPV4BaseConfig(rName), fmt.Sprintf(` +resource "aws_network_interface" "test" { + subnet_id = aws_subnet.test.id + ipv4_prefixes = ["172.16.10.16/28", "172.16.10.32/28"] + security_groups = [aws_security_group.test.id] + + tags = { + Name = %[1]q + } +} +`, rName)) +} + +func testAccENIIPV4PrefixCountConfig(rName string, ipv4PrefixCount int) string { + return acctest.ConfigCompose(testAccENIIPV4BaseConfig(rName) + fmt.Sprintf(` +resource "aws_network_interface" "test" { + subnet_id = aws_subnet.test.id + ipv4_prefix_count = %[2]d + security_groups = [aws_security_group.test.id] + + tags = { + Name = %[1]q + } +} +`, rName, ipv4PrefixCount)) +} + +func testAccENIIPV6PrefixConfig(rName string) string { + return acctest.ConfigCompose(testAccENIIPV6BaseConfig(rName), fmt.Sprintf(` +resource "aws_network_interface" "test" { + subnet_id = aws_subnet.test.id + private_ips = ["172.16.10.100"] + ipv6_prefixes = [cidrsubnet(aws_subnet.test.ipv6_cidr_block, 16, 2)] + security_groups = [aws_security_group.test.id] + + tags = { + Name = %[1]q + } +} +`, rName)) +} + +func testAccENIIPV6PrefixMultipleConfig(rName string) string { + return acctest.ConfigCompose(testAccENIIPV6BaseConfig(rName), fmt.Sprintf(` +resource "aws_network_interface" "test" { + subnet_id = aws_subnet.test.id + private_ips = ["172.16.10.100"] + ipv6_prefixes = [cidrsubnet(aws_subnet.test.ipv6_cidr_block, 16, 2), cidrsubnet(aws_subnet.test.ipv6_cidr_block, 16, 3)] + security_groups = [aws_security_group.test.id] + + tags = { + Name = %[1]q + } +} +`, rName)) +} + func testAccENIIPV6PrefixCountConfig(rName string, ipv6PrefixCount int) string { return acctest.ConfigCompose(testAccENIIPV6BaseConfig(rName) + fmt.Sprintf(` resource "aws_network_interface" "test" { diff --git a/website/docs/r/network_interface.markdown b/website/docs/r/network_interface.markdown index e48ddbc6472..fee7ab277d2 100644 --- a/website/docs/r/network_interface.markdown +++ b/website/docs/r/network_interface.markdown @@ -38,9 +38,9 @@ The following arguments are supported: * `security_groups` - (Optional) List of security group IDs to assign to the ENI. * `attachment` - (Optional) Block to define the attachment of the ENI. Documented below. * `source_dest_check` - (Optional) Whether to enable source destination checking for the ENI. Default true. -* `ipv4_prefix` - (Optional) One or more IPv4 prefixes assigned to the network interface. Documented below. +* `ipv4_prefixes` - (Optional) One or more IPv4 prefixes assigned to the network interface. * `ipv4_prefix_count` - (Optional) The number of IPv4 prefixes that AWS automatically assigns to the network interface. -* `ipv6_prefix` - (Optional) One or more IPv6 prefixes assigned to the network interface. Documented below. +* `ipv6_prefixes` - (Optional) One or more IPv6 prefixes assigned to the network interface. * `ipv6_prefix_count` - (Optional) The number of IPv6 prefixes that AWS automatically assigns to the network interface. -> **NOTE:** Changing `interface_type` will cause the resource to be destroyed and re-created. From aca0e4f994f00005d6197edd6c0a869438719ea7 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 2 Nov 2021 16:27:04 -0400 Subject: [PATCH 26/28] r/aws_network_interface: If IPv4 or IPv6 prefixes are specified, tag after create. --- internal/service/ec2/network_interface.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/internal/service/ec2/network_interface.go b/internal/service/ec2/network_interface.go index 6ad969f828d..e8ca7708eac 100644 --- a/internal/service/ec2/network_interface.go +++ b/internal/service/ec2/network_interface.go @@ -174,6 +174,9 @@ func resourceNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) er defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + ipv4PrefixesSpecified := false + ipv6PrefixesSpecified := false + input := &ec2.CreateNetworkInterfaceInput{ SubnetId: aws.String(d.Get("subnet_id").(string)), } @@ -187,6 +190,7 @@ func resourceNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) er } if v, ok := d.GetOk("ipv4_prefixes"); ok && v.(*schema.Set).Len() > 0 { + ipv4PrefixesSpecified = true input.Ipv4Prefixes = expandIpv4PrefixSpecificationRequests(v.(*schema.Set).List()) } @@ -203,6 +207,7 @@ func resourceNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) er } if v, ok := d.GetOk("ipv6_prefixes"); ok && v.(*schema.Set).Len() > 0 { + ipv6PrefixesSpecified = true input.Ipv6Prefixes = expandIpv6PrefixSpecificationRequests(v.(*schema.Set).List()) } @@ -222,7 +227,9 @@ func resourceNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) er input.Groups = flex.ExpandStringSet(v.(*schema.Set)) } - if len(tags) > 0 { + // If IPv4 or IPv6 prefixes are specified, tag after create. + // Otherwise "An error occurred (InternalError) when calling the CreateNetworkInterface operation". + if len(tags) > 0 && !(ipv4PrefixesSpecified || ipv6PrefixesSpecified) { input.TagSpecifications = ec2TagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeNetworkInterface) } @@ -239,6 +246,12 @@ func resourceNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("error waiting for EC2 Network Interface (%s) create: %w", d.Id(), err) } + if len(tags) > 0 && (ipv4PrefixesSpecified || ipv6PrefixesSpecified) { + if err := UpdateTags(conn, d.Id(), nil, tags); err != nil { + return fmt.Errorf("error updating EC2 Network Interface (%s) tags: %w", d.Id(), err) + } + } + // Default value is enabled. if !d.Get("source_dest_check").(bool) { input := &ec2.ModifyNetworkInterfaceAttributeInput{ From 740134626a10de15bebc6fcb329e6d14a58b1c67 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 2 Nov 2021 17:35:08 -0400 Subject: [PATCH 27/28] r/aws_network_interface: Tidy up some flex. Acceptance test output: % make testacc PKG_NAME=internal/service/ec2 TESTARGS='-run=TestAccEC2NetworkInterface_' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/ec2/... -v -count 1 -parallel 20 -run=TestAccEC2NetworkInterface_ -timeout 180m === RUN TestAccEC2NetworkInterface_ENI_basic === PAUSE TestAccEC2NetworkInterface_ENI_basic === RUN TestAccEC2NetworkInterface_ENI_ipv6 === PAUSE TestAccEC2NetworkInterface_ENI_ipv6 === RUN TestAccEC2NetworkInterface_ENI_tags === PAUSE TestAccEC2NetworkInterface_ENI_tags === RUN TestAccEC2NetworkInterface_ENI_ipv6Count === PAUSE TestAccEC2NetworkInterface_ENI_ipv6Count === RUN TestAccEC2NetworkInterface_ENI_disappears === PAUSE TestAccEC2NetworkInterface_ENI_disappears === RUN TestAccEC2NetworkInterface_ENI_description === PAUSE TestAccEC2NetworkInterface_ENI_description === RUN TestAccEC2NetworkInterface_ENI_attachment === PAUSE TestAccEC2NetworkInterface_ENI_attachment === RUN TestAccEC2NetworkInterface_ENI_ignoreExternalAttachment === PAUSE TestAccEC2NetworkInterface_ENI_ignoreExternalAttachment === RUN TestAccEC2NetworkInterface_ENI_sourceDestCheck === PAUSE TestAccEC2NetworkInterface_ENI_sourceDestCheck === RUN TestAccEC2NetworkInterface_ENI_privateIPsCount === PAUSE TestAccEC2NetworkInterface_ENI_privateIPsCount === RUN TestAccEC2NetworkInterface_ENIInterfaceType_efa === PAUSE TestAccEC2NetworkInterface_ENIInterfaceType_efa === RUN TestAccEC2NetworkInterface_ENI_ipv4Prefix === PAUSE TestAccEC2NetworkInterface_ENI_ipv4Prefix === RUN TestAccEC2NetworkInterface_ENI_ipv4PrefixCount === PAUSE TestAccEC2NetworkInterface_ENI_ipv4PrefixCount === RUN TestAccEC2NetworkInterface_ENI_ipv6Prefix === PAUSE TestAccEC2NetworkInterface_ENI_ipv6Prefix === RUN TestAccEC2NetworkInterface_ENI_ipv6PrefixCount === PAUSE TestAccEC2NetworkInterface_ENI_ipv6PrefixCount === CONT TestAccEC2NetworkInterface_ENI_basic === CONT TestAccEC2NetworkInterface_ENI_sourceDestCheck === CONT TestAccEC2NetworkInterface_ENI_disappears === CONT TestAccEC2NetworkInterface_ENI_tags === CONT TestAccEC2NetworkInterface_ENI_ipv6 === CONT TestAccEC2NetworkInterface_ENI_ipv4PrefixCount === CONT TestAccEC2NetworkInterface_ENI_ipv6PrefixCount === CONT TestAccEC2NetworkInterface_ENI_ipv6Prefix === CONT TestAccEC2NetworkInterface_ENIInterfaceType_efa === CONT TestAccEC2NetworkInterface_ENI_ipv4Prefix === CONT TestAccEC2NetworkInterface_ENI_description === CONT TestAccEC2NetworkInterface_ENI_ipv6Count === CONT TestAccEC2NetworkInterface_ENI_privateIPsCount === CONT TestAccEC2NetworkInterface_ENI_ignoreExternalAttachment === CONT TestAccEC2NetworkInterface_ENI_attachment --- PASS: TestAccEC2NetworkInterface_ENI_disappears (48.88s) --- PASS: TestAccEC2NetworkInterface_ENI_basic (54.46s) --- PASS: TestAccEC2NetworkInterface_ENIInterfaceType_efa (55.39s) --- PASS: TestAccEC2NetworkInterface_ENI_description (79.83s) --- PASS: TestAccEC2NetworkInterface_ENI_sourceDestCheck (100.99s) --- PASS: TestAccEC2NetworkInterface_ENI_tags (101.36s) --- PASS: TestAccEC2NetworkInterface_ENI_ipv6 (105.92s) --- PASS: TestAccEC2NetworkInterface_ENI_ipv6Prefix (105.92s) --- PASS: TestAccEC2NetworkInterface_ENI_ipv4Prefix (105.94s) --- PASS: TestAccEC2NetworkInterface_ENI_ipv4PrefixCount (122.78s) --- PASS: TestAccEC2NetworkInterface_ENI_ipv6Count (127.57s) --- PASS: TestAccEC2NetworkInterface_ENI_ipv6PrefixCount (127.58s) --- PASS: TestAccEC2NetworkInterface_ENI_privateIPsCount (131.80s) --- PASS: TestAccEC2NetworkInterface_ENI_ignoreExternalAttachment (302.54s) --- PASS: TestAccEC2NetworkInterface_ENI_attachment (335.89s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/ec2 338.767s --- internal/service/ec2/flex.go | 43 ---- internal/service/ec2/flex_test.go | 92 ------- internal/service/ec2/network_interface.go | 241 +++++++++++++++--- .../ec2/network_interface_data_source.go | 12 +- 4 files changed, 207 insertions(+), 181 deletions(-) diff --git a/internal/service/ec2/flex.go b/internal/service/ec2/flex.go index 043111415e1..045b88bd2e6 100644 --- a/internal/service/ec2/flex.go +++ b/internal/service/ec2/flex.go @@ -9,21 +9,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -//Flattens network interface attachment into a map[string]interface -func FlattenAttachment(a *ec2.NetworkInterfaceAttachment) map[string]interface{} { - att := make(map[string]interface{}) - if a.InstanceId != nil { - att["instance"] = *a.InstanceId - } - if a.DeviceIndex != nil { - att["device_index"] = *a.DeviceIndex - } - if a.AttachmentId != nil { - att["attachment_id"] = *a.AttachmentId - } - return att -} - func flattenAttributeValues(l []*ec2.AttributeValue) []string { values := make([]string, 0, len(l)) for _, v := range l { @@ -163,34 +148,6 @@ func ExpandIPPerms( return perms, nil } -func flattenNetworkInterfaceAssociation(a *ec2.NetworkInterfaceAssociation) []interface{} { - tfMap := map[string]interface{}{} - - if a.AllocationId != nil { - tfMap["allocation_id"] = aws.StringValue(a.AllocationId) - } - if a.AssociationId != nil { - tfMap["association_id"] = aws.StringValue(a.AssociationId) - } - if a.CarrierIp != nil { - tfMap["carrier_ip"] = aws.StringValue(a.CarrierIp) - } - if a.CustomerOwnedIp != nil { - tfMap["customer_owned_ip"] = aws.StringValue(a.CustomerOwnedIp) - } - if a.IpOwnerId != nil { - tfMap["ip_owner_id"] = aws.StringValue(a.IpOwnerId) - } - if a.PublicDnsName != nil { - tfMap["public_dns_name"] = aws.StringValue(a.PublicDnsName) - } - if a.PublicIp != nil { - tfMap["public_ip"] = aws.StringValue(a.PublicIp) - } - - return []interface{}{tfMap} -} - // Flattens an array of UserSecurityGroups into a []*GroupIdentifier func FlattenSecurityGroups(list []*ec2.UserIdGroupPair, ownerId *string) []*GroupIdentifier { result := make([]*GroupIdentifier, 0, len(list)) diff --git a/internal/service/ec2/flex_test.go b/internal/service/ec2/flex_test.go index 3a1d9c4bb10..5216528ed4a 100644 --- a/internal/service/ec2/flex_test.go +++ b/internal/service/ec2/flex_test.go @@ -9,49 +9,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func TestFlattenAttachment(t *testing.T) { - expanded := &ec2.NetworkInterfaceAttachment{ - InstanceId: aws.String("i-00001"), - DeviceIndex: aws.Int64(1), - AttachmentId: aws.String("at-002"), - } - - result := FlattenAttachment(expanded) - - if result == nil { - t.Fatal("expected result to have value, but got nil") - } - - if result["instance"] != "i-00001" { - t.Fatalf("expected instance to be i-00001, but got %s", result["instance"]) - } - - if result["device_index"] != int64(1) { - t.Fatalf("expected device_index to be 1, but got %d", result["device_index"]) - } - - if result["attachment_id"] != "at-002" { - t.Fatalf("expected attachment_id to be at-002, but got %s", result["attachment_id"]) - } -} - -func TestFlattenAttachmentWhenNoInstanceId(t *testing.T) { - expanded := &ec2.NetworkInterfaceAttachment{ - DeviceIndex: aws.Int64(1), - AttachmentId: aws.String("at-002"), - } - - result := FlattenAttachment(expanded) - - if result == nil { - t.Fatal("expected result to have value, but got nil") - } - - if result["instance"] != nil { - t.Fatalf("expected instance to be nil, but got %s", result["instance"]) - } -} - func TestFlattenGroupIdentifiers(t *testing.T) { expanded := []*ec2.GroupIdentifier{ {GroupId: aws.String("sg-001")}, @@ -378,55 +335,6 @@ func TestExpandIPPerms_nonVPC(t *testing.T) { } } -func TestFlattenNetworkInterfacesPrivateIPAddresses(t *testing.T) { - expanded := []*ec2.NetworkInterfacePrivateIpAddress{ - {PrivateIpAddress: aws.String("192.168.0.1")}, - {PrivateIpAddress: aws.String("192.168.0.2")}, - } - - result := FlattenNetworkInterfacesPrivateIPAddresses(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]) - } -} - -func TestExpandPrivateIPAddresses(t *testing.T) { - - ip1 := "192.168.0.1" - ip2 := "192.168.0.2" - flattened := []interface{}{ - ip1, - ip2, - } - - result := ExpandPrivateIPAddresses(flattened) - - if len(result) != 2 { - t.Fatalf("expected result had %d elements, but got %d", 2, len(result)) - } - - if aws.StringValue(result[0].PrivateIpAddress) != "192.168.0.1" || !aws.BoolValue(result[0].Primary) { - t.Fatalf("expected ip to be 192.168.0.1 and Primary, but got %v, %t", aws.StringValue(result[0].PrivateIpAddress), aws.BoolValue(result[0].Primary)) - } - - if aws.StringValue(result[1].PrivateIpAddress) != "192.168.0.2" || aws.BoolValue(result[1].Primary) { - t.Fatalf("expected ip to be 192.168.0.2 and not Primary, but got %v, %t", aws.StringValue(result[1].PrivateIpAddress), aws.BoolValue(result[1].Primary)) - } -} - func TestFlattenSecurityGroups(t *testing.T) { cases := []struct { ownerId *string diff --git a/internal/service/ec2/network_interface.go b/internal/service/ec2/network_interface.go index e8ca7708eac..f0f3b57c563 100644 --- a/internal/service/ec2/network_interface.go +++ b/internal/service/ec2/network_interface.go @@ -203,7 +203,7 @@ func resourceNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) er } if v, ok := d.GetOk("ipv6_addresses"); ok && v.(*schema.Set).Len() > 0 { - input.Ipv6Addresses = expandIP6Addresses(v.(*schema.Set).List()) + input.Ipv6Addresses = expandInstanceIpv6Addresses(v.(*schema.Set).List()) } if v, ok := d.GetOk("ipv6_prefixes"); ok && v.(*schema.Set).Len() > 0 { @@ -216,7 +216,7 @@ func resourceNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) er } if v, ok := d.GetOk("private_ips"); ok && v.(*schema.Set).Len() > 0 { - input.PrivateIpAddresses = ExpandPrivateIPAddresses(v.(*schema.Set).List()) + input.PrivateIpAddresses = expandPrivateIpAddressSpecifications(v.(*schema.Set).List()) } if v, ok := d.GetOk("private_ips_count"); ok { @@ -301,16 +301,6 @@ func resourceNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) erro eni := outputRaw.(*ec2.NetworkInterface) - attachment := []map[string]interface{}{} - - if eni.Attachment != nil { - attachment = []map[string]interface{}{FlattenAttachment(eni.Attachment)} - } - - if err := d.Set("attachment", attachment); err != nil { - return fmt.Errorf("error setting attachment: %w", err) - } - ownerID := aws.StringValue(eni.OwnerId) arn := arn.ARN{ Partition: meta.(*conns.AWSClient).Partition, @@ -321,6 +311,14 @@ func resourceNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) erro }.String() d.Set("arn", arn) + if eni.Attachment != nil { + if err := d.Set("attachment", []interface{}{flattenNetworkInterfaceAttachment(eni.Attachment)}); err != nil { + return fmt.Errorf("error setting attachment: %w", err) + } + } else { + d.Set("attachment", nil) + } + d.Set("description", eni.Description) d.Set("interface_type", eni.InterfaceType) @@ -332,7 +330,7 @@ func resourceNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) erro d.Set("ipv6_address_count", len(eni.Ipv6Addresses)) - if err := d.Set("ipv6_addresses", flattenNetworkInterfaceIPv6Address(eni.Ipv6Addresses)); err != nil { + if err := d.Set("ipv6_addresses", flattenNetworkInterfaceIPv6Addresses(eni.Ipv6Addresses)); err != nil { return fmt.Errorf("error setting ipv6_addresses: %w", err) } @@ -348,7 +346,7 @@ func resourceNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) erro d.Set("private_dns_name", eni.PrivateDnsName) d.Set("private_ip", eni.PrivateIpAddress) - if err := d.Set("private_ips", FlattenNetworkInterfacesPrivateIPAddresses(eni.PrivateIpAddresses)); err != nil { + if err := d.Set("private_ips", flattenNetworkInterfacePrivateIpAddresses(eni.PrivateIpAddresses)); err != nil { return fmt.Errorf("error setting private_ips: %w", err) } @@ -863,49 +861,208 @@ func detachNetworkInterface(conn *ec2.EC2, networkInterfaceID, attachmentID stri return nil } -//Expands an array of IPs into a ec2 Private IP Address Spec -func ExpandPrivateIPAddresses(ips []interface{}) []*ec2.PrivateIpAddressSpecification { - dtos := make([]*ec2.PrivateIpAddressSpecification, 0, len(ips)) - for i, v := range ips { - new_private_ip := &ec2.PrivateIpAddressSpecification{ - PrivateIpAddress: aws.String(v.(string)), +func flattenNetworkInterfaceAssociation(apiObject *ec2.NetworkInterfaceAssociation) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.AllocationId; v != nil { + tfMap["allocation_id"] = aws.StringValue(v) + } + + if v := apiObject.AssociationId; v != nil { + tfMap["association_id"] = aws.StringValue(v) + } + + if v := apiObject.CarrierIp; v != nil { + tfMap["carrier_ip"] = aws.StringValue(v) + } + + if v := apiObject.CustomerOwnedIp; v != nil { + tfMap["customer_owned_ip"] = aws.StringValue(v) + } + + if v := apiObject.IpOwnerId; v != nil { + tfMap["ip_owner_id"] = aws.StringValue(v) + } + + if v := apiObject.PublicDnsName; v != nil { + tfMap["public_dns_name"] = aws.StringValue(v) + } + + if v := apiObject.PublicIp; v != nil { + tfMap["public_ip"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenNetworkInterfaceAttachment(apiObject *ec2.NetworkInterfaceAttachment) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.AttachmentId; v != nil { + tfMap["attachment_id"] = aws.StringValue(v) + } + + if v := apiObject.DeviceIndex; v != nil { + tfMap["device_index"] = aws.Int64Value(v) + } + + if v := apiObject.InstanceId; v != nil { + tfMap["instance"] = aws.StringValue(v) + } + + return tfMap +} + +func expandPrivateIpAddressSpecification(tfString string) *ec2.PrivateIpAddressSpecification { + if tfString == "" { + return nil + } + + apiObject := &ec2.PrivateIpAddressSpecification{ + PrivateIpAddress: aws.String(tfString), + } + + return apiObject +} + +func expandPrivateIpAddressSpecifications(tfList []interface{}) []*ec2.PrivateIpAddressSpecification { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*ec2.PrivateIpAddressSpecification + + for i, tfMapRaw := range tfList { + tfString, ok := tfMapRaw.(string) + + if !ok { + continue + } + + apiObject := expandPrivateIpAddressSpecification(tfString) + + if apiObject == nil { + continue + } + + if i == 0 { + apiObject.Primary = aws.Bool(true) + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func expandInstanceIpv6Address(tfString string) *ec2.InstanceIpv6Address { + if tfString == "" { + return nil + } + + apiObject := &ec2.InstanceIpv6Address{ + Ipv6Address: aws.String(tfString), + } + + return apiObject +} + +func expandInstanceIpv6Addresses(tfList []interface{}) []*ec2.InstanceIpv6Address { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*ec2.InstanceIpv6Address + + for _, tfMapRaw := range tfList { + tfString, ok := tfMapRaw.(string) + + if !ok { + continue } - new_private_ip.Primary = aws.Bool(i == 0) + apiObject := expandInstanceIpv6Address(tfString) + + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func flattenNetworkInterfacePrivateIpAddress(apiObject *ec2.NetworkInterfacePrivateIpAddress) string { + if apiObject == nil { + return "" + } + + tfString := "" - dtos = append(dtos, new_private_ip) + if v := apiObject.PrivateIpAddress; v != nil { + tfString = aws.StringValue(v) } - return dtos + + return tfString } -func expandIP6Addresses(ips []interface{}) []*ec2.InstanceIpv6Address { - dtos := make([]*ec2.InstanceIpv6Address, 0, len(ips)) - for _, v := range ips { - ipv6Address := &ec2.InstanceIpv6Address{ - Ipv6Address: aws.String(v.(string)), +func flattenNetworkInterfacePrivateIpAddresses(apiObjects []*ec2.NetworkInterfacePrivateIpAddress) []string { + if len(apiObjects) == 0 { + return nil + } + + var tfList []string + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue } - dtos = append(dtos, ipv6Address) + tfList = append(tfList, flattenNetworkInterfacePrivateIpAddress(apiObject)) } - return dtos + + return tfList } -//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 FlattenNetworkInterfacesPrivateIPAddresses(dtos []*ec2.NetworkInterfacePrivateIpAddress) []string { - ips := make([]string, 0, len(dtos)) - for _, v := range dtos { - ip := *v.PrivateIpAddress - ips = append(ips, ip) +func flattenNetworkInterfaceIPv6Address(apiObject *ec2.NetworkInterfaceIpv6Address) string { + if apiObject == nil { + return "" } - return ips + + tfString := "" + + if v := apiObject.Ipv6Address; v != nil { + tfString = aws.StringValue(v) + } + + return tfString } -func flattenNetworkInterfaceIPv6Address(niia []*ec2.NetworkInterfaceIpv6Address) []string { - ips := make([]string, 0, len(niia)) - for _, v := range niia { - ips = append(ips, *v.Ipv6Address) +func flattenNetworkInterfaceIPv6Addresses(apiObjects []*ec2.NetworkInterfaceIpv6Address) []string { + if len(apiObjects) == 0 { + return nil + } + + var tfList []string + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenNetworkInterfaceIPv6Address(apiObject)) } - return ips + + return tfList } func expandIpv4PrefixSpecificationRequest(tfString string) *ec2.Ipv4PrefixSpecificationRequest { diff --git a/internal/service/ec2/network_interface_data_source.go b/internal/service/ec2/network_interface_data_source.go index 28af76cc10c..50db9db500a 100644 --- a/internal/service/ec2/network_interface_data_source.go +++ b/internal/service/ec2/network_interface_data_source.go @@ -180,26 +180,30 @@ func dataSourceNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) er }.String() d.Set("arn", arn) if eni.Association != nil { - if err := d.Set("association", flattenNetworkInterfaceAssociation(eni.Association)); err != nil { + if err := d.Set("association", []interface{}{flattenNetworkInterfaceAssociation(eni.Association)}); err != nil { return fmt.Errorf("error setting association: %w", err) } + } else { + d.Set("association", nil) } if eni.Attachment != nil { - if err := d.Set("attachment", []interface{}{FlattenAttachment(eni.Attachment)}); err != nil { + if err := d.Set("attachment", []interface{}{flattenNetworkInterfaceAttachment(eni.Attachment)}); err != nil { return fmt.Errorf("error setting attachment: %w", err) } + } else { + d.Set("attachment", nil) } d.Set("availability_zone", eni.AvailabilityZone) d.Set("description", eni.Description) d.Set("security_groups", FlattenGroupIdentifiers(eni.Groups)) d.Set("interface_type", eni.InterfaceType) - d.Set("ipv6_addresses", flattenNetworkInterfaceIPv6Address(eni.Ipv6Addresses)) + d.Set("ipv6_addresses", flattenNetworkInterfaceIPv6Addresses(eni.Ipv6Addresses)) d.Set("mac_address", eni.MacAddress) d.Set("outpost_arn", eni.OutpostArn) d.Set("owner_id", ownerID) d.Set("private_dns_name", eni.PrivateDnsName) d.Set("private_ip", eni.PrivateIpAddress) - d.Set("private_ips", FlattenNetworkInterfacesPrivateIPAddresses(eni.PrivateIpAddresses)) + d.Set("private_ips", flattenNetworkInterfacePrivateIpAddresses(eni.PrivateIpAddresses)) d.Set("requester_id", eni.RequesterId) d.Set("subnet_id", eni.SubnetId) d.Set("vpc_id", eni.VpcId) From 787bf3a20ce9b9c34b18b32346961f6f97b864a3 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 2 Nov 2021 18:25:46 -0400 Subject: [PATCH 28/28] Fix terrafmt errors. --- .../ec2/network_interface_sg_attachment_test.go | 14 +++++++------- internal/service/ec2/network_interface_test.go | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/service/ec2/network_interface_sg_attachment_test.go b/internal/service/ec2/network_interface_sg_attachment_test.go index 43c83772325..d02ff0eb9f5 100644 --- a/internal/service/ec2/network_interface_sg_attachment_test.go +++ b/internal/service/ec2/network_interface_sg_attachment_test.go @@ -248,14 +248,14 @@ resource "aws_security_group" "test" { } resource "aws_instance" "test" { - ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id - instance_type = data.aws_ec2_instance_type_offering.available.instance_type - subnet_id = aws_subnet.test.id - - tags = { - Name = %[1]q - } + ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id + instance_type = data.aws_ec2_instance_type_offering.available.instance_type + subnet_id = aws_subnet.test.id + + tags = { + Name = %[1]q } +} resource "aws_network_interface_sg_attachment" "test" { network_interface_id = aws_instance.test.primary_network_interface_id diff --git a/internal/service/ec2/network_interface_test.go b/internal/service/ec2/network_interface_test.go index a289cf7ec27..f73a0370422 100644 --- a/internal/service/ec2/network_interface_test.go +++ b/internal/service/ec2/network_interface_test.go @@ -1093,7 +1093,7 @@ resource "aws_network_interface" "test" { func testAccENIIPV4PrefixCountConfig(rName string, ipv4PrefixCount int) string { return acctest.ConfigCompose(testAccENIIPV4BaseConfig(rName) + fmt.Sprintf(` resource "aws_network_interface" "test" { - subnet_id = aws_subnet.test.id + subnet_id = aws_subnet.test.id ipv4_prefix_count = %[2]d security_groups = [aws_security_group.test.id] @@ -1137,8 +1137,8 @@ resource "aws_network_interface" "test" { func testAccENIIPV6PrefixCountConfig(rName string, ipv6PrefixCount int) string { return acctest.ConfigCompose(testAccENIIPV6BaseConfig(rName) + fmt.Sprintf(` resource "aws_network_interface" "test" { - subnet_id = aws_subnet.test.id - private_ips = ["172.16.10.100"] + subnet_id = aws_subnet.test.id + private_ips = ["172.16.10.100"] ipv6_prefix_count = %[2]d security_groups = [aws_security_group.test.id]