diff --git a/.changelog/24402.txt b/.changelog/24402.txt new file mode 100644 index 000000000000..6789df0f0637 --- /dev/null +++ b/.changelog/24402.txt @@ -0,0 +1,11 @@ +```release-note:new-resource +aws_ce_cost_category +``` + +```release-note:new-data-source +aws_ce_cost_category +``` + +```release-note:new-data-source +aws_ce_tags +``` \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 623e936065ea..a5185dc34ecf 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -33,6 +33,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/service/backup" "github.com/hashicorp/terraform-provider-aws/internal/service/batch" "github.com/hashicorp/terraform-provider-aws/internal/service/budgets" + "github.com/hashicorp/terraform-provider-aws/internal/service/ce" "github.com/hashicorp/terraform-provider-aws/internal/service/chime" "github.com/hashicorp/terraform-provider-aws/internal/service/cloud9" "github.com/hashicorp/terraform-provider-aws/internal/service/cloudcontrol" @@ -444,6 +445,9 @@ func Provider() *schema.Provider { "aws_batch_job_queue": batch.DataSourceJobQueue(), "aws_batch_scheduling_policy": batch.DataSourceSchedulingPolicy(), + "aws_ce_cost_category": ce.DataSourceCostCategory(), + "aws_ce_tags": ce.DataSourceTags(), + "aws_cloudcontrolapi_resource": cloudcontrol.DataSourceResource(), "aws_cloudformation_export": cloudformation.DataSourceExport(), @@ -1008,6 +1012,8 @@ func Provider() *schema.Provider { "aws_budgets_budget": budgets.ResourceBudget(), "aws_budgets_budget_action": budgets.ResourceBudgetAction(), + "aws_ce_cost_category": ce.ResourceCostCategory(), + "aws_chime_voice_connector": chime.ResourceVoiceConnector(), "aws_chime_voice_connector_group": chime.ResourceVoiceConnectorGroup(), "aws_chime_voice_connector_logging": chime.ResourceVoiceConnectorLogging(), diff --git a/internal/service/ce/README.md b/internal/service/ce/README.md new file mode 100644 index 000000000000..7b151bf2b843 --- /dev/null +++ b/internal/service/ce/README.md @@ -0,0 +1,11 @@ +# Terraform AWS Provider CE Package + +This area is primarily for AWS provider contributors and maintainers. For information on _using_ Terraform and the AWS provider, see the links below. + + +## Handy Links + +* [Find out about contributing](../../../docs/contributing) to the AWS provider! +* AWS Provider Docs: [Home](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) +* AWS Provider Docs: [One of the CE resources](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ce_cost_category) +* AWS Docs: [AWS SDK for Go CE](https://docs.aws.amazon.com/sdk-for-go/api/service/costexplorer/) diff --git a/internal/service/ce/consts.go b/internal/service/ce/consts.go new file mode 100644 index 000000000000..fa8b0989ba7c --- /dev/null +++ b/internal/service/ce/consts.go @@ -0,0 +1,6 @@ +package ce + +const ( + ResCostCategory = "Cost Category" + ResTags = "Tags" +) diff --git a/internal/service/ce/cost_category.go b/internal/service/ce/cost_category.go new file mode 100644 index 000000000000..b1e35e934baf --- /dev/null +++ b/internal/service/ce/cost_category.go @@ -0,0 +1,994 @@ +package ce + +import ( + "bytes" + "context" + "fmt" + + "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/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "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/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func ResourceCostCategory() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceCostCategoryCreate, + ReadContext: resourceCostCategoryRead, + UpdateContext: resourceCostCategoryUpdate, + DeleteContext: resourceCostCategoryDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + 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, + 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), + }, + }, + }, + }, + "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_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.TypeSet, + Optional: true, + MinItems: 1, + MaxItems: 500, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(0, 1024), + }, + Set: schema.HashString, + }, + }, + }, + Set: costExplorerCostCategorySplitChargesParameter, + }, + "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), + }, + Set: schema.HashString, + }, + }, + }, + Set: costExplorerCostCategorySplitCharges, + }, + }, + } +} + +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{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(costexplorer.MatchOption_Values(), false), + }, + Set: schema.HashString, + }, + "values": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(0, 1024), + }, + Set: schema.HashString, + }, + }, + }, + }, + "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), + }, + Set: schema.HashString, + }, + "values": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(0, 1024), + }, + Set: schema.HashString, + }, + }, + }, + }, + "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), + }, + Set: schema.HashString, + }, + "values": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(0, 1024), + }, + Set: schema.HashString, + }, + }, + }, + }, + }, + } +} + +func schemaCostCategoryRuleExpression() *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, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 50), + }, + "match_options": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(costexplorer.MatchOption_Values(), false), + }, + Set: schema.HashString, + }, + "values": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(0, 1024), + }, + Set: schema.HashString, + }, + }, + }, + }, + "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), + }, + Set: schema.HashString, + }, + "values": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(0, 1024), + }, + Set: schema.HashString, + }, + }, + }, + }, + "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), + }, + Set: schema.HashString, + }, + "values": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(0, 1024), + }, + Set: schema.HashString, + }, + }, + }, + }, + }, + } +} + +func resourceCostCategoryCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).CEConn + input := &costexplorer.CreateCostCategoryDefinitionInput{ + Name: aws.String(d.Get("name").(string)), + Rules: expandCostCategoryRules(d.Get("rule").(*schema.Set).List()), + RuleVersion: aws.String(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()) + } + + var err error + var output *costexplorer.CreateCostCategoryDefinitionOutput + err = resource.RetryContext(ctx, d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { + output, err = conn.CreateCostCategoryDefinition(input) + if err != nil { + if tfawserr.ErrCodeEquals(err, costexplorer.ErrCodeResourceNotFoundException) { + return resource.RetryableError(err) + } + + return resource.NonRetryableError(err) + } + + return nil + }) + + if tfresource.TimedOut(err) { + output, err = conn.CreateCostCategoryDefinition(input) + } + + if err != nil { + return names.DiagError(names.CE, names.ErrActionCreating, ResCostCategory, d.Id(), err) + } + + d.SetId(aws.StringValue(output.CostCategoryArn)) + + return resourceCostCategoryRead(ctx, d, meta) +} + +func resourceCostCategoryRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).CEConn + + resp, err := conn.DescribeCostCategoryDefinitionWithContext(ctx, &costexplorer.DescribeCostCategoryDefinitionInput{CostCategoryArn: aws.String(d.Id())}) + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, costexplorer.ErrCodeResourceNotFoundException) { + names.LogNotFoundRemoveState(names.CE, names.ErrActionReading, ResCostCategory, d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return names.DiagError(names.CE, names.ErrActionReading, ResCostCategory, d.Id(), err) + } + + d.Set("arn", resp.CostCategory.CostCategoryArn) + d.Set("default_value", resp.CostCategory.DefaultValue) + d.Set("effective_end", resp.CostCategory.EffectiveEnd) + d.Set("effective_start", resp.CostCategory.EffectiveStart) + d.Set("name", resp.CostCategory.Name) + if err = d.Set("rule", flattenCostCategoryRules(resp.CostCategory.Rules)); err != nil { + return names.DiagError(names.CE, "setting rule", ResCostCategory, d.Id(), err) + } + d.Set("rule_version", resp.CostCategory.RuleVersion) + if err = d.Set("split_charge_rule", flattenCostCategorySplitChargeRules(resp.CostCategory.SplitChargeRules)); err != nil { + return names.DiagError(names.CE, "setting split_charge_rule", ResCostCategory, d.Id(), err) + } + + return nil +} + +func resourceCostCategoryUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).CEConn + + input := &costexplorer.UpdateCostCategoryDefinitionInput{ + CostCategoryArn: aws.String(d.Id()), + Rules: expandCostCategoryRules(d.Get("rule").(*schema.Set).List()), + RuleVersion: aws.String(d.Get("rule_version").(string)), + } + + if d.HasChange("default_value") { + input.DefaultValue = aws.String(d.Get("default_value").(string)) + } + + if d.HasChange("split_charge_rule") { + input.SplitChargeRules = expandCostCategorySplitChargeRules(d.Get("split_charge_rule").(*schema.Set).List()) + } + + _, err := conn.UpdateCostCategoryDefinitionWithContext(ctx, input) + + if err != nil { + return names.DiagError(names.CE, names.ErrActionUpdating, ResCostCategory, d.Id(), err) + } + + return resourceCostCategoryRead(ctx, d, meta) +} + +func resourceCostCategoryDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).CEConn + + _, err := conn.DeleteCostCategoryDefinitionWithContext(ctx, &costexplorer.DeleteCostCategoryDefinitionInput{ + CostCategoryArn: aws.String(d.Id()), + }) + if err != nil && tfawserr.ErrCodeEquals(err, costexplorer.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return names.DiagError(names.CE, names.ErrActionDeleting, ResCostCategory, d.Id(), err) + } + + return nil +} + +func expandCostCategoryRule(tfMap map[string]interface{}) *costexplorer.CostCategoryRule { + if tfMap == nil { + return nil + } + + apiObject := &costexplorer.CostCategoryRule{} + if v, ok := tfMap["inherited_value"]; ok { + apiObject.InheritedValue = expandCostCategoryInheritedValue(v.([]interface{})) + } + if v, ok := tfMap["rule"]; ok { + apiObject.Rule = expandCostExpressions(v.([]interface{}))[0] + } + if v, ok := tfMap["type"]; ok { + apiObject.Type = aws.String(v.(string)) + } + if v, ok := tfMap["value"]; ok { + apiObject.Value = aws.String(v.(string)) + } + + return apiObject +} + +func expandCostCategoryInheritedValue(tfList []interface{}) *costexplorer.CostCategoryInheritedValueDimension { + if len(tfList) == 0 { + return nil + } + + tfMap := tfList[0].(map[string]interface{}) + + apiObject := &costexplorer.CostCategoryInheritedValueDimension{} + if v, ok := tfMap["dimension_key"]; ok { + apiObject.DimensionKey = aws.String(v.(string)) + } + if v, ok := tfMap["dimension_name"]; ok { + apiObject.DimensionName = aws.String(v.(string)) + } + + return apiObject +} + +func expandCostExpression(tfMap map[string]interface{}) *costexplorer.Expression { + if tfMap == nil { + return nil + } + + apiObject := &costexplorer.Expression{} + if v, ok := tfMap["and"]; ok { + apiObject.And = expandCostExpressions(v.(*schema.Set).List()) + } + if v, ok := tfMap["cost_category"]; ok { + apiObject.CostCategories = expandCostExpressionCostCategory(v.([]interface{})) + } + if v, ok := tfMap["dimension"]; ok { + apiObject.Dimensions = expandCostExpressionDimension(v.([]interface{})) + } + if v, ok := tfMap["not"]; ok && len(v.([]interface{})) > 0 { + apiObject.Not = expandCostExpressions(v.([]interface{}))[0] + } + if v, ok := tfMap["or"]; ok { + apiObject.Or = expandCostExpressions(v.(*schema.Set).List()) + } + if v, ok := tfMap["tags"]; ok { + apiObject.Tags = expandCostExpressionTag(v.([]interface{})) + } + + return apiObject +} + +func expandCostExpressionCostCategory(tfList []interface{}) *costexplorer.CostCategoryValues { + if len(tfList) == 0 { + return nil + } + + tfMap := tfList[0].(map[string]interface{}) + + apiObject := &costexplorer.CostCategoryValues{} + if v, ok := tfMap["key"]; ok { + apiObject.Key = aws.String(v.(string)) + } + if v, ok := tfMap["match_options"]; ok { + apiObject.MatchOptions = flex.ExpandStringSet(v.(*schema.Set)) + } + if v, ok := tfMap["values"]; ok { + apiObject.Values = flex.ExpandStringSet(v.(*schema.Set)) + } + + return apiObject +} + +func expandCostExpressionDimension(tfList []interface{}) *costexplorer.DimensionValues { + if len(tfList) == 0 { + return nil + } + + tfMap := tfList[0].(map[string]interface{}) + + apiObject := &costexplorer.DimensionValues{} + if v, ok := tfMap["key"]; ok { + apiObject.Key = aws.String(v.(string)) + } + if v, ok := tfMap["match_options"]; ok { + apiObject.MatchOptions = flex.ExpandStringSet(v.(*schema.Set)) + } + if v, ok := tfMap["values"]; ok { + apiObject.Values = flex.ExpandStringSet(v.(*schema.Set)) + } + + return apiObject +} + +func expandCostExpressionTag(tfList []interface{}) *costexplorer.TagValues { + if len(tfList) == 0 { + return nil + } + + tfMap := tfList[0].(map[string]interface{}) + + apiObject := &costexplorer.TagValues{} + if v, ok := tfMap["key"]; ok { + apiObject.Key = aws.String(v.(string)) + } + if v, ok := tfMap["match_options"]; ok { + apiObject.MatchOptions = flex.ExpandStringSet(v.(*schema.Set)) + } + if v, ok := tfMap["values"]; ok { + apiObject.Values = flex.ExpandStringSet(v.(*schema.Set)) + } + + return apiObject +} + +func expandCostExpressions(tfList []interface{}) []*costexplorer.Expression { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*costexplorer.Expression + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandCostExpression(tfMap) + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func expandCostCategoryRules(tfList []interface{}) []*costexplorer.CostCategoryRule { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*costexplorer.CostCategoryRule + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandCostCategoryRule(tfMap) + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func expandCostCategorySplitChargeRule(tfMap map[string]interface{}) *costexplorer.CostCategorySplitChargeRule { + if tfMap == nil { + return nil + } + + apiObject := &costexplorer.CostCategorySplitChargeRule{ + Method: aws.String(tfMap["method"].(string)), + Source: aws.String(tfMap["source"].(string)), + Targets: flex.ExpandStringSet(tfMap["targets"].(*schema.Set)), + } + if v, ok := tfMap["parameter"]; ok { + apiObject.Parameters = expandCostCategorySplitChargeRuleParameters(v.(*schema.Set).List()) + } + + return apiObject +} + +func expandCostCategorySplitChargeRuleParameter(tfMap map[string]interface{}) *costexplorer.CostCategorySplitChargeRuleParameter { + if tfMap == nil { + return nil + } + + apiObject := &costexplorer.CostCategorySplitChargeRuleParameter{ + Type: aws.String(tfMap["method"].(string)), + Values: flex.ExpandStringSet(tfMap["values"].(*schema.Set)), + } + + return apiObject +} + +func expandCostCategorySplitChargeRuleParameters(tfList []interface{}) []*costexplorer.CostCategorySplitChargeRuleParameter { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*costexplorer.CostCategorySplitChargeRuleParameter + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandCostCategorySplitChargeRuleParameter(tfMap) + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func expandCostCategorySplitChargeRules(tfList []interface{}) []*costexplorer.CostCategorySplitChargeRule { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*costexplorer.CostCategorySplitChargeRule + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandCostCategorySplitChargeRule(tfMap) + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func flattenCostCategoryRule(apiObject *costexplorer.CostCategoryRule) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + var expressions []*costexplorer.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) + + return tfMap +} + +func flattenCostCategoryRuleInheritedValue(apiObject *costexplorer.CostCategoryInheritedValueDimension) []map[string]interface{} { + if apiObject == nil { + return nil + } + + var tfList []map[string]interface{} + tfMap := map[string]interface{}{} + + tfMap["dimension_key"] = aws.StringValue(apiObject.DimensionKey) + tfMap["dimension_name"] = aws.StringValue(apiObject.DimensionName) + + tfList = append(tfList, tfMap) + + return tfList +} + +func flattenCostCategoryRuleExpression(apiObject *costexplorer.Expression) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + tfMap["and"] = flattenCostCategoryRuleOperandExpressions(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["tags"] = flattenCostCategoryRuleExpressionTag(apiObject.Tags) + + return tfMap +} + +func flattenCostCategoryRuleExpressionCostCategory(apiObject *costexplorer.CostCategoryValues) []map[string]interface{} { + if apiObject == nil { + return nil + } + + 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) + + tfList = append(tfList, tfMap) + + return tfList +} + +func flattenCostCategoryRuleExpressionDimension(apiObject *costexplorer.DimensionValues) []map[string]interface{} { + if apiObject == nil { + return nil + } + + 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) + + tfList = append(tfList, tfMap) + + return tfList +} + +func flattenCostCategoryRuleExpressionTag(apiObject *costexplorer.TagValues) []map[string]interface{} { + if apiObject == nil { + return nil + } + + 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) + + tfList = append(tfList, tfMap) + + return tfList +} + +func flattenCostCategoryRuleExpressions(apiObjects []*costexplorer.Expression) []map[string]interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []map[string]interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenCostCategoryRuleExpression(apiObject)) + } + + return tfList +} + +func flattenCostCategoryRuleOperandExpression(apiObject *costexplorer.Expression) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + tfMap["cost_category"] = flattenCostCategoryRuleExpressionCostCategory(apiObject.CostCategories) + tfMap["dimension"] = flattenCostCategoryRuleExpressionDimension(apiObject.Dimensions) + tfMap["tags"] = flattenCostCategoryRuleExpressionTag(apiObject.Tags) + + return tfMap +} + +func flattenCostCategoryRuleOperandExpressions(apiObjects []*costexplorer.Expression) []map[string]interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []map[string]interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenCostCategoryRuleOperandExpression(apiObject)) + } + + return tfList +} + +func flattenCostCategoryRules(apiObjects []*costexplorer.CostCategoryRule) []map[string]interface{} { + if len(apiObjects) == 0 { + return nil + } + + 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 + } + + tfMap := map[string]interface{}{} + tfMap["method"] = aws.StringValue(apiObject.Method) + tfMap["parameter"] = flattenCostCategorySplitChargeRuleParameters(apiObject.Parameters) + tfMap["source"] = aws.StringValue(apiObject.Source) + tfMap["targets"] = flex.FlattenStringList(apiObject.Targets) + + return tfMap +} + +func flattenCostCategorySplitChargeRuleParameter(apiObject *costexplorer.CostCategorySplitChargeRuleParameter) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + tfMap["type"] = aws.StringValue(apiObject.Type) + tfMap["values"] = flex.FlattenStringList(apiObject.Values) + + return tfMap +} + +func flattenCostCategorySplitChargeRuleParameters(apiObjects []*costexplorer.CostCategorySplitChargeRuleParameter) []map[string]interface{} { + if len(apiObjects) == 0 { + return nil + } + + 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{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []map[string]interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenCostCategorySplitChargeRule(apiObject)) + } + + return tfList +} + +func costExplorerCostCategorySplitCharges(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(m["method"].(string)) + buf.WriteString(fmt.Sprintf("%+v", m["parameter"].(*schema.Set))) + buf.WriteString(m["source"].(string)) + buf.WriteString(fmt.Sprintf("%+v", m["targets"].(*schema.Set))) + return schema.HashString(buf.String()) +} + +func costExplorerCostCategorySplitChargesParameter(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(m["type"].(string)) + buf.WriteString(fmt.Sprintf("%+v", m["values"].(*schema.Set))) + return schema.HashString(buf.String()) +} diff --git a/internal/service/ce/cost_category_data_source.go b/internal/service/ce/cost_category_data_source.go new file mode 100644 index 000000000000..cf8fd49cf8e2 --- /dev/null +++ b/internal/service/ce/cost_category_data_source.go @@ -0,0 +1,350 @@ +package ce + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/costexplorer" + "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/names" +) + +func DataSourceCostCategory() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceCostCategoryRead, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "cost_category_arn": { + Type: schema.TypeString, + Required: 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), + }, + Set: schema.HashString, + }, + }, + }, + Set: costExplorerCostCategorySplitChargesParameter, + }, + "source": { + Type: schema.TypeString, + Computed: true, + }, + "targets": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(0, 1024), + }, + Set: schema.HashString, + }, + }, + }, + Set: costExplorerCostCategorySplitCharges, + }, + }, + } +} + +func schemaCostCategoryRuleComputed() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "and": { + Type: schema.TypeSet, + Computed: true, + Elem: schemaCostCategoryRuleExpressionComputed(), + }, + "cost_category": { + Type: schema.TypeList, + 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, + }, + Set: schema.HashString, + }, + "values": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Set: schema.HashString, + }, + }, + }, + }, + "dimension": { + Type: schema.TypeList, + 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, + }, + Set: schema.HashString, + }, + "values": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Set: schema.HashString, + }, + }, + }, + }, + "not": { + Type: schema.TypeList, + Computed: true, + Elem: schemaCostCategoryRuleExpressionComputed(), + }, + "or": { + Type: schema.TypeSet, + Computed: true, + Elem: schemaCostCategoryRuleExpressionComputed(), + }, + "tags": { + Type: schema.TypeList, + 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, + }, + Set: schema.HashString, + }, + "values": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Set: schema.HashString, + }, + }, + }, + }, + }, + } +} + +func schemaCostCategoryRuleExpressionComputed() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cost_category": { + Type: schema.TypeList, + 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, + }, + Set: schema.HashString, + }, + "values": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Set: schema.HashString, + }, + }, + }, + }, + "dimension": { + Type: schema.TypeList, + 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, + }, + Set: schema.HashString, + }, + }, + }, + }, + "tags": { + Type: schema.TypeList, + 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, + }, + Set: schema.HashString, + }, + "values": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Set: schema.HashString, + }, + }, + }, + }, + }, + } +} + +func dataSourceCostCategoryRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).CEConn + + resp, err := conn.DescribeCostCategoryDefinitionWithContext(ctx, &costexplorer.DescribeCostCategoryDefinitionInput{CostCategoryArn: aws.String(d.Get("cost_category_arn").(string))}) + + if err != nil { + return names.DiagError(names.CE, names.ErrActionReading, ResCostCategory, d.Id(), err) + } + + d.Set("effective_end", resp.CostCategory.EffectiveEnd) + d.Set("effective_start", resp.CostCategory.EffectiveStart) + d.Set("name", resp.CostCategory.Name) + if err = d.Set("rule", flattenCostCategoryRules(resp.CostCategory.Rules)); err != nil { + return names.DiagError(names.CE, "setting rule", ResCostCategory, d.Id(), err) + } + d.Set("rule_version", resp.CostCategory.RuleVersion) + if err = d.Set("split_charge_rule", flattenCostCategorySplitChargeRules(resp.CostCategory.SplitChargeRules)); err != nil { + return names.DiagError(names.CE, "setting split_charge_rule", ResCostCategory, d.Id(), err) + } + + d.SetId(aws.StringValue(resp.CostCategory.CostCategoryArn)) + + return nil +} diff --git a/internal/service/ce/cost_category_data_source_test.go b/internal/service/ce/cost_category_data_source_test.go new file mode 100644 index 000000000000..00569ed1d226 --- /dev/null +++ b/internal/service/ce/cost_category_data_source_test.go @@ -0,0 +1,45 @@ +package ce_test + +import ( + "testing" + + "github.com/aws/aws-sdk-go/service/costexplorer" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func TestAccCECostCategoryDataSource_basic(t *testing.T) { + var output costexplorer.CostCategory + resourceName := "aws_ce_cost_category.test" + dataSourceName := "data.aws_ce_cost_category.test" + rName := sdkacctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProviderFactories: acctest.ProviderFactories, + ErrorCheck: acctest.ErrorCheck(t, costexplorer.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccCostCategoryDataSourceConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCostCategoryExists(resourceName, &output), + resource.TestCheckResourceAttrPair(dataSourceName, "cost_category_arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(dataSourceName, "name", resourceName, "name"), + resource.TestCheckResourceAttrPair(dataSourceName, "rule_version", resourceName, "rule_version"), + resource.TestCheckResourceAttrPair(dataSourceName, "rule.%", resourceName, "rule.%"), + ), + }, + }, + }) +} + +func testAccCostCategoryDataSourceConfig(rName string) string { + return acctest.ConfigCompose( + testAccCostCategoryConfig(rName), + ` +data "aws_ce_cost_category" "test" { + cost_category_arn = aws_ce_cost_category.test.arn +} +`) +} diff --git a/internal/service/ce/cost_category_test.go b/internal/service/ce/cost_category_test.go new file mode 100644 index 000000000000..1b7e59bd48c9 --- /dev/null +++ b/internal/service/ce/cost_category_test.go @@ -0,0 +1,377 @@ +package ce_test + +import ( + "errors" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go/service/costexplorer" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfce "github.com/hashicorp/terraform-provider-aws/internal/service/ce" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccCECostCategory_basic(t *testing.T) { + var output costexplorer.CostCategory + resourceName := "aws_ce_cost_category.test" + rName := sdkacctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckCostCategoryDestroy, + ErrorCheck: acctest.ErrorCheck(t, costexplorer.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccCostCategoryConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCostCategoryExists(resourceName, &output), + resource.TestCheckResourceAttr(resourceName, "name", rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccCECostCategory_complete(t *testing.T) { + var output costexplorer.CostCategory + resourceName := "aws_ce_cost_category.test" + rName := sdkacctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckCostCategoryDestroy, + ErrorCheck: acctest.ErrorCheck(t, costexplorer.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccCostCategoryConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCostCategoryExists(resourceName, &output), + resource.TestCheckResourceAttr(resourceName, "name", rName), + ), + }, + { + Config: testAccCostCategoryOperandAndConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCostCategoryExists(resourceName, &output), + resource.TestCheckResourceAttr(resourceName, "name", rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccCECostCategory_splitCharge(t *testing.T) { + var output costexplorer.CostCategory + resourceName := "aws_ce_cost_category.test" + rName := sdkacctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckCostCategoryDestroy, + ErrorCheck: acctest.ErrorCheck(t, costexplorer.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccCostCategorySplitChargesConfig(rName, "PROPORTIONAL"), + Check: resource.ComposeTestCheckFunc( + testAccCheckCostCategoryExists(resourceName, &output), + resource.TestCheckResourceAttr(resourceName, "name", rName), + ), + }, + { + Config: testAccCostCategorySplitChargesConfig(rName, "EVEN"), + Check: resource.ComposeTestCheckFunc( + testAccCheckCostCategoryExists(resourceName, &output), + resource.TestCheckResourceAttr(resourceName, "name", rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccCECostCategory_disappears(t *testing.T) { + var output costexplorer.CostCategory + resourceName := "aws_ce_cost_category.test" + rName := sdkacctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckCostCategoryDestroy, + ErrorCheck: acctest.ErrorCheck(t, costexplorer.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccCostCategoryConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCostCategoryExists(resourceName, &output), + acctest.CheckResourceDisappears(acctest.Provider, tfce.ResourceCostCategory(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckCostCategoryExists(resourceName string, output *costexplorer.CostCategory) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return names.Error(names.CE, names.ErrActionCheckingExistence, tfce.ResCostCategory, resourceName, errors.New("not found in state")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).CEConn + resp, err := conn.DescribeCostCategoryDefinition(&costexplorer.DescribeCostCategoryDefinitionInput{CostCategoryArn: aws.String(rs.Primary.ID)}) + + if err != nil { + return names.Error(names.CE, names.ErrActionCheckingExistence, tfce.ResCostCategory, rs.Primary.ID, err) + } + + if resp == nil { + return names.Error(names.CE, names.ErrActionCheckingExistence, tfce.ResCostCategory, rs.Primary.ID, errors.New("not found")) + } + + *output = *resp.CostCategory + + return nil + } +} + +func testAccCheckCostCategoryDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).CEConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ce_cost_category" { + continue + } + + resp, err := conn.DescribeCostCategoryDefinition(&costexplorer.DescribeCostCategoryDefinitionInput{CostCategoryArn: aws.String(rs.Primary.ID)}) + + if tfawserr.ErrCodeEquals(err, costexplorer.ErrCodeResourceNotFoundException) { + continue + } + + if err != nil { + return names.Error(names.CE, names.ErrActionCheckingDestroyed, tfce.ResCostCategory, rs.Primary.ID, err) + } + + if resp != nil && resp.CostCategory != nil { + return names.Error(names.CE, names.ErrActionCheckingDestroyed, tfce.ResCostCategory, rs.Primary.ID, errors.New("still exists")) + } + } + + return nil + +} + +func testAccCostCategoryConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_ce_cost_category" "test" { + name = %[1]q + rule_version = "CostCategoryExpression.v1" + rule { + value = "production" + rule { + dimension { + key = "LINKED_ACCOUNT_NAME" + values = ["-prod"] + match_options = ["ENDS_WITH"] + } + } + type = "REGULAR" + } + rule { + value = "staging" + rule { + dimension { + key = "LINKED_ACCOUNT_NAME" + values = ["-stg"] + match_options = ["ENDS_WITH"] + } + } + type = "REGULAR" + } + rule { + value = "testing" + rule { + dimension { + key = "LINKED_ACCOUNT_NAME" + values = ["-dev"] + match_options = ["ENDS_WITH"] + } + } + type = "REGULAR" + } +} +`, rName) +} + +func testAccCostCategoryOperandAndConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_ce_cost_category" "test" { + name = %[1]q + rule_version = "CostCategoryExpression.v1" + rule { + value = "production" + rule { + and { + dimension { + key = "LINKED_ACCOUNT_NAME" + values = ["-prod"] + match_options = ["ENDS_WITH"] + } + } + and { + dimension { + key = "LINKED_ACCOUNT_NAME" + values = ["-stg"] + match_options = ["ENDS_WITH"] + } + } + and { + dimension { + key = "LINKED_ACCOUNT_NAME" + values = ["-dev"] + match_options = ["ENDS_WITH"] + } + } + } + type = "REGULAR" + } +} +`, rName) +} + +func testAccCostCategorySplitChargesConfig(rName, method string) string { + return fmt.Sprintf(` +resource "aws_ce_cost_category" "test1" { + name = "%[1]s-1" + rule_version = "CostCategoryExpression.v1" + + rule { + value = "production" + + rule { + dimension { + key = "LINKED_ACCOUNT_NAME" + values = ["-prod"] + match_options = ["ENDS_WITH"] + } + } + + type = "REGULAR" + } + + rule { + value = "staging" + + rule { + dimension { + key = "LINKED_ACCOUNT_NAME" + values = ["-stg"] + match_options = ["ENDS_WITH"] + } + } + + type = "REGULAR" + } + + rule { + value = "testing" + + rule { + dimension { + key = "LINKED_ACCOUNT_NAME" + values = ["-dev"] + match_options = ["ENDS_WITH"] + } + } + + type = "REGULAR" + } +} + +resource "aws_ce_cost_category" "test2" { + name = "%[1]s-2" + rule_version = "CostCategoryExpression.v1" + + rule { + value = "production" + + rule { + and { + dimension { + key = "LINKED_ACCOUNT_NAME" + values = ["-prod"] + match_options = ["ENDS_WITH"] + } + } + + and { + dimension { + key = "LINKED_ACCOUNT_NAME" + values = ["-stg"] + match_options = ["ENDS_WITH"] + } + } + + and { + dimension { + key = "LINKED_ACCOUNT_NAME" + values = ["-dev"] + match_options = ["ENDS_WITH"] + } + } + } + + type = "REGULAR" + } +} + +resource "aws_ce_cost_category" "test" { + name = %[1]q + rule_version = "CostCategoryExpression.v1" + + rule { + value = "production" + rule { + dimension { + key = "LINKED_ACCOUNT_NAME" + values = ["-prod"] + match_options = ["ENDS_WITH"] + } + } + + type = "REGULAR" + } + + split_charge_rule { + method = %[2]q + source = aws_ce_cost_category.test1.id + targets = [aws_ce_cost_category.test2.id] + } +} +`, rName, method) +} diff --git a/internal/service/ce/tags_data_source.go b/internal/service/ce/tags_data_source.go new file mode 100644 index 000000000000..e028bab8935d --- /dev/null +++ b/internal/service/ce/tags_data_source.go @@ -0,0 +1,171 @@ +package ce + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/costexplorer" + "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/flex" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func DataSourceTags() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceTagsRead, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "filter": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: schemaCostCategoryRule(), + }, + "search_string": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 1024), + ConflictsWith: []string{"sort_by"}, + }, + "sort_by": { + Type: schema.TypeList, + Optional: true, + ConflictsWith: []string{"search_string"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(costexplorer.Metric_Values(), false), + }, + "sort_order": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(costexplorer.SortOrder_Values(), false), + }, + }, + }, + }, + "tag_key": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 1024), + }, + "tags": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "time_period": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "end": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(0, 40), + }, + "start": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(0, 40), + }, + }, + }, + }, + }, + } +} + +func dataSourceTagsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).CEConn + + 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("search_string"); ok { + input.SearchString = aws.String(v.(string)) + } + + if v, ok := d.GetOk("sort_by"); ok { + input.SortBy = expandTagsSortBys(v.([]interface{})) + } + + if v, ok := d.GetOk("tag_key"); ok { + input.TagKey = aws.String(v.(string)) + } + + resp, err := conn.GetTagsWithContext(ctx, input) + + if err != nil { + return names.DiagError(names.CE, names.ErrActionReading, ResTags, d.Id(), err) + } + + d.Set("tags", flex.FlattenStringList(resp.Tags)) + + d.SetId(meta.(*conns.AWSClient).AccountID) + + return nil +} + +func expandTagsSortBys(tfList []interface{}) []*costexplorer.SortDefinition { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*costexplorer.SortDefinition + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandTagsSortBy(tfMap) + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func expandTagsSortBy(tfMap map[string]interface{}) *costexplorer.SortDefinition { + if tfMap == nil { + return nil + } + + apiObject := &costexplorer.SortDefinition{} + apiObject.Key = aws.String(tfMap["key"].(string)) + if v, ok := tfMap["sort_order"]; ok { + apiObject.SortOrder = aws.String(v.(string)) + } + + return apiObject +} + +func expandTagsTimePeriod(tfMap map[string]interface{}) *costexplorer.DateInterval { + if tfMap == nil { + return nil + } + + apiObject := &costexplorer.DateInterval{} + apiObject.Start = aws.String(tfMap["start"].(string)) + apiObject.End = aws.String(tfMap["end"].(string)) + + return apiObject +} diff --git a/internal/service/ce/tags_data_source_test.go b/internal/service/ce/tags_data_source_test.go new file mode 100644 index 000000000000..e11bd3481bf8 --- /dev/null +++ b/internal/service/ce/tags_data_source_test.go @@ -0,0 +1,132 @@ +package ce_test + +import ( + "fmt" + "testing" + "time" + + "github.com/aws/aws-sdk-go/service/costexplorer" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func TestAccCETagsDataSource_basic(t *testing.T) { + var output costexplorer.CostCategory + resourceName := "aws_ce_cost_category.test" + dataSourceName := "data.aws_ce_tags.test" + rName := sdkacctest.RandomWithPrefix("tf-acc-test") + + formatDate := "2006-01-02" + currentTime := time.Now() + monthsAgo := currentTime.AddDate(0, -10, 0) + startDate := monthsAgo.Format(formatDate) + endDate := currentTime.Format(formatDate) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProviderFactories: acctest.ProviderFactories, + ErrorCheck: acctest.ErrorCheck(t, costexplorer.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccTagsSourceConfig(rName, startDate, endDate), + Check: resource.ComposeTestCheckFunc( + testAccCheckCostCategoryExists(resourceName, &output), + resource.TestCheckResourceAttr(dataSourceName, "tags.#", "1"), + ), + }, + }, + }) +} + +func TestAccCETagsDataSource_filter(t *testing.T) { + var output costexplorer.CostCategory + resourceName := "aws_ce_cost_category.test" + dataSourceName := "data.aws_ce_tags.test" + rName := sdkacctest.RandomWithPrefix("tf-acc-test") + + formatDate := "2006-01-02" + currentTime := time.Now() + monthsAgo := currentTime.AddDate(0, -10, 0) + startDate := monthsAgo.Format(formatDate) + endDate := currentTime.Format(formatDate) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProviderFactories: acctest.ProviderFactories, + ErrorCheck: acctest.ErrorCheck(t, costexplorer.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccTagsSourceFilterConfig(rName, startDate, endDate), + Check: resource.ComposeTestCheckFunc( + testAccCheckCostCategoryExists(resourceName, &output), + resource.TestCheckResourceAttr(dataSourceName, "tags.#", "1"), + ), + }, + }, + }) +} + +func testAccTagsSourceConfig(rName, start, end string) string { + return fmt.Sprintf(` +resource "aws_ce_cost_category" "test" { + name = %[1]q + rule_version = "CostCategoryExpression.v1" + rule { + value = "production" + rule { + tags { + key = %[1]q + values = ["abc", "123"] + } + } + type = "REGULAR" + } +} + +data "aws_ce_tags" "test" { + time_period { + start = %[2]q + end = %[3]q + } + tag_key = %[1]q +} +`, rName, start, end) +} + +func testAccTagsSourceFilterConfig(rName, start, end string) string { + return fmt.Sprintf(` +data "aws_region" "current" {} + + +resource "aws_ce_cost_category" "test" { + name = %[1]q + rule_version = "CostCategoryExpression.v1" + rule { + value = "production" + rule { + tags { + key = %[1]q + values = ["abc", "123"] + } + } + type = "REGULAR" + } +} + +data "aws_ce_tags" "test" { + time_period { + start = %[2]q + end = %[3]q + } + filter { + dimension { + key = "REGION" + values = [data.aws_region.current.name] + match_options = ["EQUALS"] + } + } + tag_key = %[1]q +} +`, rName, start, end) +} diff --git a/names/errors.go b/names/errors.go new file mode 100644 index 000000000000..420b33539d7b --- /dev/null +++ b/names/errors.go @@ -0,0 +1,64 @@ +package names + +import ( + "errors" + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" +) + +const ( + ErrActionReading = "reading" + ErrActionDeleting = "deleting" + ErrActionUpdating = "updating" + ErrActionCreating = "creating" + ErrActionCheckingExistence = "checking existence" + ErrActionCheckingDestroyed = "checking destroyed" +) + +func Error(service, action, resource, id string, gotError error) error { + hf, err := FullHumanFriendly(service) + + if err != nil { + return fmt.Errorf("finding human-friendly name for service (%s) while creating error (%s, %s, %s, %s): %w", service, action, resource, id, gotError, err) + } + + if gotError == nil { + return fmt.Errorf("%s %s %s (%s)", action, hf, resource, id) + } + + return fmt.Errorf("%s %s %s (%s): %w", action, hf, resource, id, gotError) +} + +func DiagError(service, action, resource, id string, gotError error) diag.Diagnostics { + hf, err := FullHumanFriendly(service) + + if err != nil { + return diag.Errorf("finding human-friendly name for service (%s) while creating error (%s, %s, %s, %s): %s", service, action, resource, id, gotError, err) + } + + if gotError == nil { + return diag.Errorf("%s %s %s (%s)", action, hf, resource, id) + } + + return diag.Errorf("%s %s %s (%s): %s", action, hf, resource, id, gotError) +} + +func WarnLog(service, action, resource, id string, gotError error) { + hf, err := FullHumanFriendly(service) + + if err != nil { + log.Printf("[ERROR] finding human-friendly name for service (%s) while logging warn (%s, %s, %s, %s): %s", service, action, resource, id, gotError, err) + } + + if gotError == nil { + log.Printf("[WARN] %s %s %s (%s)", action, hf, resource, id) + } + + log.Printf("[WARN] %s %s %s (%s): %s", action, hf, resource, id, gotError) +} + +func LogNotFoundRemoveState(service, action, resource, id string) { + WarnLog(service, action, resource, id, errors.New("not found, removing from state")) +} diff --git a/website/docs/d/ce_cost_category.html.markdown b/website/docs/d/ce_cost_category.html.markdown new file mode 100644 index 000000000000..99911c089086 --- /dev/null +++ b/website/docs/d/ce_cost_category.html.markdown @@ -0,0 +1,88 @@ +--- +subcategory: "CE (Cost Explorer)" +layout: "aws" +page_title: "AWS: aws_ce_cost_category" +description: |- + Provides details about a specific CostExplorer Cost Category Definition +--- + +# Resource: aws_ce_cost_category + +Provides details about a specific CostExplorer Cost Category. + +## Example Usage + +```terraform +data "aws_ce_cost_category" "example" { + cost_category_arn = "costCategoryARN" +} +``` + +## Argument Reference + +The following arguments are required: + +* `cost_category_arn` - (Required) Unique name for the Cost Category. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - ARN of the cost category. +* `effective_end` - Effective end data of your Cost Category. +* `effective_start` - Effective state data of your Cost Category. +* `id` - Unique ID of the cost category. +* `rule` - Configuration block for the Cost Category rules used to categorize costs. See below. +* `rule_version` - Rule schema version in this particular Cost Category. +* `split_charge_rule` - Configuration block for the split charge rules used to allocate your charges between your Cost Category values. See below. + +### `rule` + +* `inherited_value` - Configuration block for the value the line item is categorized as if the line item contains the matched dimension. See below. +* `rule` - Configuration block for the `Expression` object used to categorize costs. See below. +* `type` - You can define the CostCategoryRule rule type as either `REGULAR` or `INHERITED_VALUE`. +* `value` - Default value for the cost category. + +### `inherited_value` + +* `dimension_key` - Key to extract cost category values. +* `dimension_name` - Name of the dimension that's used to group costs. If you specify `LINKED_ACCOUNT_NAME`, the cost category value is based on account name. If you specify `TAG`, the cost category value will be based on the value of the specified tag key. Valid values are `LINKED_ACCOUNT_NAME`, `TAG` + +### `rule` + +* `and` - Return results that match both `Dimension` objects. +* `cost_category` - Configuration block for the filter that's based on `CostCategory` values. See below. +* `dimension` - Configuration block for the specific `Dimension` to use for `Expression`. See below. +* `not` - Return results that match both `Dimension` object. +* `or` - Return results that match both `Dimension` object. +* `tag` - Configuration block for the specific `Tag` to use for `Expression`. See below. + +### `cost_category` + +* `key` - Unique name of the Cost Category. +* `match_options` - Match options that you can use to filter your results. MatchOptions is only applicable for actions related to cost category. The default values for MatchOptions is `EQUALS` and `CASE_SENSITIVE`. Valid values are: `EQUALS`, `ABSENT`, `STARTS_WITH`, `ENDS_WITH`, `CONTAINS`, `CASE_SENSITIVE`, `CASE_INSENSITIVE`. +* `values` - Specific value of the Cost Category. + +### `dimension` + +* `key` - Unique name of the Cost Category. +* `match_options` - Match options that you can use to filter your results. MatchOptions is only applicable for actions related to cost category. The default values for MatchOptions is `EQUALS` and `CASE_SENSITIVE`. Valid values are: `EQUALS`, `ABSENT`, `STARTS_WITH`, `ENDS_WITH`, `CONTAINS`, `CASE_SENSITIVE`, `CASE_INSENSITIVE`. +* `values` - Specific value of the Cost Category. + +### `tag` + +* `key` - Key for the tag. +* `match_options` - Match options that you can use to filter your results. MatchOptions is only applicable for actions related to cost category. The default values for MatchOptions is `EQUALS` and `CASE_SENSITIVE`. Valid values are: `EQUALS`, `ABSENT`, `STARTS_WITH`, `ENDS_WITH`, `CONTAINS`, `CASE_SENSITIVE`, `CASE_INSENSITIVE`. +* `values` - Specific value of the Cost Category. + +### `split_charge_rule` + +* `method` - Method that's used to define how to split your source costs across your targets. Valid values are `FIXED`, `PROPORTIONAL`, `EVEN` +* `parameter` - Configuration block for the parameters for a split charge method. This is only required for the `FIXED` method. See below. +* `source` - Cost Category value that you want to split. +* `targets` - Cost Category values that you want to split costs across. These values can't be used as a source in other split charge rules. + +### `parameter` + +* `type` - Parameter type. +* `values` - Parameter values. diff --git a/website/docs/d/ce_tags.html.markdown b/website/docs/d/ce_tags.html.markdown new file mode 100644 index 000000000000..548e57e33ccf --- /dev/null +++ b/website/docs/d/ce_tags.html.markdown @@ -0,0 +1,83 @@ +--- +subcategory: "CE (Cost Explorer)" +layout: "aws" +page_title: "AWS: aws_ce_tags" +description: |- + Provides details about a specific CE Tags +--- + +# Resource: aws_ce_tags + +Provides details about a specific CE Tags. + +## Example Usage + +```terraform +data "aws_ce_tags" "test" { + time_period { + start = "2021-01-01" + end = "2022-12-01" + } +} +``` + +## Argument Reference + +The following arguments are required: + +* `time_period` - (Required) Configuration block for the start and end dates for retrieving the dimension values. + +The following arguments are optional: + +* `filter` - (Optional) Configuration block for the `Expression` object used to categorize costs. See below. +* `search_string` - (Optional) Value that you want to search for. +* `sort_by` - (Optional) Configuration block for the value by which you want to sort the data. See below. +* `tag_key` - (Optional) Key of the tag that you want to return values for. + + +### `time_period` + +* `start` - (Required) End of the time period. +* `end` - (Required) Beginning of the time period. + +### `filter` + +* `and` - (Optional) Return results that match both `Dimension` objects. +* `cost_category` - (Optional) Configuration block for the filter that's based on `CostCategory` values. See below. +* `dimension` - (Optional) Configuration block for the specific `Dimension` to use for `Expression`. See below. +* `not` - (Optional) Return results that match both `Dimension` object. +* `or` - (Optional) Return results that match both `Dimension` object. +* `tag` - (Optional) Configuration block for the specific `Tag` to use for `Expression`. See below. + +### `cost_category` + +* `key` - (Optional) Unique name of the Cost Category. +* `match_options` - (Optional) Match options that you can use to filter your results. MatchOptions is only applicable for actions related to cost category. The default values for MatchOptions is `EQUALS` and `CASE_SENSITIVE`. Valid values are: `EQUALS`, `ABSENT`, `STARTS_WITH`, `ENDS_WITH`, `CONTAINS`, `CASE_SENSITIVE`, `CASE_INSENSITIVE`. +* `values` - (Optional) Specific value of the Cost Category. + +### `dimension` + +* `key` - (Optional) Unique name of the Cost Category. +* `match_options` - (Optional) Match options that you can use to filter your results. MatchOptions is only applicable for actions related to cost category. The default values for MatchOptions is `EQUALS` and `CASE_SENSITIVE`. Valid values are: `EQUALS`, `ABSENT`, `STARTS_WITH`, `ENDS_WITH`, `CONTAINS`, `CASE_SENSITIVE`, `CASE_INSENSITIVE`. +* `values` - (Optional) Specific value of the Cost Category. + +### `tag` + +* `key` - (Optional) Key for the tag. +* `match_options` - (Optional) Match options that you can use to filter your results. MatchOptions is only applicable for actions related to cost category. The default values for MatchOptions is `EQUALS` and `CASE_SENSITIVE`. Valid values are: `EQUALS`, `ABSENT`, `STARTS_WITH`, `ENDS_WITH`, `CONTAINS`, `CASE_SENSITIVE`, `CASE_INSENSITIVE`. +* `values` - (Optional) Specific value of the Cost Category. + + +### `sort_by` + +* `key` - (Required) key that's used to sort the data. Valid values are: `BlendedCost`, `UnblendedCost`, `AmortizedCost`, `NetAmortizedCost`, `NetUnblendedCost`, `UsageQuantity`, `NormalizedUsageAmount`. +* `sort_order` - (Optional) order that's used to sort the data. Valid values are: `ASCENDING`, `DESCENDING`. + + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - Unique ID of the tag. +* `tags` - Tags that match your request. + diff --git a/website/docs/r/ce_cost_category.html.markdown b/website/docs/r/ce_cost_category.html.markdown new file mode 100644 index 000000000000..64972da481bb --- /dev/null +++ b/website/docs/r/ce_cost_category.html.markdown @@ -0,0 +1,132 @@ +--- +subcategory: "CE (Cost Explorer)" +layout: "aws" +page_title: "AWS: aws_ce_cost_category" +description: |- + Provides a CE Cost Category Definition +--- + +# Resource: aws_ce_cost_category + +Provides a CE Cost Category. + +## Example Usage + +```terraform +resource "aws_ce_cost_category" "test" { + name = "NAME" + rule_version = "CostCategoryExpression.v1" + rule { + value = "production" + rule { + dimension { + key = "LINKED_ACCOUNT_NAME" + values = ["-prod"] + match_options = ["ENDS_WITH"] + } + } + } + rule { + value = "staging" + rule { + dimension { + key = "LINKED_ACCOUNT_NAME" + values = ["-stg"] + match_options = ["ENDS_WITH"] + } + } + } + rule { + value = "testing" + rule { + dimension { + key = "LINKED_ACCOUNT_NAME" + values = ["-dev"] + match_options = ["ENDS_WITH"] + } + } + } +} +``` + +## Argument Reference + +The following arguments are required: + +* `name` - (Required) Unique name for the Cost Category. +* `rule` - (Required) Configuration block for the Cost Category rules used to categorize costs. See below. +* `rule_version` - (Required) Rule schema version in this particular Cost Category. + +The following arguments are optional: + +* `default_value` - (Optional) Default value for the cost category. +* `split_charge_rule` - (Optional) Configuration block for the split charge rules used to allocate your charges between your Cost Category values. See below. + +### `rule` + +* `inherited_value` - (Optional) Configuration block for the value the line item is categorized as if the line item contains the matched dimension. See below. +* `rule` - (Optional) Configuration block for the `Expression` object used to categorize costs. See below. +* `type` - (Optional) You can define the CostCategoryRule rule type as either `REGULAR` or `INHERITED_VALUE`. +* `value` - (Optional) Default value for the cost category. + +### `inherited_value` + +* `dimension_key` - (Optional) Key to extract cost category values. +* `dimension_name` - (Optional) Name of the dimension that's used to group costs. If you specify `LINKED_ACCOUNT_NAME`, the cost category value is based on account name. If you specify `TAG`, the cost category value will be based on the value of the specified tag key. Valid values are `LINKED_ACCOUNT_NAME`, `TAG` + +### `rule` + +* `and` - (Optional) Return results that match both `Dimension` objects. +* `cost_category` - (Optional) Configuration block for the filter that's based on `CostCategory` values. See below. +* `dimension` - (Optional) Configuration block for the specific `Dimension` to use for `Expression`. See below. +* `not` - (Optional) Return results that match both `Dimension` object. +* `or` - (Optional) Return results that match both `Dimension` object. +* `tag` - (Optional) Configuration block for the specific `Tag` to use for `Expression`. See below. + +### `cost_category` + +* `key` - (Optional) Unique name of the Cost Category. +* `match_options` - (Optional) Match options that you can use to filter your results. MatchOptions is only applicable for actions related to cost category. The default values for MatchOptions is `EQUALS` and `CASE_SENSITIVE`. Valid values are: `EQUALS`, `ABSENT`, `STARTS_WITH`, `ENDS_WITH`, `CONTAINS`, `CASE_SENSITIVE`, `CASE_INSENSITIVE`. +* `values` - (Optional) Specific value of the Cost Category. + +### `dimension` + +* `key` - (Optional) Unique name of the Cost Category. +* `match_options` - (Optional) Match options that you can use to filter your results. MatchOptions is only applicable for actions related to cost category. The default values for MatchOptions is `EQUALS` and `CASE_SENSITIVE`. Valid values are: `EQUALS`, `ABSENT`, `STARTS_WITH`, `ENDS_WITH`, `CONTAINS`, `CASE_SENSITIVE`, `CASE_INSENSITIVE`. +* `values` - (Optional) Specific value of the Cost Category. + +### `tag` + +* `key` - (Optional) Key for the tag. +* `match_options` - (Optional) Match options that you can use to filter your results. MatchOptions is only applicable for actions related to cost category. The default values for MatchOptions is `EQUALS` and `CASE_SENSITIVE`. Valid values are: `EQUALS`, `ABSENT`, `STARTS_WITH`, `ENDS_WITH`, `CONTAINS`, `CASE_SENSITIVE`, `CASE_INSENSITIVE`. +* `values` - (Optional) Specific value of the Cost Category. + +### `split_charge_rule` + +* `method` - (Required) Method that's used to define how to split your source costs across your targets. Valid values are `FIXED`, `PROPORTIONAL`, `EVEN` +* `parameter` - (Optional) Configuration block for the parameters for a split charge method. This is only required for the `FIXED` method. See below. +* `source` - (Required) Cost Category value that you want to split. +* `targets` - (Required) Cost Category values that you want to split costs across. These values can't be used as a source in other split charge rules. + +### `parameter` + +* `type` - (Optional) Parameter type. +* `values` - (Optional) Parameter values. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - ARN of the cost category. +* `effective_end` - Effective end data of your Cost Category. +* `effective_start` - Effective state data of your Cost Category. +* `id` - Unique ID of the cost category. + + +## Import + +`aws_ce_cost_category` can be imported using the id, e.g. + +``` +$ terraform import aws_ce_cost_category.example costCategoryARN +```