diff --git a/.changelog/36773.txt b/.changelog/36773.txt new file mode 100644 index 00000000000..1a5edf2f62e --- /dev/null +++ b/.changelog/36773.txt @@ -0,0 +1,7 @@ +```release-note:bug +resource/aws_ce_anomaly_monitor: Change `monitor_dimension` to [ForceNew](https://developer.hashicorp.com/terraform/plugin/sdkv2/schemas/schema-behaviors#forcenew) +``` + +```release-note:bug +resource/aws_ce_anomaly_subscription: Change `account_id` to [ForceNew](https://developer.hashicorp.com/terraform/plugin/sdkv2/schemas/schema-behaviors#forcenew) +``` \ No newline at end of file diff --git a/go.mod b/go.mod index ed170e05242..8f616db292f 100644 --- a/go.mod +++ b/go.mod @@ -62,6 +62,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/connectcases v1.15.4 github.com/aws/aws-sdk-go-v2/service/controltower v1.13.4 github.com/aws/aws-sdk-go-v2/service/costandusagereportservice v1.23.4 + github.com/aws/aws-sdk-go-v2/service/costexplorer v1.37.1 github.com/aws/aws-sdk-go-v2/service/costoptimizationhub v1.4.4 github.com/aws/aws-sdk-go-v2/service/customerprofiles v1.36.4 github.com/aws/aws-sdk-go-v2/service/datasync v1.36.4 diff --git a/go.sum b/go.sum index 7e61f0a4171..4d5c1cd98b2 100644 --- a/go.sum +++ b/go.sum @@ -144,6 +144,8 @@ github.com/aws/aws-sdk-go-v2/service/controltower v1.13.4 h1:vUHPzud4ZEbPNCBxRsr github.com/aws/aws-sdk-go-v2/service/controltower v1.13.4/go.mod h1:qwJIgEG0ASp7utTqwiagEW0LOE6AFsNzQL1oOWRsydU= github.com/aws/aws-sdk-go-v2/service/costandusagereportservice v1.23.4 h1:MDEvMVWZZetTacemmH+Z7ScvQkOKxqkEQfFROo//G18= github.com/aws/aws-sdk-go-v2/service/costandusagereportservice v1.23.4/go.mod h1:DSbQUgLN9rHicCWE2Wuu7yRNHT3oQvnOiPk3LAGZe9I= +github.com/aws/aws-sdk-go-v2/service/costexplorer v1.37.1 h1:xjhk+io+kPtDOG5RizvHlkGKET3dxRBzorLdPPkpZQc= +github.com/aws/aws-sdk-go-v2/service/costexplorer v1.37.1/go.mod h1:uLOg0o57AyQQhZGtUKIlcBJOKE53mO9bXKyrM9dFhy4= github.com/aws/aws-sdk-go-v2/service/costoptimizationhub v1.4.4 h1:gSO6kMlH4cXxBmZwTA1qngTVxt8Och7irFtNGrxIUEg= github.com/aws/aws-sdk-go-v2/service/costoptimizationhub v1.4.4/go.mod h1:UkyRWEyu3iT7oPmPri8xwPnKXqJQzSUDK9MOKq7xyZE= github.com/aws/aws-sdk-go-v2/service/customerprofiles v1.36.4 h1:UBo3t3uliQIP3f8duZhmJ1Z62bz/j5o7LH8f/BTt1mU= diff --git a/internal/conns/awsclient_gen.go b/internal/conns/awsclient_gen.go index e91f6e9e932..5214e62945a 100644 --- a/internal/conns/awsclient_gen.go +++ b/internal/conns/awsclient_gen.go @@ -54,6 +54,7 @@ import ( connectcases_sdkv2 "github.com/aws/aws-sdk-go-v2/service/connectcases" controltower_sdkv2 "github.com/aws/aws-sdk-go-v2/service/controltower" costandusagereportservice_sdkv2 "github.com/aws/aws-sdk-go-v2/service/costandusagereportservice" + costexplorer_sdkv2 "github.com/aws/aws-sdk-go-v2/service/costexplorer" costoptimizationhub_sdkv2 "github.com/aws/aws-sdk-go-v2/service/costoptimizationhub" customerprofiles_sdkv2 "github.com/aws/aws-sdk-go-v2/service/customerprofiles" datasync_sdkv2 "github.com/aws/aws-sdk-go-v2/service/datasync" @@ -171,7 +172,6 @@ import ( cloudwatchrum_sdkv1 "github.com/aws/aws-sdk-go/service/cloudwatchrum" cognitoidentityprovider_sdkv1 "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" connect_sdkv1 "github.com/aws/aws-sdk-go/service/connect" - costexplorer_sdkv1 "github.com/aws/aws-sdk-go/service/costexplorer" databasemigrationservice_sdkv1 "github.com/aws/aws-sdk-go/service/databasemigrationservice" dataexchange_sdkv1 "github.com/aws/aws-sdk-go/service/dataexchange" datapipeline_sdkv1 "github.com/aws/aws-sdk-go/service/datapipeline" @@ -370,8 +370,8 @@ func (c *AWSClient) BudgetsClient(ctx context.Context) *budgets_sdkv2.Client { return errs.Must(client[*budgets_sdkv2.Client](ctx, c, names.Budgets, make(map[string]any))) } -func (c *AWSClient) CEConn(ctx context.Context) *costexplorer_sdkv1.CostExplorer { - return errs.Must(conn[*costexplorer_sdkv1.CostExplorer](ctx, c, names.CE, make(map[string]any))) +func (c *AWSClient) CEClient(ctx context.Context) *costexplorer_sdkv2.Client { + return errs.Must(client[*costexplorer_sdkv2.Client](ctx, c, names.CE, make(map[string]any))) } func (c *AWSClient) CURClient(ctx context.Context) *costandusagereportservice_sdkv2.Client { diff --git a/internal/sdkv2/schema.go b/internal/sdkv2/schema.go new file mode 100644 index 00000000000..5f87af00148 --- /dev/null +++ b/internal/sdkv2/schema.go @@ -0,0 +1,63 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package sdkv2 + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// Adapted from https://github.com/hashicorp/terraform-provider-google/google/datasource_helpers.go. Thanks! + +// DataSourcePropertyFromResourceProperty is a recursive func that +// converts an existing Resource property schema to a Datasource property schema. +// All schema elements are copied, but certain attributes are ignored or changed: +// - all attributes have Computed = true +// - all attributes have ForceNew, Required = false +// - Validation funcs and attributes (e.g. MaxItems) are not copied +func DataSourcePropertyFromResourceProperty(rs *schema.Schema) *schema.Schema { + ds := &schema.Schema{ + Computed: true, + Description: rs.Description, + Type: rs.Type, + } + + switch rs.Type { + case schema.TypeSet: + ds.Set = rs.Set + fallthrough + case schema.TypeList, schema.TypeMap: + // List & Set types are generally used for 2 cases: + // - a list/set of simple primitive values (e.g. list of strings) + // - a sub resource + // Maps are usually used for maps of simple primitives + switch elem := rs.Elem.(type) { + case *schema.Resource: + // handle the case where the Element is a sub-resource + ds.Elem = DataSourceElemFromResourceElem(elem) + case *schema.Schema: + // handle simple primitive case + ds.Elem = &schema.Schema{Type: elem.Type} + } + } + + return ds +} + +func DataSourceElemFromResourceElem(rs *schema.Resource) *schema.Resource { + ds := &schema.Resource{ + Schema: DataSourceSchemaFromResourceSchema(rs.Schema), + } + + return ds +} + +func DataSourceSchemaFromResourceSchema(rs map[string]*schema.Schema) map[string]*schema.Schema { + ds := make(map[string]*schema.Schema, len(rs)) + + for k, v := range rs { + ds[k] = DataSourcePropertyFromResourceProperty(v) + } + + return ds +} diff --git a/internal/service/appmesh/gateway_route_data_source.go b/internal/service/appmesh/gateway_route_data_source.go index 77b66b00fc0..dc669bf116b 100644 --- a/internal/service/appmesh/gateway_route_data_source.go +++ b/internal/service/appmesh/gateway_route_data_source.go @@ -12,6 +12,7 @@ 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/sdkv2" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -51,7 +52,7 @@ func DataSourceGatewayRoute() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "spec": dataSourcePropertyFromResourceProperty(resourceGatewayRouteSpecSchema()), + "spec": sdkv2.DataSourcePropertyFromResourceProperty(resourceGatewayRouteSpecSchema()), names.AttrTags: tftags.TagsSchemaComputed(), "virtual_gateway_name": { Type: schema.TypeString, diff --git a/internal/service/appmesh/mesh.go b/internal/service/appmesh/mesh.go index 043d4cfedb2..8c979d93691 100644 --- a/internal/service/appmesh/mesh.go +++ b/internal/service/appmesh/mesh.go @@ -241,53 +241,3 @@ func findMesh(ctx context.Context, conn *appmesh.AppMesh, input *appmesh.Describ return output.Mesh, nil } - -// Adapted from https://github.com/hashicorp/terraform-provider-google/google/datasource_helpers.go. Thanks! -// TODO Move to a shared package. - -// dataSourceSchemaFromResourceSchema is a recursive func that -// converts an existing Resource schema to a Datasource schema. -// All schema elements are copied, but certain attributes are ignored or changed: -// - all attributes have Computed = true -// - all attributes have ForceNew, Required = false -// - Validation funcs and attributes (e.g. MaxItems) are not copied -func dataSourceSchemaFromResourceSchema(rs map[string]*schema.Schema) map[string]*schema.Schema { - ds := make(map[string]*schema.Schema, len(rs)) - - for k, v := range rs { - ds[k] = dataSourcePropertyFromResourceProperty(v) - } - - return ds -} - -func dataSourcePropertyFromResourceProperty(rs *schema.Schema) *schema.Schema { - ds := &schema.Schema{ - Computed: true, - Description: rs.Description, - Type: rs.Type, - } - - switch rs.Type { - case schema.TypeSet: - ds.Set = rs.Set - fallthrough - case schema.TypeList, schema.TypeMap: - // List & Set types are generally used for 2 cases: - // - a list/set of simple primitive values (e.g. list of strings) - // - a sub resource - // Maps are usually used for maps of simple primitives - switch elem := rs.Elem.(type) { - case *schema.Resource: - // handle the case where the Element is a sub-resource - ds.Elem = &schema.Resource{ - Schema: dataSourceSchemaFromResourceSchema(elem.Schema), - } - case *schema.Schema: - // handle simple primitive case - ds.Elem = &schema.Schema{Type: elem.Type} - } - } - - return ds -} diff --git a/internal/service/appmesh/mesh_data_source.go b/internal/service/appmesh/mesh_data_source.go index ccd8bb95f23..327d81654aa 100644 --- a/internal/service/appmesh/mesh_data_source.go +++ b/internal/service/appmesh/mesh_data_source.go @@ -12,6 +12,7 @@ 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/sdkv2" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -47,7 +48,7 @@ func DataSourceMesh() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "spec": dataSourcePropertyFromResourceProperty(resourceMeshSpecSchema()), + "spec": sdkv2.DataSourcePropertyFromResourceProperty(resourceMeshSpecSchema()), names.AttrTags: tftags.TagsSchemaComputed(), }, } diff --git a/internal/service/appmesh/route_data_source.go b/internal/service/appmesh/route_data_source.go index f8eee5ef00a..2fdf20de724 100644 --- a/internal/service/appmesh/route_data_source.go +++ b/internal/service/appmesh/route_data_source.go @@ -12,6 +12,7 @@ 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/sdkv2" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -51,7 +52,7 @@ func DataSourceRoute() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "spec": dataSourcePropertyFromResourceProperty(resourceRouteSpecSchema()), + "spec": sdkv2.DataSourcePropertyFromResourceProperty(resourceRouteSpecSchema()), names.AttrTags: tftags.TagsSchemaComputed(), "virtual_router_name": { Type: schema.TypeString, diff --git a/internal/service/appmesh/virtual_gateway_data_source.go b/internal/service/appmesh/virtual_gateway_data_source.go index 4480731d5f4..dabbba3f3ea 100644 --- a/internal/service/appmesh/virtual_gateway_data_source.go +++ b/internal/service/appmesh/virtual_gateway_data_source.go @@ -12,6 +12,7 @@ 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/sdkv2" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -50,7 +51,7 @@ func DataSourceVirtualGateway() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "spec": dataSourcePropertyFromResourceProperty(resourceVirtualGatewaySpecSchema()), + "spec": sdkv2.DataSourcePropertyFromResourceProperty(resourceVirtualGatewaySpecSchema()), names.AttrTags: tftags.TagsSchemaComputed(), }, } diff --git a/internal/service/appmesh/virtual_node_data_source.go b/internal/service/appmesh/virtual_node_data_source.go index e9b439e79bc..a952152f53a 100644 --- a/internal/service/appmesh/virtual_node_data_source.go +++ b/internal/service/appmesh/virtual_node_data_source.go @@ -12,6 +12,7 @@ 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/sdkv2" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -51,7 +52,7 @@ func DataSourceVirtualNode() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "spec": dataSourcePropertyFromResourceProperty(resourceVirtualNodeSpecSchema()), + "spec": sdkv2.DataSourcePropertyFromResourceProperty(resourceVirtualNodeSpecSchema()), names.AttrTags: tftags.TagsSchemaComputed(), }, } diff --git a/internal/service/appmesh/virtual_router_data_source.go b/internal/service/appmesh/virtual_router_data_source.go index 9bc091b45b3..5ff6958bbe1 100644 --- a/internal/service/appmesh/virtual_router_data_source.go +++ b/internal/service/appmesh/virtual_router_data_source.go @@ -12,6 +12,7 @@ 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/sdkv2" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -51,7 +52,7 @@ func DataSourceVirtualRouter() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "spec": dataSourcePropertyFromResourceProperty(resourceVirtualRouterSpecSchema()), + "spec": sdkv2.DataSourcePropertyFromResourceProperty(resourceVirtualRouterSpecSchema()), names.AttrTags: tftags.TagsSchemaComputed(), }, } diff --git a/internal/service/appmesh/virtual_service_data_source.go b/internal/service/appmesh/virtual_service_data_source.go index 54916c0ac05..c20e0f09aef 100644 --- a/internal/service/appmesh/virtual_service_data_source.go +++ b/internal/service/appmesh/virtual_service_data_source.go @@ -12,6 +12,7 @@ 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/sdkv2" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -51,7 +52,7 @@ func DataSourceVirtualService() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "spec": dataSourcePropertyFromResourceProperty(resourceVirtualServiceSpecSchema()), + "spec": sdkv2.DataSourcePropertyFromResourceProperty(resourceVirtualServiceSpecSchema()), names.AttrTags: tftags.TagsSchemaComputed(), }, } diff --git a/internal/service/ce/anomaly_monitor.go b/internal/service/ce/anomaly_monitor.go index 97650f19934..9212c8b5949 100644 --- a/internal/service/ce/anomaly_monitor.go +++ b/internal/service/ce/anomaly_monitor.go @@ -6,17 +6,20 @@ package ce import ( "context" "encoding/json" + "log" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/costexplorer" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/costexplorer" + awstypes "github.com/aws/aws-sdk-go-v2/service/costexplorer/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "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/errs/sdkdiag" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -26,25 +29,43 @@ import ( // @SDKResource("aws_ce_anomaly_monitor", name="Anomaly Monitor") // @Tags(identifierAttribute="id") -func ResourceAnomalyMonitor() *schema.Resource { +func resourceAnomalyMonitor() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceAnomalyMonitorCreate, ReadWithoutTimeout: resourceAnomalyMonitorRead, UpdateWithoutTimeout: resourceAnomalyMonitorUpdate, DeleteWithoutTimeout: resourceAnomalyMonitorDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, + Schema: map[string]*schema.Schema{ "arn": { Type: schema.TypeString, Computed: true, }, "monitor_dimension": { - Type: schema.TypeString, - Optional: true, - ConflictsWith: []string{"monitor_specification"}, - ValidateFunc: validation.StringInSlice(costexplorer.MonitorDimension_Values(), false), + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"monitor_specification"}, + ValidateDiagFunc: enum.Validate[awstypes.MonitorDimension](), + }, + "monitor_specification": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringIsJSON, + DiffSuppressFunc: verify.SuppressEquivalentJSONDiffs, + DiffSuppressOnRefresh: true, + ConflictsWith: []string{"monitor_dimension"}, + }, + "monitor_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: enum.Validate[awstypes.MonitorType](), }, "name": { Type: schema.TypeString, @@ -53,20 +74,6 @@ func ResourceAnomalyMonitor() *schema.Resource { validation.StringLenBetween(1, 1024), validation.StringMatch(regexache.MustCompile(`[\\S\\s]*`), "Must be a valid Anomaly Monitor Name matching expression: [\\S\\s]*")), }, - "monitor_specification": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validation.StringIsJSON, - DiffSuppressFunc: verify.SuppressEquivalentJSONDiffs, - ConflictsWith: []string{"monitor_dimension"}, - }, - "monitor_type": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice(costexplorer.MonitorType_Values(), false), - }, names.AttrTags: tftags.TagsSchema(), names.AttrTagsAll: tftags.TagsSchemaComputed(), }, @@ -77,48 +84,45 @@ func ResourceAnomalyMonitor() *schema.Resource { func resourceAnomalyMonitorCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).CEClient(ctx) - conn := meta.(*conns.AWSClient).CEConn(ctx) - + name := d.Get("name").(string) input := &costexplorer.CreateAnomalyMonitorInput{ - AnomalyMonitor: &costexplorer.AnomalyMonitor{ - MonitorName: aws.String(d.Get("name").(string)), - MonitorType: aws.String(d.Get("monitor_type").(string)), + AnomalyMonitor: &awstypes.AnomalyMonitor{ + MonitorName: aws.String(name), + MonitorType: awstypes.MonitorType(d.Get("monitor_type").(string)), }, ResourceTags: getTagsIn(ctx), } - switch d.Get("monitor_type").(string) { - case costexplorer.MonitorTypeDimensional: + + switch awstypes.MonitorType(d.Get("monitor_type").(string)) { + case awstypes.MonitorTypeDimensional: if v, ok := d.GetOk("monitor_dimension"); ok { - input.AnomalyMonitor.MonitorDimension = aws.String(v.(string)) + input.AnomalyMonitor.MonitorDimension = awstypes.MonitorDimension(v.(string)) } else { - return sdkdiag.AppendErrorf(diags, "If Monitor Type is %s, dimension attrribute is required", costexplorer.MonitorTypeDimensional) + return sdkdiag.AppendErrorf(diags, "If Monitor Type is %s, dimension attrribute is required", awstypes.MonitorTypeDimensional) } - case costexplorer.MonitorTypeCustom: + case awstypes.MonitorTypeCustom: if v, ok := d.GetOk("monitor_specification"); ok { - expression := costexplorer.Expression{} + expression := &awstypes.Expression{} - if err := json.Unmarshal([]byte(v.(string)), &expression); err != nil { - return sdkdiag.AppendErrorf(diags, "parsing specification: %s", err) + if err := json.Unmarshal([]byte(v.(string)), expression); err != nil { + return sdkdiag.AppendFromErr(diags, err) } - input.AnomalyMonitor.MonitorSpecification = &expression + input.AnomalyMonitor.MonitorSpecification = expression } else { - return sdkdiag.AppendErrorf(diags, "If Monitor Type is %s, dimension attrribute is required", costexplorer.MonitorTypeCustom) + return sdkdiag.AppendErrorf(diags, "If Monitor Type is %s, dimension attrribute is required", awstypes.MonitorTypeCustom) } } - resp, err := conn.CreateAnomalyMonitorWithContext(ctx, input) + output, err := conn.CreateAnomalyMonitor(ctx, input) if err != nil { - return sdkdiag.AppendErrorf(diags, "creating Anomaly Monitor: %s", err) - } - - if resp == nil || resp.MonitorArn == nil { - return sdkdiag.AppendErrorf(diags, "creating Cost Explorer Anomaly Monitor resource (%s): empty output", d.Get("name").(string)) + return sdkdiag.AppendErrorf(diags, "creating Cost Explorer Anomaly Monitor (%s): %s", name, err) } - d.SetId(aws.StringValue(resp.MonitorArn)) + d.SetId(aws.ToString(output.MonitorArn)) return append(diags, resourceAnomalyMonitorRead(ctx, d, meta)...) } @@ -126,29 +130,31 @@ func resourceAnomalyMonitorCreate(ctx context.Context, d *schema.ResourceData, m func resourceAnomalyMonitorRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).CEConn(ctx) + conn := meta.(*conns.AWSClient).CEClient(ctx) - monitor, err := FindAnomalyMonitorByARN(ctx, conn, d.Id()) + monitor, err := findAnomalyMonitorByARN(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { - create.LogNotFoundRemoveState(names.CE, create.ErrActionReading, ResNameAnomalyMonitor, d.Id()) + log.Printf("[WARN] Cost Explorer Anomaly Monitor (%s) not found, removing from state", d.Id()) d.SetId("") return diags } if err != nil { - return create.AppendDiagError(diags, names.CE, create.ErrActionReading, ResNameAnomalyMonitor, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading Cost Explorer Anomaly Monitor (%s): %s", d.Id(), err) } if monitor.MonitorSpecification != nil { specificationToJson, err := json.Marshal(monitor.MonitorSpecification) + if err != nil { - return sdkdiag.AppendErrorf(diags, "parsing specification response: %s", err) + return sdkdiag.AppendFromErr(diags, err) } + specificationToSet, err := structure.NormalizeJsonString(string(specificationToJson)) if err != nil { - return sdkdiag.AppendErrorf(diags, "Specification (%s) is invalid JSON: %s", specificationToSet, err) + return sdkdiag.AppendFromErr(diags, err) } d.Set("monitor_specification", specificationToSet) @@ -164,24 +170,21 @@ func resourceAnomalyMonitorRead(ctx context.Context, d *schema.ResourceData, met func resourceAnomalyMonitorUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).CEClient(ctx) - conn := meta.(*conns.AWSClient).CEConn(ctx) - requestUpdate := false - - input := &costexplorer.UpdateAnomalyMonitorInput{ - MonitorArn: aws.String(d.Id()), - } + if d.HasChangesExcept("tags", "tags_all") { + input := &costexplorer.UpdateAnomalyMonitorInput{ + MonitorArn: aws.String(d.Id()), + } - if d.HasChange("name") { - input.MonitorName = aws.String(d.Get("name").(string)) - requestUpdate = true - } + if d.HasChange("name") { + input.MonitorName = aws.String(d.Get("name").(string)) + } - if requestUpdate { - _, err := conn.UpdateAnomalyMonitorWithContext(ctx, input) + _, err := conn.UpdateAnomalyMonitor(ctx, input) if err != nil { - return create.AppendDiagError(diags, names.CE, create.ErrActionUpdating, ResNameAnomalyMonitor, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "updating Cost Explorer Anomaly Monitor (%s): %s", d.Id(), err) } } @@ -191,17 +194,46 @@ func resourceAnomalyMonitorUpdate(ctx context.Context, d *schema.ResourceData, m func resourceAnomalyMonitorDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).CEConn(ctx) + conn := meta.(*conns.AWSClient).CEClient(ctx) - _, err := conn.DeleteAnomalyMonitorWithContext(ctx, &costexplorer.DeleteAnomalyMonitorInput{MonitorArn: aws.String(d.Id())}) + log.Printf("[DEBUG] Deleting Cost Explorer Anomaly Monitor: %s", d.Id()) + _, err := conn.DeleteAnomalyMonitor(ctx, &costexplorer.DeleteAnomalyMonitorInput{ + MonitorArn: aws.String(d.Id()), + }) - if err != nil && tfawserr.ErrCodeEquals(err, costexplorer.ErrCodeUnknownMonitorException) { + if err != nil && errs.IsA[*awstypes.UnknownMonitorException](err) { return diags } if err != nil { - return create.AppendDiagError(diags, names.CE, create.ErrActionDeleting, ResNameAnomalyMonitor, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "deleting Cost Explorer Anomaly Monitor (%s): %s", d.Id(), err) } return diags } + +func findAnomalyMonitorByARN(ctx context.Context, conn *costexplorer.Client, arn string) (*awstypes.AnomalyMonitor, error) { + input := &costexplorer.GetAnomalyMonitorsInput{ + MonitorArnList: []string{arn}, + MaxResults: aws.Int32(1), + } + + output, err := conn.GetAnomalyMonitors(ctx, input) + + if errs.IsA[*awstypes.UnknownMonitorException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || len(output.AnomalyMonitors) == 0 { + return nil, tfresource.NewEmptyResultError(input) + } + + return &output.AnomalyMonitors[0], nil +} diff --git a/internal/service/ce/anomaly_monitor_test.go b/internal/service/ce/anomaly_monitor_test.go index a44ca643448..5ddceb3cca4 100644 --- a/internal/service/ce/anomaly_monitor_test.go +++ b/internal/service/ce/anomaly_monitor_test.go @@ -5,18 +5,16 @@ package ce_test import ( "context" - "errors" "fmt" "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/service/costexplorer" + awstypes "github.com/aws/aws-sdk-go-v2/service/costexplorer/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" tfce "github.com/hashicorp/terraform-provider-aws/internal/service/ce" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" @@ -24,7 +22,7 @@ import ( func TestAccCEAnomalyMonitor_basic(t *testing.T) { ctx := acctest.Context(t) - var monitor costexplorer.AnomalyMonitor + var monitor awstypes.AnomalyMonitor resourceName := "aws_ce_anomaly_monitor.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -55,7 +53,7 @@ func TestAccCEAnomalyMonitor_basic(t *testing.T) { func TestAccCEAnomalyMonitor_disappears(t *testing.T) { ctx := acctest.Context(t) - var monitor costexplorer.AnomalyMonitor + var monitor awstypes.AnomalyMonitor resourceName := "aws_ce_anomaly_monitor.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -79,7 +77,7 @@ func TestAccCEAnomalyMonitor_disappears(t *testing.T) { func TestAccCEAnomalyMonitor_update(t *testing.T) { ctx := acctest.Context(t) - var monitor costexplorer.AnomalyMonitor + var monitor awstypes.AnomalyMonitor resourceName := "aws_ce_anomaly_monitor.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -115,7 +113,7 @@ func TestAccCEAnomalyMonitor_update(t *testing.T) { func TestAccCEAnomalyMonitor_tags(t *testing.T) { ctx := acctest.Context(t) - var monitor costexplorer.AnomalyMonitor + var monitor awstypes.AnomalyMonitor resourceName := "aws_ce_anomaly_monitor.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -164,7 +162,7 @@ func TestAccCEAnomalyMonitor_tags(t *testing.T) { // following test in a serial test func TestAccCEAnomalyMonitor_Dimensional(t *testing.T) { ctx := acctest.Context(t) - var monitor costexplorer.AnomalyMonitor + var monitor awstypes.AnomalyMonitor resourceName := "aws_ce_anomaly_monitor.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -192,30 +190,22 @@ func TestAccCEAnomalyMonitor_Dimensional(t *testing.T) { }) } -func testAccCheckAnomalyMonitorExists(ctx context.Context, n string, anomalyMonitor *costexplorer.AnomalyMonitor) resource.TestCheckFunc { +func testAccCheckAnomalyMonitorExists(ctx context.Context, n string, v *awstypes.AnomalyMonitor) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).CEConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).CEClient(ctx) rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return fmt.Errorf("No Cost Explorer Anomaly Monitor is set") - } - - resp, err := tfce.FindAnomalyMonitorByARN(ctx, conn, rs.Primary.ID) + output, err := tfce.FindAnomalyMonitorByARN(ctx, conn, rs.Primary.ID) if err != nil { return err } - if resp == nil { - return fmt.Errorf("Cost Explorer %q does not exist", rs.Primary.ID) - } - - *anomalyMonitor = *resp + *v = *output return nil } @@ -223,7 +213,7 @@ func testAccCheckAnomalyMonitorExists(ctx context.Context, n string, anomalyMoni func testAccCheckAnomalyMonitorDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).CEConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).CEClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_ce_anomaly_monitor" { @@ -240,7 +230,7 @@ func testAccCheckAnomalyMonitorDestroy(ctx context.Context) resource.TestCheckFu return err } - return create.Error(names.CE, create.ErrActionCheckingDestroyed, tfce.ResNameAnomalyMonitor, rs.Primary.ID, errors.New("still exists")) + return fmt.Errorf("Cost Explorer Anomaly Monitor %s still exists", rs.Primary.ID) } return nil @@ -261,7 +251,7 @@ resource "aws_ce_anomaly_monitor" "test" { "Not": null, "Or": null, "Tags": { - "Key": "CostCenter", + "Key": "user:CostCenter", "MatchOptions": null, "Values": [ "10000" @@ -287,7 +277,7 @@ resource "aws_ce_anomaly_monitor" "test" { "Not": null, "Or": null, "Tags": { - "Key": "CostCenter", + "Key": "user:CostCenter", "MatchOptions": null, "Values": [ "10000" @@ -317,7 +307,7 @@ resource "aws_ce_anomaly_monitor" "test" { "Not": null, "Or": null, "Tags": { - "Key": "CostCenter", + "Key": "user:CostCenter", "MatchOptions": null, "Values": [ "10000" diff --git a/internal/service/ce/anomaly_subscription.go b/internal/service/ce/anomaly_subscription.go index 69038a3df27..e8e9b5a548d 100644 --- a/internal/service/ce/anomaly_subscription.go +++ b/internal/service/ce/anomaly_subscription.go @@ -5,17 +5,21 @@ package ce import ( "context" + "log" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/costexplorer" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/costexplorer" + awstypes "github.com/aws/aws-sdk-go-v2/service/costexplorer/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "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/errs/sdkdiag" + "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" "github.com/hashicorp/terraform-provider-aws/internal/verify" @@ -24,20 +28,23 @@ import ( // @SDKResource("aws_ce_anomaly_subscription", name="Anomaly Subscription") // @Tags(identifierAttribute="id") -func ResourceAnomalySubscription() *schema.Resource { +func resourceAnomalySubscription() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceAnomalySubscriptionCreate, ReadWithoutTimeout: resourceAnomalySubscriptionRead, UpdateWithoutTimeout: resourceAnomalySubscriptionUpdate, DeleteWithoutTimeout: resourceAnomalySubscriptionDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, + Schema: map[string]*schema.Schema{ "account_id": { Type: schema.TypeString, Optional: true, Computed: true, + ForceNew: true, ValidateFunc: verify.ValidAccountID, }, "arn": { @@ -45,9 +52,9 @@ func ResourceAnomalySubscription() *schema.Resource { Computed: true, }, "frequency": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice(costexplorer.AnomalySubscriptionFrequency_Values(), false), + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[awstypes.AnomalySubscriptionFrequency](), }, "monitor_arn_list": { Type: schema.TypeList, @@ -75,22 +82,22 @@ func ResourceAnomalySubscription() *schema.Resource { Required: true, }, "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice(costexplorer.SubscriberType_Values(), false), + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[awstypes.SubscriberType](), }, }, }, }, + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), "threshold_expression": { Type: schema.TypeList, MaxItems: 1, Computed: true, Optional: true, - Elem: schemaCostCategoryRule(), + Elem: elemExpression(), }, - names.AttrTags: tftags.TagsSchema(), - names.AttrTagsAll: tftags.TagsSchemaComputed(), }, CustomizeDiff: verify.SetTagsDiff, @@ -99,15 +106,15 @@ func ResourceAnomalySubscription() *schema.Resource { func resourceAnomalySubscriptionCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).CEClient(ctx) - conn := meta.(*conns.AWSClient).CEConn(ctx) - + name := d.Get("name").(string) input := &costexplorer.CreateAnomalySubscriptionInput{ - AnomalySubscription: &costexplorer.AnomalySubscription{ - SubscriptionName: aws.String(d.Get("name").(string)), - Frequency: aws.String(d.Get("frequency").(string)), - MonitorArnList: aws.StringSlice(expandAnomalySubscriptionMonitorARNList(d.Get("monitor_arn_list").([]interface{}))), - Subscribers: expandAnomalySubscriptionSubscribers(d.Get("subscriber").(*schema.Set).List()), + AnomalySubscription: &awstypes.AnomalySubscription{ + Frequency: awstypes.AnomalySubscriptionFrequency(d.Get("frequency").(string)), + MonitorArnList: flex.ExpandStringValueList(d.Get("monitor_arn_list").([]interface{})), + Subscribers: expandSubscribers(d.Get("subscriber").(*schema.Set).List()), + SubscriptionName: aws.String(name), }, ResourceTags: getTagsIn(ctx), } @@ -120,47 +127,41 @@ func resourceAnomalySubscriptionCreate(ctx context.Context, d *schema.ResourceDa input.AnomalySubscription.ThresholdExpression = expandCostExpression(v.([]interface{})[0].(map[string]interface{})) } - resp, err := conn.CreateAnomalySubscriptionWithContext(ctx, input) + output, err := conn.CreateAnomalySubscription(ctx, input) if err != nil { - return create.AppendDiagError(diags, names.CE, create.ErrActionCreating, ResNameAnomalySubscription, d.Id(), err) - } - - if resp == nil || resp.SubscriptionArn == nil { - return sdkdiag.AppendErrorf(diags, "creating Cost Explorer Anomaly Subscription resource (%s): empty output", d.Get("name").(string)) + return sdkdiag.AppendErrorf(diags, "creating Cost Explorer Anomaly Subscription (%s): %s", name, err) } - d.SetId(aws.StringValue(resp.SubscriptionArn)) + d.SetId(aws.ToString(output.SubscriptionArn)) return append(diags, resourceAnomalySubscriptionRead(ctx, d, meta)...) } func resourceAnomalySubscriptionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).CEClient(ctx) - conn := meta.(*conns.AWSClient).CEConn(ctx) - - subscription, err := FindAnomalySubscriptionByARN(ctx, conn, d.Id()) + subscription, err := findAnomalySubscriptionByARN(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { - create.LogNotFoundRemoveState(names.CE, create.ErrActionReading, ResNameAnomalySubscription, d.Id()) + log.Printf("[WARN] Cost Explorer Anomaly Subscription (%s) not found, removing from state", d.Id()) d.SetId("") return diags } if err != nil { - return create.AppendDiagError(diags, names.CE, create.ErrActionReading, ResNameAnomalySubscription, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading Cost Explorer Anomaly Subscription (%s): %s", d.Id(), err) } d.Set("account_id", subscription.AccountId) d.Set("arn", subscription.SubscriptionArn) d.Set("frequency", subscription.Frequency) d.Set("monitor_arn_list", subscription.MonitorArnList) - d.Set("subscriber", flattenAnomalySubscriptionSubscribers(subscription.Subscribers)) d.Set("name", subscription.SubscriptionName) - - if err = d.Set("threshold_expression", []interface{}{flattenCostCategoryRuleExpression(subscription.ThresholdExpression)}); err != nil { - return create.AppendDiagError(diags, names.CE, "setting threshold_expression", ResNameAnomalySubscription, d.Id(), err) + d.Set("subscriber", flattenSubscribers(subscription.Subscribers)) + if err := d.Set("threshold_expression", []interface{}{flattenCostCategoryRuleExpression(subscription.ThresholdExpression)}); err != nil { + return sdkdiag.AppendErrorf(diags, "setting threshold_expression: %s", err) } return diags @@ -169,33 +170,33 @@ func resourceAnomalySubscriptionRead(ctx context.Context, d *schema.ResourceData func resourceAnomalySubscriptionUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).CEConn(ctx) + conn := meta.(*conns.AWSClient).CEClient(ctx) - if d.HasChangesExcept("tags", "tags_All") { + if d.HasChangesExcept("tags", "tags_all") { input := &costexplorer.UpdateAnomalySubscriptionInput{ SubscriptionArn: aws.String(d.Id()), } if d.HasChange("frequency") { - input.Frequency = aws.String(d.Get("frequency").(string)) + input.Frequency = awstypes.AnomalySubscriptionFrequency(d.Get("frequency").(string)) } if d.HasChange("monitor_arn_list") { - input.MonitorArnList = aws.StringSlice(expandAnomalySubscriptionMonitorARNList(d.Get("monitor_arn_list").([]interface{}))) + input.MonitorArnList = flex.ExpandStringValueList(d.Get("monitor_arn_list").([]interface{})) } if d.HasChange("subscriber") { - input.Subscribers = expandAnomalySubscriptionSubscribers(d.Get("subscriber").(*schema.Set).List()) + input.Subscribers = expandSubscribers(d.Get("subscriber").(*schema.Set).List()) } if d.HasChange("threshold_expression") { input.ThresholdExpression = expandCostExpression(d.Get("threshold_expression").([]interface{})[0].(map[string]interface{})) } - _, err := conn.UpdateAnomalySubscriptionWithContext(ctx, input) + _, err := conn.UpdateAnomalySubscription(ctx, input) if err != nil { - return create.AppendDiagError(diags, names.CE, create.ErrActionUpdating, ResNameAnomalySubscription, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "updating Cost Explorer Anomaly Subscription (%s): %s", d.Id(), err) } } @@ -204,66 +205,85 @@ func resourceAnomalySubscriptionUpdate(ctx context.Context, d *schema.ResourceDa func resourceAnomalySubscriptionDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).CEClient(ctx) - conn := meta.(*conns.AWSClient).CEConn(ctx) + log.Printf("[DEBUG] Deleting Cost Explorer Anomaly Subscription: %s", d.Id()) + _, err := conn.DeleteAnomalySubscription(ctx, &costexplorer.DeleteAnomalySubscriptionInput{ + SubscriptionArn: aws.String(d.Id()), + }) - _, err := conn.DeleteAnomalySubscriptionWithContext(ctx, &costexplorer.DeleteAnomalySubscriptionInput{SubscriptionArn: aws.String(d.Id())}) - - if err != nil && tfawserr.ErrCodeEquals(err, costexplorer.ErrCodeResourceNotFoundException) { + if errs.IsA[*awstypes.UnknownSubscriptionException](err) { return diags } if err != nil { - return create.AppendDiagError(diags, names.CE, create.ErrActionDeleting, ResNameAnomalySubscription, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "deleting Cost Explorer Anomaly Subscription (%s): %s", d.Id(), err) } return diags } -func expandAnomalySubscriptionMonitorARNList(rawMonitorArnList []interface{}) []string { - if len(rawMonitorArnList) == 0 { - return nil +func findAnomalySubscriptionByARN(ctx context.Context, conn *costexplorer.Client, arn string) (*awstypes.AnomalySubscription, error) { + input := &costexplorer.GetAnomalySubscriptionsInput{ + SubscriptionArnList: []string{arn}, + MaxResults: aws.Int32(1), + } + + output, err := conn.GetAnomalySubscriptions(ctx, input) + + if errs.IsA[*awstypes.UnknownMonitorException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } } - var monitorArns []string + if err != nil { + return nil, err + } - for _, arn := range rawMonitorArnList { - monitorArns = append(monitorArns, arn.(string)) + if output == nil || len(output.AnomalySubscriptions) == 0 { + return nil, tfresource.NewEmptyResultError(input) } - return monitorArns + return &output.AnomalySubscriptions[0], nil } -func expandAnomalySubscriptionSubscribers(rawSubscribers []interface{}) []*costexplorer.Subscriber { - if len(rawSubscribers) == 0 { +func expandSubscribers(tfList []interface{}) []awstypes.Subscriber { + if len(tfList) == 0 { return nil } - var subscribers []*costexplorer.Subscriber + var apiObjects []awstypes.Subscriber + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + if !ok { + continue + } - for _, sub := range rawSubscribers { - rawSubMap := sub.(map[string]interface{}) - subscriber := &costexplorer.Subscriber{Address: aws.String(rawSubMap["address"].(string)), Type: aws.String(rawSubMap["type"].(string))} - subscribers = append(subscribers, subscriber) + apiObjects = append(apiObjects, awstypes.Subscriber{ + Address: aws.String(tfMap["address"].(string)), + Type: awstypes.SubscriberType(tfMap["type"].(string)), + }) } - return subscribers + return apiObjects } -func flattenAnomalySubscriptionSubscribers(subscribers []*costexplorer.Subscriber) []interface{} { - if subscribers == nil { - return []interface{}{} +func flattenSubscribers(apiObjects []awstypes.Subscriber) []interface{} { + if len(apiObjects) == 0 { + return nil } - var rawSubscribers []interface{} - for _, subscriber := range subscribers { - rawSubscriber := map[string]interface{}{ - "address": aws.StringValue(subscriber.Address), - "type": aws.StringValue(subscriber.Type), - } + var tfList []interface{} - rawSubscribers = append(rawSubscribers, rawSubscriber) + for _, apiObject := range apiObjects { + tfList = append(tfList, map[string]interface{}{ + "address": aws.ToString(apiObject.Address), + "type": apiObject.Type, + }) } - return rawSubscribers + return tfList } diff --git a/internal/service/ce/anomaly_subscription_test.go b/internal/service/ce/anomaly_subscription_test.go index a112af9e22c..c41f08acdf5 100644 --- a/internal/service/ce/anomaly_subscription_test.go +++ b/internal/service/ce/anomaly_subscription_test.go @@ -5,18 +5,16 @@ package ce_test import ( "context" - "errors" "fmt" "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/service/costexplorer" + awstypes "github.com/aws/aws-sdk-go-v2/service/costexplorer/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" tfce "github.com/hashicorp/terraform-provider-aws/internal/service/ce" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" @@ -24,7 +22,7 @@ import ( func TestAccCEAnomalySubscription_basic(t *testing.T) { ctx := acctest.Context(t) - var subscription costexplorer.AnomalySubscription + var subscription awstypes.AnomalySubscription resourceName := "aws_ce_anomaly_subscription.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) domain := acctest.RandomDomainName() @@ -60,7 +58,7 @@ func TestAccCEAnomalySubscription_basic(t *testing.T) { func TestAccCEAnomalySubscription_disappears(t *testing.T) { ctx := acctest.Context(t) - var subscription costexplorer.AnomalySubscription + var subscription awstypes.AnomalySubscription resourceName := "aws_ce_anomaly_subscription.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) domain := acctest.RandomDomainName() @@ -86,7 +84,7 @@ func TestAccCEAnomalySubscription_disappears(t *testing.T) { func TestAccCEAnomalySubscription_Frequency(t *testing.T) { ctx := acctest.Context(t) - var subscription costexplorer.AnomalySubscription + var subscription awstypes.AnomalySubscription resourceName := "aws_ce_anomaly_subscription.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) domain := acctest.RandomDomainName() @@ -123,7 +121,7 @@ func TestAccCEAnomalySubscription_Frequency(t *testing.T) { func TestAccCEAnomalySubscription_MonitorARNList(t *testing.T) { ctx := acctest.Context(t) - var subscription costexplorer.AnomalySubscription + var subscription awstypes.AnomalySubscription resourceName := "aws_ce_anomaly_subscription.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -162,7 +160,7 @@ func TestAccCEAnomalySubscription_MonitorARNList(t *testing.T) { func TestAccCEAnomalySubscription_Subscriber(t *testing.T) { ctx := acctest.Context(t) - var subscription costexplorer.AnomalySubscription + var subscription awstypes.AnomalySubscription resourceName := "aws_ce_anomaly_subscription.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) domain := acctest.RandomDomainName() @@ -230,7 +228,7 @@ func TestAccCEAnomalySubscription_Subscriber(t *testing.T) { func TestAccCEAnomalySubscription_Tags(t *testing.T) { ctx := acctest.Context(t) - var subscription costexplorer.AnomalySubscription + var subscription awstypes.AnomalySubscription resourceName := "aws_ce_anomaly_subscription.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) domain := acctest.RandomDomainName() @@ -243,7 +241,7 @@ func TestAccCEAnomalySubscription_Tags(t *testing.T) { ErrorCheck: acctest.ErrorCheck(t, names.CEServiceID), Steps: []resource.TestStep{ { - Config: testAccAnomalySubscriptionConfig_tags1(rName, "key1", "value1", address), + Config: testAccAnomalySubscriptionConfig_tags1(rName, address, "key1", "value1"), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckAnomalySubscriptionExists(ctx, resourceName, &subscription), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), @@ -256,7 +254,7 @@ func TestAccCEAnomalySubscription_Tags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAnomalySubscriptionConfig_tags2(rName, "key1", "value1updated", "key2", "value2", address), + Config: testAccAnomalySubscriptionConfig_tags2(rName, address, "key1", "value1updated", "key2", "value2"), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckAnomalySubscriptionExists(ctx, resourceName, &subscription), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), @@ -265,7 +263,7 @@ func TestAccCEAnomalySubscription_Tags(t *testing.T) { ), }, { - Config: testAccAnomalySubscriptionConfig_tags1(rName, "key2", "value2", address), + Config: testAccAnomalySubscriptionConfig_tags1(rName, address, "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckAnomalySubscriptionExists(ctx, resourceName, &subscription), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), @@ -276,30 +274,22 @@ func TestAccCEAnomalySubscription_Tags(t *testing.T) { }) } -func testAccCheckAnomalySubscriptionExists(ctx context.Context, n string, anomalySubscription *costexplorer.AnomalySubscription) resource.TestCheckFunc { +func testAccCheckAnomalySubscriptionExists(ctx context.Context, n string, v *awstypes.AnomalySubscription) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).CEConn(ctx) - rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return fmt.Errorf("No Cost Explorer Anomaly Subscription is set") - } + conn := acctest.Provider.Meta().(*conns.AWSClient).CEClient(ctx) - resp, err := tfce.FindAnomalySubscriptionByARN(ctx, conn, rs.Primary.ID) + output, err := tfce.FindAnomalySubscriptionByARN(ctx, conn, rs.Primary.ID) if err != nil { return err } - if resp == nil { - return fmt.Errorf("Cost Explorer %q does not exist", rs.Primary.ID) - } - - *anomalySubscription = *resp + *v = *output return nil } @@ -307,7 +297,7 @@ func testAccCheckAnomalySubscriptionExists(ctx context.Context, n string, anomal func testAccCheckAnomalySubscriptionDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).CEConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).CEClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_ce_anomaly_subscription" { @@ -324,13 +314,14 @@ func testAccCheckAnomalySubscriptionDestroy(ctx context.Context) resource.TestCh return err } - return create.Error(names.CE, create.ErrActionCheckingDestroyed, tfce.ResNameAnomalySubscription, rs.Primary.ID, errors.New("still exists")) + return fmt.Errorf("Cost Explorer Anomaly Subscription %s still exists", rs.Primary.ID) } + return nil } } -func testAccAnomalySubscriptionConfigBase(rName string) string { +func testAccAnomalySubscriptionConfig_base(rName string) string { return fmt.Sprintf(` resource "aws_ce_anomaly_monitor" "test" { name = %[1]q @@ -344,7 +335,7 @@ resource "aws_ce_anomaly_monitor" "test" { "Not": null, "Or": null, "Tags": { - "Key": "CostCenter", + "Key": "user:CostCenter", "MatchOptions": null, "Values": [ "10000" @@ -356,10 +347,8 @@ JSON `, rName) } -func testAccAnomalySubscriptionConfig_basic(rName string, address string) string { - return acctest.ConfigCompose( - testAccAnomalySubscriptionConfigBase(rName), - fmt.Sprintf(` +func testAccAnomalySubscriptionConfig_basic(rName, address string) string { + return acctest.ConfigCompose(testAccAnomalySubscriptionConfig_base(rName), fmt.Sprintf(` resource "aws_ce_anomaly_subscription" "test" { name = %[1]q frequency = "DAILY" @@ -384,10 +373,8 @@ resource "aws_ce_anomaly_subscription" "test" { `, rName, address)) } -func testAccAnomalySubscriptionConfig_monitorARNList(rName string, rName2 string, address string) string { - return acctest.ConfigCompose( - testAccAnomalySubscriptionConfigBase(rName), - fmt.Sprintf(` +func testAccAnomalySubscriptionConfig_monitorARNList(rName, rName2, address string) string { + return acctest.ConfigCompose(testAccAnomalySubscriptionConfig_base(rName), fmt.Sprintf(` resource "aws_ce_anomaly_monitor" "test2" { name = %[2]q monitor_type = "CUSTOM" @@ -400,7 +387,7 @@ resource "aws_ce_anomaly_monitor" "test2" { "Not": null, "Or": null, "Tags": { - "Key": "CostCenter", + "Key": "user:CostCenter", "MatchOptions": null, "Values": [ "10000" @@ -435,10 +422,8 @@ resource "aws_ce_anomaly_subscription" "test" { `, rName, rName2, address)) } -func testAccAnomalySubscriptionConfig_frequency(rName string, rFrequency string, address string) string { - return acctest.ConfigCompose( - testAccAnomalySubscriptionConfigBase(rName), - fmt.Sprintf(` +func testAccAnomalySubscriptionConfig_frequency(rName, rFrequency, address string) string { + return acctest.ConfigCompose(testAccAnomalySubscriptionConfig_base(rName), fmt.Sprintf(` resource "aws_ce_anomaly_subscription" "test" { name = %[1]q frequency = %[2]q @@ -463,10 +448,8 @@ resource "aws_ce_anomaly_subscription" "test" { `, rName, rFrequency, address)) } -func testAccAnomalySubscriptionConfig_subscriber2(rName string, address1 string, address2 string) string { - return acctest.ConfigCompose( - testAccAnomalySubscriptionConfigBase(rName), - fmt.Sprintf(` +func testAccAnomalySubscriptionConfig_subscriber2(rName, address1, address2 string) string { + return acctest.ConfigCompose(testAccAnomalySubscriptionConfig_base(rName), fmt.Sprintf(` resource "aws_ce_anomaly_subscription" "test" { name = %[1]q frequency = "WEEKLY" @@ -497,9 +480,7 @@ resource "aws_ce_anomaly_subscription" "test" { } func testAccAnomalySubscriptionConfig_subscriberSNS(rName string) string { - return acctest.ConfigCompose( - testAccAnomalySubscriptionConfigBase(rName), - fmt.Sprintf(` + return acctest.ConfigCompose(testAccAnomalySubscriptionConfig_base(rName), fmt.Sprintf(` resource "aws_sns_topic" "test" { name = %[1]q } @@ -599,10 +580,8 @@ resource "aws_ce_anomaly_subscription" "test" { `, rName)) } -func testAccAnomalySubscriptionConfig_tags1(rName string, tagKey1, tagValue1 string, address string) string { - return acctest.ConfigCompose( - testAccAnomalySubscriptionConfigBase(rName), - fmt.Sprintf(` +func testAccAnomalySubscriptionConfig_tags1(rName, address, tagKey1, tagValue1 string) string { + return acctest.ConfigCompose(testAccAnomalySubscriptionConfig_base(rName), fmt.Sprintf(` resource "aws_ce_anomaly_subscription" "test" { name = %[1]q frequency = "DAILY" @@ -631,10 +610,8 @@ resource "aws_ce_anomaly_subscription" "test" { `, rName, tagKey1, tagValue1, address)) } -func testAccAnomalySubscriptionConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string, address string) string { - return acctest.ConfigCompose( - testAccAnomalySubscriptionConfigBase(rName), - fmt.Sprintf(` +func testAccAnomalySubscriptionConfig_tags2(rName, address, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return acctest.ConfigCompose(testAccAnomalySubscriptionConfig_base(rName), fmt.Sprintf(` resource "aws_ce_anomaly_subscription" "test" { name = %[1]q frequency = "DAILY" diff --git a/internal/service/ce/consts.go b/internal/service/ce/consts.go deleted file mode 100644 index bd3dee92d93..00000000000 --- a/internal/service/ce/consts.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package ce - -const ( - ResNameAnomalyMonitor = "Anomaly Monitor" - ResNameAnomalySubscription = "Anomaly Subscription" - ResNameCostCategory = "Cost Category" - ResNameCostAllocationTag = "Cost Allocation Tags" - DSNameTags = "Tags Data Source" -) diff --git a/internal/service/ce/cost_allocation_tag.go b/internal/service/ce/cost_allocation_tag.go index d94ccfa7fad..ede19c67198 100644 --- a/internal/service/ce/cost_allocation_tag.go +++ b/internal/service/ce/cost_allocation_tag.go @@ -5,33 +5,36 @@ package ce import ( "context" + "log" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/costexplorer" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/costexplorer" + awstypes "github.com/aws/aws-sdk-go-v2/service/costexplorer/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "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/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" - "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_ce_cost_allocation_tag") -func ResourceCostAllocationTag() *schema.Resource { +// @SDKResource("aws_ce_cost_allocation_tag", name="Cost Allocation Tag") +func resourceCostAllocationTag() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceCostAllocationTagUpdate, ReadWithoutTimeout: resourceCostAllocationTagRead, UpdateWithoutTimeout: resourceCostAllocationTagUpdate, DeleteWithoutTimeout: resourceCostAllocationTagDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, Schema: map[string]*schema.Schema{ "status": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice(costexplorer.CostAllocationTagStatus_Values(), false), + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[awstypes.CostAllocationTagStatus](), }, "tag_key": { Type: schema.TypeString, @@ -48,68 +51,82 @@ func ResourceCostAllocationTag() *schema.Resource { func resourceCostAllocationTagRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).CEClient(ctx) - conn := meta.(*conns.AWSClient).CEConn(ctx) - - costAllocTag, err := FindCostAllocationTagByKey(ctx, conn, d.Id()) + tag, err := findCostAllocationTagByTagKey(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { - create.LogNotFoundRemoveState(names.CE, create.ErrActionReading, ResNameCostAllocationTag, d.Id()) + log.Printf("[WARN] Cost Explorer Cost Allocation Tag (%s) not found, removing from state", d.Id()) d.SetId("") return diags } if err != nil { - return create.AppendDiagError(diags, names.CE, create.ErrActionReading, ResNameCostAllocationTag, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading Cost Explorer Cost Allocation Tag (%s): %s", d.Id(), err) } - d.Set("tag_key", costAllocTag.TagKey) - d.Set("status", costAllocTag.Status) - d.Set("type", costAllocTag.Type) + d.Set("status", tag.Status) + d.Set("tag_key", tag.TagKey) + d.Set("type", tag.Type) return diags } func resourceCostAllocationTagUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).CEClient(ctx) - key := d.Get("tag_key").(string) + tagKey := d.Get("tag_key").(string) - updateTagStatus(ctx, d, meta, false) + if err := updateCostAllocationTagStatus(ctx, conn, tagKey, awstypes.CostAllocationTagStatus(d.Get("status").(string))); err != nil { + return sdkdiag.AppendErrorf(diags, "updating Cost Explorer Cost Allocation Tag (%s): %s", tagKey, err) + } - d.SetId(key) + d.SetId(tagKey) return append(diags, resourceCostAllocationTagRead(ctx, d, meta)...) } func resourceCostAllocationTagDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - return updateTagStatus(ctx, d, meta, true) -} - -func updateTagStatus(ctx context.Context, d *schema.ResourceData, meta interface{}, delete bool) diag.Diagnostics { var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).CEClient(ctx) - conn := meta.(*conns.AWSClient).CEConn(ctx) - - key := d.Get("tag_key").(string) - tagStatus := &costexplorer.CostAllocationTagStatusEntry{ - TagKey: aws.String(key), - Status: aws.String(d.Get("status").(string)), + log.Printf("[DEBUG] Deleting Cost Explorer Cost Allocation Tag: %s", d.Id()) + if err := updateCostAllocationTagStatus(ctx, conn, d.Id(), awstypes.CostAllocationTagStatusInactive); err != nil { + return sdkdiag.AppendErrorf(diags, "deleting Cost Explorer Cost Allocation Tag (%s): %s", d.Id(), err) } - if delete { - tagStatus.Status = aws.String(costexplorer.CostAllocationTagStatusInactive) - } + return diags +} +func updateCostAllocationTagStatus(ctx context.Context, conn *costexplorer.Client, tagKey string, status awstypes.CostAllocationTagStatus) error { input := &costexplorer.UpdateCostAllocationTagsStatusInput{ - CostAllocationTagsStatus: []*costexplorer.CostAllocationTagStatusEntry{tagStatus}, + CostAllocationTagsStatus: []awstypes.CostAllocationTagStatusEntry{{ + Status: status, + TagKey: aws.String(tagKey), + }}, } - _, err := conn.UpdateCostAllocationTagsStatusWithContext(ctx, input) + _, err := conn.UpdateCostAllocationTagsStatus(ctx, input) + + return err +} + +func findCostAllocationTagByTagKey(ctx context.Context, conn *costexplorer.Client, tagKey string) (*awstypes.CostAllocationTag, error) { + input := &costexplorer.ListCostAllocationTagsInput{ + TagKeys: []string{tagKey}, + MaxResults: aws.Int32(1), + } + + output, err := conn.ListCostAllocationTags(ctx, input) if err != nil { - return create.AppendDiagError(diags, names.CE, create.ErrActionUpdating, ResNameCostAllocationTag, d.Id(), err) + return nil, err } - return diags + if output == nil || len(output.CostAllocationTags) == 0 { + return nil, tfresource.NewEmptyResultError(input) + } + + return &output.CostAllocationTags[0], nil } diff --git a/internal/service/ce/cost_allocation_tag_test.go b/internal/service/ce/cost_allocation_tag_test.go index 1bda310c669..e8c03702681 100644 --- a/internal/service/ce/cost_allocation_tag_test.go +++ b/internal/service/ce/cost_allocation_tag_test.go @@ -5,30 +5,28 @@ package ce_test import ( "context" - "errors" "fmt" "testing" - "github.com/aws/aws-sdk-go/service/costexplorer" + awstypes "github.com/aws/aws-sdk-go-v2/service/costexplorer/types" "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" tfce "github.com/hashicorp/terraform-provider-aws/internal/service/ce" "github.com/hashicorp/terraform-provider-aws/names" ) func TestAccCECostAllocationTag_basic(t *testing.T) { ctx := acctest.Context(t) - var output costexplorer.CostAllocationTag + var output awstypes.CostAllocationTag resourceName := "aws_ce_cost_allocation_tag.test" rName := "Tag01" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: nil, + CheckDestroy: testAccCheckCostAllocationTagDestroy(ctx), ErrorCheck: acctest.ErrorCheck(t, names.CEServiceID), Steps: []resource.TestStep{ { @@ -66,21 +64,72 @@ func TestAccCECostAllocationTag_basic(t *testing.T) { }) } -func testAccCheckCostAllocationTagExists(ctx context.Context, resourceName string, output *costexplorer.CostAllocationTag) resource.TestCheckFunc { +func TestAccCECostAllocationTag_disappears(t *testing.T) { + ctx := acctest.Context(t) + var output awstypes.CostAllocationTag + resourceName := "aws_ce_cost_allocation_tag.test" + rName := "Tag02" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCostAllocationTagDestroy(ctx), + ErrorCheck: acctest.ErrorCheck(t, names.CEServiceID), + Steps: []resource.TestStep{ + { + Config: testAccCostAllocationTagConfig_basic(rName, "Active"), + Check: resource.ComposeTestCheckFunc( + testAccCheckCostAllocationTagExists(ctx, resourceName, &output), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfce.ResourceCostAllocationTag(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckCostAllocationTagExists(ctx context.Context, n string, v *awstypes.CostAllocationTag) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[resourceName] + rs, ok := s.RootModule().Resources[n] if !ok { - return create.Error(names.CE, create.ErrActionCheckingExistence, tfce.ResNameCostAllocationTag, resourceName, errors.New("not found in state")) + return fmt.Errorf("Not found: %s", n) } - conn := acctest.Provider.Meta().(*conns.AWSClient).CEConn(ctx) - costAllocTag, err := tfce.FindCostAllocationTagByKey(ctx, conn, rs.Primary.ID) + conn := acctest.Provider.Meta().(*conns.AWSClient).CEClient(ctx) + + output, err := tfce.FindCostAllocationTagByTagKey(ctx, conn, rs.Primary.ID) if err != nil { return err } - *output = *costAllocTag + *v = *output + + return nil + } +} + +func testAccCheckCostAllocationTagDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).CEClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ce_cost_allocation_tag" { + continue + } + + output, err := tfce.FindCostAllocationTagByTagKey(ctx, conn, rs.Primary.ID) + + if err != nil { + return err + } + + if output.Status == awstypes.CostAllocationTagStatusInactive { + continue + } + + return fmt.Errorf("Cost Explorer Anomaly Subscription %s still exists", rs.Primary.ID) + } return nil } diff --git a/internal/service/ce/cost_category.go b/internal/service/ce/cost_category.go index c0b543a51d2..c01e30f3282 100644 --- a/internal/service/ce/cost_category.go +++ b/internal/service/ce/cost_category.go @@ -5,17 +5,22 @@ package ce import ( "context" + "log" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/costexplorer" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/costexplorer" + awstypes "github.com/aws/aws-sdk-go-v2/service/costexplorer/types" "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-plugin-sdk/v2/helper/validation" "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/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" @@ -24,7 +29,7 @@ import ( // @SDKResource("aws_ce_cost_category", name="Cost Category") // @Tags(identifierAttribute="id") -func ResourceCostCategory() *schema.Resource { +func resourceCostCategory() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceCostCategoryCreate, ReadWithoutTimeout: resourceCostCategoryRead, @@ -37,252 +42,243 @@ func ResourceCostCategory() *schema.Resource { CustomizeDiff: customdiff.Sequence(verify.SetTagsDiff), - Schema: map[string]*schema.Schema{ - "arn": { - Type: schema.TypeString, - Computed: true, - }, - "default_value": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringLenBetween(1, 50), - }, - "effective_end": { - Type: schema.TypeString, - Computed: true, - }, - "effective_start": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringLenBetween(1, 50), - }, - "rule": { - Type: schema.TypeSet, - Required: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "inherited_value": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "dimension_key": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringLenBetween(0, 1024), - }, - "dimension_name": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(costexplorer.CostCategoryInheritedValueDimensionName_Values(), false), + SchemaFunc: func() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "default_value": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 50), + }, + "effective_end": { + Type: schema.TypeString, + Computed: true, + }, + "effective_start": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 50), + }, + "rule": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "inherited_value": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "dimension_key": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 1024), + }, + "dimension_name": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[awstypes.CostCategoryInheritedValueDimensionName](), + }, }, }, }, - }, - "rule": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: schemaCostCategoryRule(), - }, - "type": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(costexplorer.CostCategoryRuleType_Values(), false), - }, - "value": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringLenBetween(1, 50), + "rule": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: elemExpression(), + }, + "type": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[awstypes.CostCategoryRuleType](), + }, + "value": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 50), + }, }, }, }, - }, - "rule_version": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringLenBetween(0, 100), - }, - "split_charge_rule": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "method": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice(costexplorer.CostCategorySplitChargeMethod_Values(), false), - }, - "parameter": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(costexplorer.CostCategorySplitChargeRuleParameterType_Values(), false), - }, - "values": { - Type: schema.TypeList, - Optional: true, - MinItems: 1, - MaxItems: 500, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringLenBetween(0, 1024), + "rule_version": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(0, 100), + }, + "split_charge_rule": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "method": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[awstypes.CostCategorySplitChargeMethod](), + }, + "parameter": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[awstypes.CostCategorySplitChargeRuleParameterType](), + }, + "values": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + MaxItems: 500, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(0, 1024), + }, }, }, }, }, - }, - "source": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringLenBetween(0, 1024), - }, - "targets": { - Type: schema.TypeSet, - Required: true, - MinItems: 1, - MaxItems: 500, - Elem: &schema.Schema{ + "source": { Type: schema.TypeString, + Required: true, ValidateFunc: validation.StringLenBetween(0, 1024), }, + "targets": { + Type: schema.TypeSet, + Required: true, + MinItems: 1, + MaxItems: 500, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(0, 1024), + }, + }, }, }, }, - }, - names.AttrTags: tftags.TagsSchema(), - names.AttrTagsAll: tftags.TagsSchemaComputed(), + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), + } }, } } -func schemaCostCategoryRule() *schema.Resource { - return &schema.Resource{ - Schema: map[string]*schema.Schema{ - "and": { - Type: schema.TypeSet, - Optional: true, - Elem: schemaCostCategoryRuleExpression(), - }, - "cost_category": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringLenBetween(1, 50), - }, - "match_options": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ +func elemExpression() *schema.Resource { + elemNestedExpression := func() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cost_category": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { Type: schema.TypeString, - ValidateFunc: validation.StringInSlice(costexplorer.MatchOption_Values(), false), + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 50), }, - }, - "values": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringLenBetween(0, 1024), + "match_options": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateDiagFunc: enum.Validate[awstypes.MatchOption](), + }, + }, + "values": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(0, 1024), + }, }, }, }, }, - }, - "dimension": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(costexplorer.Dimension_Values(), false), - }, - "match_options": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringInSlice(costexplorer.MatchOption_Values(), false), + "dimension": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[awstypes.Dimension](), }, - }, - "values": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringLenBetween(0, 1024), + "match_options": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateDiagFunc: enum.Validate[awstypes.MatchOption](), + }, + }, + "values": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(0, 1024), + }, }, }, }, }, - }, - "not": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: schemaCostCategoryRuleExpression(), - }, - "or": { - Type: schema.TypeSet, - Optional: true, - Elem: schemaCostCategoryRuleExpression(), - }, - "tags": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Optional: true, - }, - "match_options": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringInSlice(costexplorer.MatchOption_Values(), false), + "tags": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Optional: true, }, - }, - "values": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringLenBetween(0, 1024), + "match_options": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateDiagFunc: enum.Validate[awstypes.MatchOption](), + }, + }, + "values": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(0, 1024), + }, }, }, }, }, }, - }, + } } -} -func schemaCostCategoryRuleExpression() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ + "and": { + Type: schema.TypeSet, + Optional: true, + Elem: elemNestedExpression(), + }, "cost_category": { Type: schema.TypeList, MaxItems: 1, @@ -298,8 +294,8 @@ func schemaCostCategoryRuleExpression() *schema.Resource { Type: schema.TypeSet, Optional: true, Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringInSlice(costexplorer.MatchOption_Values(), false), + Type: schema.TypeString, + ValidateDiagFunc: enum.Validate[awstypes.MatchOption](), }, }, "values": { @@ -320,16 +316,16 @@ func schemaCostCategoryRuleExpression() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(costexplorer.Dimension_Values(), false), + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[awstypes.Dimension](), }, "match_options": { Type: schema.TypeSet, Optional: true, Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringInSlice(costexplorer.MatchOption_Values(), false), + Type: schema.TypeString, + ValidateDiagFunc: enum.Validate[awstypes.MatchOption](), }, }, "values": { @@ -343,6 +339,17 @@ func schemaCostCategoryRuleExpression() *schema.Resource { }, }, }, + "not": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: elemNestedExpression(), + }, + "or": { + Type: schema.TypeSet, + Optional: true, + Elem: elemNestedExpression(), + }, "tags": { Type: schema.TypeList, MaxItems: 1, @@ -357,8 +364,8 @@ func schemaCostCategoryRuleExpression() *schema.Resource { Type: schema.TypeSet, Optional: true, Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringInSlice(costexplorer.MatchOption_Values(), false), + Type: schema.TypeString, + ValidateDiagFunc: enum.Validate[awstypes.MatchOption](), }, }, "values": { @@ -378,57 +385,56 @@ func schemaCostCategoryRuleExpression() *schema.Resource { func resourceCostCategoryCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).CEClient(ctx) - conn := meta.(*conns.AWSClient).CEConn(ctx) - + name := d.Get("name").(string) input := &costexplorer.CreateCostCategoryDefinitionInput{ - Name: aws.String(d.Get("name").(string)), + Name: aws.String(name), ResourceTags: getTagsIn(ctx), Rules: expandCostCategoryRules(d.Get("rule").(*schema.Set).List()), - RuleVersion: aws.String(d.Get("rule_version").(string)), + RuleVersion: awstypes.CostCategoryRuleVersion(d.Get("rule_version").(string)), } if v, ok := d.GetOk("default_value"); ok { input.DefaultValue = aws.String(v.(string)) } - if v, ok := d.GetOk("split_charge_rule"); ok { - input.SplitChargeRules = expandCostCategorySplitChargeRules(v.(*schema.Set).List()) - } - if v, ok := d.GetOk("effective_start"); ok { input.EffectiveStart = aws.String(v.(string)) } - outputRaw, err := tfresource.RetryWhenAWSErrCodeEquals(ctx, d.Timeout(schema.TimeoutCreate), + if v, ok := d.GetOk("split_charge_rule"); ok { + input.SplitChargeRules = expandCostCategorySplitChargeRules(v.(*schema.Set).List()) + } + + outputRaw, err := tfresource.RetryWhenIsA[*awstypes.ResourceNotFoundException](ctx, d.Timeout(schema.TimeoutCreate), func() (interface{}, error) { - return conn.CreateCostCategoryDefinitionWithContext(ctx, input) - }, - costexplorer.ErrCodeResourceNotFoundException) + return conn.CreateCostCategoryDefinition(ctx, input) + }) if err != nil { - return create.AppendDiagError(diags, names.CE, create.ErrActionCreating, ResNameCostCategory, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "creating Cost Explorer Cost Category (%s): %s", name, err) } - d.SetId(aws.StringValue(outputRaw.(*costexplorer.CreateCostCategoryDefinitionOutput).CostCategoryArn)) + d.SetId(aws.ToString(outputRaw.(*costexplorer.CreateCostCategoryDefinitionOutput).CostCategoryArn)) return append(diags, resourceCostCategoryRead(ctx, d, meta)...) } func resourceCostCategoryRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).CEClient(ctx) - conn := meta.(*conns.AWSClient).CEConn(ctx) - - costCategory, err := FindCostCategoryByARN(ctx, conn, d.Id()) + costCategory, err := findCostCategoryByARN(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Cost Explorer Cost Category (%s) not found, removing from state", d.Id()) d.SetId("") return diags } if err != nil { - return create.AppendDiagError(diags, names.CE, create.ErrActionReading, ResNameCostCategory, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading Cost Explorer Cost Category (%s): %s", d.Id(), err) } d.Set("arn", costCategory.CostCategoryArn) @@ -437,11 +443,11 @@ func resourceCostCategoryRead(ctx context.Context, d *schema.ResourceData, meta d.Set("effective_start", costCategory.EffectiveStart) d.Set("name", costCategory.Name) if err = d.Set("rule", flattenCostCategoryRules(costCategory.Rules)); err != nil { - return create.AppendDiagError(diags, names.CE, "setting rule", ResNameCostCategory, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "setting rule: %s", err) } d.Set("rule_version", costCategory.RuleVersion) if err = d.Set("split_charge_rule", flattenCostCategorySplitChargeRules(costCategory.SplitChargeRules)); err != nil { - return create.AppendDiagError(diags, names.CE, "setting split_charge_rule", ResNameCostCategory, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "setting split_charge_rule: %s", err) } return diags @@ -449,15 +455,14 @@ func resourceCostCategoryRead(ctx context.Context, d *schema.ResourceData, meta func resourceCostCategoryUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - - conn := meta.(*conns.AWSClient).CEConn(ctx) + conn := meta.(*conns.AWSClient).CEClient(ctx) if d.HasChangesExcept("tags", "tags_all") { input := &costexplorer.UpdateCostCategoryDefinitionInput{ CostCategoryArn: aws.String(d.Id()), EffectiveStart: aws.String(d.Get("effective_start").(string)), Rules: expandCostCategoryRules(d.Get("rule").(*schema.Set).List()), - RuleVersion: aws.String(d.Get("rule_version").(string)), + RuleVersion: awstypes.CostCategoryRuleVersion(d.Get("rule_version").(string)), } if d.HasChange("default_value") { @@ -468,10 +473,10 @@ func resourceCostCategoryUpdate(ctx context.Context, d *schema.ResourceData, met input.SplitChargeRules = expandCostCategorySplitChargeRules(d.Get("split_charge_rule").(*schema.Set).List()) } - _, err := conn.UpdateCostCategoryDefinitionWithContext(ctx, input) + _, err := conn.UpdateCostCategoryDefinition(ctx, input) if err != nil { - return create.AppendDiagError(diags, names.CE, create.ErrActionUpdating, ResNameCostCategory, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "updating Cost Explorer Cost Category (%s): %s", d.Id(), err) } } @@ -481,182 +486,220 @@ func resourceCostCategoryUpdate(ctx context.Context, d *schema.ResourceData, met func resourceCostCategoryDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).CEConn(ctx) + conn := meta.(*conns.AWSClient).CEClient(ctx) - _, err := conn.DeleteCostCategoryDefinitionWithContext(ctx, &costexplorer.DeleteCostCategoryDefinitionInput{ + log.Printf("[DEBUG] Deleting Cost Explorer Cost Category: %s", d.Id()) + _, err := conn.DeleteCostCategoryDefinition(ctx, &costexplorer.DeleteCostCategoryDefinitionInput{ CostCategoryArn: aws.String(d.Id()), }) - if tfawserr.ErrCodeEquals(err, costexplorer.ErrCodeResourceNotFoundException) { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { return diags } if err != nil { - return create.AppendDiagError(diags, names.CE, create.ErrActionDeleting, ResNameCostCategory, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "deleting Cost Explorer Cost Category (%s): %s", d.Id(), err) } return diags } -func expandCostCategoryRule(tfMap map[string]interface{}) *costexplorer.CostCategoryRule { - if tfMap == nil { - return nil +func findCostCategoryByARN(ctx context.Context, conn *costexplorer.Client, arn string) (*awstypes.CostCategory, error) { + input := &costexplorer.DescribeCostCategoryDefinitionInput{ + CostCategoryArn: aws.String(arn), } - apiObject := &costexplorer.CostCategoryRule{} - if v, ok := tfMap["inherited_value"]; ok { - apiObject.InheritedValue = expandCostCategoryInheritedValue(v.([]interface{})) + output, err := conn.DescribeCostCategoryDefinition(ctx, input) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } } - if v, ok := tfMap["rule"]; ok { - apiObject.Rule = expandCostExpressions(v.([]interface{}))[0] + + if err != nil { + return nil, err } - if v, ok := tfMap["type"]; ok { - apiObject.Type = aws.String(v.(string)) + + if output == nil || output.CostCategory == nil { + return nil, tfresource.NewEmptyResultError(input) } - if v, ok := tfMap["value"]; ok { - apiObject.Value = aws.String(v.(string)) + + return output.CostCategory, nil +} + +func expandCostCategoryRule(tfMap map[string]interface{}) awstypes.CostCategoryRule { + apiObject := awstypes.CostCategoryRule{} + + if v, ok := tfMap["inherited_value"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + apiObject.InheritedValue = expandCostCategoryInheritedValueDimension(v[0].(map[string]interface{})) + } + + if v, ok := tfMap["rule"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + apiObject.Rule = expandCostExpression(v[0].(map[string]interface{})) + } + + if v, ok := tfMap["type"].(string); ok && v != "" { + apiObject.Type = awstypes.CostCategoryRuleType(v) + } + + if v, ok := tfMap["value"].(string); ok && v != "" { + apiObject.Value = aws.String(v) } return apiObject } -func expandCostCategoryInheritedValue(tfList []interface{}) *costexplorer.CostCategoryInheritedValueDimension { - if len(tfList) == 0 { +func expandCostCategoryInheritedValueDimension(tfMap map[string]interface{}) *awstypes.CostCategoryInheritedValueDimension { + if tfMap == nil { return nil } - tfMap := tfList[0].(map[string]interface{}) + apiObject := &awstypes.CostCategoryInheritedValueDimension{} - apiObject := &costexplorer.CostCategoryInheritedValueDimension{} - if v, ok := tfMap["dimension_key"]; ok { - apiObject.DimensionKey = aws.String(v.(string)) + if v, ok := tfMap["dimension_key"].(string); ok && v != "" { + apiObject.DimensionKey = aws.String(v) } - if v, ok := tfMap["dimension_name"]; ok { - apiObject.DimensionName = aws.String(v.(string)) + + if v, ok := tfMap["dimension_name"].(string); ok && v != "" { + apiObject.DimensionName = awstypes.CostCategoryInheritedValueDimensionName(v) } return apiObject } -func expandCostExpression(tfMap map[string]interface{}) *costexplorer.Expression { +func expandCostExpression(tfMap map[string]interface{}) *awstypes.Expression { if tfMap == nil { return nil } - apiObject := &costexplorer.Expression{} - if v, ok := tfMap["and"]; ok { - apiObject.And = expandCostExpressions(v.(*schema.Set).List()) + apiObject := &awstypes.Expression{} + + if v, ok := tfMap["and"].(*schema.Set); ok && v.Len() > 0 { + apiObject.And = expandCostExpressions(v.List()) } - if v, ok := tfMap["cost_category"]; ok { - apiObject.CostCategories = expandCostExpressionCostCategory(v.([]interface{})) + + if v, ok := tfMap["cost_category"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + apiObject.CostCategories = expandCostCategoryValues(v[0].(map[string]interface{})) } - if v, ok := tfMap["dimension"]; ok { - apiObject.Dimensions = expandCostExpressionDimension(v.([]interface{})) + + if v, ok := tfMap["dimension"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + apiObject.Dimensions = expandDimensionValues(v[0].(map[string]interface{})) } - if v, ok := tfMap["not"]; ok && len(v.([]interface{})) > 0 { - apiObject.Not = expandCostExpressions(v.([]interface{}))[0] + + if v, ok := tfMap["not"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + apiObject.Not = expandCostExpression(v[0].(map[string]interface{})) } - if v, ok := tfMap["or"]; ok { - apiObject.Or = expandCostExpressions(v.(*schema.Set).List()) + + if v, ok := tfMap["or"].(*schema.Set); ok && v.Len() > 0 { + apiObject.Or = expandCostExpressions(v.List()) } - if v, ok := tfMap["tags"]; ok { - apiObject.Tags = expandCostExpressionTag(v.([]interface{})) + + if v, ok := tfMap["tags"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + apiObject.Tags = expandTagValues(v[0].(map[string]interface{})) } return apiObject } -func expandCostExpressionCostCategory(tfList []interface{}) *costexplorer.CostCategoryValues { - if len(tfList) == 0 { +func expandCostCategoryValues(tfMap map[string]interface{}) *awstypes.CostCategoryValues { + if tfMap == nil { return nil } - tfMap := tfList[0].(map[string]interface{}) + apiObject := &awstypes.CostCategoryValues{} - apiObject := &costexplorer.CostCategoryValues{} - if v, ok := tfMap["key"]; ok { - apiObject.Key = aws.String(v.(string)) + if v, ok := tfMap["key"].(string); ok && v != "" { + apiObject.Key = aws.String(v) } - if v, ok := tfMap["match_options"]; ok { - apiObject.MatchOptions = flex.ExpandStringSet(v.(*schema.Set)) + + if v, ok := tfMap["match_options"].(*schema.Set); ok && v.Len() > 0 { + apiObject.MatchOptions = flex.ExpandStringyValueSet[awstypes.MatchOption](v) } - if v, ok := tfMap["values"]; ok { - apiObject.Values = flex.ExpandStringSet(v.(*schema.Set)) + + if v, ok := tfMap["values"].(*schema.Set); ok && v.Len() > 0 { + apiObject.Values = flex.ExpandStringValueSet(v) } return apiObject } -func expandCostExpressionDimension(tfList []interface{}) *costexplorer.DimensionValues { - if len(tfList) == 0 { +func expandDimensionValues(tfMap map[string]interface{}) *awstypes.DimensionValues { + if tfMap == nil { return nil } - tfMap := tfList[0].(map[string]interface{}) + apiObject := &awstypes.DimensionValues{} - apiObject := &costexplorer.DimensionValues{} - if v, ok := tfMap["key"]; ok { - apiObject.Key = aws.String(v.(string)) + if v, ok := tfMap["key"].(string); ok && v != "" { + apiObject.Key = awstypes.Dimension(v) } - if v, ok := tfMap["match_options"]; ok { - apiObject.MatchOptions = flex.ExpandStringSet(v.(*schema.Set)) + + if v, ok := tfMap["match_options"].(*schema.Set); ok && v.Len() > 0 { + apiObject.MatchOptions = flex.ExpandStringyValueSet[awstypes.MatchOption](v) } - if v, ok := tfMap["values"]; ok { - apiObject.Values = flex.ExpandStringSet(v.(*schema.Set)) + + if v, ok := tfMap["values"].(*schema.Set); ok && v.Len() > 0 { + apiObject.Values = flex.ExpandStringValueSet(v) } return apiObject } -func expandCostExpressionTag(tfList []interface{}) *costexplorer.TagValues { - if len(tfList) == 0 { +func expandTagValues(tfMap map[string]interface{}) *awstypes.TagValues { + if tfMap == nil { return nil } - tfMap := tfList[0].(map[string]interface{}) + apiObject := &awstypes.TagValues{} - apiObject := &costexplorer.TagValues{} - if v, ok := tfMap["key"]; ok { - apiObject.Key = aws.String(v.(string)) + if v, ok := tfMap["key"].(string); ok && v != "" { + apiObject.Key = aws.String(v) } - if v, ok := tfMap["match_options"]; ok { - apiObject.MatchOptions = flex.ExpandStringSet(v.(*schema.Set)) + + if v, ok := tfMap["match_options"].(*schema.Set); ok && v.Len() > 0 { + apiObject.MatchOptions = flex.ExpandStringyValueSet[awstypes.MatchOption](v) } - if v, ok := tfMap["values"]; ok { - apiObject.Values = flex.ExpandStringSet(v.(*schema.Set)) + + if v, ok := tfMap["values"].(*schema.Set); ok && v.Len() > 0 { + apiObject.Values = flex.ExpandStringValueSet(v) } return apiObject } -func expandCostExpressions(tfList []interface{}) []*costexplorer.Expression { +func expandCostExpressions(tfList []interface{}) []awstypes.Expression { if len(tfList) == 0 { return nil } - var apiObjects []*costexplorer.Expression + var apiObjects []awstypes.Expression for _, tfMapRaw := range tfList { tfMap, ok := tfMapRaw.(map[string]interface{}) - if !ok { continue } apiObject := expandCostExpression(tfMap) - apiObjects = append(apiObjects, apiObject) + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, *apiObject) } return apiObjects } -func expandCostCategoryRules(tfList []interface{}) []*costexplorer.CostCategoryRule { +func expandCostCategoryRules(tfList []interface{}) []awstypes.CostCategoryRule { if len(tfList) == 0 { return nil } - var apiObjects []*costexplorer.CostCategoryRule + var apiObjects []awstypes.CostCategoryRule for _, tfMapRaw := range tfList { tfMap, ok := tfMapRaw.(map[string]interface{}) @@ -673,15 +716,11 @@ func expandCostCategoryRules(tfList []interface{}) []*costexplorer.CostCategoryR return apiObjects } -func expandCostCategorySplitChargeRule(tfMap map[string]interface{}) *costexplorer.CostCategorySplitChargeRule { - if tfMap == nil { - return nil - } - - apiObject := &costexplorer.CostCategorySplitChargeRule{ - Method: aws.String(tfMap["method"].(string)), +func expandCostCategorySplitChargeRule(tfMap map[string]interface{}) awstypes.CostCategorySplitChargeRule { + apiObject := awstypes.CostCategorySplitChargeRule{ + Method: awstypes.CostCategorySplitChargeMethod(tfMap["method"].(string)), Source: aws.String(tfMap["source"].(string)), - Targets: flex.ExpandStringSet(tfMap["targets"].(*schema.Set)), + Targets: flex.ExpandStringValueSet(tfMap["targets"].(*schema.Set)), } if v, ok := tfMap["parameter"]; ok { apiObject.Parameters = expandCostCategorySplitChargeRuleParameters(v.(*schema.Set).List()) @@ -690,25 +729,21 @@ func expandCostCategorySplitChargeRule(tfMap map[string]interface{}) *costexplor return apiObject } -func expandCostCategorySplitChargeRuleParameter(tfMap map[string]interface{}) *costexplorer.CostCategorySplitChargeRuleParameter { - if tfMap == nil { - return nil - } - - apiObject := &costexplorer.CostCategorySplitChargeRuleParameter{ - Type: aws.String(tfMap["type"].(string)), - Values: flex.ExpandStringList(tfMap["values"].([]interface{})), +func expandCostCategorySplitChargeRuleParameter(tfMap map[string]interface{}) awstypes.CostCategorySplitChargeRuleParameter { + apiObject := awstypes.CostCategorySplitChargeRuleParameter{ + Type: awstypes.CostCategorySplitChargeRuleParameterType(tfMap["type"].(string)), + Values: flex.ExpandStringValueList(tfMap["values"].([]interface{})), } return apiObject } -func expandCostCategorySplitChargeRuleParameters(tfList []interface{}) []*costexplorer.CostCategorySplitChargeRuleParameter { +func expandCostCategorySplitChargeRuleParameters(tfList []interface{}) []awstypes.CostCategorySplitChargeRuleParameter { if len(tfList) == 0 { return nil } - var apiObjects []*costexplorer.CostCategorySplitChargeRuleParameter + var apiObjects []awstypes.CostCategorySplitChargeRuleParameter for _, tfMapRaw := range tfList { tfMap, ok := tfMapRaw.(map[string]interface{}) @@ -725,12 +760,12 @@ func expandCostCategorySplitChargeRuleParameters(tfList []interface{}) []*costex return apiObjects } -func expandCostCategorySplitChargeRules(tfList []interface{}) []*costexplorer.CostCategorySplitChargeRule { +func expandCostCategorySplitChargeRules(tfList []interface{}) []awstypes.CostCategorySplitChargeRule { if len(tfList) == 0 { return nil } - var apiObjects []*costexplorer.CostCategorySplitChargeRule + var apiObjects []awstypes.CostCategorySplitChargeRule for _, tfMapRaw := range tfList { tfMap, ok := tfMapRaw.(map[string]interface{}) @@ -747,25 +782,21 @@ func expandCostCategorySplitChargeRules(tfList []interface{}) []*costexplorer.Co return apiObjects } -func flattenCostCategoryRule(apiObject *costexplorer.CostCategoryRule) map[string]interface{} { - if apiObject == nil { - return nil - } - +func flattenCostCategoryRule(apiObject awstypes.CostCategoryRule) map[string]interface{} { tfMap := map[string]interface{}{} - var expressions []*costexplorer.Expression + var expressions []*awstypes.Expression expressions = append(expressions, apiObject.Rule) tfMap["inherited_value"] = flattenCostCategoryRuleInheritedValue(apiObject.InheritedValue) tfMap["rule"] = flattenCostCategoryRuleExpressions(expressions) - tfMap["type"] = aws.StringValue(apiObject.Type) - tfMap["value"] = aws.StringValue(apiObject.Value) + tfMap["type"] = string(apiObject.Type) + tfMap["value"] = aws.ToString(apiObject.Value) return tfMap } -func flattenCostCategoryRuleInheritedValue(apiObject *costexplorer.CostCategoryInheritedValueDimension) []map[string]interface{} { +func flattenCostCategoryRuleInheritedValue(apiObject *awstypes.CostCategoryInheritedValueDimension) []map[string]interface{} { if apiObject == nil { return nil } @@ -773,31 +804,31 @@ func flattenCostCategoryRuleInheritedValue(apiObject *costexplorer.CostCategoryI var tfList []map[string]interface{} tfMap := map[string]interface{}{} - tfMap["dimension_key"] = aws.StringValue(apiObject.DimensionKey) - tfMap["dimension_name"] = aws.StringValue(apiObject.DimensionName) + tfMap["dimension_key"] = aws.ToString(apiObject.DimensionKey) + tfMap["dimension_name"] = string(apiObject.DimensionName) tfList = append(tfList, tfMap) return tfList } -func flattenCostCategoryRuleExpression(apiObject *costexplorer.Expression) map[string]interface{} { +func flattenCostCategoryRuleExpression(apiObject *awstypes.Expression) map[string]interface{} { if apiObject == nil { return nil } tfMap := map[string]interface{}{} - tfMap["and"] = flattenCostCategoryRuleOperandExpressions(apiObject.And) + tfMap["and"] = flattenCostCategoryRuleOperandExpressions(tfslices.ToPointers[[]awstypes.Expression](apiObject.And)) tfMap["cost_category"] = flattenCostCategoryRuleExpressionCostCategory(apiObject.CostCategories) tfMap["dimension"] = flattenCostCategoryRuleExpressionDimension(apiObject.Dimensions) - tfMap["not"] = flattenCostCategoryRuleOperandExpressions([]*costexplorer.Expression{apiObject.Not}) - tfMap["or"] = flattenCostCategoryRuleOperandExpressions(apiObject.Or) + tfMap["not"] = flattenCostCategoryRuleOperandExpressions([]*awstypes.Expression{apiObject.Not}) + tfMap["or"] = flattenCostCategoryRuleOperandExpressions(tfslices.ToPointers[[]awstypes.Expression](apiObject.Or)) tfMap["tags"] = flattenCostCategoryRuleExpressionTag(apiObject.Tags) return tfMap } -func flattenCostCategoryRuleExpressionCostCategory(apiObject *costexplorer.CostCategoryValues) []map[string]interface{} { +func flattenCostCategoryRuleExpressionCostCategory(apiObject *awstypes.CostCategoryValues) []map[string]interface{} { if apiObject == nil { return nil } @@ -805,16 +836,16 @@ func flattenCostCategoryRuleExpressionCostCategory(apiObject *costexplorer.CostC var tfList []map[string]interface{} tfMap := map[string]interface{}{} - tfMap["key"] = aws.StringValue(apiObject.Key) - tfMap["match_options"] = flex.FlattenStringList(apiObject.MatchOptions) - tfMap["values"] = flex.FlattenStringList(apiObject.Values) + tfMap["key"] = aws.ToString(apiObject.Key) + tfMap["match_options"] = flex.FlattenStringyValueList(apiObject.MatchOptions) + tfMap["values"] = apiObject.Values tfList = append(tfList, tfMap) return tfList } -func flattenCostCategoryRuleExpressionDimension(apiObject *costexplorer.DimensionValues) []map[string]interface{} { +func flattenCostCategoryRuleExpressionDimension(apiObject *awstypes.DimensionValues) []map[string]interface{} { if apiObject == nil { return nil } @@ -822,16 +853,16 @@ func flattenCostCategoryRuleExpressionDimension(apiObject *costexplorer.Dimensio var tfList []map[string]interface{} tfMap := map[string]interface{}{} - tfMap["key"] = aws.StringValue(apiObject.Key) - tfMap["match_options"] = flex.FlattenStringList(apiObject.MatchOptions) - tfMap["values"] = flex.FlattenStringList(apiObject.Values) + tfMap["key"] = string(apiObject.Key) + tfMap["match_options"] = flex.FlattenStringyValueList(apiObject.MatchOptions) + tfMap["values"] = apiObject.Values tfList = append(tfList, tfMap) return tfList } -func flattenCostCategoryRuleExpressionTag(apiObject *costexplorer.TagValues) []map[string]interface{} { +func flattenCostCategoryRuleExpressionTag(apiObject *awstypes.TagValues) []map[string]interface{} { if apiObject == nil { return nil } @@ -839,16 +870,16 @@ func flattenCostCategoryRuleExpressionTag(apiObject *costexplorer.TagValues) []m var tfList []map[string]interface{} tfMap := map[string]interface{}{} - tfMap["key"] = aws.StringValue(apiObject.Key) - tfMap["match_options"] = flex.FlattenStringList(apiObject.MatchOptions) - tfMap["values"] = flex.FlattenStringList(apiObject.Values) + tfMap["key"] = aws.ToString(apiObject.Key) + tfMap["match_options"] = flex.FlattenStringyValueList(apiObject.MatchOptions) + tfMap["values"] = apiObject.Values tfList = append(tfList, tfMap) return tfList } -func flattenCostCategoryRuleExpressions(apiObjects []*costexplorer.Expression) []map[string]interface{} { +func flattenCostCategoryRuleExpressions(apiObjects []*awstypes.Expression) []map[string]interface{} { if len(apiObjects) == 0 { return nil } @@ -866,7 +897,7 @@ func flattenCostCategoryRuleExpressions(apiObjects []*costexplorer.Expression) [ return tfList } -func flattenCostCategoryRuleOperandExpression(apiObject *costexplorer.Expression) map[string]interface{} { +func flattenCostCategoryRuleOperandExpression(apiObject *awstypes.Expression) map[string]interface{} { if apiObject == nil { return nil } @@ -879,7 +910,7 @@ func flattenCostCategoryRuleOperandExpression(apiObject *costexplorer.Expression return tfMap } -func flattenCostCategoryRuleOperandExpressions(apiObjects []*costexplorer.Expression) []map[string]interface{} { +func flattenCostCategoryRuleOperandExpressions(apiObjects []*awstypes.Expression) []map[string]interface{} { if len(apiObjects) == 0 { return nil } @@ -897,7 +928,7 @@ func flattenCostCategoryRuleOperandExpressions(apiObjects []*costexplorer.Expres return tfList } -func flattenCostCategoryRules(apiObjects []*costexplorer.CostCategoryRule) []map[string]interface{} { +func flattenCostCategoryRules(apiObjects []awstypes.CostCategoryRule) []map[string]interface{} { if len(apiObjects) == 0 { return nil } @@ -905,43 +936,31 @@ func flattenCostCategoryRules(apiObjects []*costexplorer.CostCategoryRule) []map var tfList []map[string]interface{} for _, apiObject := range apiObjects { - if apiObject == nil { - continue - } - tfList = append(tfList, flattenCostCategoryRule(apiObject)) } return tfList } -func flattenCostCategorySplitChargeRule(apiObject *costexplorer.CostCategorySplitChargeRule) map[string]interface{} { - if apiObject == nil { - return nil - } - +func flattenCostCategorySplitChargeRule(apiObject awstypes.CostCategorySplitChargeRule) map[string]interface{} { tfMap := map[string]interface{}{} - tfMap["method"] = aws.StringValue(apiObject.Method) + tfMap["method"] = string(apiObject.Method) tfMap["parameter"] = flattenCostCategorySplitChargeRuleParameters(apiObject.Parameters) - tfMap["source"] = aws.StringValue(apiObject.Source) - tfMap["targets"] = flex.FlattenStringList(apiObject.Targets) + tfMap["source"] = aws.ToString(apiObject.Source) + tfMap["targets"] = apiObject.Targets return tfMap } -func flattenCostCategorySplitChargeRuleParameter(apiObject *costexplorer.CostCategorySplitChargeRuleParameter) map[string]interface{} { - if apiObject == nil { - return nil - } - +func flattenCostCategorySplitChargeRuleParameter(apiObject awstypes.CostCategorySplitChargeRuleParameter) map[string]interface{} { tfMap := map[string]interface{}{} - tfMap["type"] = aws.StringValue(apiObject.Type) - tfMap["values"] = flex.FlattenStringList(apiObject.Values) + tfMap["type"] = string(apiObject.Type) + tfMap["values"] = apiObject.Values return tfMap } -func flattenCostCategorySplitChargeRuleParameters(apiObjects []*costexplorer.CostCategorySplitChargeRuleParameter) []map[string]interface{} { +func flattenCostCategorySplitChargeRuleParameters(apiObjects []awstypes.CostCategorySplitChargeRuleParameter) []map[string]interface{} { if len(apiObjects) == 0 { return nil } @@ -949,17 +968,13 @@ func flattenCostCategorySplitChargeRuleParameters(apiObjects []*costexplorer.Cos var tfList []map[string]interface{} for _, apiObject := range apiObjects { - if apiObject == nil { - continue - } - tfList = append(tfList, flattenCostCategorySplitChargeRuleParameter(apiObject)) } return tfList } -func flattenCostCategorySplitChargeRules(apiObjects []*costexplorer.CostCategorySplitChargeRule) []map[string]interface{} { +func flattenCostCategorySplitChargeRules(apiObjects []awstypes.CostCategorySplitChargeRule) []map[string]interface{} { if len(apiObjects) == 0 { return nil } @@ -967,10 +982,6 @@ func flattenCostCategorySplitChargeRules(apiObjects []*costexplorer.CostCategory var tfList []map[string]interface{} for _, apiObject := range apiObjects { - if apiObject == nil { - continue - } - tfList = append(tfList, flattenCostCategorySplitChargeRule(apiObject)) } diff --git a/internal/service/ce/cost_category_data_source.go b/internal/service/ce/cost_category_data_source.go index fb1ff675f0d..ccf7fb13c5e 100644 --- a/internal/service/ce/cost_category_data_source.go +++ b/internal/service/ce/cost_category_data_source.go @@ -6,350 +6,167 @@ package ce import ( "context" - "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/sdkv2" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" - "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKDataSource("aws_ce_cost_category") -func DataSourceCostCategory() *schema.Resource { - schemaCostCategoryRuleExpressionComputed := func() *schema.Resource { - return &schema.Resource{ - Schema: map[string]*schema.Schema{ - "cost_category": { - Type: schema.TypeList, +// @SDKDataSource("aws_ce_cost_category", name="Cost Category") +func dataSourceCostCategory() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceCostCategoryRead, + + SchemaFunc: func() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "cost_category_arn": { + Type: schema.TypeString, + Required: true, + }, + "default_value": { + Type: schema.TypeString, Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Computed: true, - }, - "match_options": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "values": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - }, - }, }, - "dimension": { - Type: schema.TypeList, + "effective_end": { + Type: schema.TypeString, Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Computed: true, - }, - "match_options": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "values": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - }, - }, }, - "tags": { - Type: schema.TypeList, + "effective_start": { + Type: schema.TypeString, Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Computed: true, - }, - "match_options": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "values": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - }, - }, }, - }, - } - } - schemaCostCategoryRuleComputed := func() *schema.Resource { - return &schema.Resource{ - Schema: map[string]*schema.Schema{ - "and": { - Type: schema.TypeSet, + "name": { + Type: schema.TypeString, Computed: true, - Elem: schemaCostCategoryRuleExpressionComputed(), }, - "cost_category": { - Type: schema.TypeList, + "rule": { + Type: schema.TypeSet, Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Computed: true, - }, - "match_options": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, + "inherited_value": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "dimension_key": { + Type: schema.TypeString, + Computed: true, + }, + "dimension_name": { + Type: schema.TypeString, + Computed: true, + }, + }, }, }, - "values": { - Type: schema.TypeSet, + "rule": { + Type: schema.TypeList, Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, + Elem: sdkv2.DataSourceElemFromResourceElem(elemExpression()), }, - }, - }, - }, - "dimension": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { + "type": { Type: schema.TypeString, Computed: true, }, - "match_options": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "values": { - Type: schema.TypeSet, + "value": { + Type: schema.TypeString, Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, }, }, }, }, - "not": { - Type: schema.TypeList, + "rule_version": { + Type: schema.TypeString, Computed: true, - Elem: schemaCostCategoryRuleExpressionComputed(), }, - "or": { + "split_charge_rule": { Type: schema.TypeSet, Computed: true, - Elem: schemaCostCategoryRuleExpressionComputed(), - }, - "tags": { - Type: schema.TypeList, - Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "key": { + "method": { Type: schema.TypeString, Computed: true, }, - "match_options": { + "parameter": { Type: schema.TypeSet, Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Computed: true, + }, + "values": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(0, 1024), + }, + }, + }, }, }, - "values": { + "source": { + Type: schema.TypeString, + Computed: true, + }, + "targets": { Type: schema.TypeSet, Computed: true, Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - }, - }, - }, - }, - } - } - - return &schema.Resource{ - ReadWithoutTimeout: dataSourceCostCategoryRead, - - Schema: map[string]*schema.Schema{ - "cost_category_arn": { - Type: schema.TypeString, - Required: true, - }, - "default_value": { - Type: schema.TypeString, - Computed: true, - }, - "effective_end": { - Type: schema.TypeString, - Computed: true, - }, - "effective_start": { - Type: schema.TypeString, - Computed: true, - }, - "name": { - Type: schema.TypeString, - Computed: true, - }, - "rule": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "inherited_value": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "dimension_key": { - Type: schema.TypeString, - Computed: true, - }, - "dimension_name": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - "rule": { - Type: schema.TypeList, - Computed: true, - Elem: schemaCostCategoryRuleComputed(), - }, - "type": { - Type: schema.TypeString, - Computed: true, - }, - "value": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - "rule_version": { - Type: schema.TypeString, - Computed: true, - }, - "split_charge_rule": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "method": { - Type: schema.TypeString, - Computed: true, - }, - "parameter": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Computed: true, - }, - "values": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringLenBetween(0, 1024), - }, - }, + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(0, 1024), }, }, }, - "source": { - Type: schema.TypeString, - Computed: true, - }, - "targets": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringLenBetween(0, 1024), - }, - }, }, }, - }, - "tags": tftags.TagsSchemaComputed(), + "tags": tftags.TagsSchemaComputed(), + } }, } } func dataSourceCostCategoryRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - - conn := meta.(*conns.AWSClient).CEConn(ctx) + conn := meta.(*conns.AWSClient).CEClient(ctx) ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - costCategory, err := FindCostCategoryByARN(ctx, conn, d.Get("cost_category_arn").(string)) + arn := d.Get("cost_category_arn").(string) + costCategory, err := findCostCategoryByARN(ctx, conn, arn) if err != nil { - return create.AppendDiagError(diags, names.CE, create.ErrActionReading, ResNameCostCategory, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading Cost Explorer Cost Category (%s): %s", arn, err) } + d.SetId(aws.ToString(costCategory.CostCategoryArn)) d.Set("default_value", costCategory.DefaultValue) d.Set("effective_end", costCategory.EffectiveEnd) d.Set("effective_start", costCategory.EffectiveStart) d.Set("name", costCategory.Name) if err = d.Set("rule", flattenCostCategoryRules(costCategory.Rules)); err != nil { - return create.AppendDiagError(diags, names.CE, "setting rule", ResNameCostCategory, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "setting rule: %s", err) } d.Set("rule_version", costCategory.RuleVersion) if err = d.Set("split_charge_rule", flattenCostCategorySplitChargeRules(costCategory.SplitChargeRules)); err != nil { - return create.AppendDiagError(diags, names.CE, "setting split_charge_rule", ResNameCostCategory, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "setting split_charge_rule: %s", err) } - d.SetId(aws.StringValue(costCategory.CostCategoryArn)) - tags, err := listTags(ctx, conn, d.Id()) if err != nil { - return create.AppendDiagError(diags, names.CE, "listing tags", ResNameCostCategory, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "listing Cost Explorer Cost Category (%s) tags: %s", d.Id(), err) } if err := d.Set("tags", tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { - return create.AppendDiagError(diags, names.CE, "setting tags", ResNameCostCategory, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "setting split_charge_rule: %s", err) } return diags diff --git a/internal/service/ce/cost_category_data_source_test.go b/internal/service/ce/cost_category_data_source_test.go index be13b2ee223..5b49ef3c4af 100644 --- a/internal/service/ce/cost_category_data_source_test.go +++ b/internal/service/ce/cost_category_data_source_test.go @@ -6,7 +6,7 @@ package ce_test import ( "testing" - "github.com/aws/aws-sdk-go/service/costexplorer" + awstypes "github.com/aws/aws-sdk-go-v2/service/costexplorer/types" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-provider-aws/internal/acctest" @@ -15,7 +15,7 @@ import ( func TestAccCECostCategoryDataSource_basic(t *testing.T) { ctx := acctest.Context(t) - var output costexplorer.CostCategory + var output awstypes.CostCategory resourceName := "aws_ce_cost_category.test" dataSourceName := "data.aws_ce_cost_category.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) diff --git a/internal/service/ce/cost_category_test.go b/internal/service/ce/cost_category_test.go index 200b1b839f0..7df532737ac 100644 --- a/internal/service/ce/cost_category_test.go +++ b/internal/service/ce/cost_category_test.go @@ -7,9 +7,10 @@ import ( "context" "fmt" "testing" + "time" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/service/costexplorer" + awstypes "github.com/aws/aws-sdk-go-v2/service/costexplorer/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" @@ -22,7 +23,7 @@ import ( func TestAccCECostCategory_basic(t *testing.T) { ctx := acctest.Context(t) - var output costexplorer.CostCategory + var output awstypes.CostCategory resourceName := "aws_ce_cost_category.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -52,9 +53,15 @@ func TestAccCECostCategory_basic(t *testing.T) { func TestAccCECostCategory_effectiveStart(t *testing.T) { ctx := acctest.Context(t) - var output costexplorer.CostCategory + var output awstypes.CostCategory resourceName := "aws_ce_cost_category.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + firstDayOfMonth := func(v time.Time) string { + return time.Date(v.Year(), v.Month(), 1, 0, 0, 0, 0, time.UTC).Format(time.RFC3339) + } + now := time.Now() + firstOfThisMonth := firstDayOfMonth(now) + firstOfLastMonth := firstDayOfMonth(now.AddDate(0, -1, 0)) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, @@ -63,11 +70,11 @@ func TestAccCECostCategory_effectiveStart(t *testing.T) { ErrorCheck: acctest.ErrorCheck(t, names.CEServiceID), Steps: []resource.TestStep{ { - Config: testAccCostCategoryConfig_effectiveStart(rName, "2022-11-01T00:00:00Z"), + Config: testAccCostCategoryConfig_effectiveStart(rName, firstOfThisMonth), Check: resource.ComposeTestCheckFunc( testAccCheckCostCategoryExists(ctx, resourceName, &output), resource.TestCheckResourceAttr(resourceName, "name", rName), - resource.TestCheckResourceAttr(resourceName, "effective_start", "2022-11-01T00:00:00Z"), + resource.TestCheckResourceAttr(resourceName, "effective_start", firstOfThisMonth), ), }, { @@ -76,11 +83,11 @@ func TestAccCECostCategory_effectiveStart(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccCostCategoryConfig_effectiveStart(rName, "2022-10-01T00:00:00Z"), + Config: testAccCostCategoryConfig_effectiveStart(rName, firstOfLastMonth), Check: resource.ComposeTestCheckFunc( testAccCheckCostCategoryExists(ctx, resourceName, &output), resource.TestCheckResourceAttr(resourceName, "name", rName), - resource.TestCheckResourceAttr(resourceName, "effective_start", "2022-10-01T00:00:00Z"), + resource.TestCheckResourceAttr(resourceName, "effective_start", firstOfLastMonth), ), }, }, @@ -89,7 +96,7 @@ func TestAccCECostCategory_effectiveStart(t *testing.T) { func TestAccCECostCategory_disappears(t *testing.T) { ctx := acctest.Context(t) - var output costexplorer.CostCategory + var output awstypes.CostCategory resourceName := "aws_ce_cost_category.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -113,7 +120,7 @@ func TestAccCECostCategory_disappears(t *testing.T) { func TestAccCECostCategory_complete(t *testing.T) { ctx := acctest.Context(t) - var output costexplorer.CostCategory + var output awstypes.CostCategory resourceName := "aws_ce_cost_category.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -148,7 +155,7 @@ func TestAccCECostCategory_complete(t *testing.T) { func TestAccCECostCategory_splitCharge(t *testing.T) { ctx := acctest.Context(t) - var output costexplorer.CostCategory + var output awstypes.CostCategory resourceName := "aws_ce_cost_category.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -183,7 +190,7 @@ func TestAccCECostCategory_splitCharge(t *testing.T) { func TestAccCECostCategory_tags(t *testing.T) { ctx := acctest.Context(t) - var output costexplorer.CostCategory + var output awstypes.CostCategory resourceName := "aws_ce_cost_category.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -227,18 +234,14 @@ func TestAccCECostCategory_tags(t *testing.T) { }) } -func testAccCheckCostCategoryExists(ctx context.Context, n string, v *costexplorer.CostCategory) resource.TestCheckFunc { +func testAccCheckCostCategoryExists(ctx context.Context, n string, v *awstypes.CostCategory) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return fmt.Errorf("No CE Cost Category ID is set") - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).CEConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).CEClient(ctx) output, err := tfce.FindCostCategoryByARN(ctx, conn, rs.Primary.ID) @@ -254,7 +257,7 @@ func testAccCheckCostCategoryExists(ctx context.Context, n string, v *costexplor func testAccCheckCostCategoryDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).CEConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).CEClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_ce_cost_category" { diff --git a/internal/service/ce/exports_test.go b/internal/service/ce/exports_test.go new file mode 100644 index 00000000000..57dde1dec05 --- /dev/null +++ b/internal/service/ce/exports_test.go @@ -0,0 +1,17 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ce + +// Exports for use in tests only. +var ( + ResourceAnomalyMonitor = resourceAnomalyMonitor // nosemgrep:ci.ce-in-var-name + ResourceAnomalySubscription = resourceAnomalySubscription // nosemgrep:ci.ce-in-var-name + ResourceCostAllocationTag = resourceCostAllocationTag // nosemgrep:ci.ce-in-var-name + ResourceCostCategory = resourceCostCategory // nosemgrep:ci.ce-in-var-name + + FindAnomalyMonitorByARN = findAnomalyMonitorByARN + FindAnomalySubscriptionByARN = findAnomalySubscriptionByARN + FindCostAllocationTagByTagKey = findCostAllocationTagByTagKey + FindCostCategoryByARN = findCostCategoryByARN +) diff --git a/internal/service/ce/find.go b/internal/service/ce/find.go deleted file mode 100644 index d6553ae01e6..00000000000 --- a/internal/service/ce/find.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package ce - -import ( - "context" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/costexplorer" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" -) - -func FindAnomalyMonitorByARN(ctx context.Context, conn *costexplorer.CostExplorer, arn string) (*costexplorer.AnomalyMonitor, error) { - in := &costexplorer.GetAnomalyMonitorsInput{ - MonitorArnList: aws.StringSlice([]string{arn}), - MaxResults: aws.Int64(1), - } - - out, err := conn.GetAnomalyMonitorsWithContext(ctx, in) - - if tfawserr.ErrCodeEquals(err, costexplorer.ErrCodeUnknownMonitorException) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: in, - } - } - - if err != nil { - return nil, err - } - - if out == nil || len(out.AnomalyMonitors) == 0 || out.AnomalyMonitors[0] == nil { - return nil, tfresource.NewEmptyResultError(in) - } - - return out.AnomalyMonitors[0], nil -} - -func FindAnomalySubscriptionByARN(ctx context.Context, conn *costexplorer.CostExplorer, arn string) (*costexplorer.AnomalySubscription, error) { - in := &costexplorer.GetAnomalySubscriptionsInput{ - SubscriptionArnList: aws.StringSlice([]string{arn}), - MaxResults: aws.Int64(1), - } - - out, err := conn.GetAnomalySubscriptionsWithContext(ctx, in) - - if tfawserr.ErrCodeEquals(err, costexplorer.ErrCodeUnknownMonitorException) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: in, - } - } - - if err != nil { - return nil, err - } - - if out == nil || len(out.AnomalySubscriptions) == 0 || out.AnomalySubscriptions[0] == nil { - return nil, tfresource.NewEmptyResultError(in) - } - - return out.AnomalySubscriptions[0], nil -} - -func FindCostAllocationTagByKey(ctx context.Context, conn *costexplorer.CostExplorer, key string) (*costexplorer.CostAllocationTag, error) { - in := &costexplorer.ListCostAllocationTagsInput{ - TagKeys: aws.StringSlice([]string{key}), - MaxResults: aws.Int64(1), - } - - out, err := conn.ListCostAllocationTagsWithContext(ctx, in) - - if tfawserr.ErrCodeEquals(err, costexplorer.ErrCodeUnknownMonitorException) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: in, - } - } - - if err != nil { - return nil, err - } - - if out == nil || len(out.CostAllocationTags) == 0 || out.CostAllocationTags[0] == nil { - return nil, tfresource.NewEmptyResultError(in) - } - - return out.CostAllocationTags[0], nil -} - -func FindCostCategoryByARN(ctx context.Context, conn *costexplorer.CostExplorer, arn string) (*costexplorer.CostCategory, error) { - in := &costexplorer.DescribeCostCategoryDefinitionInput{ - CostCategoryArn: aws.String(arn), - } - - out, err := conn.DescribeCostCategoryDefinitionWithContext(ctx, in) - - if tfawserr.ErrCodeEquals(err, costexplorer.ErrCodeResourceNotFoundException) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: in, - } - } - - if err != nil { - return nil, err - } - - if out == nil || out.CostCategory == nil { - return nil, tfresource.NewEmptyResultError(in) - } - - return out.CostCategory, nil -} diff --git a/internal/service/ce/generate.go b/internal/service/ce/generate.go index 4336a942c2c..f7d3be3eb43 100644 --- a/internal/service/ce/generate.go +++ b/internal/service/ce/generate.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -//go:generate go run ../../generate/tags/main.go -ListTags -ListTagsOutTagsElem=ResourceTags -ServiceTagsSlice -TagInTagsElem=ResourceTags -UpdateTags -UntagInTagsElem=ResourceTagKeys -UntagInTagsElem=ResourceTagKeys -TagType=ResourceTag +//go:generate go run ../../generate/tags/main.go -AWSSDKVersion=2 -ListTags -ListTagsOutTagsElem=ResourceTags -ServiceTagsSlice -TagInTagsElem=ResourceTags -UpdateTags -UntagInTagsElem=ResourceTagKeys -UntagInTagsElem=ResourceTagKeys -TagType=ResourceTag //go:generate go run ../../generate/servicepackage/main.go // ONLY generate directives and package declaration! Do not add anything else to this file. diff --git a/internal/service/ce/service_endpoints_gen_test.go b/internal/service/ce/service_endpoints_gen_test.go index 328490945c3..dd33d66552c 100644 --- a/internal/service/ce/service_endpoints_gen_test.go +++ b/internal/service/ce/service_endpoints_gen_test.go @@ -4,17 +4,17 @@ package ce_test import ( "context" + "errors" "fmt" "maps" - "net/url" "os" "path/filepath" "reflect" "strings" "testing" - "github.com/aws/aws-sdk-go/aws/endpoints" - costexplorer_sdkv1 "github.com/aws/aws-sdk-go/service/costexplorer" + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + costexplorer_sdkv2 "github.com/aws/aws-sdk-go-v2/service/costexplorer" "github.com/aws/smithy-go/middleware" smithyhttp "github.com/aws/smithy-go/transport/http" "github.com/google/go-cmp/cmp" @@ -266,32 +266,42 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S } func defaultEndpoint(region string) string { - r := endpoints.DefaultResolver() + r := costexplorer_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.EndpointFor(costexplorer_sdkv1.EndpointsID, region) + ep, err := r.ResolveEndpoint(context.Background(), costexplorer_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) if err != nil { return err.Error() } - url, _ := url.Parse(ep.URL) - - if url.Path == "" { - url.Path = "/" + if ep.URI.Path == "" { + ep.URI.Path = "/" } - return url.String() + return ep.URI.String() } func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { t.Helper() - client := meta.CEConn(ctx) - - req, _ := client.ListCostCategoryDefinitionsRequest(&costexplorer_sdkv1.ListCostCategoryDefinitionsInput{}) + var endpoint string - req.HTTPRequest.URL.Path = "/" + client := meta.CEClient(ctx) - endpoint := req.HTTPRequest.URL.String() + _, err := client.ListCostCategoryDefinitions(ctx, &costexplorer_sdkv2.ListCostCategoryDefinitionsInput{}, + func(opts *costexplorer_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } return endpoint } diff --git a/internal/service/ce/service_package_gen.go b/internal/service/ce/service_package_gen.go index 2dde43272d5..061e23075b7 100644 --- a/internal/service/ce/service_package_gen.go +++ b/internal/service/ce/service_package_gen.go @@ -5,9 +5,8 @@ package ce import ( "context" - aws_sdkv1 "github.com/aws/aws-sdk-go/aws" - session_sdkv1 "github.com/aws/aws-sdk-go/aws/session" - costexplorer_sdkv1 "github.com/aws/aws-sdk-go/service/costexplorer" + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + costexplorer_sdkv2 "github.com/aws/aws-sdk-go-v2/service/costexplorer" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/types" "github.com/hashicorp/terraform-provider-aws/names" @@ -26,12 +25,14 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { return []*types.ServicePackageSDKDataSource{ { - Factory: DataSourceCostCategory, + Factory: dataSourceCostCategory, TypeName: "aws_ce_cost_category", + Name: "Cost Category", }, { - Factory: DataSourceTags, + Factory: dataSourceTags, TypeName: "aws_ce_tags", + Name: "Tags", }, } } @@ -39,7 +40,7 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePackageSDKResource { return []*types.ServicePackageSDKResource{ { - Factory: ResourceAnomalyMonitor, + Factory: resourceAnomalyMonitor, TypeName: "aws_ce_anomaly_monitor", Name: "Anomaly Monitor", Tags: &types.ServicePackageResourceTags{ @@ -47,7 +48,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka }, }, { - Factory: ResourceAnomalySubscription, + Factory: resourceAnomalySubscription, TypeName: "aws_ce_anomaly_subscription", Name: "Anomaly Subscription", Tags: &types.ServicePackageResourceTags{ @@ -55,11 +56,12 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka }, }, { - Factory: ResourceCostAllocationTag, + Factory: resourceCostAllocationTag, TypeName: "aws_ce_cost_allocation_tag", + Name: "Cost Allocation Tag", }, { - Factory: ResourceCostCategory, + Factory: resourceCostCategory, TypeName: "aws_ce_cost_category", Name: "Cost Category", Tags: &types.ServicePackageResourceTags{ @@ -73,11 +75,15 @@ func (p *servicePackage) ServicePackageName() string { return names.CE } -// NewConn returns a new AWS SDK for Go v1 client for this service package's AWS API. -func (p *servicePackage) NewConn(ctx context.Context, config map[string]any) (*costexplorer_sdkv1.CostExplorer, error) { - sess := config["session"].(*session_sdkv1.Session) +// NewClient returns a new AWS SDK for Go v2 client for this service package's AWS API. +func (p *servicePackage) NewClient(ctx context.Context, config map[string]any) (*costexplorer_sdkv2.Client, error) { + cfg := *(config["aws_sdkv2_config"].(*aws_sdkv2.Config)) - return costexplorer_sdkv1.New(sess.Copy(&aws_sdkv1.Config{Endpoint: aws_sdkv1.String(config["endpoint"].(string))})), nil + return costexplorer_sdkv2.NewFromConfig(cfg, func(o *costexplorer_sdkv2.Options) { + if endpoint := config["endpoint"].(string); endpoint != "" { + o.BaseEndpoint = aws_sdkv2.String(endpoint) + } + }), nil } func ServicePackage(ctx context.Context) conns.ServicePackage { diff --git a/internal/service/ce/tags_data_source.go b/internal/service/ce/tags_data_source.go index c2c6274213b..2ed6750a1a7 100644 --- a/internal/service/ce/tags_data_source.go +++ b/internal/service/ce/tags_data_source.go @@ -6,30 +6,28 @@ package ce import ( "context" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/costexplorer" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/costexplorer" + awstypes "github.com/aws/aws-sdk-go-v2/service/costexplorer/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/create" - "github.com/hashicorp/terraform-provider-aws/internal/flex" - "github.com/hashicorp/terraform-provider-aws/names" + "github.com/hashicorp/terraform-provider-aws/internal/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" ) -// @SDKDataSource("aws_ce_tags") -func DataSourceTags() *schema.Resource { +// @SDKDataSource("aws_ce_tags", name="Tags") +func dataSourceTags() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataSourceTagsRead, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, + Schema: map[string]*schema.Schema{ "filter": { Type: schema.TypeList, MaxItems: 1, Optional: true, - Elem: schemaCostCategoryRule(), + Elem: elemExpression(), }, "search_string": { Type: schema.TypeString, @@ -44,14 +42,14 @@ func DataSourceTags() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(costexplorer.Metric_Values(), false), + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[awstypes.Metric](), }, "sort_order": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(costexplorer.SortOrder_Values(), false), + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[awstypes.SortOrder](), }, }, }, @@ -93,15 +91,14 @@ func DataSourceTags() *schema.Resource { func dataSourceTagsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - - conn := meta.(*conns.AWSClient).CEConn(ctx) + conn := meta.(*conns.AWSClient).CEClient(ctx) input := &costexplorer.GetTagsInput{ TimePeriod: expandTagsTimePeriod(d.Get("time_period").([]interface{})[0].(map[string]interface{})), } - if v, ok := d.GetOk("filter"); ok { - input.Filter = expandCostExpressions(v.([]interface{}))[0] + if v, ok := d.GetOk("filter"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.Filter = expandCostExpression(v.([]interface{})[0].(map[string]interface{})) } if v, ok := d.GetOk("search_string"); ok { @@ -116,25 +113,24 @@ func dataSourceTagsRead(ctx context.Context, d *schema.ResourceData, meta interf input.TagKey = aws.String(v.(string)) } - resp, err := conn.GetTagsWithContext(ctx, input) + output, err := conn.GetTags(ctx, input) if err != nil { - return create.AppendDiagError(diags, names.CE, create.ErrActionReading, DSNameTags, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading Cost Explorer Tags: %s", err) } - d.Set("tags", flex.FlattenStringList(resp.Tags)) - d.SetId(meta.(*conns.AWSClient).AccountID) + d.Set("tags", output.Tags) return diags } -func expandTagsSortBys(tfList []interface{}) []*costexplorer.SortDefinition { +func expandTagsSortBys(tfList []interface{}) []awstypes.SortDefinition { if len(tfList) == 0 { return nil } - var apiObjects []*costexplorer.SortDefinition + var apiObjects []awstypes.SortDefinition for _, tfMapRaw := range tfList { tfMap, ok := tfMapRaw.(map[string]interface{}) @@ -151,26 +147,22 @@ func expandTagsSortBys(tfList []interface{}) []*costexplorer.SortDefinition { return apiObjects } -func expandTagsSortBy(tfMap map[string]interface{}) *costexplorer.SortDefinition { - if tfMap == nil { - return nil - } - - apiObject := &costexplorer.SortDefinition{} +func expandTagsSortBy(tfMap map[string]interface{}) awstypes.SortDefinition { + apiObject := awstypes.SortDefinition{} apiObject.Key = aws.String(tfMap["key"].(string)) if v, ok := tfMap["sort_order"]; ok { - apiObject.SortOrder = aws.String(v.(string)) + apiObject.SortOrder = awstypes.SortOrder(v.(string)) } return apiObject } -func expandTagsTimePeriod(tfMap map[string]interface{}) *costexplorer.DateInterval { +func expandTagsTimePeriod(tfMap map[string]interface{}) *awstypes.DateInterval { if tfMap == nil { return nil } - apiObject := &costexplorer.DateInterval{} + apiObject := &awstypes.DateInterval{} apiObject.Start = aws.String(tfMap["start"].(string)) apiObject.End = aws.String(tfMap["end"].(string)) diff --git a/internal/service/ce/tags_data_source_test.go b/internal/service/ce/tags_data_source_test.go index 97576bff012..12d9f1c916b 100644 --- a/internal/service/ce/tags_data_source_test.go +++ b/internal/service/ce/tags_data_source_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/aws/aws-sdk-go/service/costexplorer" + awstypes "github.com/aws/aws-sdk-go-v2/service/costexplorer/types" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-provider-aws/internal/acctest" @@ -17,7 +17,7 @@ import ( func TestAccCETagsDataSource_basic(t *testing.T) { ctx := acctest.Context(t) - var output costexplorer.CostCategory + var output awstypes.CostCategory resourceName := "aws_ce_cost_category.test" dataSourceName := "data.aws_ce_tags.test" rName := sdkacctest.RandomWithPrefix("tf-acc-test") @@ -46,7 +46,7 @@ func TestAccCETagsDataSource_basic(t *testing.T) { func TestAccCETagsDataSource_filter(t *testing.T) { ctx := acctest.Context(t) - var output costexplorer.CostCategory + var output awstypes.CostCategory resourceName := "aws_ce_cost_category.test" dataSourceName := "data.aws_ce_tags.test" rName := sdkacctest.RandomWithPrefix("tf-acc-test") diff --git a/internal/service/ce/tags_gen.go b/internal/service/ce/tags_gen.go index 726ac69dc83..eb9025f560d 100644 --- a/internal/service/ce/tags_gen.go +++ b/internal/service/ce/tags_gen.go @@ -5,9 +5,9 @@ import ( "context" "fmt" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/costexplorer" - "github.com/aws/aws-sdk-go/service/costexplorer/costexploreriface" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/costexplorer" + awstypes "github.com/aws/aws-sdk-go-v2/service/costexplorer/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/logging" @@ -19,12 +19,12 @@ import ( // listTags lists ce service tags. // The identifier is typically the Amazon Resource Name (ARN), although // it may also be a different identifier depending on the service. -func listTags(ctx context.Context, conn costexploreriface.CostExplorerAPI, identifier string) (tftags.KeyValueTags, error) { +func listTags(ctx context.Context, conn *costexplorer.Client, identifier string, optFns ...func(*costexplorer.Options)) (tftags.KeyValueTags, error) { input := &costexplorer.ListTagsForResourceInput{ ResourceArn: aws.String(identifier), } - output, err := conn.ListTagsForResourceWithContext(ctx, input) + output, err := conn.ListTagsForResource(ctx, input, optFns...) if err != nil { return tftags.New(ctx, nil), err @@ -36,7 +36,7 @@ func listTags(ctx context.Context, conn costexploreriface.CostExplorerAPI, ident // ListTags lists ce service tags and set them in Context. // It is called from outside this package. func (p *servicePackage) ListTags(ctx context.Context, meta any, identifier string) error { - tags, err := listTags(ctx, meta.(*conns.AWSClient).CEConn(ctx), identifier) + tags, err := listTags(ctx, meta.(*conns.AWSClient).CEClient(ctx), identifier) if err != nil { return err @@ -52,11 +52,11 @@ func (p *servicePackage) ListTags(ctx context.Context, meta any, identifier stri // []*SERVICE.Tag handling // Tags returns ce service tags. -func Tags(tags tftags.KeyValueTags) []*costexplorer.ResourceTag { - result := make([]*costexplorer.ResourceTag, 0, len(tags)) +func Tags(tags tftags.KeyValueTags) []awstypes.ResourceTag { + result := make([]awstypes.ResourceTag, 0, len(tags)) for k, v := range tags.Map() { - tag := &costexplorer.ResourceTag{ + tag := awstypes.ResourceTag{ Key: aws.String(k), Value: aws.String(v), } @@ -68,11 +68,11 @@ func Tags(tags tftags.KeyValueTags) []*costexplorer.ResourceTag { } // KeyValueTags creates tftags.KeyValueTags from costexplorer service tags. -func KeyValueTags(ctx context.Context, tags []*costexplorer.ResourceTag) tftags.KeyValueTags { +func KeyValueTags(ctx context.Context, tags []awstypes.ResourceTag) tftags.KeyValueTags { m := make(map[string]*string, len(tags)) for _, tag := range tags { - m[aws.StringValue(tag.Key)] = tag.Value + m[aws.ToString(tag.Key)] = tag.Value } return tftags.New(ctx, m) @@ -80,7 +80,7 @@ func KeyValueTags(ctx context.Context, tags []*costexplorer.ResourceTag) tftags. // getTagsIn returns ce service tags from Context. // nil is returned if there are no input tags. -func getTagsIn(ctx context.Context) []*costexplorer.ResourceTag { +func getTagsIn(ctx context.Context) []awstypes.ResourceTag { if inContext, ok := tftags.FromContext(ctx); ok { if tags := Tags(inContext.TagsIn.UnwrapOrDefault()); len(tags) > 0 { return tags @@ -91,7 +91,7 @@ func getTagsIn(ctx context.Context) []*costexplorer.ResourceTag { } // setTagsOut sets ce service tags in Context. -func setTagsOut(ctx context.Context, tags []*costexplorer.ResourceTag) { +func setTagsOut(ctx context.Context, tags []awstypes.ResourceTag) { if inContext, ok := tftags.FromContext(ctx); ok { inContext.TagsOut = option.Some(KeyValueTags(ctx, tags)) } @@ -100,7 +100,7 @@ func setTagsOut(ctx context.Context, tags []*costexplorer.ResourceTag) { // updateTags updates ce 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 costexploreriface.CostExplorerAPI, identifier string, oldTagsMap, newTagsMap any) error { +func updateTags(ctx context.Context, conn *costexplorer.Client, identifier string, oldTagsMap, newTagsMap any, optFns ...func(*costexplorer.Options)) error { oldTags := tftags.New(ctx, oldTagsMap) newTags := tftags.New(ctx, newTagsMap) @@ -111,10 +111,10 @@ func updateTags(ctx context.Context, conn costexploreriface.CostExplorerAPI, ide if len(removedTags) > 0 { input := &costexplorer.UntagResourceInput{ ResourceArn: aws.String(identifier), - ResourceTagKeys: aws.StringSlice(removedTags.Keys()), + ResourceTagKeys: removedTags.Keys(), } - _, err := conn.UntagResourceWithContext(ctx, input) + _, err := conn.UntagResource(ctx, input, optFns...) if err != nil { return fmt.Errorf("untagging resource (%s): %w", identifier, err) @@ -129,7 +129,7 @@ func updateTags(ctx context.Context, conn costexploreriface.CostExplorerAPI, ide ResourceTags: Tags(updatedTags), } - _, err := conn.TagResourceWithContext(ctx, input) + _, err := conn.TagResource(ctx, input, optFns...) if err != nil { return fmt.Errorf("tagging resource (%s): %w", identifier, err) @@ -142,5 +142,5 @@ func updateTags(ctx context.Context, conn costexploreriface.CostExplorerAPI, ide // UpdateTags updates ce 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).CEConn(ctx), identifier, oldTags, newTags) + return updateTags(ctx, meta.(*conns.AWSClient).CEClient(ctx), identifier, oldTags, newTags) } diff --git a/internal/slices/slices.go b/internal/slices/slices.go index 147d788c696..95193e9d84f 100644 --- a/internal/slices/slices.go +++ b/internal/slices/slices.go @@ -48,6 +48,13 @@ func ToPointers[S ~[]E, E any](s S) []*E { }) } +// Values returns a new slice containing values from the pointers in each element of the original slice `s`. +func Values[S ~[]*E, E any](s S) []E { + return ApplyToAll(s, func(e *E) E { + return *e + }) +} + // Predicate represents a predicate (boolean-valued function) of one argument. type Predicate[T any] func(T) bool diff --git a/names/data/names_data.csv b/names/data/names_data.csv index 2e28f22ad32..08754f2f0c8 100644 --- a/names/data/names_data.csv +++ b/names/data/names_data.csv @@ -40,7 +40,7 @@ bedrock-agent,bedrockagent,bedrockagent,bedrockagent,,bedrockagent,,,BedrockAgen bcmdataexports,bcmdataexports,bcmdataexports,bcmdataexports,,bcmdataexports,,,BCMDataExports,BCMDataExports,,,2,,aws_bcmdataexports_,,bcmdataexports_,BCM Data Exports,Amazon,,,,,,,BCM Data Exports,ListExports,, billingconductor,billingconductor,billingconductor,,,billingconductor,,,BillingConductor,BillingConductor,,1,,,aws_billingconductor_,,billingconductor_,Billing Conductor,AWS,,x,,,,,billingconductor,,, braket,braket,braket,braket,,braket,,,Braket,Braket,,1,,,aws_braket_,,braket_,Braket,Amazon,,x,,,,,Braket,,, -ce,ce,costexplorer,costexplorer,,ce,,costexplorer,CE,CostExplorer,,1,,,aws_ce_,,ce_,CE (Cost Explorer),AWS,,,,,,,Cost Explorer,ListCostCategoryDefinitions,, +ce,ce,costexplorer,costexplorer,,ce,,costexplorer,CE,CostExplorer,,,2,,aws_ce_,,ce_,CE (Cost Explorer),AWS,,,,,,,Cost Explorer,ListCostCategoryDefinitions,, ,,,,,,,,,,,,,,,,,Chatbot,AWS,x,,,,,,,,,No SDK support chime,chime,chime,chime,,chime,,,Chime,Chime,,1,,,aws_chime_,,chime_,Chime,Amazon,,,,,,,Chime,ListAccounts,, chime-sdk-identity,chimesdkidentity,chimesdkidentity,chimesdkidentity,,chimesdkidentity,,,ChimeSDKIdentity,ChimeSDKIdentity,,1,,,aws_chimesdkidentity_,,chimesdkidentity_,Chime SDK Identity,Amazon,,x,,,,,Chime SDK Identity,,, diff --git a/names/names.go b/names/names.go index 57f0372bc29..2f0d70adf6e 100644 --- a/names/names.go +++ b/names/names.go @@ -49,6 +49,7 @@ const ( CognitoIdentityEndpointID = "cognito-identity" ComprehendEndpointID = "comprehend" ConfigServiceEndpointID = "config" + CostExploereEndpointID = "ce" DevOpsGuruEndpointID = "devops-guru" ECREndpointID = "api.ecr" EKSEndpointID = "eks"