From a987d7778b1451420b6ed09d9d89c96ab80ee600 Mon Sep 17 00:00:00 2001 From: Roberth Kulbin Date: Wed, 2 Sep 2020 19:08:01 +0100 Subject: [PATCH 1/3] r/aws_cloudwatch_composite_alarm: add resource --- aws/provider.go | 1 + ...resource_aws_cloudwatch_composite_alarm.go | 269 ++++++++++++++++++ ...rce_aws_cloudwatch_composite_alarm_test.go | 256 +++++++++++++++++ .../cloudwatch_composite_alarm.html.markdown | 56 ++++ 4 files changed, 582 insertions(+) create mode 100644 aws/resource_aws_cloudwatch_composite_alarm.go create mode 100644 aws/resource_aws_cloudwatch_composite_alarm_test.go create mode 100644 website/docs/r/cloudwatch_composite_alarm.html.markdown diff --git a/aws/provider.go b/aws/provider.go index 0543109d0f13..d98ac926dc75 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -513,6 +513,7 @@ func Provider() *schema.Provider { "aws_cloudhsm_v2_cluster": resourceAwsCloudHsmV2Cluster(), "aws_cloudhsm_v2_hsm": resourceAwsCloudHsmV2Hsm(), "aws_cognito_resource_server": resourceAwsCognitoResourceServer(), + "aws_cloudwatch_composite_alarm": resourceAwsCloudWatchCompositeAlarm(), "aws_cloudwatch_metric_alarm": resourceAwsCloudWatchMetricAlarm(), "aws_cloudwatch_dashboard": resourceAwsCloudWatchDashboard(), "aws_codedeploy_app": resourceAwsCodeDeployApp(), diff --git a/aws/resource_aws_cloudwatch_composite_alarm.go b/aws/resource_aws_cloudwatch_composite_alarm.go new file mode 100644 index 000000000000..dada6aa8f425 --- /dev/null +++ b/aws/resource_aws_cloudwatch_composite_alarm.go @@ -0,0 +1,269 @@ +package aws + +import ( + "context" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudwatch" + "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" +) + +func resourceAwsCloudWatchCompositeAlarm() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceAwsCloudWatchCompositeAlarmCreate, + ReadContext: resourceAwsCloudWatchCompositeAlarmRead, + UpdateContext: resourceAwsCloudWatchCompositeAlarmUpdate, + DeleteContext: resourceAwsCloudWatchCompositeAlarmDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "actions_enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "alarm_actions": { + Type: schema.TypeSet, + Optional: true, + Set: schema.HashString, + MaxItems: 5, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateArn, + }, + }, + "alarm_description": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 1024), + }, + "alarm_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(0, 255), + }, + "alarm_rule": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 10240), + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "insufficient_data_actions": { + Type: schema.TypeSet, + Optional: true, + Set: schema.HashString, + MaxItems: 5, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateArn, + }, + }, + "ok_actions": { + Type: schema.TypeSet, + Optional: true, + Set: schema.HashString, + MaxItems: 5, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateArn, + }, + }, + "tags": tagsSchema(), + }, + } +} + +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("create composite alarm: %s", 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 { + conn := meta.(*AWSClient).cloudwatchconn + 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 + } + + 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) + } + + d.Set("alarm_description", alarm.AlarmDescription) + d.Set("alarm_name", alarm.AlarmName) + d.Set("alarm_rule", alarm.AlarmRule) + 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) + } + + if err := d.Set("ok_actions", flattenStringSet(alarm.OKActions)); err != nil { + return diag.Errorf("set 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) + } + + if err := d.Set("tags", tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return diag.Errorf("set tags: %s", err) + } + + return nil +} + +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("create composite alarm: %s", err) + } + + arn := d.Get("arn").(string) + if d.HasChange("tags") { + o, n := d.GetChange("tags") + + if err := keyvaluetags.CloudwatchUpdateTags(conn, arn, o, n); err != nil { + return diag.Errorf("update tags: %s", err) + } + } + + return resourceAwsCloudWatchCompositeAlarmRead(ctx, d, meta) +} + +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}), + } + + _, 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) + } + + 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)) + } + + if v, ok := d.GetOk("alarm_actions"); ok { + out.AlarmActions = expandStringSet(v.(*schema.Set)) + } + + if v, ok := d.GetOk("alarm_description"); ok { + out.AlarmDescription = aws.String(v.(string)) + } + + if v, ok := d.GetOk("alarm_name"); ok { + out.AlarmName = aws.String(v.(string)) + } + + if v, ok := d.GetOk("alarm_rule"); ok { + out.AlarmRule = aws.String(v.(string)) + } + + if v, ok := d.GetOk("insufficient_data_actions"); ok { + out.InsufficientDataActions = expandStringSet(v.(*schema.Set)) + } + + if v, ok := d.GetOk("ok_actions"); ok { + out.OKActions = expandStringSet(v.(*schema.Set)) + } + + if v, ok := d.GetOk("tags"); ok { + out.Tags = keyvaluetags.New(v.(map[string]interface{})).IgnoreAws().CloudwatchTags() + } + + 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 new file mode 100644 index 000000000000..fb9862b5c8bb --- /dev/null +++ b/aws/resource_aws_cloudwatch_composite_alarm_test.go @@ -0,0 +1,256 @@ +package aws + +import ( + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudwatch" + "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" +) + +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 + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_cloudwatch_composite_alarm" { + continue + } + + params := cloudwatch.DescribeAlarmsInput{ + AlarmNames: []*string{aws.String(rs.Primary.ID)}, + } + + resp, err := conn.DescribeAlarms(¶ms) + + if err == nil { + if len(resp.MetricAlarms) != 0 && + *resp.MetricAlarms[0].AlarmName == rs.Primary.ID { + return fmt.Errorf("Alarm Still Exists: %s", rs.Primary.ID) + } + } + } + + return nil +} + +func TestAccAwsCloudWatchCompositeAlarm_basic(t *testing.T) { + alarm := cloudwatch.CompositeAlarm{} + 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_create(suffix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName, &alarm), + 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, + }, + { + Config: testAccAwsCloudWatchCompositeAlarmConfig_update(suffix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCloudWatchCompositeAlarmExists(resourceName, &alarm), + 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"), + ), + }, + }, + }) +} + +func testAccAwsCloudWatchCompositeAlarmConfig_create(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 testAccAwsCloudWatchCompositeAlarmConfig_update(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 = 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 + + tags = { + Foo = "Bar" + 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 { + conn := testAccProvider.Meta().(*AWSClient).cloudwatchconn + alarmNames := []string{ + "tf-test-composite-" + suffix, + "tf-test-metric-" + suffix, + } + + for _, name := range alarmNames { + input := cloudwatch.DeleteAlarmsInput{ + AlarmNames: []*string{&name}, + } + + _, err := conn.DeleteAlarms(&input) + if err != nil { + return err + } + } + + 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 new file mode 100644 index 000000000000..4a140d0f4656 --- /dev/null +++ b/website/docs/r/cloudwatch_composite_alarm.html.markdown @@ -0,0 +1,56 @@ +--- +subcategory: "CloudWatch" +layout: "aws" +page_title: "AWS: aws_cloudwatch_composite_alarm" +description: |- + Provides a CloudWatch Composite Alarm resource. +--- + +# Resource: aws_cloudwatch_composite_alarm + +Provides a CloudWatch Composite Alarm resource. + +~> **NOTE:** An alarm (composite or metric) cannot be destroyed when there are other composite alarms depending on it. This can lead to a cyclical dependency on update, as Terraform will unsuccessfully attempt to destroy alarms before updating the rule. Consider using `depends_on`, references to alarm names, and two-stage updates. + +## Example Usage + +```hcl +resource "aws_cloudwatch_composite_alarm" "example" { + alarm_description = "This is a composite alarm!" + alarm_name = "example-composite-alarm" + + alarm_actions = aws_sns_topic.example.arn + ok_actions = aws_sns_topic.example.arn + + alarm_rule = < Date: Sat, 9 Jan 2021 14:41:10 +0000 Subject: [PATCH 2/3] r/aws_cloudwatch_composite_alarm: code review --- .../service/cloudwatch/finder/finder.go | 24 ++ ...resource_aws_cloudwatch_composite_alarm.go | 72 +++--- ...rce_aws_cloudwatch_composite_alarm_test.go | 233 +++++++++++------- .../cloudwatch_composite_alarm.html.markdown | 6 +- 4 files changed, 194 insertions(+), 141 deletions(-) create mode 100644 aws/internal/service/cloudwatch/finder/finder.go 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 From 757ee46f386336db950fc4ed715ac45fa96a42ad Mon Sep 17 00:00:00 2001 From: Angie Pinilla Date: Wed, 13 Jan 2021 21:41:09 -0500 Subject: [PATCH 3/3] 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.