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

aws_cloudwatch_metric alarm validate return_data flag on metric_query blocks #29925

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions .changelog/29925.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_cloudwatch_metric_alarm: Add validation for `return_data` on `metric_query` that one and only one has `return_data` as `true`
```
23 changes: 23 additions & 0 deletions internal/service/cloudwatch/metric_alarm.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,16 +295,35 @@ func validMetricAlarm(d *schema.ResourceData) error {
return fmt.Errorf("One of `statistic` or `extended_statistic` must be set for a cloudwatch metric alarm")
}

numOfResultDataTrue := 0
hasMetricQuery := false
if v := d.Get("metric_query"); v != nil {
for _, v := range v.(*schema.Set).List() {
metricQueryResource := v.(map[string]interface{})
hasMetricQuery = true
if v, ok := metricQueryResource["expression"]; ok && v.(string) != "" {
if v := metricQueryResource["metric"]; v != nil {
if len(v.([]interface{})) > 0 {
return fmt.Errorf("No metric_query may have both `expression` and a `metric` specified")
}
}
}

if v, ok := metricQueryResource["return_data"]; ok {
if v.(bool) {
numOfResultDataTrue++
}
}
}
}

if hasMetricQuery {
if numOfResultDataTrue == 0 {
return fmt.Errorf("One of `metric_query` must have `return_data` as `true` for a cloudwatch metric alarm")
}

if numOfResultDataTrue > 1 {
return fmt.Errorf("Multiple `metric_query` blocks have `return_data` as `true` for a cloudwatch metric alarm")
}
}

Expand Down Expand Up @@ -416,6 +435,10 @@ func resourceMetricAlarmRead(ctx context.Context, d *schema.ResourceData, meta i
func resourceMetricAlarmUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
var diags diag.Diagnostics
conn := meta.(*conns.AWSClient).CloudWatchConn()
err := validMetricAlarm(d)
if err != nil {
return sdkdiag.AppendErrorf(diags, "updating CloudWatch Metric Alarm (%s): %s", d.Get("alarm_name").(string), err)
}

if d.HasChangesExcept("tags", "tags_all") {
input := expandPutMetricAlarmInput(ctx, d)
Expand Down
112 changes: 112 additions & 0 deletions internal/service/cloudwatch/metric_alarm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,40 @@ func TestAccCloudWatchMetricAlarm_missingStatistic(t *testing.T) {
})
}

func TestAccCloudWatchMetricAlarm_missingReturnDataFromMetricQuery(t *testing.T) {
ctx := acctest.Context(t)
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, cloudwatch.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckMetricAlarmDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccMetricAlarmConfig_missingReturnDataMetricQuery(rName),
ExpectError: regexp.MustCompile("One of `metric_query` must have `return_data` as `true` for a cloudwatch metric alarm"),
},
},
})
}

func TestAccCloudWatchMetricAlarm_moreThanOneReturnDataFromMetricQuery(t *testing.T) {
ctx := acctest.Context(t)
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, cloudwatch.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckMetricAlarmDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccMetricAlarmConfig_moreThanOneReturnDataFromMetricQuery(rName),
ExpectError: regexp.MustCompile("Multiple `metric_query` blocks have `return_data` as `true` for a cloudwatch metric alarm"),
},
},
})
}

func TestAccCloudWatchMetricAlarm_tags(t *testing.T) {
ctx := acctest.Context(t)
var alarm cloudwatch.MetricAlarm
Expand Down Expand Up @@ -884,6 +918,84 @@ resource "aws_cloudwatch_metric_alarm" "test" {
`, rName)
}

func testAccMetricAlarmConfig_missingReturnDataMetricQuery(rName string) string {
return fmt.Sprintf(`
resource "aws_cloudwatch_metric_alarm" "test" {
alarm_name = "%s"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = "2"
threshold = "80"
alarm_description = "This metric monitors ec2 cpu utilization"
insufficient_data_actions = []

metric_query {
id = "e1"
expression = "m1"
label = "cat"
}

metric_query {
id = "m1"

metric {
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = "120"
stat = "Average"
unit = "Count"

dimensions = {
InstanceId = "i-abc123"
}
}
}
}
`, rName)
}

func testAccMetricAlarmConfig_moreThanOneReturnDataFromMetricQuery(rName string) string {
return fmt.Sprintf(`
resource "aws_cloudwatch_metric_alarm" "test" {
alarm_name = "%s"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = "2"
threshold = "80"
alarm_description = "This metric monitors ec2 cpu utilization"
insufficient_data_actions = []

metric_query {
id = "e1"
expression = "m1"
label = "cat"
return_data = "true"
}

metric_query {
id = "e2"
expression = "m1"
label = "dog"
return_data = "true"
}

metric_query {
id = "m1"

metric {
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = "120"
stat = "Average"
unit = "Count"

dimensions = {
InstanceId = "i-abc123"
}
}
}
}
`, rName)
}

func testAccMetricAlarmConfig_metricQueryExpressionReference(rName string) string {
return fmt.Sprintf(`
resource "aws_cloudwatch_metric_alarm" "test" {
Expand Down
1 change: 1 addition & 0 deletions website/docs/r/cloudwatch_metric_alarm.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ The following values are supported: `ignore`, and `evaluate`.
* `return_data` - (Optional) Specify exactly one `metric_query` to be `true` to use that `metric_query` result as the alarm.

~> **NOTE:** You must specify either `metric` or `expression`. Not both.
~> **NOTE:** One `metric_query` block must set `return_data` as `true` and only one.

#### `metric`

Expand Down