Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

r/aws_ec2_network_insights_path: Avoid recreating resource when passing an ARN as source or destination #33168

Merged
merged 12 commits into from
Aug 24, 2023
Merged
19 changes: 19 additions & 0 deletions .changelog/33168.txt
Original file line number Diff line number Diff line change
@@ -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
```
25 changes: 12 additions & 13 deletions internal/service/ec2/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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...)
}
2 changes: 1 addition & 1 deletion internal/service/ec2/vpc_network_insights_analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 0 additions & 6 deletions internal/service/ec2/vpc_network_insights_analysis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
45 changes: 36 additions & 9 deletions internal/service/ec2/vpc_network_insights_path.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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)
}
10 changes: 10 additions & 0 deletions internal/service/ec2/vpc_network_insights_path_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.%"),
),
Expand Down
52 changes: 52 additions & 0 deletions internal/service/ec2/vpc_network_insights_path_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
),
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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" {
Expand Down
2 changes: 2 additions & 0 deletions website/docs/d/ec2_network_insights_path.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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.
6 changes: 4 additions & 2 deletions website/docs/r/ec2_network_insights_path.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
Loading