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

fix: fix aws_vpclattice_service_network_vpc_association forced replacement #32658

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changelog/32658.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:bug
resource/aws_vpclattice_service_network_vpc_association: Avoid recreating resource when passing an ARN as `service_network_identifier`
```

```release-note:bug
resource/aws_vpclattice_service_network_service_association: Avoid recreating resource when passing an ARN as `service_identifier` or `service_network_identifier`
```
22 changes: 7 additions & 15 deletions internal/service/ec2/vpc_.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"github.com/aws/aws-sdk-go-v2/aws/arn"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/hashicorp/aws-sdk-go-base/v2/tfawserr"
tfawserr_sdkv2 "github.com/hashicorp/aws-sdk-go-base/v2/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
Expand Down Expand Up @@ -213,18 +213,10 @@ func resourceVPCCreate(ctx context.Context, d *schema.ResourceData, meta interfa
input.Ipv6NetmaskLength = aws.Int32(int32(v.(int)))
}

outputRaw, err := tfresource.RetryWhen(ctx, ec2PropagationTimeout,
func() (interface{}, error) {
return conn.CreateVpc(ctx, input)
},
func(err error) (bool, error) {
// "UnsupportedOperation: The operation AllocateIpamPoolCidr is not supported. Account 123456789012 is not monitored by IPAM ipam-07b079e3392782a55."
if tfawserr.ErrMessageContains(err, errCodeUnsupportedOperation, "is not monitored by IPAM") {
return true, err
}
return false, err
},
)
// "UnsupportedOperation: The operation AllocateIpamPoolCidr is not supported. Account 123456789012 is not monitored by IPAM ipam-07b079e3392782a55."
outputRaw, err := tfresource.RetryWhenAWSErrMessageContainsV2(ctx, ec2PropagationTimeout, func() (interface{}, error) {
return conn.CreateVpc(ctx, input)
}, errCodeUnsupportedOperation, "is not monitored by IPAM")

if err != nil {
return sdkdiag.AppendErrorf(diags, "creating EC2 VPC: %s", err)
Expand Down Expand Up @@ -458,11 +450,11 @@ func resourceVPCDelete(ctx context.Context, d *schema.ResourceData, meta interfa
}

log.Printf("[INFO] Deleting EC2 VPC: %s", d.Id())
_, err := tfresource.RetryWhenAWSErrCodeEquals(ctx, vpcDeletedTimeout, func() (interface{}, error) {
_, err := tfresource.RetryWhenAWSErrCodeEqualsV2(ctx, vpcDeletedTimeout, func() (interface{}, error) {
return conn.DeleteVpc(ctx, input)
}, errCodeDependencyViolation)

if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCIDNotFound) {
if tfawserr_sdkv2.ErrCodeEquals(err, errCodeInvalidVPCIDNotFound) {
return diags
}

Expand Down
11 changes: 9 additions & 2 deletions internal/service/vpclattice/exports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ package vpclattice

// Exports for use in tests only.
var (
FindTargetByThreePartKey = findTargetByThreePartKey
FindServiceNetworkServiceAssociationByID = findServiceNetworkServiceAssociationByID
FindServiceNetworkVPCAssociationByID = findServiceNetworkVPCAssociationByID
FindTargetByThreePartKey = findTargetByThreePartKey

ResourceTargetGroupAttachment = resourceTargetGroupAttachment
IDFromIDOrARN = idFromIDOrARN
SuppressEquivalentIDOrARN = suppressEquivalentIDOrARN

ResourceServiceNetworkServiceAssociation = resourceServiceNetworkServiceAssociation
ResourceServiceNetworkVPCAssociation = resourceServiceNetworkVPCAssociation
ResourceTargetGroupAttachment = resourceTargetGroupAttachment
)
14 changes: 14 additions & 0 deletions internal/service/vpclattice/service_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
"errors"
"log"
"strings"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/vpclattice"
Expand Down Expand Up @@ -181,3 +182,16 @@ func findServiceNetworkByID(ctx context.Context, conn *vpclattice.Client, id str

return out, nil
}

// idFromIDOrARN return a resource ID from an ID or ARN.
func idFromIDOrARN(idOrARN string) string {
// e.g. "sn-1234567890abcdefg" or
// "arn:aws:vpc-lattice:us-east-1:123456789012:servicenetwork/sn-1234567890abcdefg".
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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/hashicorp/terraform-provider-aws/internal/conns"
"github.com/hashicorp/terraform-provider-aws/internal/create"
"github.com/hashicorp/terraform-provider-aws/internal/enum"
"github.com/hashicorp/terraform-provider-aws/internal/errs"
tftags "github.com/hashicorp/terraform-provider-aws/internal/tags"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/internal/verify"
Expand All @@ -27,7 +28,7 @@ import (

// @SDKResource("aws_vpclattice_service_network_service_association", name="Service Network Service Association")
// @Tags(identifierAttribute="arn")
func ResourceServiceNetworkServiceAssociation() *schema.Resource {
func resourceServiceNetworkServiceAssociation() *schema.Resource {
return &schema.Resource{
CreateWithoutTimeout: resourceServiceNetworkServiceAssociationCreate,
ReadWithoutTimeout: resourceServiceNetworkServiceAssociationRead,
Expand Down Expand Up @@ -74,14 +75,16 @@ func ResourceServiceNetworkServiceAssociation() *schema.Resource {
},
},
"service_identifier": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Type: schema.TypeString,
Required: true,
ForceNew: true,
DiffSuppressFunc: suppressEquivalentIDOrARN,
},
"service_network_identifier": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Type: schema.TypeString,
Required: true,
ForceNew: true,
DiffSuppressFunc: suppressEquivalentIDOrARN,
},
"status": {
Type: schema.TypeString,
Expand Down Expand Up @@ -173,12 +176,11 @@ func resourceServiceNetworkServiceAssociationDelete(ctx context.Context, d *sche
ServiceNetworkServiceAssociationIdentifier: aws.String(d.Id()),
})

if err != nil {
var nfe *types.ResourceNotFoundException
if errors.As(err, &nfe) {
return nil
}
if errs.IsA[*types.ResourceNotFoundException](err) {
return nil
}

if err != nil {
return create.DiagError(names.VPCLattice, create.ErrActionDeleting, ResNameServiceNetworkAssociation, d.Id(), err)
}

Expand All @@ -194,15 +196,15 @@ func findServiceNetworkServiceAssociationByID(ctx context.Context, conn *vpclatt
ServiceNetworkServiceAssociationIdentifier: aws.String(id),
}
out, err := conn.GetServiceNetworkServiceAssociation(ctx, in)
if err != nil {
var nfe *types.ResourceNotFoundException
if errors.As(err, &nfe) {
return nil, &retry.NotFoundError{
LastError: err,
LastRequest: in,
}

if errs.IsA[*types.ResourceNotFoundException](err) {
return nil, &retry.NotFoundError{
LastError: err,
LastRequest: in,
}
}

if err != nil {
return nil, err
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,15 @@ import (
"regexp"
"testing"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/vpclattice"
"github.com/aws/aws-sdk-go-v2/service/vpclattice/types"
sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/hashicorp/terraform-provider-aws/internal/acctest"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
"github.com/hashicorp/terraform-provider-aws/internal/create"
tfvpclattice "github.com/hashicorp/terraform-provider-aws/internal/service/vpclattice"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/names"
)

Expand Down Expand Up @@ -56,6 +55,39 @@ func TestAccVPCLatticeServiceNetworkServiceAssociation_basic(t *testing.T) {
})
}

func TestAccVPCLatticeServiceNetworkServiceAssociation_arn(t *testing.T) {
ctx := acctest.Context(t)

var servicenetworkasc vpclattice.GetServiceNetworkServiceAssociationOutput
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_vpclattice_service_network_service_association.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
acctest.PreCheck(ctx, t)
acctest.PreCheckPartitionHasService(t, names.VPCLatticeEndpointID)
testAccPreCheck(ctx, t)
},
ErrorCheck: acctest.ErrorCheck(t, names.VPCLatticeEndpointID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckServiceNetworkServiceAssociationDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccServiceNetworkServiceAssociationConfig_arn(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckServiceNetworkServiceAssociationExists(ctx, resourceName, &servicenetworkasc),
acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "vpc-lattice", regexp.MustCompile("servicenetworkserviceassociation/.+$")),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccVPCLatticeServiceNetworkServiceAssociation_disappears(t *testing.T) {
ctx := acctest.Context(t)

Expand Down Expand Up @@ -144,18 +176,17 @@ func testAccCheckServiceNetworkServiceAssociationDestroy(ctx context.Context) re
continue
}

_, err := conn.GetServiceNetworkServiceAssociation(ctx, &vpclattice.GetServiceNetworkServiceAssociationInput{
ServiceNetworkServiceAssociationIdentifier: aws.String(rs.Primary.ID),
})
_, err := tfvpclattice.FindServiceNetworkServiceAssociationByID(ctx, conn, rs.Primary.ID)

if tfresource.NotFound(err) {
continue
}

if err != nil {
var nfe *types.ResourceNotFoundException
if errors.As(err, &nfe) {
return nil
}
return err
}

return create.Error(names.VPCLattice, create.ErrActionCheckingDestroyed, tfvpclattice.ResNameService, rs.Primary.ID, errors.New("not destroyed"))
return fmt.Errorf("VPC Lattice Service Network Service Association %s still exists", rs.Primary.ID)
}

return nil
Expand All @@ -174,12 +205,10 @@ func testAccCheckServiceNetworkServiceAssociationExists(ctx context.Context, nam
}

conn := acctest.Provider.Meta().(*conns.AWSClient).VPCLatticeClient(ctx)
resp, err := conn.GetServiceNetworkServiceAssociation(ctx, &vpclattice.GetServiceNetworkServiceAssociationInput{
ServiceNetworkServiceAssociationIdentifier: aws.String(rs.Primary.ID),
})
resp, err := tfvpclattice.FindServiceNetworkServiceAssociationByID(ctx, conn, rs.Primary.ID)

if err != nil {
return create.Error(names.VPCLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameService, rs.Primary.ID, err)
return err
}

*service = *resp
Expand All @@ -188,7 +217,7 @@ func testAccCheckServiceNetworkServiceAssociationExists(ctx context.Context, nam
}
}

func testAccServiceNetworkServiceAssociationConfig_basic(rName string) string {
func testAccServiceNetworkServiceAssociationConfig_base(rName string) string {
return fmt.Sprintf(`
resource "aws_vpclattice_service" "test" {
name = %[1]q
Expand All @@ -197,53 +226,50 @@ resource "aws_vpclattice_service" "test" {
resource "aws_vpclattice_service_network" "test" {
name = %[1]q
}
`, rName)
}

func testAccServiceNetworkServiceAssociationConfig_basic(rName string) string {
return acctest.ConfigCompose(testAccServiceNetworkServiceAssociationConfig_base(rName), `
resource "aws_vpclattice_service_network_service_association" "test" {
service_identifier = aws_vpclattice_service.test.id
service_network_identifier = aws_vpclattice_service_network.test.id
}
`, rName)
`)
}

func testAccServiceNetworkServiceAssociationConfig_tags1(rName, tagKey1, tagValue1 string) string {
return fmt.Sprintf(`
resource "aws_vpclattice_service" "test" {
name = %[1]q
func testAccServiceNetworkServiceAssociationConfig_arn(rName string) string {
return acctest.ConfigCompose(testAccServiceNetworkServiceAssociationConfig_base(rName), `
resource "aws_vpclattice_service_network_service_association" "test" {
service_identifier = aws_vpclattice_service.test.arn
service_network_identifier = aws_vpclattice_service_network.test.arn
}

resource "aws_vpclattice_service_network" "test" {
name = %[1]q
`)
}

func testAccServiceNetworkServiceAssociationConfig_tags1(rName, tagKey1, tagValue1 string) string {
return acctest.ConfigCompose(testAccServiceNetworkServiceAssociationConfig_base(rName), fmt.Sprintf(`
resource "aws_vpclattice_service_network_service_association" "test" {
service_identifier = aws_vpclattice_service.test.id
service_network_identifier = aws_vpclattice_service_network.test.id

tags = {
%[2]q = %[3]q
%[1]q = %[2]q
}
}
`, rName, tagKey1, tagValue1)
`, tagKey1, tagValue1))
}

func testAccServiceNetworkServiceAssociationConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string {
return fmt.Sprintf(`
resource "aws_vpclattice_service" "test" {
name = %[1]q
}

resource "aws_vpclattice_service_network" "test" {
name = %[1]q
}

return acctest.ConfigCompose(testAccServiceNetworkServiceAssociationConfig_base(rName), fmt.Sprintf(`
resource "aws_vpclattice_service_network_service_association" "test" {
service_identifier = aws_vpclattice_service.test.id
service_network_identifier = aws_vpclattice_service_network.test.id

tags = {
%[2]q = %[3]q
%[4]q = %[5]q
%[1]q = %[2]q
%[3]q = %[4]q
}
}
`, rName, tagKey1, tagValue1, tagKey2, tagValue2)
`, tagKey1, tagValue1, tagKey2, tagValue2))
}
Loading
Loading