Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Resource: aws_ce_anomaly_monitor #25177

Merged
merged 27 commits into from
Jun 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3d6067a
Initial working Monitor
brittandeyoung Jun 4, 2022
d4048b8
fix empty specification
brittandeyoung Jun 4, 2022
b79ff4c
update CUSTOM test to use tags
brittandeyoung Jun 4, 2022
5073064
add null key
brittandeyoung Jun 4, 2022
9961fac
lint fix tests
brittandeyoung Jun 4, 2022
d378594
add !d.IsNewResource() check
brittandeyoung Jun 4, 2022
1c95dfa
Added Doc, resolved semgrep, and added validations
brittandeyoung Jun 6, 2022
3a750d9
Update test name to include "CE"
brittandeyoung Jun 6, 2022
d409ee2
fix casing in non serial test
brittandeyoung Jun 6, 2022
9715d02
Added generate.go to generate tag functions and added resource tagging
brittandeyoung Jun 6, 2022
e9e1b26
resolved semgrep naming complaints
brittandeyoung Jun 6, 2022
653141d
added tags and tags_all to docs
brittandeyoung Jun 6, 2022
23bcecc
ce: Amend anomaly monitor acceptance tests
zhelding Jun 6, 2022
27cb41b
ce: Amend anomaly monitor resource listtags
brittandeyoung Jun 7, 2022
621f82c
ce: Amend anomaly monitor resource update name
brittandeyoung Jun 7, 2022
d8b7ecf
ce: Amend anomaly monitor acceptance tests
brittandeyoung Jun 7, 2022
6a1d648
ce: Amend Docs
brittandeyoung Jun 7, 2022
0eb5bf7
ce: Amend anomaly monitor acceptance tests
zhelding Jun 7, 2022
c44bec2
ce: Amend anomaly monitor docs
brittandeyoung Jun 8, 2022
62a8d61
Amend anomaly monitor schema
zhelding Jun 8, 2022
143ac73
ce: Amend anomaly monitor tests
brittandeyoung Jun 9, 2022
6dc8452
ce: Amend anomaly monitor
brittandeyoung Jun 9, 2022
3c4ee71
Implement cost anomaly monitor finder function
zhelding Jun 9, 2022
1dafb10
Add error for empty output when reading cost anomaly monitor
zhelding Jun 9, 2022
5842963
Amend cost anomaly monitor docs for style
zhelding Jun 9, 2022
70452c8
Rearrange anomaly monitor tests to maximize parallel runs
zhelding Jun 9, 2022
7611c21
Add changelog for new anomaly monitor resource
zhelding Jun 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/251777.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
aws_ce_anomaly_monitor
```
3 changes: 2 additions & 1 deletion internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -1040,7 +1040,8 @@ func Provider() *schema.Provider {
"aws_budgets_budget": budgets.ResourceBudget(),
"aws_budgets_budget_action": budgets.ResourceBudgetAction(),

"aws_ce_cost_category": ce.ResourceCostCategory(),
"aws_ce_anomaly_monitor": ce.ResourceAnomalyMonitor(),
"aws_ce_cost_category": ce.ResourceCostCategory(),

"aws_chime_voice_connector": chime.ResourceVoiceConnector(),
"aws_chime_voice_connector_group": chime.ResourceVoiceConnectorGroup(),
Expand Down
232 changes: 232 additions & 0 deletions internal/service/ce/anomaly_monitor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
package ce

import (
"context"
"encoding/json"
"regexp"

"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/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
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"
)

func ResourceAnomalyMonitor() *schema.Resource {
return &schema.Resource{
CreateContext: resourceAnomalyMonitorCreate,
ReadContext: resourceAnomalyMonitorRead,
UpdateContext: resourceAnomalyMonitorUpdate,
DeleteContext: resourceAnomalyMonitorDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Computed: true,
},
"monitor_dimension": {
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"monitor_specification"},
ValidateFunc: validation.StringInSlice(costexplorer.MonitorDimension_Values(), false),
},
"name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.All(
validation.StringLenBetween(1, 1024),
validation.StringMatch(regexp.MustCompile(`[\\S\\s]*`), "Must be a valid Anomaly Monitor Name matching expression: [\\S\\s]*")),
},
"monitor_specification": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validation.StringIsJSON,
DiffSuppressFunc: verify.SuppressEquivalentJSONDiffs,
ConflictsWith: []string{"monitor_dimension"},
},
"monitor_type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice(costexplorer.MonitorType_Values(), false),
},
"tags": tftags.TagsSchema(),
"tags_all": tftags.TagsSchemaComputed(),
},

CustomizeDiff: verify.SetTagsDiff,
}
}

func resourceAnomalyMonitorCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
conn := meta.(*conns.AWSClient).CEConn
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{})))

input := &costexplorer.CreateAnomalyMonitorInput{
AnomalyMonitor: &costexplorer.AnomalyMonitor{
MonitorName: aws.String(d.Get("name").(string)),
MonitorType: aws.String(d.Get("monitor_type").(string)),
},
}
switch d.Get("monitor_type").(string) {
case costexplorer.MonitorTypeDimensional:
if v, ok := d.GetOk("monitor_dimension"); ok {
input.AnomalyMonitor.MonitorDimension = aws.String(v.(string))
} else {
return diag.Errorf("If Monitor Type is %s, dimension attrribute is required", costexplorer.MonitorTypeDimensional)
}
case costexplorer.MonitorTypeCustom:
if v, ok := d.GetOk("monitor_specification"); ok {
expression := costexplorer.Expression{}

if err := json.Unmarshal([]byte(v.(string)), &expression); err != nil {
return diag.Errorf("Error parsing specification: %s", err)
}

input.AnomalyMonitor.MonitorSpecification = &expression

} else {
return diag.Errorf("If Monitor Type is %s, dimension attrribute is required", costexplorer.MonitorTypeCustom)
}
}

if len(tags) > 0 {
input.ResourceTags = Tags(tags.IgnoreAWS())
}

resp, err := conn.CreateAnomalyMonitorWithContext(ctx, input)

if err != nil {
return diag.Errorf("Error creating Anomaly Monitor: %s", err)
}

if resp == nil || resp.MonitorArn == nil {
return diag.Errorf("creating Cost Explorer Anomaly Monitor resource (%s): empty output", d.Get("name").(string))
}

d.SetId(aws.StringValue(resp.MonitorArn))

return resourceAnomalyMonitorRead(ctx, d, meta)
}

func resourceAnomalyMonitorRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
conn := meta.(*conns.AWSClient).CEConn
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig

monitor, err := FindAnomalyMonitorByARN(ctx, conn, d.Id())

if !d.IsNewResource() && tfresource.NotFound(err) {
names.LogNotFoundRemoveState(names.CE, names.ErrActionReading, ResAnomalyMonitor, d.Id())
d.SetId("")
return nil
}

if err != nil {
return names.DiagError(names.CE, names.ErrActionReading, ResAnomalyMonitor, d.Id(), err)
}

if monitor.MonitorSpecification != nil {
specificationToJson, err := json.Marshal(monitor.MonitorSpecification)
if err != nil {
return diag.Errorf("Error parsing specification response: %s", err)
}
specificationToSet, err := structure.NormalizeJsonString(string(specificationToJson))

if err != nil {
return diag.Errorf("Specification (%s) is invalid JSON: %s", specificationToSet, err)
}

d.Set("monitor_specification", specificationToSet)
}

d.Set("arn", monitor.MonitorArn)
d.Set("monitor_dimension", monitor.MonitorDimension)
d.Set("name", monitor.MonitorName)
d.Set("monitor_type", monitor.MonitorType)

tags, err := ListTags(conn, aws.StringValue(monitor.MonitorArn))
tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig)

if err != nil {
return names.DiagError(names.CE, names.ErrActionReading, ResTags, d.Id(), err)
}

//lintignore:AWSR002
if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {
return names.DiagError(names.CE, names.ErrActionReading, ResTags, d.Id(), err)
}

if err := d.Set("tags_all", tags.Map()); err != nil {
return names.DiagError(names.CE, names.ErrActionReading, ResTags, d.Id(), err)
}

return nil
}

func resourceAnomalyMonitorUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
conn := meta.(*conns.AWSClient).CEConn
requestUpdate := false

input := &costexplorer.UpdateAnomalyMonitorInput{
MonitorArn: aws.String(d.Id()),
}

if d.HasChange("name") {
input.MonitorName = aws.String(d.Get("name").(string))
requestUpdate = true
}

if d.HasChange("tags") {
o, n := d.GetChange("tags")

if err := UpdateTags(conn, d.Id(), o, n); err != nil {
return names.DiagError(names.CE, names.ErrActionUpdating, ResTags, d.Id(), err)
}
}

if d.HasChange("tags_all") {
o, n := d.GetChange("tags_all")

if err := UpdateTags(conn, d.Id(), o, n); err != nil {
return names.DiagError(names.CE, names.ErrActionUpdating, ResTags, d.Id(), err)
}
}

if requestUpdate {
_, err := conn.UpdateAnomalyMonitorWithContext(ctx, input)

if err != nil {
return names.DiagError(names.CE, names.ErrActionUpdating, ResAnomalyMonitor, d.Id(), err)
}
}

return resourceAnomalyMonitorRead(ctx, d, meta)
}

func resourceAnomalyMonitorDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
conn := meta.(*conns.AWSClient).CEConn

_, err := conn.DeleteAnomalyMonitorWithContext(ctx, &costexplorer.DeleteAnomalyMonitorInput{MonitorArn: aws.String(d.Id())})

if err != nil && tfawserr.ErrCodeEquals(err, costexplorer.ErrCodeUnknownMonitorException) {
return nil
}

if err != nil {
return names.DiagError(names.CE, names.ErrActionDeleting, ResAnomalyMonitor, d.Id(), err)
}

return nil
}
Loading