diff --git a/.changelog/35887.txt b/.changelog/35887.txt new file mode 100644 index 00000000000..5c8df39e561 --- /dev/null +++ b/.changelog/35887.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +data-source/aws_elasticache_subnet_group: Add `vpc_id` attribute +``` + +```release-note:enhancement +resource/aws_elasticache_subnet_group: Add `vpc_id` attribute +``` \ No newline at end of file diff --git a/internal/service/elasticache/exports_test.go b/internal/service/elasticache/exports_test.go index 8e9d85d050f..c2997f6f97b 100644 --- a/internal/service/elasticache/exports_test.go +++ b/internal/service/elasticache/exports_test.go @@ -6,4 +6,7 @@ package elasticache // Exports for use in tests only. var ( ResourceServerlessCache = newResourceServerlessCache + ResourceSubnetGroup = resourceSubnetGroup + + FindCacheSubnetGroupByName = findCacheSubnetGroupByName ) diff --git a/internal/service/elasticache/find.go b/internal/service/elasticache/find.go index f548617bde1..dc6556f8f0d 100644 --- a/internal/service/elasticache/find.go +++ b/internal/service/elasticache/find.go @@ -301,32 +301,3 @@ func FilterRedisParameterGroupNameClusterEnabledDefault(group *elasticache.Cache } return false } - -func FindCacheSubnetGroupByName(ctx context.Context, conn *elasticache.ElastiCache, name string) (*elasticache.CacheSubnetGroup, error) { - input := elasticache.DescribeCacheSubnetGroupsInput{ - CacheSubnetGroupName: aws.String(name), - } - - output, err := conn.DescribeCacheSubnetGroupsWithContext(ctx, &input) - - if tfawserr.ErrCodeEquals(err, elasticache.ErrCodeCacheSubnetGroupNotFoundFault) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: input, - } - } - - if err != nil { - return nil, err - } - - if output == nil || len(output.CacheSubnetGroups) == 0 || output.CacheSubnetGroups[0] == nil { - return nil, tfresource.NewEmptyResultError(input) - } - - if count := len(output.CacheSubnetGroups); count > 1 { - return nil, tfresource.NewTooManyResultsError(count, input) - } - - return output.CacheSubnetGroups[0], nil -} diff --git a/internal/service/elasticache/service_package_gen.go b/internal/service/elasticache/service_package_gen.go index ec7ef7876a8..a669495d605 100644 --- a/internal/service/elasticache/service_package_gen.go +++ b/internal/service/elasticache/service_package_gen.go @@ -44,8 +44,9 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac TypeName: "aws_elasticache_replication_group", }, { - Factory: DataSourceSubnetGroup, + Factory: dataSourceSubnetGroup, TypeName: "aws_elasticache_subnet_group", + Name: "Subnet Group", }, { Factory: DataSourceUser, @@ -85,7 +86,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka }, }, { - Factory: ResourceSubnetGroup, + Factory: resourceSubnetGroup, TypeName: "aws_elasticache_subnet_group", Name: "Subnet Group", Tags: &types.ServicePackageResourceTags{ diff --git a/internal/service/elasticache/subnet_group.go b/internal/service/elasticache/subnet_group.go index 47ffb9fa19f..d0ccfcef0fd 100644 --- a/internal/service/elasticache/subnet_group.go +++ b/internal/service/elasticache/subnet_group.go @@ -13,11 +13,14 @@ import ( "github.com/aws/aws-sdk-go/service/elasticache" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/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" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" 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" @@ -26,7 +29,7 @@ import ( // @SDKResource("aws_elasticache_subnet_group", name="Subnet Group") // @Tags(identifierAttribute="arn") -func ResourceSubnetGroup() *schema.Resource { +func resourceSubnetGroup() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceSubnetGroupCreate, ReadWithoutTimeout: resourceSubnetGroupRead, @@ -65,22 +68,17 @@ func ResourceSubnetGroup() *schema.Resource { }, names.AttrTags: tftags.TagsSchema(), names.AttrTagsAll: tftags.TagsSchemaComputed(), + "vpc_id": { + Type: schema.TypeString, + Computed: true, + }, }, - CustomizeDiff: resourceSubnetGroupDiff, - } -} - -func resourceSubnetGroupDiff(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error { - // Reserved ElastiCache Subnet Groups with the name "default" do not support tagging; - // thus we must suppress the diff originating from the provider-level default_tags configuration - // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/19213 - defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig - if len(defaultTagsConfig.GetTags()) > 0 && diff.Get("name").(string) == "default" { - return nil + CustomizeDiff: customdiff.All( + resourceSubnetGroupCustomizeDiff, + verify.SetTagsDiff, + ), } - - return verify.SetTagsDiff(ctx, diff, meta) } func resourceSubnetGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { @@ -108,7 +106,7 @@ func resourceSubnetGroupCreate(ctx context.Context, d *schema.ResourceData, meta return sdkdiag.AppendErrorf(diags, "creating ElastiCache Subnet Group (%s): %s", name, err) } - // Assign the group name as the resource ID + // Assign the group name as the resource ID. // ElastiCache always retains the name in lower case, so we have to // mimic that or else we won't be able to refresh a resource whose // name contained uppercase characters. @@ -135,7 +133,7 @@ func resourceSubnetGroupRead(ctx context.Context, d *schema.ResourceData, meta i var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ElastiCacheConn(ctx) - group, err := FindCacheSubnetGroupByName(ctx, conn, d.Id()) + group, err := findCacheSubnetGroupByName(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] ElastiCache Subnet Group (%s) not found, removing from state", d.Id()) @@ -147,15 +145,13 @@ func resourceSubnetGroupRead(ctx context.Context, d *schema.ResourceData, meta i return sdkdiag.AppendErrorf(diags, "reading ElastiCache Subnet Group (%s): %s", d.Id(), err) } - var subnetIDs []*string - for _, subnet := range group.Subnets { - subnetIDs = append(subnetIDs, subnet.SubnetIdentifier) - } - d.Set("arn", group.ARN) - d.Set("name", group.CacheSubnetGroupName) d.Set("description", group.CacheSubnetGroupDescription) - d.Set("subnet_ids", aws.StringValueSlice(subnetIDs)) + d.Set("name", group.CacheSubnetGroupName) + d.Set("subnet_ids", tfslices.ApplyToAll(group.Subnets, func(v *elasticache.Subnet) string { + return aws.StringValue(v.SubnetIdentifier) + })) + d.Set("vpc_id", group.VpcId) return diags } @@ -202,3 +198,64 @@ func resourceSubnetGroupDelete(ctx context.Context, d *schema.ResourceData, meta return diags } + +func resourceSubnetGroupCustomizeDiff(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error { + // Reserved ElastiCache Subnet Groups with the name "default" do not support tagging, + // thus we must suppress the diff originating from the provider-level default_tags configuration. + // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/19213. + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + if len(defaultTagsConfig.GetTags()) > 0 && diff.Get("name").(string) == "default" { + return nil + } + + return nil +} + +func findCacheSubnetGroupByName(ctx context.Context, conn *elasticache.ElastiCache, name string) (*elasticache.CacheSubnetGroup, error) { + input := &elasticache.DescribeCacheSubnetGroupsInput{ + CacheSubnetGroupName: aws.String(name), + } + + return findCacheSubnetGroup(ctx, conn, input, tfslices.PredicateTrue[*elasticache.CacheSubnetGroup]()) +} + +func findCacheSubnetGroup(ctx context.Context, conn *elasticache.ElastiCache, input *elasticache.DescribeCacheSubnetGroupsInput, filter tfslices.Predicate[*elasticache.CacheSubnetGroup]) (*elasticache.CacheSubnetGroup, error) { + output, err := findCacheSubnetGroups(ctx, conn, input, filter) + + if err != nil { + return nil, err + } + + return tfresource.AssertSinglePtrResult(output) +} + +func findCacheSubnetGroups(ctx context.Context, conn *elasticache.ElastiCache, input *elasticache.DescribeCacheSubnetGroupsInput, filter tfslices.Predicate[*elasticache.CacheSubnetGroup]) ([]*elasticache.CacheSubnetGroup, error) { + var output []*elasticache.CacheSubnetGroup + + err := conn.DescribeCacheSubnetGroupsPagesWithContext(ctx, input, func(page *elasticache.DescribeCacheSubnetGroupsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.CacheSubnetGroups { + if v != nil && filter(v) { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, elasticache.ErrCodeCacheSubnetGroupNotFoundFault) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} diff --git a/internal/service/elasticache/subnet_group_data_source.go b/internal/service/elasticache/subnet_group_data_source.go index 52e5314b73b..80dd525134c 100644 --- a/internal/service/elasticache/subnet_group_data_source.go +++ b/internal/service/elasticache/subnet_group_data_source.go @@ -7,16 +7,17 @@ import ( "context" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/elasticache" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" - "github.com/hashicorp/terraform-provider-aws/internal/flex" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" ) -// @SDKDataSource("aws_elasticache_subnet_group") -func DataSourceSubnetGroup() *schema.Resource { +// @SDKDataSource("aws_elasticache_subnet_group", name="Subnet Group") +func dataSourceSubnetGroup() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataSourceSubnetGroupRead, @@ -39,6 +40,10 @@ func DataSourceSubnetGroup() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, }, "tags": tftags.TagsSchema(), + "vpc_id": { + Type: schema.TypeString, + Computed: true, + }, }, } } @@ -50,23 +55,20 @@ func dataSourceSubnetGroupRead(ctx context.Context, d *schema.ResourceData, meta name := d.Get("name").(string) - group, err := FindCacheSubnetGroupByName(ctx, conn, name) + group, err := findCacheSubnetGroupByName(ctx, conn, name) if err != nil { return sdkdiag.AppendErrorf(diags, "reading ElastiCache Subnet Group (%s): %s", name, err) } d.SetId(aws.StringValue(group.CacheSubnetGroupName)) - - var subnetIds []*string - for _, subnet := range group.Subnets { - subnetIds = append(subnetIds, subnet.SubnetIdentifier) - } - d.Set("arn", group.ARN) d.Set("description", group.CacheSubnetGroupDescription) - d.Set("subnet_ids", flex.FlattenStringSet(subnetIds)) d.Set("name", group.CacheSubnetGroupName) + d.Set("subnet_ids", tfslices.ApplyToAll(group.Subnets, func(v *elasticache.Subnet) string { + return aws.StringValue(v.SubnetIdentifier) + })) + d.Set("vpc_id", group.VpcId) tags, err := listTags(ctx, conn, d.Get("arn").(string)) diff --git a/internal/service/elasticache/subnet_group_data_source_test.go b/internal/service/elasticache/subnet_group_data_source_test.go index c3c2ef99ef9..7467f72872c 100644 --- a/internal/service/elasticache/subnet_group_data_source_test.go +++ b/internal/service/elasticache/subnet_group_data_source_test.go @@ -37,6 +37,7 @@ func TestAccElastiCacheSubnetGroupDataSource_basic(t *testing.T) { resource.TestCheckResourceAttrPair(dataSourceName, "name", resourceName, "name"), resource.TestCheckResourceAttrPair(dataSourceName, "subnet_ids.#", resourceName, "subnet_ids.#"), resource.TestCheckResourceAttrPair(dataSourceName, "tags.%", resourceName, "tags.%"), + resource.TestCheckResourceAttrPair(dataSourceName, "vpc_id", resourceName, "vpc_id"), ), }, }, diff --git a/internal/service/elasticache/subnet_group_test.go b/internal/service/elasticache/subnet_group_test.go index 9e8346c91ef..b6b494b78a4 100644 --- a/internal/service/elasticache/subnet_group_test.go +++ b/internal/service/elasticache/subnet_group_test.go @@ -39,14 +39,13 @@ func TestAccElastiCacheSubnetGroup_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", "1"), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttrSet(resourceName, "vpc_id"), ), }, { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "description"}, }, }, }) @@ -100,8 +99,6 @@ func TestAccElastiCacheSubnetGroup_tags(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "description"}, }, { Config: testAccSubnetGroupConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), @@ -150,8 +147,6 @@ func TestAccElastiCacheSubnetGroup_update(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "description"}, }, { Config: testAccSubnetGroupConfig_updatePost(rName), diff --git a/internal/service/elasticache/sweep.go b/internal/service/elasticache/sweep.go index 4db4212ef35..3cbe5799fe2 100644 --- a/internal/service/elasticache/sweep.go +++ b/internal/service/elasticache/sweep.go @@ -281,38 +281,47 @@ func sweepSubnetGroups(region string) error { return fmt.Errorf("error getting client: %w", err) } conn := client.ElastiCacheConn(ctx) + input := &elasticache.DescribeCacheSubnetGroupsInput{} + sweepResources := make([]sweep.Sweepable, 0) - err = conn.DescribeCacheSubnetGroupsPagesWithContext(ctx, &elasticache.DescribeCacheSubnetGroupsInput{}, func(page *elasticache.DescribeCacheSubnetGroupsOutput, lastPage bool) bool { - if len(page.CacheSubnetGroups) == 0 { - log.Print("[DEBUG] No ElastiCache Subnet Groups to sweep") - return false + err = conn.DescribeCacheSubnetGroupsPagesWithContext(ctx, input, func(page *elasticache.DescribeCacheSubnetGroupsOutput, lastPage bool) bool { + if page == nil { + return !lastPage } - for _, subnetGroup := range page.CacheSubnetGroups { - name := aws.StringValue(subnetGroup.CacheSubnetGroupName) + for _, v := range page.CacheSubnetGroups { + name := aws.StringValue(v.CacheSubnetGroupName) if name == "default" { log.Printf("[INFO] Skipping ElastiCache Subnet Group: %s", name) continue } - log.Printf("[INFO] Deleting ElastiCache Subnet Group: %s", name) - _, err := conn.DeleteCacheSubnetGroupWithContext(ctx, &elasticache.DeleteCacheSubnetGroupInput{ - CacheSubnetGroupName: aws.String(name), - }) - if err != nil { - log.Printf("[ERROR] Failed to delete ElastiCache Subnet Group (%s): %s", name, err) - } + r := resourceSubnetGroup() + d := r.Data(nil) + d.SetId(name) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } + return !lastPage }) + + if awsv1.SkipSweepError(err) { + log.Printf("[WARN] Skipping ElastiCache Subnet Group sweep for %s: %s", region, err) + return nil + } + if err != nil { - if awsv1.SkipSweepError(err) { - log.Printf("[WARN] Skipping ElastiCache Subnet Group sweep for %s: %s", region, err) - return nil - } - return fmt.Errorf("Error retrieving ElastiCache Subnet Groups: %w", err) + return fmt.Errorf("error listing ElastiCache Subnet Groups (%s): %w", region, err) } + + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping ElastiCache Subnet Groups (%s): %w", region, err) + } + return nil } diff --git a/website/docs/d/elasticache_subnet_group.html.markdown b/website/docs/d/elasticache_subnet_group.html.markdown index 557677d09ed..87a7c9f6563 100644 --- a/website/docs/d/elasticache_subnet_group.html.markdown +++ b/website/docs/d/elasticache_subnet_group.html.markdown @@ -33,3 +33,4 @@ This data source exports the following attributes in addition to the arguments a * `description` - Description of the subnet group. * `subnet_ids` - Set of VPC Subnet ID-s of the subnet group. * `tags` - Map of tags assigned to the subnet group. +* `vpc_id` - The Amazon Virtual Private Cloud identifier (VPC ID) of the cache subnet group. diff --git a/website/docs/r/elasticache_subnet_group.html.markdown b/website/docs/r/elasticache_subnet_group.html.markdown index 4a299c1b896..86ec1c14374 100644 --- a/website/docs/r/elasticache_subnet_group.html.markdown +++ b/website/docs/r/elasticache_subnet_group.html.markdown @@ -50,10 +50,8 @@ This resource supports the following arguments: This resource exports the following attributes in addition to the arguments above: -* `description` - The Description of the ElastiCache Subnet Group. -* `name` - The Name of the ElastiCache Subnet Group. -* `subnet_ids` - The Subnet IDs of the ElastiCache Subnet Group. * `tags_all` - A 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). +* `vpc_id` - The Amazon Virtual Private Cloud identifier (VPC ID) of the cache subnet group. ## Import