diff --git a/.changelog/31602.txt b/.changelog/31602.txt new file mode 100644 index 000000000000..339f30e7902f --- /dev/null +++ b/.changelog/31602.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +data-source/aws_db_cluster_snapshot: Add `tags` argument +``` \ No newline at end of file diff --git a/internal/service/rds/cluster_snapshot.go b/internal/service/rds/cluster_snapshot.go index 3459e9d11950..f14e48abdf76 100644 --- a/internal/service/rds/cluster_snapshot.go +++ b/internal/service/rds/cluster_snapshot.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + 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 +27,7 @@ import ( const clusterSnapshotCreateTimeout = 2 * time.Minute -// @SDKResource("aws_db_cluster_snapshot", name="Cluster Snapshot") +// @SDKResource("aws_db_cluster_snapshot", name="DB Cluster Snapshot") // @Tags(identifierAttribute="db_cluster_snapshot_arn") func ResourceClusterSnapshot() *schema.Resource { return &schema.Resource{ @@ -219,38 +220,61 @@ func FindDBClusterSnapshotByID(ctx context.Context, conn *rds.RDS, id string) (* input := &rds.DescribeDBClusterSnapshotsInput{ DBClusterSnapshotIdentifier: aws.String(id), } + output, err := findDBClusterSnapshot(ctx, conn, input) - output, err := conn.DescribeDBClusterSnapshotsWithContext(ctx, input) + if err != nil { + return nil, err + } - if tfawserr.ErrCodeEquals(err, rds.ErrCodeDBClusterSnapshotNotFoundFault) { + // Eventual consistency check. + if aws.StringValue(output.DBClusterSnapshotIdentifier) != id { return nil, &retry.NotFoundError{ - LastError: err, LastRequest: input, } } + return output, nil +} + +func findDBClusterSnapshot(ctx context.Context, conn *rds.RDS, input *rds.DescribeDBClusterSnapshotsInput) (*rds.DBClusterSnapshot, error) { + output, err := findDBClusterSnapshots(ctx, conn, input, tfslices.PredicateTrue[*rds.DBClusterSnapshot]()) + if err != nil { return nil, err } - if output == nil || len(output.DBClusterSnapshots) == 0 || output.DBClusterSnapshots[0] == nil { - return nil, tfresource.NewEmptyResultError(input) - } + return tfresource.AssertSinglePtrResult(output) +} - if count := len(output.DBClusterSnapshots); count > 1 { - return nil, tfresource.NewTooManyResultsError(count, input) - } +func findDBClusterSnapshots(ctx context.Context, conn *rds.RDS, input *rds.DescribeDBClusterSnapshotsInput, filter tfslices.Predicate[*rds.DBClusterSnapshot]) ([]*rds.DBClusterSnapshot, error) { + var output []*rds.DBClusterSnapshot - dbClusterSnapshot := output.DBClusterSnapshots[0] + err := conn.DescribeDBClusterSnapshotsPagesWithContext(ctx, input, func(page *rds.DescribeDBClusterSnapshotsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } - // Eventual consistency check. - if aws.StringValue(dbClusterSnapshot.DBClusterSnapshotIdentifier) != id { + for _, v := range page.DBClusterSnapshots { + if v != nil && filter(v) { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, rds.ErrCodeDBClusterSnapshotNotFoundFault) { return nil, &retry.NotFoundError{ + LastError: err, LastRequest: input, } } - return dbClusterSnapshot, nil + if err != nil { + return nil, err + } + + return output, nil } func statusDBClusterSnapshot(ctx context.Context, conn *rds.RDS, id string) retry.StateRefreshFunc { diff --git a/internal/service/rds/cluster_snapshot_data_source.go b/internal/service/rds/cluster_snapshot_data_source.go index daf96f42a548..bf5336deadae 100644 --- a/internal/service/rds/cluster_snapshot_data_source.go +++ b/internal/service/rds/cluster_snapshot_data_source.go @@ -14,10 +14,13 @@ import ( "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" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKDataSource("aws_db_cluster_snapshot") +// @SDKDataSource("aws_db_cluster_snapshot", name="DB Cluster Snapshot") +// @Tags func DataSourceClusterSnapshot() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataSourceClusterSnapshotRead, @@ -33,18 +36,16 @@ func DataSourceClusterSnapshot() *schema.Resource { Computed: true, }, "db_cluster_identifier": { - Type: schema.TypeString, - Optional: true, - AtLeastOneOf: []string{"db_cluster_identifier", "db_cluster_snapshot_identifier"}, + Type: schema.TypeString, + Optional: true, }, "db_cluster_snapshot_arn": { Type: schema.TypeString, Computed: true, }, "db_cluster_snapshot_identifier": { - Type: schema.TypeString, - Optional: true, - AtLeastOneOf: []string{"db_cluster_identifier", "db_cluster_snapshot_identifier"}, + Type: schema.TypeString, + Optional: true, }, "engine": { Type: schema.TypeString, @@ -101,7 +102,7 @@ func DataSourceClusterSnapshot() *schema.Resource { Type: schema.TypeBool, Computed: true, }, - "tags": tftags.TagsSchemaComputed(), + names.AttrTags: tftags.TagsSchemaComputed(), "vpc_id": { Type: schema.TypeString, Computed: true, @@ -113,7 +114,6 @@ func DataSourceClusterSnapshot() *schema.Resource { func dataSourceClusterSnapshotRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).RDSConn(ctx) - ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig input := &rds.DescribeDBClusterSnapshotsInput{ IncludePublic: aws.Bool(d.Get("include_public").(bool)), @@ -132,25 +132,32 @@ func dataSourceClusterSnapshotRead(ctx context.Context, d *schema.ResourceData, input.SnapshotType = aws.String(v.(string)) } - output, err := conn.DescribeDBClusterSnapshotsWithContext(ctx, input) + f := tfslices.PredicateTrue[*rds.DBClusterSnapshot]() + if tags := getTagsIn(ctx); len(tags) > 0 { + f = func(v *rds.DBClusterSnapshot) bool { + return KeyValueTags(ctx, v.TagList).ContainsAll(KeyValueTags(ctx, tags)) + } + } + + snapshots, err := findDBClusterSnapshots(ctx, conn, input, f) if err != nil { - return sdkdiag.AppendErrorf(diags, "reading RDS Cluster Snapshots: %s", err) + return sdkdiag.AppendErrorf(diags, "reading RDS DB Cluster Snapshots: %s", err) } - if len(output.DBClusterSnapshots) < 1 { + if len(snapshots) < 1 { return sdkdiag.AppendErrorf(diags, "Your query returned no results. Please change your search criteria and try again.") } var snapshot *rds.DBClusterSnapshot - if len(output.DBClusterSnapshots) > 1 { + if len(snapshots) > 1 { if d.Get("most_recent").(bool) { - snapshot = mostRecentClusterSnapshot(output.DBClusterSnapshots) + snapshot = mostRecentClusterSnapshot(snapshots) } else { return sdkdiag.AppendErrorf(diags, "Your query returned more than one result. Please try a more specific search criteria.") } } else { - snapshot = output.DBClusterSnapshots[0] + snapshot = snapshots[0] } d.SetId(aws.StringValue(snapshot.DBClusterSnapshotIdentifier)) @@ -173,11 +180,7 @@ func dataSourceClusterSnapshotRead(ctx context.Context, d *schema.ResourceData, d.Set("storage_encrypted", snapshot.StorageEncrypted) d.Set("vpc_id", snapshot.VpcId) - tags := KeyValueTags(ctx, snapshot.TagList) - - if err := d.Set("tags", tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { - return sdkdiag.AppendErrorf(diags, "setting tags: %s", err) - } + setTagsOut(ctx, snapshot.TagList) return diags } diff --git a/internal/service/rds/cluster_snapshot_data_source_test.go b/internal/service/rds/cluster_snapshot_data_source_test.go index 5037834cfca2..003d96d11437 100644 --- a/internal/service/rds/cluster_snapshot_data_source_test.go +++ b/internal/service/rds/cluster_snapshot_data_source_test.go @@ -42,8 +42,8 @@ func TestAccRDSClusterSnapshotDataSource_dbClusterSnapshotIdentifier(t *testing. resource.TestCheckResourceAttrPair(dataSourceName, "source_db_cluster_snapshot_arn", resourceName, "source_db_cluster_snapshot_arn"), resource.TestCheckResourceAttrPair(dataSourceName, "status", resourceName, "status"), resource.TestCheckResourceAttrPair(dataSourceName, "storage_encrypted", resourceName, "storage_encrypted"), + resource.TestCheckResourceAttrPair(dataSourceName, "tags.%", resourceName, "tags.%"), resource.TestCheckResourceAttrPair(dataSourceName, "vpc_id", resourceName, "vpc_id"), - resource.TestCheckResourceAttrPair(dataSourceName, "tags", resourceName, "tags"), ), }, }, @@ -79,8 +79,8 @@ func TestAccRDSClusterSnapshotDataSource_dbClusterIdentifier(t *testing.T) { resource.TestCheckResourceAttrPair(dataSourceName, "source_db_cluster_snapshot_arn", resourceName, "source_db_cluster_snapshot_arn"), resource.TestCheckResourceAttrPair(dataSourceName, "status", resourceName, "status"), resource.TestCheckResourceAttrPair(dataSourceName, "storage_encrypted", resourceName, "storage_encrypted"), + resource.TestCheckResourceAttrPair(dataSourceName, "tags.%", resourceName, "tags.%"), resource.TestCheckResourceAttrPair(dataSourceName, "vpc_id", resourceName, "vpc_id"), - resource.TestCheckResourceAttrPair(dataSourceName, "tags", resourceName, "tags"), ), }, }, @@ -109,6 +109,31 @@ func TestAccRDSClusterSnapshotDataSource_mostRecent(t *testing.T) { }) } +func TestAccRDSClusterSnapshotDataSource_tags(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSourceName := "data.aws_db_cluster_snapshot.test" + resourceName := "aws_db_cluster_snapshot.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccClusterSnapshotDataSourceConfig_tags(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "db_cluster_identifier", resourceName, "db_cluster_identifier"), + resource.TestCheckResourceAttrPair(dataSourceName, "db_cluster_snapshot_arn", resourceName, "db_cluster_snapshot_arn"), + resource.TestCheckResourceAttrPair(dataSourceName, "db_cluster_snapshot_identifier", resourceName, "db_cluster_snapshot_identifier"), + resource.TestCheckResourceAttrPair(dataSourceName, "tags.%", resourceName, "tags.%"), + resource.TestCheckResourceAttr(dataSourceName, "tags.Name", rName), + ), + }, + }, + }) +} + func testAccClusterSnapshotDataSourceConfig_clusterSnapshotIdentifier(rName string) string { return acctest.ConfigCompose(testAccClusterSnapshotConfig_base(rName), fmt.Sprintf(` resource "aws_db_cluster_snapshot" "test" { @@ -161,3 +186,35 @@ data "aws_db_cluster_snapshot" "test" { } `, rName)) } + +func testAccClusterSnapshotDataSourceConfig_tags(rName string) string { + return acctest.ConfigCompose(testAccClusterSnapshotConfig_base(rName), fmt.Sprintf(` +resource "aws_db_cluster_snapshot" "incorrect" { + db_cluster_identifier = aws_rds_cluster.test.id + db_cluster_snapshot_identifier = "%[1]s-incorrect" + + tags = { + Name = "%[1]s-incorrect" + Test = "true" + } +} + +resource "aws_db_cluster_snapshot" "test" { + db_cluster_identifier = aws_db_cluster_snapshot.incorrect.db_cluster_identifier + db_cluster_snapshot_identifier = %[1]q + + tags = { + Name = %[1]q + Test = "true" + } +} + +data "aws_db_cluster_snapshot" "test" { + tags = { + Name = %[1]q + } + + depends_on = [aws_db_cluster_snapshot.test] +} +`, rName)) +} diff --git a/internal/service/rds/service_package_gen.go b/internal/service/rds/service_package_gen.go index f0c082acef9d..a8589234c255 100644 --- a/internal/service/rds/service_package_gen.go +++ b/internal/service/rds/service_package_gen.go @@ -34,6 +34,8 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac { Factory: DataSourceClusterSnapshot, TypeName: "aws_db_cluster_snapshot", + Name: "DB Cluster Snapshot", + Tags: &types.ServicePackageResourceTags{}, }, { Factory: DataSourceEventCategories, @@ -93,7 +95,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka { Factory: ResourceClusterSnapshot, TypeName: "aws_db_cluster_snapshot", - Name: "Cluster Snapshot", + Name: "DB Cluster Snapshot", Tags: &types.ServicePackageResourceTags{ IdentifierAttribute: "db_cluster_snapshot_arn", }, diff --git a/internal/service/rds/snapshot_data_source.go b/internal/service/rds/snapshot_data_source.go index 627ad5d31424..08b0f03d75ba 100644 --- a/internal/service/rds/snapshot_data_source.go +++ b/internal/service/rds/snapshot_data_source.go @@ -14,8 +14,9 @@ import ( "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/slices" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/names" ) // @SDKDataSource("aws_db_snapshot", name="DB Snapshot") @@ -116,7 +117,7 @@ func DataSourceSnapshot() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "tags": tftags.TagsSchemaComputed(), + names.AttrTags: tftags.TagsSchemaComputed(), "vpc_id": { Type: schema.TypeString, Computed: true, @@ -146,7 +147,7 @@ func dataSourceSnapshotRead(ctx context.Context, d *schema.ResourceData, meta in input.SnapshotType = aws.String(v.(string)) } - f := slices.PredicateTrue[*rds.DBSnapshot]() + f := tfslices.PredicateTrue[*rds.DBSnapshot]() if tags := getTagsIn(ctx); len(tags) > 0 { f = func(v *rds.DBSnapshot) bool { return KeyValueTags(ctx, v.TagList).ContainsAll(KeyValueTags(ctx, tags)) diff --git a/website/docs/d/db_cluster_snapshot.html.markdown b/website/docs/d/db_cluster_snapshot.html.markdown index cf2fe30db1e0..142f9688f013 100644 --- a/website/docs/d/db_cluster_snapshot.html.markdown +++ b/website/docs/d/db_cluster_snapshot.html.markdown @@ -45,21 +45,18 @@ resource "aws_rds_cluster_instance" "aurora" { This data source supports the following arguments: * `most_recent` - (Optional) If more than one result is returned, use the most recent Snapshot. - * `db_cluster_identifier` - (Optional) Returns the list of snapshots created by the specific db_cluster - * `db_cluster_snapshot_identifier` - (Optional) Returns information on a specific snapshot_id. - * `snapshot_type` - (Optional) Type of snapshots to be returned. If you don't specify a SnapshotType value, then both automated and manual DB cluster snapshots are returned. Shared and public DB Cluster Snapshots are not included in the returned results by default. Possible values are, `automated`, `manual`, `shared`, `public` and `awsbackup`. - * `include_shared` - (Optional) Set this value to true to include shared manual DB Cluster Snapshots from other AWS accounts that this AWS account has been given permission to copy or restore, otherwise set this value to false. The default is `false`. - * `include_public` - (Optional) Set this value to true to include manual DB Cluster Snapshots that are public and can be copied or restored by any AWS account, otherwise set this value to false. The default is `false`. +* `tags` - (Optional) Mapping of tags, each pair of which must exactly match + a pair on the desired DB cluster snapshot. ## Attribute Reference