diff --git a/.changelog/32658.txt b/.changelog/32658.txt new file mode 100644 index 00000000000..f58717ce93d --- /dev/null +++ b/.changelog/32658.txt @@ -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` +``` \ No newline at end of file diff --git a/internal/service/ec2/vpc_.go b/internal/service/ec2/vpc_.go index 8aaa044f5cb..8b0270842c3 100644 --- a/internal/service/ec2/vpc_.go +++ b/internal/service/ec2/vpc_.go @@ -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" @@ -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) @@ -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 } diff --git a/internal/service/vpclattice/exports_test.go b/internal/service/vpclattice/exports_test.go index a60aebfb708..6b11ba97ca3 100644 --- a/internal/service/vpclattice/exports_test.go +++ b/internal/service/vpclattice/exports_test.go @@ -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 ) diff --git a/internal/service/vpclattice/service_network.go b/internal/service/vpclattice/service_network.go index ef574a819b6..85a9894de98 100644 --- a/internal/service/vpclattice/service_network.go +++ b/internal/service/vpclattice/service_network.go @@ -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" @@ -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) +} diff --git a/internal/service/vpclattice/service_network_service_association.go b/internal/service/vpclattice/service_network_service_association.go index b3c0498d935..4e07c7568f2 100644 --- a/internal/service/vpclattice/service_network_service_association.go +++ b/internal/service/vpclattice/service_network_service_association.go @@ -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" @@ -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, @@ -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, @@ -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) } @@ -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 } diff --git a/internal/service/vpclattice/service_network_service_association_test.go b/internal/service/vpclattice/service_network_service_association_test.go index 4e903ee821c..bf1016ae27c 100644 --- a/internal/service/vpclattice/service_network_service_association_test.go +++ b/internal/service/vpclattice/service_network_service_association_test.go @@ -10,9 +10,7 @@ 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" @@ -20,6 +18,7 @@ import ( "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" ) @@ -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) @@ -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 @@ -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 @@ -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 @@ -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)) } diff --git a/internal/service/vpclattice/service_network_test.go b/internal/service/vpclattice/service_network_test.go index 87eac468701..77e9f2a57e8 100644 --- a/internal/service/vpclattice/service_network_test.go +++ b/internal/service/vpclattice/service_network_test.go @@ -23,6 +23,74 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) +func TestIDFromIDOrARN(t *testing.T) { + t.Parallel() + + testCases := []struct { + idOrARN string + want string + }{ + { + idOrARN: "", + want: "", + }, + { + idOrARN: "sn-1234567890abcdefg", + want: "sn-1234567890abcdefg", + }, + { + idOrARN: "arn:aws:vpc-lattice:us-east-1:123456789012:servicenetwork/sn-1234567890abcdefg", //lintignore:AWSAT003,AWSAT005 + want: "sn-1234567890abcdefg", + }, + } + for _, testCase := range testCases { + if got, want := tfvpclattice.IDFromIDOrARN(testCase.idOrARN), testCase.want; got != want { + t.Errorf("IDFromIDOrARN(%q) = %v, want %v", testCase.idOrARN, got, want) + } + } +} + +func TestSuppressEquivalentIDOrARN(t *testing.T) { + t.Parallel() + + testCases := []struct { + old string + new string + want bool + }{ + { + old: "sn-1234567890abcdefg", + new: "sn-1234567890abcdefg", + want: true, + }, + { + old: "sn-1234567890abcdefg", + new: "sn-1234567890abcdefh", + want: false, + }, + { + old: "arn:aws:vpc-lattice:us-east-1:123456789012:servicenetwork/sn-1234567890abcdefg", //lintignore:AWSAT003,AWSAT005 + new: "sn-1234567890abcdefg", + want: true, + }, + { + old: "sn-1234567890abcdefg", + new: "arn:aws:vpc-lattice:us-east-1:123456789012:servicenetwork/sn-1234567890abcdefg", //lintignore:AWSAT003,AWSAT005 + want: true, + }, + { + old: "arn:aws:vpc-lattice:us-east-1:123456789012:servicenetwork/sn-1234567890abcdefg", //lintignore:AWSAT003,AWSAT005 + new: "sn-1234567890abcdefh", + want: false, + }, + } + for _, testCase := range testCases { + if got, want := tfvpclattice.SuppressEquivalentIDOrARN("test_property", testCase.old, testCase.new, nil), testCase.want; got != want { + t.Errorf("SuppressEquivalentIDOrARN(%q, %q) = %v, want %v", testCase.old, testCase.new, got, want) + } + } +} + func TestAccVPCLatticeServiceNetwork_basic(t *testing.T) { ctx := acctest.Context(t) diff --git a/internal/service/vpclattice/service_network_vpc_association.go b/internal/service/vpclattice/service_network_vpc_association.go index 4f2ae54ba87..6337a621865 100644 --- a/internal/service/vpclattice/service_network_vpc_association.go +++ b/internal/service/vpclattice/service_network_vpc_association.go @@ -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" "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" @@ -28,7 +29,7 @@ import ( // @SDKResource("aws_vpclattice_service_network_vpc_association", name="Service Network VPC Association") // @Tags(identifierAttribute="arn") -func ResourceServiceNetworkVPCAssociation() *schema.Resource { +func resourceServiceNetworkVPCAssociation() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceServiceNetworkVPCAssociationCreate, ReadWithoutTimeout: resourceServiceNetworkVPCAssociationRead, @@ -61,9 +62,10 @@ func ResourceServiceNetworkVPCAssociation() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, }, "service_network_identifier": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: suppressEquivalentIDOrARN, }, "status": { Type: schema.TypeString, @@ -173,12 +175,11 @@ func resourceServiceNetworkVPCAssociationDelete(ctx context.Context, d *schema.R ServiceNetworkVpcAssociationIdentifier: 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, ResNameServiceNetworkVPCAssociation, d.Id(), err) } @@ -194,15 +195,15 @@ func findServiceNetworkVPCAssociationByID(ctx context.Context, conn *vpclattice. ServiceNetworkVpcAssociationIdentifier: aws.String(id), } out, err := conn.GetServiceNetworkVpcAssociation(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 } diff --git a/internal/service/vpclattice/service_network_vpc_association_test.go b/internal/service/vpclattice/service_network_vpc_association_test.go index 706d356c7ba..42830e2ee99 100644 --- a/internal/service/vpclattice/service_network_vpc_association_test.go +++ b/internal/service/vpclattice/service_network_vpc_association_test.go @@ -10,9 +10,7 @@ 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" @@ -20,6 +18,7 @@ import ( "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" ) @@ -56,6 +55,40 @@ func TestAccVPCLatticeServiceNetworkVPCAssociation_basic(t *testing.T) { }) } +func TestAccVPCLatticeServiceNetworkVPCAssociation_arn(t *testing.T) { + ctx := acctest.Context(t) + + var servicenetworkvpcasc vpclattice.GetServiceNetworkVpcAssociationOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_vpclattice_service_network_vpc_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: testAccCheckServiceNetworkVPCAssociationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccServiceNetworkVPCAssociationConfig_arn(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceNetworkVPCAssociationExists(ctx, resourceName, &servicenetworkvpcasc), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "vpc-lattice", regexp.MustCompile("servicenetworkvpcassociation/.+$")), + resource.TestCheckResourceAttrSet(resourceName, "service_network_identifier"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccVPCLatticeServiceNetworkVPCAssociation_disappears(t *testing.T) { ctx := acctest.Context(t) @@ -180,18 +213,17 @@ func testAccCheckServiceNetworkVPCAssociationDestroy(ctx context.Context) resour continue } - _, err := conn.GetServiceNetworkVpcAssociation(ctx, &vpclattice.GetServiceNetworkVpcAssociationInput{ - ServiceNetworkVpcAssociationIdentifier: aws.String(rs.Primary.ID), - }) + _, err := tfvpclattice.FindServiceNetworkVPCAssociationByID(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 VPC Association %s still exists", rs.Primary.ID) } return nil @@ -210,12 +242,10 @@ func testAccCheckServiceNetworkVPCAssociationExists(ctx context.Context, name st } conn := acctest.Provider.Meta().(*conns.AWSClient).VPCLatticeClient(ctx) - resp, err := conn.GetServiceNetworkVpcAssociation(ctx, &vpclattice.GetServiceNetworkVpcAssociationInput{ - ServiceNetworkVpcAssociationIdentifier: aws.String(rs.Primary.ID), - }) + resp, err := tfvpclattice.FindServiceNetworkVPCAssociationByID(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 @@ -224,35 +254,41 @@ func testAccCheckServiceNetworkVPCAssociationExists(ctx context.Context, name st } } -func testAccServiceNetworkVPCAssociationConfig_basic(rName string) string { - return fmt.Sprintf(` -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" -} - +func testAccServiceNetworkVPCAssociationConfig_base(rName string) string { + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 0), fmt.Sprintf(` resource "aws_vpclattice_service_network" "test" { name = %[1]q } +`, rName)) +} +func testAccServiceNetworkVPCAssociationConfig_basic(rName string) string { + return acctest.ConfigCompose(testAccServiceNetworkVPCAssociationConfig_base(rName), ` resource "aws_vpclattice_service_network_vpc_association" "test" { vpc_identifier = aws_vpc.test.id service_network_identifier = aws_vpclattice_service_network.test.id } -`, rName) +`) } -func testAccServiceNetworkVPCAssociationConfig_full(rName string) string { - return fmt.Sprintf(` -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" +func testAccServiceNetworkVPCAssociationConfig_arn(rName string) string { + return acctest.ConfigCompose(testAccServiceNetworkVPCAssociationConfig_base(rName), ` +resource "aws_vpclattice_service_network_vpc_association" "test" { + vpc_identifier = aws_vpc.test.id + service_network_identifier = aws_vpclattice_service_network.test.arn +} +`) } +func testAccServiceNetworkVPCAssociationConfig_full(rName string) string { + return acctest.ConfigCompose(testAccServiceNetworkVPCAssociationConfig_base(rName), fmt.Sprintf(` resource "aws_security_group" "test" { + name = %[1]q vpc_id = aws_vpc.test.id -} -resource "aws_vpclattice_service_network" "test" { - name = %[1]q + tags = { + Name = %[1]q + } } resource "aws_vpclattice_service_network_vpc_association" "test" { @@ -260,48 +296,32 @@ resource "aws_vpclattice_service_network_vpc_association" "test" { security_group_ids = [aws_security_group.test.id] service_network_identifier = aws_vpclattice_service_network.test.id } -`, rName) +`, rName)) } func testAccServiceNetworkVPCAssociationConfig_tags1(rName, tagKey1, tagValue1 string) string { - return fmt.Sprintf(` -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" -} - -resource "aws_vpclattice_service_network" "test" { - name = %[1]q -} - + return acctest.ConfigCompose(testAccServiceNetworkVPCAssociationConfig_base(rName), fmt.Sprintf(` resource "aws_vpclattice_service_network_vpc_association" "test" { vpc_identifier = aws_vpc.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 testAccServiceNetworkVPCAssociationConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { - return fmt.Sprintf(` -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" -} - -resource "aws_vpclattice_service_network" "test" { - name = %[1]q -} - + return acctest.ConfigCompose(testAccServiceNetworkVPCAssociationConfig_base(rName), fmt.Sprintf(` resource "aws_vpclattice_service_network_vpc_association" "test" { vpc_identifier = aws_vpc.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)) } diff --git a/internal/service/vpclattice/service_package_gen.go b/internal/service/vpclattice/service_package_gen.go index dd682c2fd80..26e1ab516d4 100644 --- a/internal/service/vpclattice/service_package_gen.go +++ b/internal/service/vpclattice/service_package_gen.go @@ -102,7 +102,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka }, }, { - Factory: ResourceServiceNetworkServiceAssociation, + Factory: resourceServiceNetworkServiceAssociation, TypeName: "aws_vpclattice_service_network_service_association", Name: "Service Network Service Association", Tags: &types.ServicePackageResourceTags{ @@ -110,7 +110,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka }, }, { - Factory: ResourceServiceNetworkVPCAssociation, + Factory: resourceServiceNetworkVPCAssociation, TypeName: "aws_vpclattice_service_network_vpc_association", Name: "Service Network VPC Association", Tags: &types.ServicePackageResourceTags{ diff --git a/internal/tfresource/retry.go b/internal/tfresource/retry.go index fe2aabd9b2a..f8e1f5bd65d 100644 --- a/internal/tfresource/retry.go +++ b/internal/tfresource/retry.go @@ -11,6 +11,7 @@ import ( "time" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + tfawserr_sdkv2 "github.com/hashicorp/aws-sdk-go-base/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-provider-aws/internal/errs" ) @@ -66,6 +67,17 @@ func RetryWhenAWSErrCodeEquals(ctx context.Context, timeout time.Duration, f fun }) } +// RetryWhenAWSErrCodeEqualsV2 retries the specified function when it returns one of the specified AWS SDK for Go v2 error code. +func RetryWhenAWSErrCodeEqualsV2(ctx context.Context, timeout time.Duration, f func() (interface{}, error), codes ...string) (interface{}, error) { // nosemgrep:ci.aws-in-func-name + return RetryWhen(ctx, timeout, f, func(err error) (bool, error) { + if tfawserr_sdkv2.ErrCodeEquals(err, codes...) { + return true, err + } + + return false, err + }) +} + // RetryWhenAWSErrMessageContains retries the specified function when it returns an AWS error containing the specified message. func RetryWhenAWSErrMessageContains(ctx context.Context, timeout time.Duration, f func() (interface{}, error), code, message string) (interface{}, error) { // nosemgrep:ci.aws-in-func-name return RetryWhen(ctx, timeout, f, func(err error) (bool, error) { @@ -77,6 +89,17 @@ func RetryWhenAWSErrMessageContains(ctx context.Context, timeout time.Duration, }) } +// RetryWhenAWSErrMessageContainsV2 retries the specified function when it returns an AWS SDK for Go v2 error containing the specified message. +func RetryWhenAWSErrMessageContainsV2(ctx context.Context, timeout time.Duration, f func() (interface{}, error), code, message string) (interface{}, error) { // nosemgrep:ci.aws-in-func-name + return RetryWhen(ctx, timeout, f, func(err error) (bool, error) { + if tfawserr_sdkv2.ErrMessageContains(err, code, message) { + return true, err + } + + return false, err + }) +} + func RetryWhenIsA[T error](ctx context.Context, timeout time.Duration, f func() (interface{}, error)) (interface{}, error) { return RetryWhen(ctx, timeout, f, func(err error) (bool, error) { if errs.IsA[T](err) {