diff --git a/aws/internal/service/cloudwatch/finder/finder.go b/aws/internal/service/cloudwatch/finder/finder.go new file mode 100644 index 000000000000..7fa7069ea897 --- /dev/null +++ b/aws/internal/service/cloudwatch/finder/finder.go @@ -0,0 +1,24 @@ +package finder + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudwatch" +) + +func CompositeAlarmByName(conn *cloudwatch.CloudWatch, name string) (*cloudwatch.CompositeAlarm, error) { + input := cloudwatch.DescribeAlarmsInput{ + AlarmNames: aws.StringSlice([]string{name}), + AlarmTypes: aws.StringSlice([]string{cloudwatch.AlarmTypeCompositeAlarm}), + } + + output, err := conn.DescribeAlarms(&input) + if err != nil { + return nil, err + } + + if output == nil || len(output.CompositeAlarms) != 1 { + return nil, nil + } + + return output.CompositeAlarms[0], nil +} diff --git a/aws/resource_aws_cloudwatch_composite_alarm.go b/aws/resource_aws_cloudwatch_composite_alarm.go index dada6aa8f425..f5cbb0d9c0b7 100644 --- a/aws/resource_aws_cloudwatch_composite_alarm.go +++ b/aws/resource_aws_cloudwatch_composite_alarm.go @@ -6,11 +6,12 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/hashicorp/aws-sdk-go-base/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/validation" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/cloudwatch/finder" ) func resourceAwsCloudWatchCompositeAlarm() *schema.Resource { @@ -96,7 +97,7 @@ func resourceAwsCloudWatchCompositeAlarmCreate( input := expandAwsCloudWatchPutCompositeAlarmInput(d) _, err := conn.PutCompositeAlarmWithContext(ctx, &input) if err != nil { - return diag.Errorf("create composite alarm: %s", err) + return diag.Errorf("error creating composite alarm: %s", err) } log.Printf("[INFO] Created Composite Alarm %s.", name) @@ -114,20 +115,25 @@ func resourceAwsCloudWatchCompositeAlarmRead( ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig name := d.Id() - alarm, ok, err := getAwsCloudWatchCompositeAlarm(ctx, conn, name) - switch { - case err != nil: - return diag.FromErr(err) - case !ok: - log.Printf("[WARN] Composite alarm %s has disappeared!", name) - d.SetId("") - return nil + alarm, err := finder.CompositeAlarmByName(conn, name) + if err != nil { + return diag.Errorf("error reading composite alarm (%s): %s", name, err) + } + + if alarm == nil { + if !d.IsNewResource() { + log.Printf("[WARN] CloudWatch Composite alarm %s not found, removing from state", name) + d.SetId("") + return nil + } + + return diag.Errorf("error reading composite alarm (%s): alarm not filtered", name) } d.Set("actions_enabled", alarm.ActionsEnabled) if err := d.Set("alarm_actions", flattenStringSet(alarm.AlarmActions)); err != nil { - return diag.Errorf("set alarm_actions: %s", err) + return diag.Errorf("error setting alarm_actions: %s", err) } d.Set("alarm_description", alarm.AlarmDescription) @@ -136,20 +142,20 @@ func resourceAwsCloudWatchCompositeAlarmRead( d.Set("arn", alarm.AlarmArn) if err := d.Set("insufficient_data_actions", flattenStringSet(alarm.InsufficientDataActions)); err != nil { - return diag.Errorf("set insufficient_data_actions: %s", err) + return diag.Errorf("error setting insufficient_data_actions: %s", err) } if err := d.Set("ok_actions", flattenStringSet(alarm.OKActions)); err != nil { - return diag.Errorf("set ok_actions: %s", err) + return diag.Errorf("error setting ok_actions: %s", err) } tags, err := keyvaluetags.CloudwatchListTags(conn, aws.StringValue(alarm.AlarmArn)) if err != nil { - return diag.Errorf("list tags of alarm: %s", err) + return diag.Errorf("error listing tags of alarm: %s", err) } if err := d.Set("tags", tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { - return diag.Errorf("set tags: %s", err) + return diag.Errorf("error setting tags: %s", err) } return nil @@ -168,7 +174,7 @@ func resourceAwsCloudWatchCompositeAlarmUpdate( input := expandAwsCloudWatchPutCompositeAlarmInput(d) _, err := conn.PutCompositeAlarmWithContext(ctx, &input) if err != nil { - return diag.Errorf("create composite alarm: %s", err) + return diag.Errorf("error creating composite alarm: %s", err) } arn := d.Get("arn").(string) @@ -176,7 +182,7 @@ func resourceAwsCloudWatchCompositeAlarmUpdate( o, n := d.GetChange("tags") if err := keyvaluetags.CloudwatchUpdateTags(conn, arn, o, n); err != nil { - return diag.Errorf("update tags: %s", err) + return diag.Errorf("error updating tags: %s", err) } } @@ -198,12 +204,11 @@ func resourceAwsCloudWatchCompositeAlarmDelete( } _, err := conn.DeleteAlarmsWithContext(ctx, &input) - switch { - case isAWSErr(err, "ResourceNotFound", ""): - log.Printf("[WARN] Composite Alarm %s has disappeared!", name) - return nil - case err != nil: - return diag.FromErr(err) + if err != nil { + if tfawserr.ErrCodeEquals(err, cloudwatch.ErrCodeResourceNotFound) { + return nil + } + return diag.Errorf("error deleting composite alarm: %s", err) } return nil @@ -246,24 +251,3 @@ func expandAwsCloudWatchPutCompositeAlarmInput(d *schema.ResourceData) cloudwatc return out } - -func getAwsCloudWatchCompositeAlarm( - ctx context.Context, - conn *cloudwatch.CloudWatch, - name string, -) (*cloudwatch.CompositeAlarm, bool, error) { - input := cloudwatch.DescribeAlarmsInput{ - AlarmNames: aws.StringSlice([]string{name}), - AlarmTypes: aws.StringSlice([]string{cloudwatch.AlarmTypeCompositeAlarm}), - } - - output, err := conn.DescribeAlarmsWithContext(ctx, &input) - switch { - case err != nil: - return nil, false, err - case len(output.CompositeAlarms) != 1: - return nil, false, nil - } - - return output.CompositeAlarms[0], true, nil -} diff --git a/aws/resource_aws_cloudwatch_composite_alarm_test.go b/aws/resource_aws_cloudwatch_composite_alarm_test.go index fb9862b5c8bb..8a033cffebb3 100644 --- a/aws/resource_aws_cloudwatch_composite_alarm_test.go +++ b/aws/resource_aws_cloudwatch_composite_alarm_test.go @@ -12,31 +12,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) -func testAccCheckAwsCloudWatchCompositeAlarmExists(n string, alarm *cloudwatch.CompositeAlarm) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - conn := testAccProvider.Meta().(*AWSClient).cloudwatchconn - params := cloudwatch.DescribeAlarmsInput{ - AlarmNames: []*string{aws.String(rs.Primary.ID)}, - AlarmTypes: []*string{aws.String(cloudwatch.AlarmTypeCompositeAlarm)}, - } - resp, err := conn.DescribeAlarms(¶ms) - if err != nil { - return err - } - if len(resp.CompositeAlarms) == 0 { - return fmt.Errorf("Alarm not found") - } - *alarm = *resp.CompositeAlarms[0] - - return nil - } -} - func testAccCheckAwsCloudWatchCompositeAlarmDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).cloudwatchconn @@ -53,7 +28,7 @@ func testAccCheckAwsCloudWatchCompositeAlarmDestroy(s *terraform.State) error { if err == nil { if len(resp.MetricAlarms) != 0 && - *resp.MetricAlarms[0].AlarmName == rs.Primary.ID { + aws.StringValue(resp.MetricAlarms[0].AlarmName) == rs.Primary.ID { return fmt.Errorf("Alarm Still Exists: %s", rs.Primary.ID) } } @@ -63,7 +38,6 @@ func testAccCheckAwsCloudWatchCompositeAlarmDestroy(s *terraform.State) error { } func TestAccAwsCloudWatchCompositeAlarm_basic(t *testing.T) { - alarm := cloudwatch.CompositeAlarm{} suffix := acctest.RandString(8) resourceName := "aws_cloudwatch_composite_alarm.test" @@ -73,9 +47,125 @@ func TestAccAwsCloudWatchCompositeAlarm_basic(t *testing.T) { CheckDestroy: testAccCheckAwsCloudWatchCompositeAlarmDestroy, Steps: []resource.TestStep{ { - Config: testAccAwsCloudWatchCompositeAlarmConfig_create(suffix), + Config: testAccAwsCloudWatchCompositeAlarmConfig_basic(suffix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "alarm_actions.#", "1"), + resource.TestCheckResourceAttr(resourceName, "alarm_description", "Test 1"), + resource.TestCheckResourceAttr(resourceName, "alarm_name", "tf-test-composite-"+suffix), + resource.TestCheckResourceAttr(resourceName, "alarm_rule", fmt.Sprintf("ALARM(tf-test-metric-0-%[1]s) OR ALARM(tf-test-metric-1-%[1]s)", suffix)), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "cloudwatch", regexp.MustCompile(`alarm:.+`)), + resource.TestCheckResourceAttr(resourceName, "insufficient_data_actions.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ok_actions.#", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAwsCloudWatchCompositeAlarmConfig_basic(suffix string) string { + return fmt.Sprintf(` +resource "aws_cloudwatch_metric_alarm" "test" { + count = 2 + + alarm_name = "tf-test-metric-${count.index}-%[1]s" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = 2 + metric_name = "CPUUtilization" + namespace = "AWS/EC2" + period = 120 + statistic = "Average" + threshold = 80 + + dimensions = { + InstanceId = "i-abc123" + } +} + +resource "aws_sns_topic" "test" { + count = 1 + name = "tf-test-alarms-${count.index}-%[1]s" +} + +resource "aws_cloudwatch_composite_alarm" "test" { + alarm_actions = aws_sns_topic.test.*.arn + alarm_description = "Test 1" + alarm_name = "tf-test-composite-%[1]s" + alarm_rule = join(" OR ", formatlist("ALARM(%%s)", aws_cloudwatch_metric_alarm.test.*.alarm_name)) + insufficient_data_actions = aws_sns_topic.test.*.arn + ok_actions = aws_sns_topic.test.*.arn + + tags = { + Foo = "Bar" + } +} +`, suffix) +} + +func TestAccAwsCloudWatchCompositeAlarm_disappears(t *testing.T) { + suffix := acctest.RandString(8) + resourceName := "aws_cloudwatch_composite_alarm.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsCloudWatchCompositeAlarmDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsCloudWatchCompositeAlarmConfig_disappears(suffix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), + testAccCheckResourceDisappears(testAccProvider, resourceAwsCloudWatchCompositeAlarm(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccAwsCloudWatchCompositeAlarmConfig_disappears(suffix string) string { + return fmt.Sprintf(` +resource "aws_cloudwatch_metric_alarm" "test" { + alarm_name = "tf-test-metric-%[1]s" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = 2 + metric_name = "CPUUtilization" + namespace = "AWS/EC2" + period = 120 + statistic = "Average" + threshold = 80 + + dimensions = { + InstanceId = "i-abc123" + } +} + +resource "aws_cloudwatch_composite_alarm" "test" { + alarm_name = "tf-test-composite-%[1]s" + alarm_rule = "ALARM(${aws_cloudwatch_metric_alarm.test.alarm_name})" +} +`, suffix) +} + +func TestAccAwsCloudWatchCompositeAlarm_update(t *testing.T) { + suffix := acctest.RandString(8) + resourceName := "aws_cloudwatch_composite_alarm.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsCloudWatchCompositeAlarmDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsCloudWatchCompositeAlarmConfig_update_before(suffix), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName, &alarm), + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), resource.TestCheckResourceAttr(resourceName, "alarm_actions.#", "1"), resource.TestCheckResourceAttr(resourceName, "alarm_description", "Test 1"), resource.TestCheckResourceAttr(resourceName, "alarm_name", "tf-test-composite-"+suffix), @@ -92,9 +182,9 @@ func TestAccAwsCloudWatchCompositeAlarm_basic(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAwsCloudWatchCompositeAlarmConfig_update(suffix), + Config: testAccAwsCloudWatchCompositeAlarmConfig_update_after(suffix), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName, &alarm), + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), resource.TestCheckResourceAttr(resourceName, "alarm_actions.#", "2"), resource.TestCheckResourceAttr(resourceName, "alarm_description", "Test 2"), resource.TestCheckResourceAttr(resourceName, "alarm_name", "tf-test-composite-"+suffix), @@ -109,7 +199,7 @@ func TestAccAwsCloudWatchCompositeAlarm_basic(t *testing.T) { }) } -func testAccAwsCloudWatchCompositeAlarmConfig_create(suffix string) string { +func testAccAwsCloudWatchCompositeAlarmConfig_update_before(suffix string) string { return fmt.Sprintf(` resource "aws_cloudwatch_metric_alarm" "test" { count = 2 @@ -148,7 +238,7 @@ resource "aws_cloudwatch_composite_alarm" "test" { `, suffix) } -func testAccAwsCloudWatchCompositeAlarmConfig_update(suffix string) string { +func testAccAwsCloudWatchCompositeAlarmConfig_update_after(suffix string) string { return fmt.Sprintf(` resource "aws_cloudwatch_metric_alarm" "test" { count = 2 @@ -182,75 +272,30 @@ resource "aws_cloudwatch_composite_alarm" "test" { tags = { Foo = "Bar" - Bax = "Baf" + Bax = "Baf" } } `, suffix) } -func TestAccAwsCloudWatchCompositeAlarm_disappears(t *testing.T) { - alarm := cloudwatch.CompositeAlarm{} - suffix := acctest.RandString(8) - resourceName := "aws_cloudwatch_composite_alarm.test" - - checkDisappears := func(*terraform.State) error { +func testAccCheckAwsCloudWatchCompositeAlarmExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } conn := testAccProvider.Meta().(*AWSClient).cloudwatchconn - alarmNames := []string{ - "tf-test-composite-" + suffix, - "tf-test-metric-" + suffix, + params := cloudwatch.DescribeAlarmsInput{ + AlarmNames: []*string{aws.String(rs.Primary.ID)}, + AlarmTypes: []*string{aws.String(cloudwatch.AlarmTypeCompositeAlarm)}, } - - for _, name := range alarmNames { - input := cloudwatch.DeleteAlarmsInput{ - AlarmNames: []*string{&name}, - } - - _, err := conn.DeleteAlarms(&input) - if err != nil { - return err - } + resp, err := conn.DescribeAlarms(¶ms) + if err != nil { + return err + } + if len(resp.CompositeAlarms) == 0 { + return fmt.Errorf("Alarm not found") } - return nil } - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAwsCloudWatchCompositeAlarmDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAwsCloudWatchCompositeAlarmConfig_disappears(suffix), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName, &alarm), - checkDisappears, - ), - ExpectNonEmptyPlan: true, - }, - }, - }) -} - -func testAccAwsCloudWatchCompositeAlarmConfig_disappears(suffix string) string { - return fmt.Sprintf(` -resource "aws_cloudwatch_metric_alarm" "test" { - alarm_name = "tf-test-metric-%[1]s" - comparison_operator = "GreaterThanOrEqualToThreshold" - evaluation_periods = 2 - metric_name = "CPUUtilization" - namespace = "AWS/EC2" - period = 120 - statistic = "Average" - threshold = 80 - - dimensions = { - InstanceId = "i-abc123" - } -} - -resource "aws_cloudwatch_composite_alarm" "test" { - alarm_name = "tf-test-composite-%[1]s" - alarm_rule = "ALARM(${aws_cloudwatch_metric_alarm.test.alarm_name})" -} -`, suffix) } diff --git a/website/docs/r/cloudwatch_composite_alarm.html.markdown b/website/docs/r/cloudwatch_composite_alarm.html.markdown index 4a140d0f4656..5190bd0b27ad 100644 --- a/website/docs/r/cloudwatch_composite_alarm.html.markdown +++ b/website/docs/r/cloudwatch_composite_alarm.html.markdown @@ -32,12 +32,12 @@ EOF ## Argument Reference * `actions_enabled` - (Optional) Indicates whether actions should be executed during any changes to the alarm state of the composite alarm. Defaults to `true`. -* `alarm_actions` - (Optional) The set of actions to execute when this alarm transitions to the ALARM state from any other state. Each action is specified as an ARN. Up to 5 actions are allowed. +* `alarm_actions` - (Optional) The set of actions to execute when this alarm transitions to the `ALARM` state from any other state. Each action is specified as an ARN. Up to 5 actions are allowed. * `alarm_description` - (Optional) The description for the composite alarm. * `alarm_name` - (Required) The name for the composite alarm. This name must be unique within the region. * `alarm_rule` - (Required) An expression that specifies which other alarms are to be evaluated to determine this composite alarm's state. For syntax, see [Creating a Composite Alarm](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Create_Composite_Alarm.html). The maximum length is 10240 characters. -* `insufficient_data_actions` - (Optional) The set of actions to execute when this alarm transitions to the INSUFFICIENT_DATA state from any other state. Each action is specified as an ARN. Up to 5 actions are allowed. -* `ok_actions` - (Optional) The set of actions to execute when this alarm transitions to an OK state from any other state. Each action is specified as an ARN. Up to 5 actions are allowed. +* `insufficient_data_actions` - (Optional) The set of actions to execute when this alarm transitions to the `INSUFFICIENT_DATA` state from any other state. Each action is specified as an ARN. Up to 5 actions are allowed. +* `ok_actions` - (Optional) The set of actions to execute when this alarm transitions to an `OK` state from any other state. Each action is specified as an ARN. Up to 5 actions are allowed. * `tags` - (Optional) A map of tags to associate with the alarm. Up to 50 tags are allowed. ## Attributes Reference