Skip to content

Commit

Permalink
resource/aws_cloudwatch_metric_alarm: Add validations (#12817)
Browse files Browse the repository at this point in the history
References:

- https://docs.aws.amazon.com/sdk-for-go/api/service/cloudwatch/#pkg-constants
- https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_PutMetricAlarm.html

Output from acceptance testing in AWS Commercial:

```
--- PASS: TestAccAWSCloudWatchMetricAlarm_AlarmActions_EC2Automate (177.84s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_AlarmActions_SNSTopic (26.40s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_AlarmActions_SWFAction (25.53s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_basic (25.28s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_datapointsToAlarm (22.65s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_disappears (19.51s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_evaluateLowSampleCountPercentiles (33.19s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_expression (67.61s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_extendedStatistic (22.59s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_missingStatistic (11.62s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_tags (47.01s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_treatMissingData (33.18s)
```

Output from acceptance testing in AWS GovCloud (US):

```
--- PASS: TestAccAWSCloudWatchMetricAlarm_AlarmActions_EC2Automate (199.57s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_AlarmActions_SNSTopic (23.82s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_AlarmActions_SWFAction (23.14s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_basic (22.45s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_datapointsToAlarm (19.66s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_disappears (16.42s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_evaluateLowSampleCountPercentiles (33.05s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_expression (78.23s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_extendedStatistic (18.22s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_missingStatistic (8.10s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_tags (48.94s)
--- PASS: TestAccAWSCloudWatchMetricAlarm_treatMissingData (33.07s)
```
  • Loading branch information
DrFaust92 authored Feb 18, 2021
1 parent 993b015 commit bf4f55c
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 64 deletions.
3 changes: 3 additions & 0 deletions .changelog/12817.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_cloudwatch_metric_alarm: Add plan time validation to `alarm_name`, `comparison_operator`, `metric_name`, `metric_query.id`, `metric_query.expression`, `metric_query.metric.metric_name`, `metric_query.metric.namespace`, `metric_query.metric.unit`, `namespace`, `period`, `statistic`, `alarm_description`, `insufficient_data_actions`, `ok_actions`, `unit`, and `extended_statistic`
```
135 changes: 71 additions & 64 deletions aws/resource_aws_cloudwatch_metric_alarm.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package aws
import (
"fmt"
"log"
"regexp"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudwatch"
Expand All @@ -27,17 +28,19 @@ func resourceAwsCloudWatchMetricAlarm() *schema.Resource {

Schema: map[string]*schema.Schema{
"alarm_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringLenBetween(1, 255),
},
"arn": {
Type: schema.TypeString,
Computed: true,
},
"comparison_operator": {
Type: schema.TypeString,
Required: true,
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice(cloudwatch.ComparisonOperator_Values(), false),
},
"evaluation_periods": {
Type: schema.TypeInt,
Expand All @@ -48,6 +51,7 @@ func resourceAwsCloudWatchMetricAlarm() *schema.Resource {
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"metric_query"},
ValidateFunc: validation.StringLenBetween(1, 255),
},
"metric_query": {
Type: schema.TypeSet,
Expand All @@ -56,12 +60,14 @@ func resourceAwsCloudWatchMetricAlarm() *schema.Resource {
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Required: true,
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(1, 255),
},
"expression": {
Type: schema.TypeString,
Optional: true,
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringLenBetween(1, 1024),
},
"metric": {
Type: schema.TypeList,
Expand All @@ -75,12 +81,17 @@ func resourceAwsCloudWatchMetricAlarm() *schema.Resource {
Elem: &schema.Schema{Type: schema.TypeString},
},
"metric_name": {
Type: schema.TypeString,
Required: true,
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(1, 255),
},
"namespace": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.All(
validation.StringLenBetween(1, 255),
validation.StringMatch(regexp.MustCompile(`[^:].*`), "must not contain colon characters"),
),
},
"period": {
Type: schema.TypeInt,
Expand All @@ -91,8 +102,9 @@ func resourceAwsCloudWatchMetricAlarm() *schema.Resource {
Required: true,
},
"unit": {
Type: schema.TypeString,
Optional: true,
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice(cloudwatch.StandardUnit_Values(), false),
},
},
},
Expand All @@ -113,16 +125,25 @@ func resourceAwsCloudWatchMetricAlarm() *schema.Resource {
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"metric_query"},
ValidateFunc: validation.All(
validation.StringLenBetween(1, 255),
validation.StringMatch(regexp.MustCompile(`[^:].*`), "must not contain colon characters"),
),
},
"period": {
Type: schema.TypeInt,
Optional: true,
ConflictsWith: []string{"metric_query"},
ValidateFunc: validation.Any(
validation.IntInSlice([]int{10, 30}),
validation.IntDivisibleBy(60),
),
},
"statistic": {
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"extended_statistic", "metric_query"},
ValidateFunc: validation.StringInSlice(cloudwatch.Statistic_Values(), false),
},
"threshold": {
Type: schema.TypeFloat,
Expand Down Expand Up @@ -153,8 +174,9 @@ func resourceAwsCloudWatchMetricAlarm() *schema.Resource {
Set: schema.HashString,
},
"alarm_description": {
Type: schema.TypeString,
Optional: true,
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringLenBetween(0, 1024),
},
"datapoints_to_alarm": {
Type: schema.TypeInt,
Expand All @@ -170,23 +192,37 @@ func resourceAwsCloudWatchMetricAlarm() *schema.Resource {
"insufficient_data_actions": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
MaxItems: 5,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.Any(
validateArn,
validateEC2AutomateARN,
),
},
},
"ok_actions": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
MaxItems: 5,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.Any(
validateArn,
validateEC2AutomateARN,
),
},
},
"unit": {
Type: schema.TypeString,
Optional: true,
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice(cloudwatch.StandardUnit_Values(), false),
},
"extended_statistic": {
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"statistic", "metric_query"},
ValidateFunc: validation.StringMatch(regexp.MustCompile(`p(\d{1,2}(\.\d{0,2})?|100)`), "must specify a value between p0.0 and p100"),
},
"treat_missing_data": {
Type: schema.TypeString,
Expand Down Expand Up @@ -268,7 +304,7 @@ func resourceAwsCloudWatchMetricAlarmRead(d *schema.ResourceData, meta interface

d.Set("actions_enabled", resp.ActionsEnabled)

if err := d.Set("alarm_actions", _strArrPtrToList(resp.AlarmActions)); err != nil {
if err := d.Set("alarm_actions", flattenStringSet(resp.AlarmActions)); err != nil {
log.Printf("[WARN] Error setting Alarm Actions: %s", err)
}
arn := *resp.AlarmArn
Expand All @@ -282,7 +318,7 @@ func resourceAwsCloudWatchMetricAlarmRead(d *schema.ResourceData, meta interface
}
d.Set("evaluation_periods", resp.EvaluationPeriods)

if err := d.Set("insufficient_data_actions", _strArrPtrToList(resp.InsufficientDataActions)); err != nil {
if err := d.Set("insufficient_data_actions", flattenStringSet(resp.InsufficientDataActions)); err != nil {
log.Printf("[WARN] Error setting Insufficient Data Actions: %s", err)
}
d.Set("metric_name", resp.MetricName)
Expand Down Expand Up @@ -315,7 +351,7 @@ func resourceAwsCloudWatchMetricAlarmRead(d *schema.ResourceData, meta interface
}
}

if err := d.Set("ok_actions", _strArrPtrToList(resp.OKActions)); err != nil {
if err := d.Set("ok_actions", flattenStringSet(resp.OKActions)); err != nil {
log.Printf("[WARN] Error setting OK Actions: %s", err)
}
d.Set("period", resp.Period)
Expand Down Expand Up @@ -364,23 +400,17 @@ func resourceAwsCloudWatchMetricAlarmUpdate(d *schema.ResourceData, meta interfa
}

func resourceAwsCloudWatchMetricAlarmDelete(d *schema.ResourceData, meta interface{}) error {
resp, err := getAwsCloudWatchMetricAlarm(d, meta)
if err != nil {
return err
}
if resp == nil {
log.Printf("[DEBUG] CloudWatch Metric Alarm %s is already gone", d.Id())
return nil
}

log.Printf("[INFO] Deleting CloudWatch Metric Alarm: %s", d.Id())

conn := meta.(*AWSClient).cloudwatchconn
params := cloudwatch.DeleteAlarmsInput{
AlarmNames: []*string{aws.String(d.Id())},
}

log.Printf("[INFO] Deleting CloudWatch Metric Alarm: %s", d.Id())

if _, err := conn.DeleteAlarms(&params); err != nil {
if isAWSErr(err, cloudwatch.ErrCodeResourceNotFoundException, "") {
return nil
}
return fmt.Errorf("Error deleting CloudWatch Metric Alarm: %s", err)
}
log.Println("[INFO] CloudWatch Metric Alarm deleted")
Expand Down Expand Up @@ -442,22 +472,12 @@ func getAwsCloudWatchPutMetricAlarmInput(d *schema.ResourceData) cloudwatch.PutM
params.Threshold = aws.Float64(d.Get("threshold").(float64))
}

var alarmActions []*string
if v := d.Get("alarm_actions"); v != nil {
for _, v := range v.(*schema.Set).List() {
str := v.(string)
alarmActions = append(alarmActions, aws.String(str))
}
params.AlarmActions = alarmActions
if v, ok := d.GetOk("alarm_actions"); ok {
params.AlarmActions = expandStringSet(v.(*schema.Set))
}

var insufficientDataActions []*string
if v := d.Get("insufficient_data_actions"); v != nil {
for _, v := range v.(*schema.Set).List() {
str := v.(string)
insufficientDataActions = append(insufficientDataActions, aws.String(str))
}
params.InsufficientDataActions = insufficientDataActions
if v, ok := d.GetOk("insufficient_data_actions"); ok {
params.InsufficientDataActions = expandStringSet(v.(*schema.Set))
}

var metrics []*cloudwatch.MetricDataQuery
Expand Down Expand Up @@ -516,13 +536,8 @@ func getAwsCloudWatchPutMetricAlarmInput(d *schema.ResourceData) cloudwatch.PutM
params.Metrics = metrics
}

var okActions []*string
if v := d.Get("ok_actions"); v != nil {
for _, v := range v.(*schema.Set).List() {
str := v.(string)
okActions = append(okActions, aws.String(str))
}
params.OKActions = okActions
if v, ok := d.GetOk("ok_actions"); ok {
params.OKActions = expandStringSet(v.(*schema.Set))
}

a := d.Get("dimensions").(map[string]interface{})
Expand Down Expand Up @@ -560,14 +575,6 @@ func getAwsCloudWatchMetricAlarm(d *schema.ResourceData, meta interface{}) (*clo
return nil, nil
}

func _strArrPtrToList(strArrPtr []*string) []string {
var result []string
for _, elem := range strArrPtr {
result = append(result, aws.StringValue(elem))
}
return result
}

func flattenDimensions(dims []*cloudwatch.Dimension) map[string]interface{} {
flatDims := make(map[string]interface{})
for _, d := range dims {
Expand Down
22 changes: 22 additions & 0 deletions aws/resource_aws_cloudwatch_metric_alarm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,28 @@ func TestAccAWSCloudWatchMetricAlarm_tags(t *testing.T) {
})
}

func TestAccAWSCloudWatchMetricAlarm_disappears(t *testing.T) {
var alarm cloudwatch.MetricAlarm
resourceName := "aws_cloudwatch_metric_alarm.test"
rName := acctest.RandomWithPrefix("tf-acc-test")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudWatchMetricAlarmDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSCloudWatchMetricAlarmConfig(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudWatchMetricAlarmExists(resourceName, &alarm),
testAccCheckResourceDisappears(testAccProvider, resourceAwsCloudWatchMetricAlarm(), resourceName),
),
ExpectNonEmptyPlan: true,
},
},
})
}

func testAccCheckCloudWatchMetricAlarmDimension(n, k, v string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
Expand Down

0 comments on commit bf4f55c

Please sign in to comment.