diff --git a/.changelog/30862.txt b/.changelog/30862.txt new file mode 100644 index 00000000000..6cb5d5a75f1 --- /dev/null +++ b/.changelog/30862.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_ce_cost_category: Allow up to 3 levels of `and`, not` and `or` operand nesting for the `rule` argument +``` diff --git a/internal/service/ce/anomaly_subscription.go b/internal/service/ce/anomaly_subscription.go index e8e9b5a548d..b66416e81e2 100644 --- a/internal/service/ce/anomaly_subscription.go +++ b/internal/service/ce/anomaly_subscription.go @@ -26,6 +26,10 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) +const ( + anomalySubscriptionRootElementSchemaLevel = 2 +) + // @SDKResource("aws_ce_anomaly_subscription", name="Anomaly Subscription") // @Tags(identifierAttribute="id") func resourceAnomalySubscription() *schema.Resource { @@ -96,7 +100,7 @@ func resourceAnomalySubscription() *schema.Resource { MaxItems: 1, Computed: true, Optional: true, - Elem: elemExpression(), + Elem: expressionElem(anomalySubscriptionRootElementSchemaLevel), }, }, @@ -124,7 +128,7 @@ func resourceAnomalySubscriptionCreate(ctx context.Context, d *schema.ResourceDa } if v, ok := d.GetOk("threshold_expression"); ok { - input.AnomalySubscription.ThresholdExpression = expandCostExpression(v.([]interface{})[0].(map[string]interface{})) + input.AnomalySubscription.ThresholdExpression = expandExpression(v.([]interface{})[0].(map[string]interface{})) } output, err := conn.CreateAnomalySubscription(ctx, input) @@ -160,7 +164,7 @@ func resourceAnomalySubscriptionRead(ctx context.Context, d *schema.ResourceData d.Set("monitor_arn_list", subscription.MonitorArnList) d.Set("name", subscription.SubscriptionName) d.Set("subscriber", flattenSubscribers(subscription.Subscribers)) - if err := d.Set("threshold_expression", []interface{}{flattenCostCategoryRuleExpression(subscription.ThresholdExpression)}); err != nil { + if err := d.Set("threshold_expression", []interface{}{flattenExpression(subscription.ThresholdExpression)}); err != nil { return sdkdiag.AppendErrorf(diags, "setting threshold_expression: %s", err) } @@ -190,7 +194,7 @@ func resourceAnomalySubscriptionUpdate(ctx context.Context, d *schema.ResourceDa } if d.HasChange("threshold_expression") { - input.ThresholdExpression = expandCostExpression(d.Get("threshold_expression").([]interface{})[0].(map[string]interface{})) + input.ThresholdExpression = expandExpression(d.Get("threshold_expression").([]interface{})[0].(map[string]interface{})) } _, err := conn.UpdateAnomalySubscription(ctx, input) diff --git a/internal/service/ce/cost_category.go b/internal/service/ce/cost_category.go index c01e30f3282..16465820e73 100644 --- a/internal/service/ce/cost_category.go +++ b/internal/service/ce/cost_category.go @@ -20,13 +20,16 @@ import ( "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" "github.com/hashicorp/terraform-provider-aws/names" ) +const ( + costCategoryRuleRootElementSchemaLevel = 3 +) + // @SDKResource("aws_ce_cost_category", name="Cost Category") // @Tags(identifierAttribute="id") func resourceCostCategory() *schema.Resource { @@ -96,7 +99,7 @@ func resourceCostCategory() *schema.Resource { Type: schema.TypeList, MaxItems: 1, Optional: true, - Elem: elemExpression(), + Elem: expressionElem(costCategoryRuleRootElementSchemaLevel), }, "type": { Type: schema.TypeString, @@ -175,212 +178,123 @@ func resourceCostCategory() *schema.Resource { } } -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, - Optional: true, - ValidateFunc: validation.StringLenBetween(1, 50), - }, - "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, - ValidateDiagFunc: enum.Validate[awstypes.Dimension](), - }, - "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 expressionElem(level int) *schema.Resource { + // This is the non-recursive part of the schema. + expressionSchema := 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), }, - }, - "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, - ValidateDiagFunc: enum.Validate[awstypes.MatchOption](), - }, - }, - "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](), }, }, - }, - }, - } - } - - return &schema.Resource{ - Schema: map[string]*schema.Schema{ - "and": { - Type: schema.TypeSet, - Optional: true, - Elem: elemNestedExpression(), - }, - "cost_category": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { + "values": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringLenBetween(1, 50), - }, - "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), - }, + ValidateFunc: validation.StringLenBetween(0, 1024), }, }, }, }, - "dimension": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { + }, + "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](), + }, + "match_options": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ Type: schema.TypeString, - Optional: true, - ValidateDiagFunc: enum.Validate[awstypes.Dimension](), + ValidateDiagFunc: enum.Validate[awstypes.MatchOption](), }, - "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), - }, + }, + "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: elemNestedExpression(), - }, - "or": { - Type: schema.TypeSet, - Optional: true, - Elem: elemNestedExpression(), - }, - "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, - ValidateDiagFunc: enum.Validate[awstypes.MatchOption](), - }, + }, + "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, + ValidateDiagFunc: enum.Validate[awstypes.MatchOption](), }, - "values": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringLenBetween(0, 1024), - }, + }, + "values": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(0, 1024), }, }, }, }, }, } + + if level > 1 { + // Add in the recursive part of the schema. + expressionSchema["and"] = &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: expressionElem(level - 1), + } + expressionSchema["not"] = &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: expressionElem(level - 1), + } + expressionSchema["or"] = &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: expressionElem(level - 1), + } + } + + return &schema.Resource{ + Schema: expressionSchema, + } } func resourceCostCategoryCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { @@ -529,15 +443,15 @@ func findCostCategoryByARN(ctx context.Context, conn *costexplorer.Client, arn s return output.CostCategory, nil } -func expandCostCategoryRule(tfMap map[string]interface{}) awstypes.CostCategoryRule { - apiObject := awstypes.CostCategoryRule{} +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{})) + apiObject.Rule = expandExpression(v[0].(map[string]interface{})) } if v, ok := tfMap["type"].(string); ok && v != "" { @@ -551,6 +465,32 @@ func expandCostCategoryRule(tfMap map[string]interface{}) awstypes.CostCategoryR return apiObject } +func expandCostCategoryRules(tfList []interface{}) []awstypes.CostCategoryRule { + if len(tfList) == 0 { + return nil + } + + var apiObjects []awstypes.CostCategoryRule + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandCostCategoryRule(tfMap) + + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, *apiObject) + } + + return apiObjects +} + func expandCostCategoryInheritedValueDimension(tfMap map[string]interface{}) *awstypes.CostCategoryInheritedValueDimension { if tfMap == nil { return nil @@ -569,7 +509,7 @@ func expandCostCategoryInheritedValueDimension(tfMap map[string]interface{}) *aw return apiObject } -func expandCostExpression(tfMap map[string]interface{}) *awstypes.Expression { +func expandExpression(tfMap map[string]interface{}) *awstypes.Expression { if tfMap == nil { return nil } @@ -577,7 +517,7 @@ func expandCostExpression(tfMap map[string]interface{}) *awstypes.Expression { apiObject := &awstypes.Expression{} if v, ok := tfMap["and"].(*schema.Set); ok && v.Len() > 0 { - apiObject.And = expandCostExpressions(v.List()) + apiObject.And = expandExpressions(v.List()) } if v, ok := tfMap["cost_category"].([]interface{}); ok && len(v) > 0 && v[0] != nil { @@ -589,11 +529,11 @@ func expandCostExpression(tfMap map[string]interface{}) *awstypes.Expression { } if v, ok := tfMap["not"].([]interface{}); ok && len(v) > 0 && v[0] != nil { - apiObject.Not = expandCostExpression(v[0].(map[string]interface{})) + apiObject.Not = expandExpression(v[0].(map[string]interface{})) } if v, ok := tfMap["or"].(*schema.Set); ok && v.Len() > 0 { - apiObject.Or = expandCostExpressions(v.List()) + apiObject.Or = expandExpressions(v.List()) } if v, ok := tfMap["tags"].([]interface{}); ok && len(v) > 0 && v[0] != nil { @@ -603,6 +543,31 @@ func expandCostExpression(tfMap map[string]interface{}) *awstypes.Expression { return apiObject } +func expandExpressions(tfList []interface{}) []awstypes.Expression { + if len(tfList) == 0 { + return nil + } + + var apiObjects []awstypes.Expression + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + if !ok { + continue + } + + apiObject := expandExpression(tfMap) + + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, *apiObject) + } + + return apiObjects +} + func expandCostCategoryValues(tfMap map[string]interface{}) *awstypes.CostCategoryValues { if tfMap == nil { return nil @@ -669,37 +634,25 @@ func expandTagValues(tfMap map[string]interface{}) *awstypes.TagValues { return apiObject } -func expandCostExpressions(tfList []interface{}) []awstypes.Expression { - if len(tfList) == 0 { - return nil +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.ExpandStringValueSet(tfMap["targets"].(*schema.Set)), } - - var apiObjects []awstypes.Expression - - for _, tfMapRaw := range tfList { - tfMap, ok := tfMapRaw.(map[string]interface{}) - if !ok { - continue - } - - apiObject := expandCostExpression(tfMap) - - if apiObject == nil { - continue - } - - apiObjects = append(apiObjects, *apiObject) + if v, ok := tfMap["parameter"]; ok { + apiObject.Parameters = expandCostCategorySplitChargeRuleParameters(v.(*schema.Set).List()) } - return apiObjects + return apiObject } -func expandCostCategoryRules(tfList []interface{}) []awstypes.CostCategoryRule { +func expandCostCategorySplitChargeRules(tfList []interface{}) []awstypes.CostCategorySplitChargeRule { if len(tfList) == 0 { return nil } - var apiObjects []awstypes.CostCategoryRule + var apiObjects []awstypes.CostCategorySplitChargeRule for _, tfMapRaw := range tfList { tfMap, ok := tfMapRaw.(map[string]interface{}) @@ -708,29 +661,20 @@ func expandCostCategoryRules(tfList []interface{}) []awstypes.CostCategoryRule { continue } - apiObject := expandCostCategoryRule(tfMap) - - apiObjects = append(apiObjects, apiObject) - } + apiObject := expandCostCategorySplitChargeRule(tfMap) - return apiObjects -} + if apiObject == nil { + continue + } -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.ExpandStringValueSet(tfMap["targets"].(*schema.Set)), - } - if v, ok := tfMap["parameter"]; ok { - apiObject.Parameters = expandCostCategorySplitChargeRuleParameters(v.(*schema.Set).List()) + apiObjects = append(apiObjects, *apiObject) } - return apiObject + return apiObjects } -func expandCostCategorySplitChargeRuleParameter(tfMap map[string]interface{}) awstypes.CostCategorySplitChargeRuleParameter { - apiObject := awstypes.CostCategorySplitChargeRuleParameter{ +func expandCostCategorySplitChargeRuleParameter(tfMap map[string]interface{}) *awstypes.CostCategorySplitChargeRuleParameter { + apiObject := &awstypes.CostCategorySplitChargeRuleParameter{ Type: awstypes.CostCategorySplitChargeRuleParameterType(tfMap["type"].(string)), Values: flex.ExpandStringValueList(tfMap["values"].([]interface{})), } @@ -754,49 +698,46 @@ func expandCostCategorySplitChargeRuleParameters(tfList []interface{}) []awstype apiObject := expandCostCategorySplitChargeRuleParameter(tfMap) - apiObjects = append(apiObjects, apiObject) + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, *apiObject) } return apiObjects } -func expandCostCategorySplitChargeRules(tfList []interface{}) []awstypes.CostCategorySplitChargeRule { - if len(tfList) == 0 { +func flattenCostCategoryRule(apiObject *awstypes.CostCategoryRule) map[string]interface{} { + if apiObject == nil { return nil } - var apiObjects []awstypes.CostCategorySplitChargeRule - - for _, tfMapRaw := range tfList { - tfMap, ok := tfMapRaw.(map[string]interface{}) - - if !ok { - continue - } - - apiObject := expandCostCategorySplitChargeRule(tfMap) + tfMap := map[string]interface{}{} - apiObjects = append(apiObjects, apiObject) - } + tfMap["inherited_value"] = flattenCostCategoryInheritedValueDimension(apiObject.InheritedValue) + tfMap["rule"] = []interface{}{flattenExpression(apiObject.Rule)} + tfMap["type"] = string(apiObject.Type) + tfMap["value"] = aws.ToString(apiObject.Value) - return apiObjects + return tfMap } -func flattenCostCategoryRule(apiObject awstypes.CostCategoryRule) map[string]interface{} { - tfMap := map[string]interface{}{} +func flattenCostCategoryRules(apiObjects []awstypes.CostCategoryRule) []map[string]interface{} { + if len(apiObjects) == 0 { + return nil + } - var expressions []*awstypes.Expression - expressions = append(expressions, apiObject.Rule) + var tfList []map[string]interface{} - tfMap["inherited_value"] = flattenCostCategoryRuleInheritedValue(apiObject.InheritedValue) - tfMap["rule"] = flattenCostCategoryRuleExpressions(expressions) - tfMap["type"] = string(apiObject.Type) - tfMap["value"] = aws.ToString(apiObject.Value) + for _, apiObject := range apiObjects { + tfList = append(tfList, flattenCostCategoryRule(&apiObject)) + } - return tfMap + return tfList } -func flattenCostCategoryRuleInheritedValue(apiObject *awstypes.CostCategoryInheritedValueDimension) []map[string]interface{} { +func flattenCostCategoryInheritedValueDimension(apiObject *awstypes.CostCategoryInheritedValueDimension) []map[string]interface{} { if apiObject == nil { return nil } @@ -812,40 +753,43 @@ func flattenCostCategoryRuleInheritedValue(apiObject *awstypes.CostCategoryInher return tfList } -func flattenCostCategoryRuleExpression(apiObject *awstypes.Expression) map[string]interface{} { +func flattenExpression(apiObject *awstypes.Expression) map[string]interface{} { if apiObject == nil { return nil } tfMap := map[string]interface{}{} - tfMap["and"] = flattenCostCategoryRuleOperandExpressions(tfslices.ToPointers[[]awstypes.Expression](apiObject.And)) - tfMap["cost_category"] = flattenCostCategoryRuleExpressionCostCategory(apiObject.CostCategories) - tfMap["dimension"] = flattenCostCategoryRuleExpressionDimension(apiObject.Dimensions) - tfMap["not"] = flattenCostCategoryRuleOperandExpressions([]*awstypes.Expression{apiObject.Not}) - tfMap["or"] = flattenCostCategoryRuleOperandExpressions(tfslices.ToPointers[[]awstypes.Expression](apiObject.Or)) - tfMap["tags"] = flattenCostCategoryRuleExpressionTag(apiObject.Tags) + if len(apiObject.And) > 0 { + tfMap["and"] = flattenExpressions(apiObject.And) + } + tfMap["cost_category"] = flattenCostCategoryValues(apiObject.CostCategories) + tfMap["dimension"] = flattenDimensionValues(apiObject.Dimensions) + if apiObject.Not != nil { + tfMap["not"] = []interface{}{flattenExpression(apiObject.Not)} + } + if len(apiObject.Or) > 0 { + tfMap["or"] = flattenExpressions(apiObject.Or) + } + tfMap["tags"] = flattenTagValues(apiObject.Tags) return tfMap } -func flattenCostCategoryRuleExpressionCostCategory(apiObject *awstypes.CostCategoryValues) []map[string]interface{} { - if apiObject == nil { +func flattenExpressions(apiObjects []awstypes.Expression) []map[string]interface{} { + if len(apiObjects) == 0 { return nil } var tfList []map[string]interface{} - tfMap := map[string]interface{}{} - - tfMap["key"] = aws.ToString(apiObject.Key) - tfMap["match_options"] = flex.FlattenStringyValueList(apiObject.MatchOptions) - tfMap["values"] = apiObject.Values - tfList = append(tfList, tfMap) + for _, apiObject := range apiObjects { + tfList = append(tfList, flattenExpression(&apiObject)) + } return tfList } -func flattenCostCategoryRuleExpressionDimension(apiObject *awstypes.DimensionValues) []map[string]interface{} { +func flattenCostCategoryValues(apiObject *awstypes.CostCategoryValues) []map[string]interface{} { if apiObject == nil { return nil } @@ -853,7 +797,7 @@ func flattenCostCategoryRuleExpressionDimension(apiObject *awstypes.DimensionVal var tfList []map[string]interface{} tfMap := map[string]interface{}{} - tfMap["key"] = string(apiObject.Key) + tfMap["key"] = aws.ToString(apiObject.Key) tfMap["match_options"] = flex.FlattenStringyValueList(apiObject.MatchOptions) tfMap["values"] = apiObject.Values @@ -862,7 +806,7 @@ func flattenCostCategoryRuleExpressionDimension(apiObject *awstypes.DimensionVal return tfList } -func flattenCostCategoryRuleExpressionTag(apiObject *awstypes.TagValues) []map[string]interface{} { +func flattenDimensionValues(apiObject *awstypes.DimensionValues) []map[string]interface{} { if apiObject == nil { return nil } @@ -870,7 +814,7 @@ func flattenCostCategoryRuleExpressionTag(apiObject *awstypes.TagValues) []map[s var tfList []map[string]interface{} tfMap := map[string]interface{}{} - tfMap["key"] = aws.ToString(apiObject.Key) + tfMap["key"] = string(apiObject.Key) tfMap["match_options"] = flex.FlattenStringyValueList(apiObject.MatchOptions) tfMap["values"] = apiObject.Values @@ -879,38 +823,38 @@ func flattenCostCategoryRuleExpressionTag(apiObject *awstypes.TagValues) []map[s return tfList } -func flattenCostCategoryRuleExpressions(apiObjects []*awstypes.Expression) []map[string]interface{} { - if len(apiObjects) == 0 { +func flattenTagValues(apiObject *awstypes.TagValues) []map[string]interface{} { + if apiObject == nil { return nil } var tfList []map[string]interface{} + tfMap := map[string]interface{}{} - for _, apiObject := range apiObjects { - if apiObject == nil { - continue - } + tfMap["key"] = aws.ToString(apiObject.Key) + tfMap["match_options"] = flex.FlattenStringyValueList(apiObject.MatchOptions) + tfMap["values"] = apiObject.Values - tfList = append(tfList, flattenCostCategoryRuleExpression(apiObject)) - } + tfList = append(tfList, tfMap) return tfList } -func flattenCostCategoryRuleOperandExpression(apiObject *awstypes.Expression) map[string]interface{} { +func flattenCostCategorySplitChargeRule(apiObject *awstypes.CostCategorySplitChargeRule) 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) + tfMap["method"] = string(apiObject.Method) + tfMap["parameter"] = flattenCostCategorySplitChargeRuleParameters(apiObject.Parameters) + tfMap["source"] = aws.ToString(apiObject.Source) + tfMap["targets"] = apiObject.Targets return tfMap } -func flattenCostCategoryRuleOperandExpressions(apiObjects []*awstypes.Expression) []map[string]interface{} { +func flattenCostCategorySplitChargeRules(apiObjects []awstypes.CostCategorySplitChargeRule) []map[string]interface{} { if len(apiObjects) == 0 { return nil } @@ -918,41 +862,17 @@ func flattenCostCategoryRuleOperandExpressions(apiObjects []*awstypes.Expression var tfList []map[string]interface{} for _, apiObject := range apiObjects { - if apiObject == nil { - continue - } - - tfList = append(tfList, flattenCostCategoryRuleOperandExpression(apiObject)) + tfList = append(tfList, flattenCostCategorySplitChargeRule(&apiObject)) } return tfList } -func flattenCostCategoryRules(apiObjects []awstypes.CostCategoryRule) []map[string]interface{} { - if len(apiObjects) == 0 { +func flattenCostCategorySplitChargeRuleParameter(apiObject *awstypes.CostCategorySplitChargeRuleParameter) map[string]interface{} { + if apiObject == nil { return nil } - var tfList []map[string]interface{} - - for _, apiObject := range apiObjects { - tfList = append(tfList, flattenCostCategoryRule(apiObject)) - } - - return tfList -} - -func flattenCostCategorySplitChargeRule(apiObject awstypes.CostCategorySplitChargeRule) map[string]interface{} { - tfMap := map[string]interface{}{} - tfMap["method"] = string(apiObject.Method) - tfMap["parameter"] = flattenCostCategorySplitChargeRuleParameters(apiObject.Parameters) - tfMap["source"] = aws.ToString(apiObject.Source) - tfMap["targets"] = apiObject.Targets - - return tfMap -} - -func flattenCostCategorySplitChargeRuleParameter(apiObject awstypes.CostCategorySplitChargeRuleParameter) map[string]interface{} { tfMap := map[string]interface{}{} tfMap["type"] = string(apiObject.Type) tfMap["values"] = apiObject.Values @@ -968,21 +888,7 @@ func flattenCostCategorySplitChargeRuleParameters(apiObjects []awstypes.CostCate var tfList []map[string]interface{} for _, apiObject := range apiObjects { - tfList = append(tfList, flattenCostCategorySplitChargeRuleParameter(apiObject)) - } - - return tfList -} - -func flattenCostCategorySplitChargeRules(apiObjects []awstypes.CostCategorySplitChargeRule) []map[string]interface{} { - if len(apiObjects) == 0 { - return nil - } - - var tfList []map[string]interface{} - - for _, apiObject := range apiObjects { - tfList = append(tfList, flattenCostCategorySplitChargeRule(apiObject)) + tfList = append(tfList, flattenCostCategorySplitChargeRuleParameter(&apiObject)) } return tfList diff --git a/internal/service/ce/cost_category_data_source.go b/internal/service/ce/cost_category_data_source.go index ccf7fb13c5e..6c1aad94e6e 100644 --- a/internal/service/ce/cost_category_data_source.go +++ b/internal/service/ce/cost_category_data_source.go @@ -67,7 +67,7 @@ func dataSourceCostCategory() *schema.Resource { "rule": { Type: schema.TypeList, Computed: true, - Elem: sdkv2.DataSourceElemFromResourceElem(elemExpression()), + Elem: sdkv2.DataSourceElemFromResourceElem(expressionElem(costCategoryRuleRootElementSchemaLevel)), }, "type": { Type: schema.TypeString, diff --git a/internal/service/ce/cost_category_test.go b/internal/service/ce/cost_category_test.go index 7df532737ac..55195fda83e 100644 --- a/internal/service/ce/cost_category_test.go +++ b/internal/service/ce/cost_category_test.go @@ -153,6 +153,34 @@ func TestAccCECostCategory_complete(t *testing.T) { }) } +func TestAccCECostCategory_notWithAnd(t *testing.T) { + ctx := acctest.Context(t) + var output awstypes.CostCategory + resourceName := "aws_ce_cost_category.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCostCategoryDestroy(ctx), + ErrorCheck: acctest.ErrorCheck(t, names.CEServiceID), + Steps: []resource.TestStep{ + { + Config: testAccCostCategoryConfig_operandNot(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCostCategoryExists(ctx, resourceName, &output), + resource.TestCheckResourceAttr(resourceName, "name", rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccCECostCategory_splitCharge(t *testing.T) { ctx := acctest.Context(t) var output awstypes.CostCategory @@ -568,3 +596,34 @@ resource "aws_ce_cost_category" "test" { } `, rName, date) } + +func testAccCostCategoryConfig_operandNot(rName string) string { + return fmt.Sprintf(` +resource "aws_ce_cost_category" "test" { + name = %[1]q + rule_version = "CostCategoryExpression.v1" + rule { + value = "notax" + rule { + and { + dimension { + key = "LINKED_ACCOUNT_NAME" + values = ["-prod"] + match_options = ["ENDS_WITH"] + } + } + and { + not { + dimension { + key = "RECORD_TYPE" + values = ["Tax"] + match_options = ["EQUALS"] + } + } + } + } + type = "REGULAR" + } +} +`, rName) +} diff --git a/internal/service/ce/tags_data_source.go b/internal/service/ce/tags_data_source.go index 2ed6750a1a7..31e1fb6c064 100644 --- a/internal/service/ce/tags_data_source.go +++ b/internal/service/ce/tags_data_source.go @@ -17,6 +17,10 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" ) +const ( + tagRootElementSchemaLevel = 2 +) + // @SDKDataSource("aws_ce_tags", name="Tags") func dataSourceTags() *schema.Resource { return &schema.Resource{ @@ -27,7 +31,7 @@ func dataSourceTags() *schema.Resource { Type: schema.TypeList, MaxItems: 1, Optional: true, - Elem: elemExpression(), + Elem: expressionElem(tagRootElementSchemaLevel), }, "search_string": { Type: schema.TypeString, @@ -98,7 +102,7 @@ func dataSourceTagsRead(ctx context.Context, d *schema.ResourceData, meta interf } if v, ok := d.GetOk("filter"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { - input.Filter = expandCostExpression(v.([]interface{})[0].(map[string]interface{})) + input.Filter = expandExpression(v.([]interface{})[0].(map[string]interface{})) } if v, ok := d.GetOk("search_string"); ok { diff --git a/names/names.go b/names/names.go index 6005202fec0..f332204633a 100644 --- a/names/names.go +++ b/names/names.go @@ -50,7 +50,6 @@ const ( CognitoIdentityEndpointID = "cognito-identity" ComprehendEndpointID = "comprehend" ConfigServiceEndpointID = "config" - CostExploereEndpointID = "ce" DevOpsGuruEndpointID = "devops-guru" ECREndpointID = "api.ecr" EKSEndpointID = "eks"