From 757ee46f386336db950fc4ed715ac45fa96a42ad Mon Sep 17 00:00:00 2001 From: Angie Pinilla Date: Wed, 13 Jan 2021 21:41:09 -0500 Subject: [PATCH] additional acctests; reduce logging --- .../service/cloudwatch/finder/finder.go | 6 +- ...resource_aws_cloudwatch_composite_alarm.go | 64 +- ...rce_aws_cloudwatch_composite_alarm_test.go | 663 ++++++++++++++---- .../cloudwatch_composite_alarm.html.markdown | 2 +- 4 files changed, 549 insertions(+), 186 deletions(-) diff --git a/aws/internal/service/cloudwatch/finder/finder.go b/aws/internal/service/cloudwatch/finder/finder.go index 7fa7069ea897..1de5bf2c9d68 100644 --- a/aws/internal/service/cloudwatch/finder/finder.go +++ b/aws/internal/service/cloudwatch/finder/finder.go @@ -1,17 +1,19 @@ package finder import ( + "context" + "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) { +func CompositeAlarmByName(ctx context.Context, 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) + output, err := conn.DescribeAlarmsWithContext(ctx, &input) if err != nil { return nil, err } diff --git a/aws/resource_aws_cloudwatch_composite_alarm.go b/aws/resource_aws_cloudwatch_composite_alarm.go index f5cbb0d9c0b7..46ac4c36a432 100644 --- a/aws/resource_aws_cloudwatch_composite_alarm.go +++ b/aws/resource_aws_cloudwatch_composite_alarm.go @@ -30,6 +30,7 @@ func resourceAwsCloudWatchCompositeAlarm() *schema.Resource { Type: schema.TypeBool, Optional: true, Default: true, + ForceNew: true, }, "alarm_actions": { Type: schema.TypeSet, @@ -86,48 +87,46 @@ func resourceAwsCloudWatchCompositeAlarm() *schema.Resource { } } -func resourceAwsCloudWatchCompositeAlarmCreate( - ctx context.Context, - d *schema.ResourceData, - meta interface{}, -) diag.Diagnostics { +func resourceAwsCloudWatchCompositeAlarmCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).cloudwatchconn name := d.Get("alarm_name").(string) input := expandAwsCloudWatchPutCompositeAlarmInput(d) + _, err := conn.PutCompositeAlarmWithContext(ctx, &input) if err != nil { - return diag.Errorf("error creating composite alarm: %s", err) + return diag.Errorf("error creating CloudWatch Composite Alarm (%s): %s", name, err) } - log.Printf("[INFO] Created Composite Alarm %s.", name) d.SetId(name) return resourceAwsCloudWatchCompositeAlarmRead(ctx, d, meta) } -func resourceAwsCloudWatchCompositeAlarmRead( - ctx context.Context, - d *schema.ResourceData, - meta interface{}, -) diag.Diagnostics { +func resourceAwsCloudWatchCompositeAlarmRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).cloudwatchconn ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig name := d.Id() - alarm, err := finder.CompositeAlarmByName(conn, name) + alarm, err := finder.CompositeAlarmByName(ctx, conn, name) + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, cloudwatch.ErrCodeResourceNotFound) { + log.Printf("[WARN] CloudWatch Composite Alarm %s not found, removing from state", name) + d.SetId("") + return nil + } + if err != nil { - return diag.Errorf("error reading composite alarm (%s): %s", name, err) + return diag.Errorf("error reading CloudWatch 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 + if d.IsNewResource() { + return diag.Errorf("error reading CloudWatch Composite Alarm (%s): not found", name) } - return diag.Errorf("error reading composite alarm (%s): alarm not filtered", name) + log.Printf("[WARN] CloudWatch Composite Alarm %s not found, removing from state", name) + d.SetId("") + return nil } d.Set("actions_enabled", alarm.ActionsEnabled) @@ -161,20 +160,15 @@ func resourceAwsCloudWatchCompositeAlarmRead( return nil } -func resourceAwsCloudWatchCompositeAlarmUpdate( - ctx context.Context, - d *schema.ResourceData, - meta interface{}, -) diag.Diagnostics { +func resourceAwsCloudWatchCompositeAlarmUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).cloudwatchconn name := d.Id() - log.Printf("[INFO] Updating Composite Alarm %s...", name) - input := expandAwsCloudWatchPutCompositeAlarmInput(d) + _, err := conn.PutCompositeAlarmWithContext(ctx, &input) if err != nil { - return diag.Errorf("error creating composite alarm: %s", err) + return diag.Errorf("error updating CloudWatch Composite Alarm (%s): %s", name, err) } arn := d.Get("arn").(string) @@ -189,16 +183,10 @@ func resourceAwsCloudWatchCompositeAlarmUpdate( return resourceAwsCloudWatchCompositeAlarmRead(ctx, d, meta) } -func resourceAwsCloudWatchCompositeAlarmDelete( - ctx context.Context, - d *schema.ResourceData, - meta interface{}, -) diag.Diagnostics { +func resourceAwsCloudWatchCompositeAlarmDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).cloudwatchconn name := d.Id() - log.Printf("[INFO] Deleting Composite Alarm %s...", name) - input := cloudwatch.DeleteAlarmsInput{ AlarmNames: aws.StringSlice([]string{name}), } @@ -208,17 +196,15 @@ func resourceAwsCloudWatchCompositeAlarmDelete( if tfawserr.ErrCodeEquals(err, cloudwatch.ErrCodeResourceNotFound) { return nil } - return diag.Errorf("error deleting composite alarm: %s", err) + return diag.Errorf("error deleting CloudWatch Composite Alarm (%s): %s", name, err) } return nil } func expandAwsCloudWatchPutCompositeAlarmInput(d *schema.ResourceData) cloudwatch.PutCompositeAlarmInput { - out := cloudwatch.PutCompositeAlarmInput{} - - if v, ok := d.GetOk("actions_enabled"); ok { - out.ActionsEnabled = aws.Bool(v.(bool)) + out := cloudwatch.PutCompositeAlarmInput{ + ActionsEnabled: aws.Bool(d.Get("actions_enabled").(bool)), } if v, ok := d.GetOk("alarm_actions"); ok { diff --git a/aws/resource_aws_cloudwatch_composite_alarm_test.go b/aws/resource_aws_cloudwatch_composite_alarm_test.go index 8a033cffebb3..baf9bd788add 100644 --- a/aws/resource_aws_cloudwatch_composite_alarm_test.go +++ b/aws/resource_aws_cloudwatch_composite_alarm_test.go @@ -1,40 +1,86 @@ package aws import ( + "context" "fmt" + "log" "regexp" "testing" "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/go-multierror" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/cloudwatch/finder" ) -func testAccCheckAwsCloudWatchCompositeAlarmDestroy(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).cloudwatchconn +func init() { + resource.AddTestSweepers("aws_cloudwatch_composite_alarm", &resource.Sweeper{ + Name: "aws_cloudwatch_composite_alarm", + F: testSweepCloudWatchCompositeAlarms, + }) +} - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_cloudwatch_composite_alarm" { - continue - } +func testSweepCloudWatchCompositeAlarms(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } - params := cloudwatch.DescribeAlarmsInput{ - AlarmNames: []*string{aws.String(rs.Primary.ID)}, + conn := client.(*AWSClient).cloudwatchconn + ctx := context.Background() + + input := &cloudwatch.DescribeAlarmsInput{ + AlarmTypes: aws.StringSlice([]string{cloudwatch.AlarmTypeCompositeAlarm}), + } + + var sweeperErrs *multierror.Error + + err = conn.DescribeAlarmsPagesWithContext(ctx, input, func(page *cloudwatch.DescribeAlarmsOutput, isLast bool) bool { + if page == nil { + return !isLast } - resp, err := conn.DescribeAlarms(¶ms) + for _, compositeAlarm := range page.CompositeAlarms { + if compositeAlarm == nil { + continue + } + + name := aws.StringValue(compositeAlarm.AlarmName) + + log.Printf("[INFO] Deleting CloudWatch Composite Alarm: %s", name) + + r := resourceAwsCloudWatchCompositeAlarm() + d := r.Data(nil) + d.SetId(name) + + diags := r.DeleteContext(ctx, d, client) - if err == nil { - if len(resp.MetricAlarms) != 0 && - aws.StringValue(resp.MetricAlarms[0].AlarmName) == rs.Primary.ID { - return fmt.Errorf("Alarm Still Exists: %s", rs.Primary.ID) + for i := range diags { + if diags[i].Severity == diag.Error { + log.Printf("[ERROR] %s", diags[i].Summary) + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf(diags[i].Summary)) + continue + } } } + + return !isLast + }) + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping CloudWatch Composite Alarms sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + } + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving CloudWatch Composite Alarms: %w", err)) } - return nil + return sweeperErrs.ErrorOrNil() } func TestAccAwsCloudWatchCompositeAlarm_basic(t *testing.T) { @@ -50,14 +96,15 @@ func TestAccAwsCloudWatchCompositeAlarm_basic(t *testing.T) { 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, "actions_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "alarm_actions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "alarm_description", ""), 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"), + resource.TestCheckResourceAttr(resourceName, "insufficient_data_actions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "ok_actions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, { @@ -69,46 +116,114 @@ func TestAccAwsCloudWatchCompositeAlarm_basic(t *testing.T) { }) } -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 +func TestAccAwsCloudWatchCompositeAlarm_disappears(t *testing.T) { + suffix := acctest.RandString(8) + resourceName := "aws_cloudwatch_composite_alarm.test" - dimensions = { - InstanceId = "i-abc123" - } + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsCloudWatchCompositeAlarmDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsCloudWatchCompositeAlarmConfig_basic(suffix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), + testAccCheckResourceDisappears(testAccProvider, resourceAwsCloudWatchCompositeAlarm(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) } -resource "aws_sns_topic" "test" { - count = 1 - name = "tf-test-alarms-${count.index}-%[1]s" +func TestAccAwsCloudWatchCompositeAlarm_actionsEnabled(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_actionsEnabled(false, suffix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "actions_enabled", "false"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsCloudWatchCompositeAlarmConfig_actionsEnabled(true, suffix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "actions_enabled", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) } -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 +func TestAccAwsCloudWatchCompositeAlarm_alarmActions(t *testing.T) { + suffix := acctest.RandString(8) + resourceName := "aws_cloudwatch_composite_alarm.test" - tags = { - Foo = "Bar" - } -} -`, suffix) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsCloudWatchCompositeAlarmDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsCloudWatchCompositeAlarmConfig_alarmActions(suffix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "alarm_actions.#", "2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsCloudWatchCompositeAlarmConfig_updateAlarmActions(suffix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "alarm_actions.#", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsCloudWatchCompositeAlarmConfig_basic(suffix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "alarm_actions.#", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) } -func TestAccAwsCloudWatchCompositeAlarm_disappears(t *testing.T) { +func TestAccAwsCloudWatchCompositeAlarm_description(t *testing.T) { suffix := acctest.RandString(8) resourceName := "aws_cloudwatch_composite_alarm.test" @@ -118,42 +233,132 @@ func TestAccAwsCloudWatchCompositeAlarm_disappears(t *testing.T) { CheckDestroy: testAccCheckAwsCloudWatchCompositeAlarmDestroy, Steps: []resource.TestStep{ { - Config: testAccAwsCloudWatchCompositeAlarmConfig_disappears(suffix), + Config: testAccAwsCloudWatchCompositeAlarmConfig_description("Test 1", suffix), Check: resource.ComposeTestCheckFunc( testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), - testAccCheckResourceDisappears(testAccProvider, resourceAwsCloudWatchCompositeAlarm(), resourceName), + resource.TestCheckResourceAttr(resourceName, "alarm_description", "Test 1"), ), - ExpectNonEmptyPlan: true, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsCloudWatchCompositeAlarmConfig_description("Test Updated", suffix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "alarm_description", "Test Updated"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: 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 +func TestAccAwsCloudWatchCompositeAlarm_insufficientDataActions(t *testing.T) { + suffix := acctest.RandString(8) + resourceName := "aws_cloudwatch_composite_alarm.test" - dimensions = { - InstanceId = "i-abc123" - } + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsCloudWatchCompositeAlarmDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsCloudWatchCompositeAlarmConfig_insufficientDataActions(suffix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "insufficient_data_actions.#", "2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsCloudWatchCompositeAlarmConfig_updateInsufficientDataActions(suffix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "insufficient_data_actions.#", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsCloudWatchCompositeAlarmConfig_basic(suffix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "insufficient_data_actions.#", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) } -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_okActions(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_okActions(suffix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "ok_actions.#", "2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsCloudWatchCompositeAlarmConfig_updateOkActions(suffix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "ok_actions.#", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsCloudWatchCompositeAlarmConfig_basic(suffix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "ok_actions.#", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) } -func TestAccAwsCloudWatchCompositeAlarm_update(t *testing.T) { +func TestAccAwsCloudWatchCompositeAlarm_allActions(t *testing.T) { suffix := acctest.RandString(8) resourceName := "aws_cloudwatch_composite_alarm.test" @@ -163,17 +368,12 @@ func TestAccAwsCloudWatchCompositeAlarm_update(t *testing.T) { CheckDestroy: testAccCheckAwsCloudWatchCompositeAlarmDestroy, Steps: []resource.TestStep{ { - Config: testAccAwsCloudWatchCompositeAlarmConfig_update_before(suffix), + Config: testAccAwsCloudWatchCompositeAlarmConfig_allActions(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"), ), }, { @@ -182,29 +382,117 @@ func TestAccAwsCloudWatchCompositeAlarm_update(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAwsCloudWatchCompositeAlarmConfig_update_after(suffix), + Config: testAccAwsCloudWatchCompositeAlarmConfig_basic(suffix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "alarm_actions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "insufficient_data_actions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "ok_actions.#", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAwsCloudWatchCompositeAlarm_updateAlarmRule(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_basic(suffix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsCloudWatchCompositeAlarmConfig_updateAlarmRule(suffix), Check: resource.ComposeTestCheckFunc( testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "alarm_actions.#", "2"), - resource.TestCheckResourceAttr(resourceName, "alarm_description", "Test 2"), - resource.TestCheckResourceAttr(resourceName, "alarm_name", "tf-test-composite-"+suffix), resource.TestCheckResourceAttr(resourceName, "alarm_rule", fmt.Sprintf("ALARM(tf-test-metric-0-%[1]s)", suffix)), - testAccMatchResourceAttrRegionalARN(resourceName, "arn", "cloudwatch", regexp.MustCompile(`alarm:.+`)), - resource.TestCheckResourceAttr(resourceName, "insufficient_data_actions.#", "2"), - resource.TestCheckResourceAttr(resourceName, "ok_actions.#", "2"), - resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } -func testAccAwsCloudWatchCompositeAlarmConfig_update_before(suffix string) string { +func testAccCheckAwsCloudWatchCompositeAlarmDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).cloudwatchconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_cloudwatch_composite_alarm" { + continue + } + + alarm, err := finder.CompositeAlarmByName(context.Background(), conn, rs.Primary.ID) + + if tfawserr.ErrCodeEquals(err, cloudwatch.ErrCodeResourceNotFound) { + continue + } + if err != nil { + return fmt.Errorf("error reading CloudWatch composite alarm (%s): %w", rs.Primary.ID, err) + } + + if alarm != nil { + return fmt.Errorf("CloudWatch composite alarm (%s) still exists", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("resource %s has not set its id", resourceName) + } + + conn := testAccProvider.Meta().(*AWSClient).cloudwatchconn + + alarm, err := finder.CompositeAlarmByName(context.Background(), conn, rs.Primary.ID) + + if err != nil { + return fmt.Errorf("error reading CloudWatch composite alarm (%s): %w", rs.Primary.ID, err) + } + + if alarm == nil { + return fmt.Errorf("CloudWatch composite alarm (%s) not found", rs.Primary.ID) + } + + return nil + } +} + +func testAccAwsCloudWatchCompositeAlarmBaseConfig(suffix string) string { return fmt.Sprintf(` resource "aws_cloudwatch_metric_alarm" "test" { count = 2 - alarm_name = "tf-test-metric-${count.index}-%[1]s" + alarm_name = "tf-test-metric-${count.index}-%s" comparison_operator = "GreaterThanOrEqualToThreshold" evaluation_periods = 2 metric_name = "CPUUtilization" @@ -217,85 +505,172 @@ resource "aws_cloudwatch_metric_alarm" "test" { InstanceId = "i-abc123" } } +`, suffix) +} + +func testAccAwsCloudWatchCompositeAlarmConfig_actionsEnabled(enabled bool, suffix string) string { + return composeConfig( + testAccAwsCloudWatchCompositeAlarmBaseConfig(suffix), + fmt.Sprintf(` +resource "aws_cloudwatch_composite_alarm" "test" { + actions_enabled = %t + alarm_name = "tf-test-composite-%s" + alarm_rule = join(" OR ", formatlist("ALARM(%%s)", aws_cloudwatch_metric_alarm.test.*.alarm_name)) +} +`, enabled, suffix)) +} + +func testAccAwsCloudWatchCompositeAlarmConfig_basic(suffix string) string { + return composeConfig( + testAccAwsCloudWatchCompositeAlarmBaseConfig(suffix), + fmt.Sprintf(` +resource "aws_cloudwatch_composite_alarm" "test" { + alarm_name = "tf-test-composite-%[1]s" + alarm_rule = join(" OR ", formatlist("ALARM(%%s)", aws_cloudwatch_metric_alarm.test.*.alarm_name)) +} +`, suffix)) +} +func testAccAwsCloudWatchCompositeAlarmConfig_description(description, suffix string) string { + return composeConfig( + testAccAwsCloudWatchCompositeAlarmBaseConfig(suffix), + fmt.Sprintf(` +resource "aws_cloudwatch_composite_alarm" "test" { + alarm_description = %q + alarm_name = "tf-test-composite-%s" + alarm_rule = join(" OR ", formatlist("ALARM(%%s)", aws_cloudwatch_metric_alarm.test.*.alarm_name)) +} +`, description, suffix)) +} + +func testAccAwsCloudWatchCompositeAlarmConfig_alarmActions(suffix string) string { + return composeConfig( + testAccAwsCloudWatchCompositeAlarmBaseConfig(suffix), + fmt.Sprintf(` resource "aws_sns_topic" "test" { - count = 1 + count = 2 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" - } + alarm_actions = aws_sns_topic.test.*.arn + alarm_name = "tf-test-composite-%[1]s" + alarm_rule = "ALARM(${aws_cloudwatch_metric_alarm.test[0].alarm_name})" } -`, suffix) +`, suffix)) } -func testAccAwsCloudWatchCompositeAlarmConfig_update_after(suffix string) string { - return fmt.Sprintf(` -resource "aws_cloudwatch_metric_alarm" "test" { +func testAccAwsCloudWatchCompositeAlarmConfig_updateAlarmActions(suffix string) string { + return composeConfig( + testAccAwsCloudWatchCompositeAlarmBaseConfig(suffix), + fmt.Sprintf(` +resource "aws_sns_topic" "test" { count = 2 + name = "tf-test-alarms-${count.index}-%[1]s" +} - 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 +resource "aws_cloudwatch_composite_alarm" "test" { + alarm_actions = [aws_sns_topic.test[0].arn] + alarm_name = "tf-test-composite-%[1]s" + alarm_rule = "ALARM(${aws_cloudwatch_metric_alarm.test[0].alarm_name})" +} +`, suffix)) +} - dimensions = { - InstanceId = "i-abc123" - } +func testAccAwsCloudWatchCompositeAlarmConfig_updateAlarmRule(suffix string) string { + return composeConfig( + testAccAwsCloudWatchCompositeAlarmBaseConfig(suffix), + fmt.Sprintf(` +resource "aws_cloudwatch_composite_alarm" "test" { + alarm_name = "tf-test-composite-%[1]s" + alarm_rule = "ALARM(${aws_cloudwatch_metric_alarm.test[0].alarm_name})" +} +`, suffix)) } +func testAccAwsCloudWatchCompositeAlarmConfig_insufficientDataActions(suffix string) string { + return composeConfig( + testAccAwsCloudWatchCompositeAlarmBaseConfig(suffix), + fmt.Sprintf(` resource "aws_sns_topic" "test" { count = 2 name = "tf-test-alarms-${count.index}-%[1]s" } resource "aws_cloudwatch_composite_alarm" "test" { - alarm_actions = aws_sns_topic.test.*.arn - alarm_description = "Test 2" alarm_name = "tf-test-composite-%[1]s" alarm_rule = "ALARM(${aws_cloudwatch_metric_alarm.test[0].alarm_name})" insufficient_data_actions = aws_sns_topic.test.*.arn - ok_actions = aws_sns_topic.test.*.arn +} +`, suffix)) +} - tags = { - Foo = "Bar" - Bax = "Baf" - } +func testAccAwsCloudWatchCompositeAlarmConfig_updateInsufficientDataActions(suffix string) string { + return composeConfig( + testAccAwsCloudWatchCompositeAlarmBaseConfig(suffix), + fmt.Sprintf(` +resource "aws_sns_topic" "test" { + count = 2 + name = "tf-test-alarms-${count.index}-%[1]s" } -`, suffix) + +resource "aws_cloudwatch_composite_alarm" "test" { + alarm_name = "tf-test-composite-%[1]s" + alarm_rule = "ALARM(${aws_cloudwatch_metric_alarm.test[0].alarm_name})" + insufficient_data_actions = [aws_sns_topic.test[0].arn] +} +`, suffix)) } -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 - 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") - } - return nil - } +func testAccAwsCloudWatchCompositeAlarmConfig_okActions(suffix string) string { + return composeConfig( + testAccAwsCloudWatchCompositeAlarmBaseConfig(suffix), + fmt.Sprintf(` +resource "aws_sns_topic" "test" { + count = 2 + name = "tf-test-alarms-${count.index}-%[1]s" +} + +resource "aws_cloudwatch_composite_alarm" "test" { + alarm_name = "tf-test-composite-%[1]s" + alarm_rule = "ALARM(${aws_cloudwatch_metric_alarm.test[0].alarm_name})" + ok_actions = aws_sns_topic.test.*.arn +} +`, suffix)) +} + +func testAccAwsCloudWatchCompositeAlarmConfig_updateOkActions(suffix string) string { + return composeConfig( + testAccAwsCloudWatchCompositeAlarmBaseConfig(suffix), + fmt.Sprintf(` +resource "aws_sns_topic" "test" { + count = 2 + name = "tf-test-alarms-${count.index}-%[1]s" +} + +resource "aws_cloudwatch_composite_alarm" "test" { + alarm_name = "tf-test-composite-%[1]s" + alarm_rule = "ALARM(${aws_cloudwatch_metric_alarm.test[0].alarm_name})" + ok_actions = [aws_sns_topic.test[0].arn] +} +`, suffix)) +} + +func testAccAwsCloudWatchCompositeAlarmConfig_allActions(suffix string) string { + return composeConfig( + testAccAwsCloudWatchCompositeAlarmBaseConfig(suffix), + fmt.Sprintf(` +resource "aws_sns_topic" "test" { + count = 3 + name = "tf-test-alarms-${count.index}-%[1]s" +} + +resource "aws_cloudwatch_composite_alarm" "test" { + alarm_actions = [aws_sns_topic.test[0].arn] + alarm_name = "tf-test-composite-%[1]s" + alarm_rule = "ALARM(${aws_cloudwatch_metric_alarm.test[0].alarm_name})" + insufficient_data_actions = [aws_sns_topic.test[1].arn] + ok_actions = [aws_sns_topic.test[2].arn] +} +`, suffix)) } diff --git a/website/docs/r/cloudwatch_composite_alarm.html.markdown b/website/docs/r/cloudwatch_composite_alarm.html.markdown index 5190bd0b27ad..0d74e77c16d8 100644 --- a/website/docs/r/cloudwatch_composite_alarm.html.markdown +++ b/website/docs/r/cloudwatch_composite_alarm.html.markdown @@ -31,7 +31,7 @@ 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`. +* `actions_enabled` - (Optional, Forces new resource) 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_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.