diff --git a/.changelog/33168.txt b/.changelog/33168.txt new file mode 100644 index 000000000000..c87b865d503e --- /dev/null +++ b/.changelog/33168.txt @@ -0,0 +1,19 @@ +```release-note:bug +resource/aws_ec2_network_insights_analysis: Fix `setting forward_path_components: Invalid address to set` errors +``` + +```release-note:enhancement +resource/aws_ec2_network_insights_path: Add `destination_arn` and `source_arn` attributes +``` + +```release-note:enhancement +data-source/aws_ec2_network_insights_path: Add `destination_arn` and `source_arn` attributes +``` + +```release-note:bug +resource/aws_ec2_network_insights_path: Avoid recreating resource when passing an ARN as `source` or `destination` +``` + +```release-note:bug +resource/aws_ec2_network_insights_path: Retry `AnalysisExistsForNetworkInsightsPath` errors on resource Delete +``` \ No newline at end of file diff --git a/internal/service/ec2/errors.go b/internal/service/ec2/errors.go index 7e41bd299882..8f9e0b137d72 100644 --- a/internal/service/ec2/errors.go +++ b/internal/service/ec2/errors.go @@ -4,15 +4,16 @@ package ec2 import ( + "errors" "fmt" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/ec2" - multierror "github.com/hashicorp/go-multierror" ) const ( + errCodeAnalysisExistsForNetworkInsightsPath = "AnalysisExistsForNetworkInsightsPath" errCodeAuthFailure = "AuthFailure" errCodeClientInvalidHostIDNotFound = "Client.InvalidHostID.NotFound" errCodeConcurrentMutationLimitExceeded = "ConcurrentMutationLimitExceeded" @@ -129,15 +130,15 @@ func CancelSpotFleetRequestError(apiObject *ec2.CancelSpotFleetRequestsErrorItem } func CancelSpotFleetRequestsError(apiObjects []*ec2.CancelSpotFleetRequestsErrorItem) error { - var errors *multierror.Error + var errs []error for _, apiObject := range apiObjects { if err := CancelSpotFleetRequestError(apiObject); err != nil { - errors = multierror.Append(errors, fmt.Errorf("%s: %w", aws.StringValue(apiObject.SpotFleetRequestId), err)) + errs = append(errs, fmt.Errorf("%s: %w", aws.StringValue(apiObject.SpotFleetRequestId), err)) } } - return errors.ErrorOrNil() + return errors.Join(errs...) } func DeleteFleetError(apiObject *ec2.DeleteFleetErrorItem) error { @@ -149,15 +150,15 @@ func DeleteFleetError(apiObject *ec2.DeleteFleetErrorItem) error { } func DeleteFleetsError(apiObjects []*ec2.DeleteFleetErrorItem) error { - var errors *multierror.Error + var errs []error for _, apiObject := range apiObjects { if err := DeleteFleetError(apiObject); err != nil { - errors = multierror.Append(errors, fmt.Errorf("%s: %w", aws.StringValue(apiObject.FleetId), err)) + errs = append(errs, fmt.Errorf("%s: %w", aws.StringValue(apiObject.FleetId), err)) } } - return errors.ErrorOrNil() + return errors.Join(errs...) } func UnsuccessfulItemError(apiObject *ec2.UnsuccessfulItemError) error { @@ -169,19 +170,17 @@ func UnsuccessfulItemError(apiObject *ec2.UnsuccessfulItemError) error { } func UnsuccessfulItemsError(apiObjects []*ec2.UnsuccessfulItem) error { - var errors *multierror.Error + var errs []error for _, apiObject := range apiObjects { if apiObject == nil { continue } - err := UnsuccessfulItemError(apiObject.Error) - - if err != nil { - errors = multierror.Append(errors, fmt.Errorf("%s: %w", aws.StringValue(apiObject.ResourceId), err)) + if err := UnsuccessfulItemError(apiObject.Error); err != nil { + errs = append(errs, fmt.Errorf("%s: %w", aws.StringValue(apiObject.ResourceId), err)) } } - return errors.ErrorOrNil() + return errors.Join(errs...) } diff --git a/internal/service/ec2/vpc_network_insights_analysis.go b/internal/service/ec2/vpc_network_insights_analysis.go index 8a1b28cb443b..5b974be44f02 100644 --- a/internal/service/ec2/vpc_network_insights_analysis.go +++ b/internal/service/ec2/vpc_network_insights_analysis.go @@ -2170,7 +2170,7 @@ func flattenTransitGatewayRouteTableRoute(apiObject *ec2.TransitGatewayRouteTabl } if v := apiObject.RouteOrigin; v != nil { - tfMap["route_orign"] = aws.StringValue(v) + tfMap["route_origin"] = aws.StringValue(v) } if v := apiObject.State; v != nil { diff --git a/internal/service/ec2/vpc_network_insights_analysis_test.go b/internal/service/ec2/vpc_network_insights_analysis_test.go index 9855dac81161..ea3af861df71 100644 --- a/internal/service/ec2/vpc_network_insights_analysis_test.go +++ b/internal/service/ec2/vpc_network_insights_analysis_test.go @@ -177,12 +177,6 @@ func TestAccVPCNetworkInsightsAnalysis_waitForCompletion(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "status", "running"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"wait_for_completion"}, - }, { Config: testAccVPCNetworkInsightsAnalysisConfig_waitForCompletion(rName, true), Check: resource.ComposeTestCheckFunc( diff --git a/internal/service/ec2/vpc_network_insights_path.go b/internal/service/ec2/vpc_network_insights_path.go index f6a5130dda05..ede94387b65c 100644 --- a/internal/service/ec2/vpc_network_insights_path.go +++ b/internal/service/ec2/vpc_network_insights_path.go @@ -6,6 +6,7 @@ package ec2 import ( "context" "log" + "strings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" @@ -38,10 +39,15 @@ func ResourceNetworkInsightsPath() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "destination": { + "destination_arn": { Type: schema.TypeString, - Required: true, - ForceNew: true, + Computed: true, + }, + "destination": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: suppressEquivalentIDOrARN, }, "destination_ip": { Type: schema.TypeString, @@ -60,9 +66,14 @@ func ResourceNetworkInsightsPath() *schema.Resource { ValidateFunc: validation.StringInSlice(ec2.Protocol_Values(), false), }, "source": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: suppressEquivalentIDOrARN, + }, + "source_arn": { Type: schema.TypeString, - Required: true, - ForceNew: true, + Computed: true, }, "source_ip": { Type: schema.TypeString, @@ -99,7 +110,6 @@ func resourceNetworkInsightsPathCreate(ctx context.Context, d *schema.ResourceDa input.SourceIp = aws.String(v.(string)) } - log.Printf("[DEBUG] Creating EC2 Network Insights Path: %s", input) output, err := conn.CreateNetworkInsightsPathWithContext(ctx, input) if err != nil { @@ -128,10 +138,12 @@ func resourceNetworkInsightsPathRead(ctx context.Context, d *schema.ResourceData d.Set("arn", nip.NetworkInsightsPathArn) d.Set("destination", nip.Destination) + d.Set("destination_arn", nip.DestinationArn) d.Set("destination_ip", nip.DestinationIp) d.Set("destination_port", nip.DestinationPort) d.Set("protocol", nip.Protocol) d.Set("source", nip.Source) + d.Set("source_arn", nip.SourceArn) d.Set("source_ip", nip.SourceIp) setTagsOut(ctx, nip.Tags) @@ -148,9 +160,11 @@ func resourceNetworkInsightsPathDelete(ctx context.Context, d *schema.ResourceDa conn := meta.(*conns.AWSClient).EC2Conn(ctx) log.Printf("[DEBUG] Deleting EC2 Network Insights Path: %s", d.Id()) - _, err := conn.DeleteNetworkInsightsPathWithContext(ctx, &ec2.DeleteNetworkInsightsPathInput{ - NetworkInsightsPathId: aws.String(d.Id()), - }) + _, err := tfresource.RetryWhenAWSErrCodeEquals(ctx, ec2PropagationTimeout, func() (interface{}, error) { + return conn.DeleteNetworkInsightsPathWithContext(ctx, &ec2.DeleteNetworkInsightsPathInput{ + NetworkInsightsPathId: aws.String(d.Id()), + }) + }, errCodeAnalysisExistsForNetworkInsightsPath) if tfawserr.ErrCodeEquals(err, errCodeInvalidNetworkInsightsPathIdNotFound) { return nil @@ -162,3 +176,16 @@ func resourceNetworkInsightsPathDelete(ctx context.Context, d *schema.ResourceDa return nil } + +// idFromIDOrARN return a resource ID from an ID or ARN. +func idFromIDOrARN(idOrARN string) string { + // e.g. "eni-02ae120b80627a68f" or + // "arn:aws:ec2:ap-southeast-2:123456789012:network-interface/eni-02ae120b80627a68f". + return idOrARN[strings.LastIndex(idOrARN, "/")+1:] +} + +// suppressEquivalentIDOrARN provides custom difference suppression +// for strings that represent equal resource IDs or ARNs. +func suppressEquivalentIDOrARN(_, old, new string, _ *schema.ResourceData) bool { + return idFromIDOrARN(old) == idFromIDOrARN(new) +} diff --git a/internal/service/ec2/vpc_network_insights_path_data_source.go b/internal/service/ec2/vpc_network_insights_path_data_source.go index f7c8e35d2ba4..de44586d8bd0 100644 --- a/internal/service/ec2/vpc_network_insights_path_data_source.go +++ b/internal/service/ec2/vpc_network_insights_path_data_source.go @@ -29,6 +29,10 @@ func DataSourceNetworkInsightsPath() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "destination_arn": { + Type: schema.TypeString, + Computed: true, + }, "destination_ip": { Type: schema.TypeString, Computed: true, @@ -51,6 +55,10 @@ func DataSourceNetworkInsightsPath() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "source_arn": { + Type: schema.TypeString, + Computed: true, + }, "source_ip": { Type: schema.TypeString, Computed: true, @@ -89,11 +97,13 @@ func dataSourceNetworkInsightsPathRead(ctx context.Context, d *schema.ResourceDa d.SetId(networkInsightsPathID) d.Set("arn", nip.NetworkInsightsPathArn) d.Set("destination", nip.Destination) + d.Set("destination_arn", nip.DestinationArn) d.Set("destination_ip", nip.DestinationIp) d.Set("destination_port", nip.DestinationPort) d.Set("network_insights_path_id", networkInsightsPathID) d.Set("protocol", nip.Protocol) d.Set("source", nip.Source) + d.Set("source_arn", nip.SourceArn) d.Set("source_ip", nip.SourceIp) if err := d.Set("tags", KeyValueTags(ctx, nip.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { diff --git a/internal/service/ec2/vpc_network_insights_path_data_source_test.go b/internal/service/ec2/vpc_network_insights_path_data_source_test.go index 64ef05cacfec..b6cf943cc690 100644 --- a/internal/service/ec2/vpc_network_insights_path_data_source_test.go +++ b/internal/service/ec2/vpc_network_insights_path_data_source_test.go @@ -29,11 +29,13 @@ func TestAccVPCNetworkInsightsPathDataSource_basic(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrPair(datasourceName, "arn", resourceName, "arn"), resource.TestCheckResourceAttrPair(datasourceName, "destination", resourceName, "destination"), + resource.TestCheckResourceAttrPair(datasourceName, "destination_arn", resourceName, "destination_arn"), resource.TestCheckResourceAttrPair(datasourceName, "destination_ip", resourceName, "destination_ip"), resource.TestCheckResourceAttrPair(datasourceName, "destination_port", resourceName, "destination_port"), resource.TestCheckResourceAttrPair(datasourceName, "network_insights_path_id", resourceName, "id"), resource.TestCheckResourceAttrPair(datasourceName, "protocol", resourceName, "protocol"), resource.TestCheckResourceAttrPair(datasourceName, "source", resourceName, "source"), + resource.TestCheckResourceAttrPair(datasourceName, "source_arn", resourceName, "source_arn"), resource.TestCheckResourceAttrPair(datasourceName, "source_ip", resourceName, "source_ip"), resource.TestCheckResourceAttrPair(datasourceName, "tags.%", resourceName, "tags.%"), ), diff --git a/internal/service/ec2/vpc_network_insights_path_test.go b/internal/service/ec2/vpc_network_insights_path_test.go index 481d21d5b2fe..4effba77d57f 100644 --- a/internal/service/ec2/vpc_network_insights_path_test.go +++ b/internal/service/ec2/vpc_network_insights_path_test.go @@ -36,10 +36,12 @@ func TestAccVPCNetworkInsightsPath_basic(t *testing.T) { testAccCheckNetworkInsightsPathExists(ctx, resourceName), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexache.MustCompile(`network-insights-path/.+$`)), resource.TestCheckResourceAttrPair(resourceName, "destination", "aws_network_interface.test.1", "id"), + resource.TestCheckResourceAttrPair(resourceName, "destination_arn", "aws_network_interface.test.1", "arn"), resource.TestCheckResourceAttr(resourceName, "destination_ip", ""), resource.TestCheckResourceAttr(resourceName, "destination_port", "0"), resource.TestCheckResourceAttr(resourceName, "protocol", "tcp"), resource.TestCheckResourceAttrPair(resourceName, "source", "aws_network_interface.test.0", "id"), + resource.TestCheckResourceAttrPair(resourceName, "source_arn", "aws_network_interface.test.0", "arn"), resource.TestCheckResourceAttr(resourceName, "source_ip", ""), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), @@ -121,6 +123,36 @@ func TestAccVPCNetworkInsightsPath_tags(t *testing.T) { }) } +func TestAccVPCNetworkInsightsPath_sourceAndDestinationARN(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_ec2_network_insights_path.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, ec2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckNetworkInsightsPathDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccVPCNetworkInsightsPathConfig_sourceAndDestinationARN(rName, "tcp"), + Check: resource.ComposeTestCheckFunc( + testAccCheckNetworkInsightsPathExists(ctx, resourceName), + resource.TestCheckResourceAttrPair(resourceName, "destination", "aws_network_interface.test.1", "id"), + resource.TestCheckResourceAttrPair(resourceName, "destination_arn", "aws_network_interface.test.1", "arn"), + resource.TestCheckResourceAttrPair(resourceName, "source", "aws_network_interface.test.0", "id"), + resource.TestCheckResourceAttrPair(resourceName, "source_arn", "aws_network_interface.test.0", "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccVPCNetworkInsightsPath_sourceIP(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_ec2_network_insights_path.test" @@ -337,6 +369,26 @@ resource "aws_ec2_network_insights_path" "test" { `, rName, tagKey1, tagValue1, tagKey2, tagValue2)) } +func testAccVPCNetworkInsightsPathConfig_sourceAndDestinationARN(rName, protocol string) string { + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 1), fmt.Sprintf(` +resource "aws_network_interface" "test" { + count = 2 + + subnet_id = aws_subnet.test[0].id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_network_insights_path" "test" { + source = aws_network_interface.test[0].arn + destination = aws_network_interface.test[1].arn + protocol = %[2]q +} +`, rName, protocol)) +} + func testAccVPCNetworkInsightsPathConfig_sourceIP(rName, sourceIP string) string { return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 1), fmt.Sprintf(` resource "aws_internet_gateway" "test" { diff --git a/website/docs/d/ec2_network_insights_path.html.markdown b/website/docs/d/ec2_network_insights_path.html.markdown index 74341373b34e..8b46e787325e 100644 --- a/website/docs/d/ec2_network_insights_path.html.markdown +++ b/website/docs/d/ec2_network_insights_path.html.markdown @@ -40,9 +40,11 @@ This data source exports the following attributes in addition to the arguments a * `arn` - ARN of the selected Network Insights Path. * `destination` - AWS resource that is the destination of the path. +* `destination_arn` - ARN of the destination. * `destination_ip` - IP address of the AWS resource that is the destination of the path. * `destination_port` - Destination port. * `protocol` - Protocol. * `source` - AWS resource that is the source of the path. +* `source_arn` - ARN of the source. * `source_ip` - IP address of the AWS resource that is the source of the path. * `tags` - Map of tags assigned to the resource. diff --git a/website/docs/r/ec2_network_insights_path.html.markdown b/website/docs/r/ec2_network_insights_path.html.markdown index 660bd8243e52..b16bda244cf4 100644 --- a/website/docs/r/ec2_network_insights_path.html.markdown +++ b/website/docs/r/ec2_network_insights_path.html.markdown @@ -24,8 +24,8 @@ resource "aws_ec2_network_insights_path" "test" { The following arguments are required: -* `source` - (Required) ID of the resource which is the source of the path. Can be an Instance, Internet Gateway, Network Interface, Transit Gateway, VPC Endpoint, VPC Peering Connection or VPN Gateway. -* `destination` - (Required) ID of the resource which is the source of the path. Can be an Instance, Internet Gateway, Network Interface, Transit Gateway, VPC Endpoint, VPC Peering Connection or VPN Gateway. +* `source` - (Required) ID or ARN of the resource which is the source of the path. Can be an Instance, Internet Gateway, Network Interface, Transit Gateway, VPC Endpoint, VPC Peering Connection or VPN Gateway. If the resource is in another account, you must specify an ARN. +* `destination` - (Required) ID or ARN of the resource which is the source of the path. Can be an Instance, Internet Gateway, Network Interface, Transit Gateway, VPC Endpoint, VPC Peering Connection or VPN Gateway. If the resource is in another account, you must specify an ARN. * `protocol` - (Required) Protocol to use for analysis. Valid options are `tcp` or `udp`. The following arguments are optional: @@ -40,7 +40,9 @@ The following arguments are optional: This resource exports the following attributes in addition to the arguments above: * `arn` - ARN of the Network Insights Path. +* `destination_arn` - ARN of the destination. * `id` - ID of the Network Insights Path. +* `source_arn` - ARN of the source. * `tags_all` - Map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). ## Import