diff --git a/.changelog/38483.txt b/.changelog/38483.txt new file mode 100644 index 00000000000..c668ede54c8 --- /dev/null +++ b/.changelog/38483.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_cur_report_definition: Add `tags` argument and `tags_all` attribute +``` + +```release-note:enhancement +data-source/aws_cur_report_definition: Add `tags` attribute +``` \ No newline at end of file diff --git a/internal/service/cur/cur_test.go b/internal/service/cur/cur_test.go index 93829a01e2a..397ca706e51 100644 --- a/internal/service/cur/cur_test.go +++ b/internal/service/cur/cur_test.go @@ -17,6 +17,7 @@ func TestAccCUR_serial(t *testing.T) { testCases := map[string]map[string]func(t *testing.T){ "ReportDefinition": { acctest.CtBasic: testAccReportDefinition_basic, + "tags": testAccReportDefinition_tags, acctest.CtDisappears: testAccReportDefinition_disappears, "textOrCsv": testAccReportDefinition_textOrCSV, "parquet": testAccReportDefinition_parquet, diff --git a/internal/service/cur/generate.go b/internal/service/cur/generate.go index 30d172b6f9e..83ffbde03ec 100644 --- a/internal/service/cur/generate.go +++ b/internal/service/cur/generate.go @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MPL-2.0 //go:generate go run ../../generate/servicepackage/main.go +//go:generate go run ../../generate/tags/main.go -AWSSDKVersion=2 -ServiceTagsSlice -ListTags -ListTagsInIDElem=ReportName -UpdateTags -TagInIDElem=ReportName -KVTValues // ONLY generate directives and package declaration! Do not add anything else to this file. package cur diff --git a/internal/service/cur/report_definition.go b/internal/service/cur/report_definition.go index 6da276f2fd0..32c670384e3 100644 --- a/internal/service/cur/report_definition.go +++ b/internal/service/cur/report_definition.go @@ -23,11 +23,14 @@ import ( "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" "github.com/hashicorp/terraform-provider-aws/names" ) // @SDKResource("aws_cur_report_definition", name="Report Definition") +// @Tags(identifierAttribute="report_name") func resourceReportDefinition() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceReportDefinitionCreate, @@ -111,7 +114,11 @@ func resourceReportDefinition() *schema.Resource { ForceNew: true, ValidateDiagFunc: enum.Validate[types.TimeUnit](), }, + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), }, + + CustomizeDiff: verify.SetTagsDiff, } } @@ -150,6 +157,7 @@ func resourceReportDefinitionCreate(ctx context.Context, d *schema.ResourceData, S3Region: types.AWSRegion(d.Get("s3_region").(string)), TimeUnit: types.TimeUnit(d.Get("time_unit").(string)), }, + Tags: getTagsIn(ctx), } _, err := conn.PutReportDefinition(ctx, input) @@ -208,43 +216,45 @@ func resourceReportDefinitionUpdate(ctx context.Context, d *schema.ResourceData, var diags diag.Diagnostics conn := meta.(*conns.AWSClient).CURClient(ctx) - additionalArtifacts := flex.ExpandStringyValueSet[types.AdditionalArtifact](d.Get("additional_artifacts").(*schema.Set)) - compression := types.CompressionFormat(d.Get("compression").(string)) - format := types.ReportFormat(d.Get(names.AttrFormat).(string)) - prefix := d.Get("s3_prefix").(string) - reportVersioning := types.ReportVersioning(d.Get("report_versioning").(string)) - - if err := checkReportDefinitionPropertyCombination( - additionalArtifacts, - compression, - format, - prefix, - reportVersioning, - ); err != nil { - return sdkdiag.AppendFromErr(diags, err) - } + if d.HasChangesExcept(names.AttrTags, names.AttrTagsAll) { + additionalArtifacts := flex.ExpandStringyValueSet[types.AdditionalArtifact](d.Get("additional_artifacts").(*schema.Set)) + compression := types.CompressionFormat(d.Get("compression").(string)) + format := types.ReportFormat(d.Get(names.AttrFormat).(string)) + prefix := d.Get("s3_prefix").(string) + reportVersioning := types.ReportVersioning(d.Get("report_versioning").(string)) + + if err := checkReportDefinitionPropertyCombination( + additionalArtifacts, + compression, + format, + prefix, + reportVersioning, + ); err != nil { + return sdkdiag.AppendFromErr(diags, err) + } - input := &cur.ModifyReportDefinitionInput{ - ReportDefinition: &types.ReportDefinition{ - AdditionalArtifacts: additionalArtifacts, - AdditionalSchemaElements: flex.ExpandStringyValueSet[types.SchemaElement](d.Get("additional_schema_elements").(*schema.Set)), - Compression: compression, - Format: format, - RefreshClosedReports: aws.Bool(d.Get("refresh_closed_reports").(bool)), - ReportName: aws.String(d.Id()), - ReportVersioning: reportVersioning, - S3Bucket: aws.String(d.Get(names.AttrS3Bucket).(string)), - S3Prefix: aws.String(prefix), - S3Region: types.AWSRegion(d.Get("s3_region").(string)), - TimeUnit: types.TimeUnit(d.Get("time_unit").(string)), - }, - ReportName: aws.String(d.Id()), - } + input := &cur.ModifyReportDefinitionInput{ + ReportDefinition: &types.ReportDefinition{ + AdditionalArtifacts: additionalArtifacts, + AdditionalSchemaElements: flex.ExpandStringyValueSet[types.SchemaElement](d.Get("additional_schema_elements").(*schema.Set)), + Compression: compression, + Format: format, + RefreshClosedReports: aws.Bool(d.Get("refresh_closed_reports").(bool)), + ReportName: aws.String(d.Id()), + ReportVersioning: reportVersioning, + S3Bucket: aws.String(d.Get(names.AttrS3Bucket).(string)), + S3Prefix: aws.String(prefix), + S3Region: types.AWSRegion(d.Get("s3_region").(string)), + TimeUnit: types.TimeUnit(d.Get("time_unit").(string)), + }, + ReportName: aws.String(d.Id()), + } - _, err := conn.ModifyReportDefinition(ctx, input) + _, err := conn.ModifyReportDefinition(ctx, input) - if err != nil { - return sdkdiag.AppendErrorf(diags, "updating Cost And Usage Report Definition (%s): %s", d.Id(), err) + if err != nil { + return sdkdiag.AppendErrorf(diags, "updating Cost And Usage Report Definition (%s): %s", d.Id(), err) + } } return append(diags, resourceReportDefinitionRead(ctx, d, meta)...) diff --git a/internal/service/cur/report_definition_data_source.go b/internal/service/cur/report_definition_data_source.go index 6fa5dd43655..453b2df2ef5 100644 --- a/internal/service/cur/report_definition_data_source.go +++ b/internal/service/cur/report_definition_data_source.go @@ -11,10 +11,12 @@ 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" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/names" ) // @SDKDataSource("aws_cur_report_definition", name="Report Definition") +// @Tags(identifierAttribute="report_name") func dataSourceReportDefinition() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataSourceReportDefinitionRead, @@ -62,6 +64,7 @@ func dataSourceReportDefinition() *schema.Resource { Type: schema.TypeString, Computed: true, }, + names.AttrTags: tftags.TagsSchemaComputed(), "time_unit": { Type: schema.TypeString, Computed: true, diff --git a/internal/service/cur/report_definition_data_source_test.go b/internal/service/cur/report_definition_data_source_test.go index 9afbb874689..49838a672be 100644 --- a/internal/service/cur/report_definition_data_source_test.go +++ b/internal/service/cur/report_definition_data_source_test.go @@ -16,7 +16,7 @@ import ( func testAccReportDefinitionDataSource_basic(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_cur_report_definition.test" - datasourceName := "data.aws_cur_report_definition.test" + dataSourceName := "data.aws_cur_report_definition.test" reportName := sdkacctest.RandomWithPrefix("tf_acc_test") bucketName := fmt.Sprintf("tf-test-bucket-%d", sdkacctest.RandInt()) @@ -29,14 +29,16 @@ func testAccReportDefinitionDataSource_basic(t *testing.T) { { Config: testAccReportDefinitionDataSourceConfig_basic(reportName, bucketName), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrPair(datasourceName, "report_name", resourceName, "report_name"), - resource.TestCheckResourceAttrPair(datasourceName, "time_unit", resourceName, "time_unit"), - resource.TestCheckResourceAttrPair(datasourceName, "compression", resourceName, "compression"), - resource.TestCheckResourceAttrPair(datasourceName, "additional_schema_elements.#", resourceName, "additional_schema_elements.#"), - resource.TestCheckResourceAttrPair(datasourceName, names.AttrS3Bucket, resourceName, names.AttrS3Bucket), - resource.TestCheckResourceAttrPair(datasourceName, "s3_prefix", resourceName, "s3_prefix"), - resource.TestCheckResourceAttrPair(datasourceName, "s3_region", resourceName, "s3_region"), - resource.TestCheckResourceAttrPair(datasourceName, "additional_artifacts.#", resourceName, "additional_artifacts.#"), + resource.TestCheckResourceAttrPair(dataSourceName, "report_name", resourceName, "report_name"), + resource.TestCheckResourceAttrPair(dataSourceName, "time_unit", resourceName, "time_unit"), + resource.TestCheckResourceAttrPair(dataSourceName, "compression", resourceName, "compression"), + resource.TestCheckResourceAttrPair(dataSourceName, "additional_schema_elements.#", resourceName, "additional_schema_elements.#"), + resource.TestCheckResourceAttrPair(dataSourceName, names.AttrS3Bucket, resourceName, names.AttrS3Bucket), + resource.TestCheckResourceAttrPair(dataSourceName, "s3_prefix", resourceName, "s3_prefix"), + resource.TestCheckResourceAttrPair(dataSourceName, "s3_region", resourceName, "s3_region"), + resource.TestCheckResourceAttrPair(dataSourceName, "additional_artifacts.#", resourceName, "additional_artifacts.#"), + resource.TestCheckResourceAttrPair(dataSourceName, acctest.CtTagsPercent, resourceName, acctest.CtTagsPercent), + resource.TestCheckResourceAttrPair(dataSourceName, acctest.CtTagsKey1, resourceName, acctest.CtTagsKey1), ), }, }, @@ -46,7 +48,7 @@ func testAccReportDefinitionDataSource_basic(t *testing.T) { func testAccReportDefinitionDataSource_additional(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_cur_report_definition.test" - datasourceName := "data.aws_cur_report_definition.test" + dataSourceName := "data.aws_cur_report_definition.test" reportName := sdkacctest.RandomWithPrefix("tf_acc_test") bucketName := fmt.Sprintf("tf-test-bucket-%d", sdkacctest.RandInt()) @@ -59,16 +61,19 @@ func testAccReportDefinitionDataSource_additional(t *testing.T) { { Config: testAccReportDefinitionDataSourceConfig_additional(reportName, bucketName), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrPair(datasourceName, "report_name", resourceName, "report_name"), - resource.TestCheckResourceAttrPair(datasourceName, "time_unit", resourceName, "time_unit"), - resource.TestCheckResourceAttrPair(datasourceName, "compression", resourceName, "compression"), - resource.TestCheckResourceAttrPair(datasourceName, "additional_schema_elements.#", resourceName, "additional_schema_elements.#"), - resource.TestCheckResourceAttrPair(datasourceName, names.AttrS3Bucket, resourceName, names.AttrS3Bucket), - resource.TestCheckResourceAttrPair(datasourceName, "s3_prefix", resourceName, "s3_prefix"), - resource.TestCheckResourceAttrPair(datasourceName, "s3_region", resourceName, "s3_region"), - resource.TestCheckResourceAttrPair(datasourceName, "additional_artifacts.#", resourceName, "additional_artifacts.#"), - resource.TestCheckResourceAttrPair(datasourceName, "refresh_closed_reports", resourceName, "refresh_closed_reports"), - resource.TestCheckResourceAttrPair(datasourceName, "report_versioning", resourceName, "report_versioning"), + resource.TestCheckResourceAttrPair(dataSourceName, "report_name", resourceName, "report_name"), + resource.TestCheckResourceAttrPair(dataSourceName, "time_unit", resourceName, "time_unit"), + resource.TestCheckResourceAttrPair(dataSourceName, "compression", resourceName, "compression"), + resource.TestCheckResourceAttrPair(dataSourceName, "additional_schema_elements.#", resourceName, "additional_schema_elements.#"), + resource.TestCheckResourceAttrPair(dataSourceName, names.AttrS3Bucket, resourceName, names.AttrS3Bucket), + resource.TestCheckResourceAttrPair(dataSourceName, "s3_prefix", resourceName, "s3_prefix"), + resource.TestCheckResourceAttrPair(dataSourceName, "s3_region", resourceName, "s3_region"), + resource.TestCheckResourceAttrPair(dataSourceName, "additional_artifacts.#", resourceName, "additional_artifacts.#"), + resource.TestCheckResourceAttrPair(dataSourceName, "refresh_closed_reports", resourceName, "refresh_closed_reports"), + resource.TestCheckResourceAttrPair(dataSourceName, "report_versioning", resourceName, "report_versioning"), + resource.TestCheckResourceAttrPair(dataSourceName, acctest.CtTagsPercent, resourceName, acctest.CtTagsPercent), + resource.TestCheckResourceAttrPair(dataSourceName, acctest.CtTagsKey1, resourceName, acctest.CtTagsKey1), + resource.TestCheckResourceAttrPair(dataSourceName, acctest.CtTagsKey2, resourceName, acctest.CtTagsKey2), ), }, }, @@ -132,6 +137,9 @@ resource "aws_cur_report_definition" "test" { s3_prefix = "" s3_region = aws_s3_bucket.test.region additional_artifacts = ["REDSHIFT", "QUICKSIGHT"] + tags = { + key1 = "value1" + } } data "aws_cur_report_definition" "test" { @@ -199,6 +207,10 @@ resource "aws_cur_report_definition" "test" { additional_artifacts = ["REDSHIFT", "QUICKSIGHT"] refresh_closed_reports = true report_versioning = "CREATE_NEW_REPORT" + tags = { + key1 = "value1" + key2 = "value2" + } } data "aws_cur_report_definition" "test" { diff --git a/internal/service/cur/report_definition_test.go b/internal/service/cur/report_definition_test.go index 9182d985113..464ddf9c393 100644 --- a/internal/service/cur/report_definition_test.go +++ b/internal/service/cur/report_definition_test.go @@ -74,6 +74,56 @@ func testAccReportDefinition_basic(t *testing.T) { }) } +func testAccReportDefinition_tags(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_cur_report_definition.test" + rName := sdkacctest.RandomWithPrefix("tf_acc_test") + s3BucketName := fmt.Sprintf("tf-test-bucket-%d", sdkacctest.RandInt()) + s3Prefix := "test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.DocDBServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckReportDefinitionDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccReportDefinitionConfig_tags1(rName, s3BucketName, s3Prefix, acctest.CtKey1, acctest.CtValue1), + Check: resource.ComposeTestCheckFunc( + testAccCheckReportDefinitionExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + names.AttrApplyImmediately, + }, + }, + { + Config: testAccReportDefinitionConfig_tags2(rName, s3BucketName, s3Prefix, acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckReportDefinitionExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct2), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1Updated), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + { + Config: testAccReportDefinitionConfig_tags1(rName, s3BucketName, s3Prefix, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckReportDefinitionExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + }, + }) +} + func testAccReportDefinition_textOrCSV(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_cur_report_definition.test" @@ -490,6 +540,133 @@ resource "aws_cur_report_definition" "test" { `, reportName, bucketName, bucketPrefix, format, compression, artifactsStr, refreshClosedReports, reportVersioning) } +func testAccReportDefinitionConfig_tags1(reportName, s3BucketName, s3Prefix, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "test" { + bucket = %[2]q + force_destroy = true +} + +data "aws_partition" "current" {} + +resource "aws_s3_bucket_policy" "test" { + bucket = aws_s3_bucket.test.id + + policy = < 0 { + return tags + } + } + + return nil +} + +// setTagsOut sets cur service tags in Context. +func setTagsOut(ctx context.Context, tags []awstypes.Tag) { + if inContext, ok := tftags.FromContext(ctx); ok { + inContext.TagsOut = option.Some(KeyValueTags(ctx, tags)) + } +} + +// updateTags updates cur service tags. +// The identifier is typically the Amazon Resource Name (ARN), although +// it may also be a different identifier depending on the service. +func updateTags(ctx context.Context, conn *costandusagereportservice.Client, identifier string, oldTagsMap, newTagsMap any, optFns ...func(*costandusagereportservice.Options)) error { + oldTags := tftags.New(ctx, oldTagsMap) + newTags := tftags.New(ctx, newTagsMap) + + ctx = tflog.SetField(ctx, logging.KeyResourceId, identifier) + + removedTags := oldTags.Removed(newTags) + removedTags = removedTags.IgnoreSystem(names.CUR) + if len(removedTags) > 0 { + input := &costandusagereportservice.UntagResourceInput{ + ReportName: aws.String(identifier), + TagKeys: removedTags.Keys(), + } + + _, err := conn.UntagResource(ctx, input, optFns...) + + if err != nil { + return fmt.Errorf("untagging resource (%s): %w", identifier, err) + } + } + + updatedTags := oldTags.Updated(newTags) + updatedTags = updatedTags.IgnoreSystem(names.CUR) + if len(updatedTags) > 0 { + input := &costandusagereportservice.TagResourceInput{ + ReportName: aws.String(identifier), + Tags: Tags(updatedTags), + } + + _, err := conn.TagResource(ctx, input, optFns...) + + if err != nil { + return fmt.Errorf("tagging resource (%s): %w", identifier, err) + } + } + + return nil +} + +// UpdateTags updates cur service tags. +// It is called from outside this package. +func (p *servicePackage) UpdateTags(ctx context.Context, meta any, identifier string, oldTags, newTags any) error { + return updateTags(ctx, meta.(*conns.AWSClient).CURClient(ctx), identifier, oldTags, newTags) +} diff --git a/website/docs/d/cur_report_definition.html.markdown b/website/docs/d/cur_report_definition.html.markdown index ff396611b79..a7023e2e936 100644 --- a/website/docs/d/cur_report_definition.html.markdown +++ b/website/docs/d/cur_report_definition.html.markdown @@ -42,3 +42,4 @@ This data source exports the following attributes in addition to the arguments a * `additional_artifacts` - A list of additional artifacts. * `refresh_closed_reports` - If true reports are updated after they have been finalized. * `report_versioning` - Overwrite the previous version of each report or to deliver the report in addition to the previous versions. +* `tags` - Map of key-value pairs assigned to the resource. diff --git a/website/docs/r/cur_report_definition.html.markdown b/website/docs/r/cur_report_definition.html.markdown index 3499a69b548..0c17b1211cb 100644 --- a/website/docs/r/cur_report_definition.html.markdown +++ b/website/docs/r/cur_report_definition.html.markdown @@ -42,12 +42,14 @@ This resource supports the following arguments: * `additional_artifacts` - (Required) A list of additional artifacts. Valid values are: `REDSHIFT`, `QUICKSIGHT`, `ATHENA`. When ATHENA exists within additional_artifacts, no other artifact type can be declared and report_versioning must be `OVERWRITE_REPORT`. * `refresh_closed_reports` - (Optional) Set to true to update your reports after they have been finalized if AWS detects charges related to previous months. * `report_versioning` - (Optional) Overwrite the previous version of each report or to deliver the report in addition to the previous versions. Valid values are: `CREATE_NEW_REPORT` and `OVERWRITE_REPORT`. +* `tags` - (Optional) Key-value pairs of resource tags to assign to the DataSync Location. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. ## Attribute Reference This resource exports the following attributes in addition to the arguments above: * `arn` - The Amazon Resource Name (ARN) specifying the cur report. +* `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). ## Import