diff --git a/aws/internal/service/cloudwatchevents/finder/finder.go b/aws/internal/service/cloudwatchevents/finder/finder.go new file mode 100644 index 00000000000..76ee55a06d9 --- /dev/null +++ b/aws/internal/service/cloudwatchevents/finder/finder.go @@ -0,0 +1,28 @@ +package finder + +import ( + "github.com/aws/aws-sdk-go/aws" + events "github.com/aws/aws-sdk-go/service/cloudwatchevents" + tfevents "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/cloudwatchevents" +) + +func Rule(conn *events.CloudWatchEvents, eventBusName, ruleName string) (*events.DescribeRuleOutput, error) { + input := events.DescribeRuleInput{ + Name: aws.String(ruleName), + } + if eventBusName != "" { + input.EventBusName = aws.String(eventBusName) + } + + return conn.DescribeRule(&input) + +} + +func RuleByID(conn *events.CloudWatchEvents, ruleID string) (*events.DescribeRuleOutput, error) { + busName, ruleName, err := tfevents.RuleParseID(ruleID) + if err != nil { + return nil, err + } + + return Rule(conn, busName, ruleName) +} diff --git a/aws/internal/service/cloudwatchevents/id.go b/aws/internal/service/cloudwatchevents/id.go new file mode 100644 index 00000000000..80d947370ff --- /dev/null +++ b/aws/internal/service/cloudwatchevents/id.go @@ -0,0 +1,29 @@ +package cloudwatchevents + +import ( + "fmt" + "strings" +) + +const DefaultEventBusName = "default" + +const ruleIDSeparator = "/" + +func RuleCreateID(eventBusName, ruleName string) string { + if eventBusName == "" || eventBusName == DefaultEventBusName { + return ruleName + } + return eventBusName + ruleIDSeparator + ruleName +} + +func RuleParseID(id string) (string, string, error) { + parts := strings.Split(id, ruleIDSeparator) + if len(parts) == 1 && parts[0] != "" { + return DefaultEventBusName, parts[0], nil + } + if len(parts) == 2 && parts[0] != "" && parts[1] != "" { + return parts[0], parts[1], nil + } + + return "", "", fmt.Errorf("unexpected format for ID (%q), expected "+ruleIDSeparator+" or ", id) +} diff --git a/aws/resource_aws_cloudwatch_event_rule.go b/aws/resource_aws_cloudwatch_event_rule.go index cc83c9cd308..50ee65e2f59 100644 --- a/aws/resource_aws_cloudwatch_event_rule.go +++ b/aws/resource_aws_cloudwatch_event_rule.go @@ -6,13 +6,17 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" events "github.com/aws/aws-sdk-go/service/cloudwatchevents" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" "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/naming" + tfevents "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/cloudwatchevents" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/cloudwatchevents/finder" + iamwaiter "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter" ) const ( @@ -39,20 +43,30 @@ func resourceAwsCloudWatchEventRule() *schema.Resource { ValidateFunc: validateCloudWatchEventRuleName, }, "name_prefix": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validateCloudWatchEventRuleName, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"name"}, + ValidateFunc: validateCloudWatchEventRuleName, }, "schedule_expression": { Type: schema.TypeString, Optional: true, ValidateFunc: validation.StringLenBetween(0, 256), + AtLeastOneOf: []string{"schedule_expression", "event_pattern"}, + }, + "event_bus_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validateCloudWatchEventBusName, + Default: tfevents.DefaultEventBusName, }, "event_pattern": { Type: schema.TypeString, Optional: true, ValidateFunc: validateEventPatternValue(), + AtLeastOneOf: []string{"schedule_expression", "event_pattern"}, StateFunc: func(v interface{}) string { json, _ := structure.NormalizeJsonString(v.(string)) return json @@ -85,28 +99,26 @@ func resourceAwsCloudWatchEventRule() *schema.Resource { func resourceAwsCloudWatchEventRuleCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).cloudwatcheventsconn - var name string - if v, ok := d.GetOk("name"); ok { - name = v.(string) - } else if v, ok := d.GetOk("name_prefix"); ok { - name = resource.PrefixedUniqueId(v.(string)) - } else { - name = resource.UniqueId() - } + name := naming.Generate(d.Get("name").(string), d.Get("name_prefix").(string)) input, err := buildPutRuleInputStruct(d, name) if err != nil { - return fmt.Errorf("Creating CloudWatch Event Rule failed: %s", err) + return fmt.Errorf("Creating CloudWatch Events Rule failed: %w", err) } - log.Printf("[DEBUG] Creating CloudWatch Event Rule: %s", input) + + if v, ok := d.GetOk("tags"); ok { + input.Tags = keyvaluetags.New(v.(map[string]interface{})).IgnoreAws().CloudwatcheventsTags() + } + + log.Printf("[DEBUG] Creating CloudWatch Events Rule: %s", input) // IAM Roles take some time to propagate var out *events.PutRuleOutput - err = resource.Retry(30*time.Second, func() *resource.RetryError { + err = resource.Retry(iamwaiter.PropagationTimeout, func() *resource.RetryError { out, err = conn.PutRule(input) if isAWSErr(err, "ValidationException", "cannot be assumed by principal") { - log.Printf("[DEBUG] Retrying update of CloudWatch Event Rule %q", *input.Name) + log.Printf("[DEBUG] Retrying update of CloudWatch Events Rule %q", aws.StringValue(input.Name)) return resource.RetryableError(err) } if err != nil { @@ -119,13 +131,15 @@ func resourceAwsCloudWatchEventRuleCreate(d *schema.ResourceData, meta interface } if err != nil { - return fmt.Errorf("Updating CloudWatch Event Rule failed: %s", err) + return fmt.Errorf("Creating CloudWatch Events Rule failed: %w", err) } d.Set("arn", out.RuleArn) - d.SetId(*input.Name) - log.Printf("[INFO] CloudWatch Event Rule %q created", *out.RuleArn) + id := tfevents.RuleCreateID(aws.StringValue(input.EventBusName), aws.StringValue(input.Name)) + d.SetId(id) + + log.Printf("[INFO] CloudWatch Events Rule (%s) created", aws.StringValue(out.RuleArn)) return resourceAwsCloudWatchEventRuleRead(d, meta) } @@ -134,36 +148,32 @@ func resourceAwsCloudWatchEventRuleRead(d *schema.ResourceData, meta interface{} conn := meta.(*AWSClient).cloudwatcheventsconn ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig - input := events.DescribeRuleInput{ - Name: aws.String(d.Id()), - } - log.Printf("[DEBUG] Reading CloudWatch Event Rule: %s", input) - out, err := conn.DescribeRule(&input) - if awsErr, ok := err.(awserr.Error); ok { - if awsErr.Code() == events.ErrCodeResourceNotFoundException { - log.Printf("[WARN] Removing CloudWatch Event Rule %q because it's gone.", d.Id()) - d.SetId("") - return nil - } + out, err := finder.RuleByID(conn, d.Id()) + if tfawserr.ErrCodeEquals(err, events.ErrCodeResourceNotFoundException) { + log.Printf("[WARN] Removing CloudWatch Events Rule (%s) because it's gone.", d.Id()) + d.SetId("") + return nil } if err != nil { - return err + return fmt.Errorf("error reading CloudWatch Events Rule (%s): %w", d.Id(), err) } log.Printf("[DEBUG] Found Event Rule: %s", out) - arn := *out.Arn + arn := aws.StringValue(out.Arn) d.Set("arn", arn) d.Set("description", out.Description) if out.EventPattern != nil { - pattern, err := structure.NormalizeJsonString(*out.EventPattern) + pattern, err := structure.NormalizeJsonString(aws.StringValue(out.EventPattern)) if err != nil { - return fmt.Errorf("event pattern contains an invalid JSON: %s", err) + return fmt.Errorf("event pattern contains an invalid JSON: %w", err) } d.Set("event_pattern", pattern) } d.Set("name", out.Name) + d.Set("name_prefix", aws.StringValue(naming.NamePrefixFromName(aws.StringValue(out.Name)))) d.Set("role_arn", out.RoleArn) d.Set("schedule_expression", out.ScheduleExpression) + d.Set("event_bus_name", out.EventBusName) boolState, err := getBooleanStateFromString(*out.State) if err != nil { @@ -175,11 +185,11 @@ func resourceAwsCloudWatchEventRuleRead(d *schema.ResourceData, meta interface{} tags, err := keyvaluetags.CloudwatcheventsListTags(conn, arn) if err != nil { - return fmt.Errorf("error listing tags for CloudWatch Event Rule (%s): %s", arn, err) + return fmt.Errorf("error listing tags for CloudWatch Events Rule (%s): %w", arn, err) } if err := d.Set("tags", tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { - return fmt.Errorf("error setting tags: %s", err) + return fmt.Errorf("error setting tags: %w", err) } return nil @@ -187,19 +197,22 @@ func resourceAwsCloudWatchEventRuleRead(d *schema.ResourceData, meta interface{} func resourceAwsCloudWatchEventRuleUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).cloudwatcheventsconn - - input, err := buildPutRuleInputStruct(d, d.Id()) + _, ruleName, err := tfevents.RuleParseID(d.Id()) if err != nil { - return fmt.Errorf("Updating CloudWatch Event Rule failed: %s", err) + return err + } + input, err := buildPutRuleInputStruct(d, ruleName) + if err != nil { + return fmt.Errorf("Updating CloudWatch Events Rule (%s) failed: %w", ruleName, err) } - log.Printf("[DEBUG] Updating CloudWatch Event Rule: %s", input) + log.Printf("[DEBUG] Updating CloudWatch Events Rule: %s", input) // IAM Roles take some time to propagate - err = resource.Retry(30*time.Second, func() *resource.RetryError { + err = resource.Retry(iamwaiter.PropagationTimeout, func() *resource.RetryError { _, err := conn.PutRule(input) if isAWSErr(err, "ValidationException", "cannot be assumed by principal") { - log.Printf("[DEBUG] Retrying update of CloudWatch Event Rule %q", *input.Name) + log.Printf("[DEBUG] Retrying update of CloudWatch Events Rule %q", aws.StringValue(input.Name)) return resource.RetryableError(err) } if err != nil { @@ -212,7 +225,7 @@ func resourceAwsCloudWatchEventRuleUpdate(d *schema.ResourceData, meta interface } if err != nil { - return fmt.Errorf("Updating CloudWatch Event Rule failed: %s", err) + return fmt.Errorf("Updating CloudWatch Events Rule (%s) failed: %w", ruleName, err) } arn := d.Get("arn").(string) @@ -220,7 +233,7 @@ func resourceAwsCloudWatchEventRuleUpdate(d *schema.ResourceData, meta interface o, n := d.GetChange("tags") if err := keyvaluetags.CloudwatcheventsUpdateTags(conn, arn, o, n); err != nil { - return fmt.Errorf("error updating CloudwWatch Event Rule (%s) tags: %s", arn, err) + return fmt.Errorf("error updating CloudwWatch Event Rule (%s) tags: %w", arn, err) } } @@ -229,12 +242,16 @@ func resourceAwsCloudWatchEventRuleUpdate(d *schema.ResourceData, meta interface func resourceAwsCloudWatchEventRuleDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).cloudwatcheventsconn - + busName, ruleName, err := tfevents.RuleParseID(d.Id()) + if err != nil { + return err + } input := &events.DeleteRuleInput{ - Name: aws.String(d.Id()), + Name: aws.String(ruleName), + EventBusName: aws.String(busName), } - err := resource.Retry(cloudWatchEventRuleDeleteRetryTimeout, func() *resource.RetryError { + err = resource.Retry(cloudWatchEventRuleDeleteRetryTimeout, func() *resource.RetryError { _, err := conn.DeleteRule(input) if isAWSErr(err, "ValidationException", "Rule can't be deleted since it has targets") { @@ -253,7 +270,7 @@ func resourceAwsCloudWatchEventRuleDelete(d *schema.ResourceData, meta interface } if err != nil { - return fmt.Errorf("error deleting CloudWatch Event Rule (%s): %s", d.Id(), err) + return fmt.Errorf("error deleting CloudWatch Events Rule (%s): %w", d.Id(), err) } return nil @@ -263,13 +280,18 @@ func buildPutRuleInputStruct(d *schema.ResourceData, name string) (*events.PutRu input := events.PutRuleInput{ Name: aws.String(name), } + var eventBusName string if v, ok := d.GetOk("description"); ok { input.Description = aws.String(v.(string)) } + if v, ok := d.GetOk("event_bus_name"); ok { + eventBusName = v.(string) + input.EventBusName = aws.String(eventBusName) + } if v, ok := d.GetOk("event_pattern"); ok { pattern, err := structure.NormalizeJsonString(v) if err != nil { - return nil, fmt.Errorf("event pattern contains an invalid JSON: %s", err) + return nil, fmt.Errorf("event pattern contains an invalid JSON: %w", err) } input.EventPattern = aws.String(pattern) } @@ -280,10 +302,6 @@ func buildPutRuleInputStruct(d *schema.ResourceData, name string) (*events.PutRu input.ScheduleExpression = aws.String(v.(string)) } - if v, ok := d.GetOk("tags"); ok { - input.Tags = keyvaluetags.New(v.(map[string]interface{})).IgnoreAws().CloudwatcheventsTags() - } - input.State = aws.String(getStringStateFromBoolean(d.Get("is_enabled").(bool))) return &input, nil @@ -313,7 +331,7 @@ func validateEventPatternValue() schema.SchemaValidateFunc { return func(v interface{}, k string) (ws []string, errors []error) { json, err := structure.NormalizeJsonString(v) if err != nil { - errors = append(errors, fmt.Errorf("%q contains an invalid JSON: %s", k, err)) + errors = append(errors, fmt.Errorf("%q contains an invalid JSON: %w", k, err)) // Invalid JSON? Return immediately, // there is no need to collect other @@ -323,8 +341,7 @@ func validateEventPatternValue() schema.SchemaValidateFunc { // Check whether the normalized JSON is within the given length. if len(json) > 2048 { - errors = append(errors, fmt.Errorf( - "%q cannot be longer than %d characters: %q", k, 2048, json)) + errors = append(errors, fmt.Errorf("%q cannot be longer than %d characters: %q", k, 2048, json)) } return } diff --git a/aws/resource_aws_cloudwatch_event_rule_test.go b/aws/resource_aws_cloudwatch_event_rule_test.go index 105b50787f2..c5bb9c565c4 100644 --- a/aws/resource_aws_cloudwatch_event_rule_test.go +++ b/aws/resource_aws_cloudwatch_event_rule_test.go @@ -12,6 +12,8 @@ import ( "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/naming" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/cloudwatchevents/finder" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/cloudwatchevents/lister" ) @@ -28,7 +30,7 @@ func init() { func testSweepCloudWatchEventRules(region string) error { client, err := sharedClientForRegion(region) if err != nil { - return fmt.Errorf("Error getting client: %s", err) + return fmt.Errorf("Error getting client: %w", err) } conn := client.(*AWSClient).cloudwatcheventsconn @@ -46,7 +48,7 @@ func testSweepCloudWatchEventRules(region string) error { count++ name := aws.StringValue(rule.Name) - log.Printf("[INFO] Deleting CloudWatch Events rule %s", name) + log.Printf("[INFO] Deleting CloudWatch Events rule (%s)", name) _, err := conn.DeleteRule(&events.DeleteRuleInput{ Name: aws.String(name), Force: aws.Bool(true), // Required for AWS-managed rules, ignored otherwise @@ -75,7 +77,7 @@ func testSweepCloudWatchEventRules(region string) error { } func TestAccAWSCloudWatchEventRule_basic(t *testing.T) { - var rule events.DescribeRuleOutput + var v1, v2, v3 events.DescribeRuleOutput rName := acctest.RandomWithPrefix("tf-acc-test") rName2 := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_cloudwatch_event_rule.test" @@ -88,10 +90,14 @@ func TestAccAWSCloudWatchEventRule_basic(t *testing.T) { { Config: testAccAWSCloudWatchEventRuleConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckCloudWatchEventRuleExists(resourceName, &rule), - testAccMatchResourceAttrRegionalARN(resourceName, "arn", "events", regexp.MustCompile(`rule/.+`)), + testAccCheckCloudWatchEventRuleExists(resourceName, &v1), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "events", regexp.MustCompile(fmt.Sprintf(`rule/%s$`, rName))), resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "name_prefix", ""), resource.TestCheckResourceAttr(resourceName, "schedule_expression", "rate(1 hour)"), + resource.TestCheckResourceAttr(resourceName, "event_bus_name", "default"), + resource.TestCheckNoResourceAttr(resourceName, "event_pattern"), + resource.TestCheckResourceAttr(resourceName, "description", ""), resource.TestCheckResourceAttr(resourceName, "role_arn", ""), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttr(resourceName, "is_enabled", "true"), @@ -99,16 +105,24 @@ func TestAccAWSCloudWatchEventRule_basic(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"is_enabled"}, //this has a default value + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccAWSCloudWatchEventRuleNoBusNameImportStateIdFunc(resourceName), + ImportStateVerify: true, }, { Config: testAccAWSCloudWatchEventRuleConfig(rName2), Check: resource.ComposeTestCheckFunc( - testAccCheckCloudWatchEventRuleExists(resourceName, &rule), + testAccCheckCloudWatchEventRuleExists(resourceName, &v2), + testAccCheckCloudWatchEventRuleRecreated(&v1, &v2), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "events", regexp.MustCompile(fmt.Sprintf(`rule/%s$`, rName2))), resource.TestCheckResourceAttr(resourceName, "name", rName2), + resource.TestCheckResourceAttr(resourceName, "event_bus_name", "default"), resource.TestCheckResourceAttr(resourceName, "schedule_expression", "rate(1 hour)"), resource.TestCheckResourceAttr(resourceName, "role_arn", ""), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), @@ -116,14 +130,76 @@ func TestAccAWSCloudWatchEventRule_basic(t *testing.T) { testAccCheckCloudWatchEventRuleEnabled(resourceName, "ENABLED"), ), }, + { + Config: testAccAWSCloudWatchEventRuleConfigDefaultEventBusName(rName2), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchEventRuleExists(resourceName, &v3), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "events", regexp.MustCompile(fmt.Sprintf(`rule/%s$`, rName2))), + testAccCheckCloudWatchEventRuleNotRecreated(&v2, &v3), + resource.TestCheckResourceAttr(resourceName, "name", rName2), + resource.TestCheckResourceAttr(resourceName, "event_bus_name", "default"), + ), + }, + }, + }) +} + +func TestAccAWSCloudWatchEventRule_EventBusName(t *testing.T) { + var v1, v2, v3 events.DescribeRuleOutput + rName := acctest.RandomWithPrefix("tf-acc-test-rule") + rName2 := acctest.RandomWithPrefix("tf-acc-test-rule") + busName := acctest.RandomWithPrefix("tf-acc-test-bus") + busName2 := acctest.RandomWithPrefix("tf-acc-test-bus") + resourceName := "aws_cloudwatch_event_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCloudWatchEventRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudWatchEventRuleConfigEventBusName(rName, busName, "description 1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchEventRuleExists(resourceName, &v1), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "event_bus_name", busName), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "events", regexp.MustCompile(fmt.Sprintf(`rule/%s/%s$`, busName, rName))), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSCloudWatchEventRuleConfigEventBusName(rName, busName, "description 2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchEventRuleExists(resourceName, &v2), + testAccCheckCloudWatchEventRuleNotRecreated(&v1, &v2), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "event_bus_name", busName), + ), + }, + { + Config: testAccAWSCloudWatchEventRuleConfigEventBusName(rName2, busName2, "description 2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchEventRuleExists(resourceName, &v3), + testAccCheckCloudWatchEventRuleRecreated(&v2, &v3), + resource.TestCheckResourceAttr(resourceName, "name", rName2), + resource.TestCheckResourceAttr(resourceName, "event_bus_name", busName2), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "events", regexp.MustCompile(fmt.Sprintf(`rule/%s/%s$`, busName2, rName2))), + ), + }, }, }) } func TestAccAWSCloudWatchEventRule_role(t *testing.T) { - var rule events.DescribeRuleOutput + var v events.DescribeRuleOutput rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_cloudwatch_event_rule.test" + iamRoleResourceName := "aws_iam_role.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -133,9 +209,9 @@ func TestAccAWSCloudWatchEventRule_role(t *testing.T) { { Config: testAccAWSCloudWatchEventRuleConfigRole(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckCloudWatchEventRuleExists(resourceName, &rule), + testAccCheckCloudWatchEventRuleExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "name", rName), - testAccMatchResourceAttrGlobalARN(resourceName, "role_arn", "iam", regexp.MustCompile(`role/.+`)), + resource.TestCheckResourceAttrPair(resourceName, "role_arn", iamRoleResourceName, "arn"), ), }, { @@ -148,7 +224,7 @@ func TestAccAWSCloudWatchEventRule_role(t *testing.T) { } func TestAccAWSCloudWatchEventRule_description(t *testing.T) { - var rule events.DescribeRuleOutput + var v1, v2 events.DescribeRuleOutput rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_cloudwatch_event_rule.test" @@ -160,7 +236,7 @@ func TestAccAWSCloudWatchEventRule_description(t *testing.T) { { Config: testAccAWSCloudWatchEventRuleConfigDescription(rName, "description1"), Check: resource.ComposeTestCheckFunc( - testAccCheckCloudWatchEventRuleExists(resourceName, &rule), + testAccCheckCloudWatchEventRuleExists(resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "description", "description1"), ), @@ -173,7 +249,7 @@ func TestAccAWSCloudWatchEventRule_description(t *testing.T) { { Config: testAccAWSCloudWatchEventRuleConfigDescription(rName, "description2"), Check: resource.ComposeTestCheckFunc( - testAccCheckCloudWatchEventRuleExists(resourceName, &rule), + testAccCheckCloudWatchEventRuleExists(resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "description", "description2"), ), @@ -183,7 +259,7 @@ func TestAccAWSCloudWatchEventRule_description(t *testing.T) { } func TestAccAWSCloudWatchEventRule_pattern(t *testing.T) { - var rule events.DescribeRuleOutput + var v1, v2 events.DescribeRuleOutput rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_cloudwatch_event_rule.test" @@ -195,9 +271,10 @@ func TestAccAWSCloudWatchEventRule_pattern(t *testing.T) { { Config: testAccAWSCloudWatchEventRuleConfigPattern(rName, "{\"source\":[\"aws.ec2\"]}"), Check: resource.ComposeTestCheckFunc( - testAccCheckCloudWatchEventRuleExists(resourceName, &rule), + testAccCheckCloudWatchEventRuleExists(resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "name", rName), - resource.TestCheckResourceAttr(resourceName, "event_pattern", "{\"source\":[\"aws.ec2\"]}"), + resource.TestCheckResourceAttr(resourceName, "schedule_expression", ""), + testAccCheckResourceAttrEquivalentJSON(resourceName, "event_pattern", "{\"source\":[\"aws.ec2\"]}"), ), }, { @@ -208,19 +285,18 @@ func TestAccAWSCloudWatchEventRule_pattern(t *testing.T) { { Config: testAccAWSCloudWatchEventRuleConfigPattern(rName, "{\"source\":[\"aws.lambda\"]}"), Check: resource.ComposeTestCheckFunc( - testAccCheckCloudWatchEventRuleExists(resourceName, &rule), + testAccCheckCloudWatchEventRuleExists(resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "name", rName), - resource.TestCheckResourceAttr(resourceName, "event_pattern", "{\"source\":[\"aws.lambda\"]}"), + testAccCheckResourceAttrEquivalentJSON(resourceName, "event_pattern", "{\"source\":[\"aws.lambda\"]}"), ), }, }, }) } -func TestAccAWSCloudWatchEventRule_prefix(t *testing.T) { - var rule events.DescribeRuleOutput +func TestAccAWSCloudWatchEventRule_ScheduleAndPattern(t *testing.T) { + var v events.DescribeRuleOutput rName := acctest.RandomWithPrefix("tf-acc-test") - startsWithPrefix := regexp.MustCompile(rName) resourceName := "aws_cloudwatch_event_rule.test" resource.ParallelTest(t, resource.TestCase{ @@ -229,18 +305,78 @@ func TestAccAWSCloudWatchEventRule_prefix(t *testing.T) { CheckDestroy: testAccCheckAWSCloudWatchEventRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSCloudWatchEventRuleConfigPrefix(rName), + Config: testAccAWSCloudWatchEventRuleConfigScheduleAndPattern(rName, "{\"source\":[\"aws.ec2\"]}"), Check: resource.ComposeTestCheckFunc( - testAccCheckCloudWatchEventRuleExists(resourceName, &rule), - resource.TestMatchResourceAttr(resourceName, "name", startsWithPrefix), + testAccCheckCloudWatchEventRuleExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "schedule_expression", "rate(1 hour)"), + testAccCheckResourceAttrEquivalentJSON(resourceName, "event_pattern", "{\"source\":[\"aws.ec2\"]}"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSCloudWatchEventRule_NamePrefix(t *testing.T) { + var v events.DescribeRuleOutput + rName := "tf-acc-test-prefix-" + resourceName := "aws_cloudwatch_event_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCloudWatchEventRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudWatchEventRuleConfigNamePrefix(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchEventRuleExists(resourceName, &v), + naming.TestCheckResourceAttrNameFromPrefix(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "name_prefix", rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSCloudWatchEventRule_Name_Generated(t *testing.T) { + var v events.DescribeRuleOutput + resourceName := "aws_cloudwatch_event_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCloudWatchEventRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudWatchEventRuleConfigNameGenerated, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchEventRuleExists(resourceName, &v), + naming.TestCheckResourceAttrNameGenerated(resourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "name_prefix", ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } func TestAccAWSCloudWatchEventRule_tags(t *testing.T) { - var rule events.DescribeRuleOutput + var v1, v2, v3 events.DescribeRuleOutput rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_cloudwatch_event_rule.test" @@ -252,7 +388,7 @@ func TestAccAWSCloudWatchEventRule_tags(t *testing.T) { { Config: testAccAWSCloudWatchEventRuleConfigTags1(rName, "key1", "value1"), Check: resource.ComposeTestCheckFunc( - testAccCheckCloudWatchEventRuleExists(resourceName, &rule), + testAccCheckCloudWatchEventRuleExists(resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), ), @@ -265,7 +401,7 @@ func TestAccAWSCloudWatchEventRule_tags(t *testing.T) { { Config: testAccAWSCloudWatchEventRuleConfigTags2(rName, "key1", "value1updated", "key2", "value2"), Check: resource.ComposeTestCheckFunc( - testAccCheckCloudWatchEventRuleExists(resourceName, &rule), + testAccCheckCloudWatchEventRuleExists(resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), @@ -274,17 +410,24 @@ func TestAccAWSCloudWatchEventRule_tags(t *testing.T) { { Config: testAccAWSCloudWatchEventRuleConfigTags1(rName, "key2", "value2"), Check: resource.ComposeTestCheckFunc( - testAccCheckCloudWatchEventRuleExists(resourceName, &rule), + testAccCheckCloudWatchEventRuleExists(resourceName, &v3), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), ), }, + { + Config: testAccAWSCloudWatchEventRuleConfigTags0(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchEventRuleExists(resourceName, &v3), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, }, }) } func TestAccAWSCloudWatchEventRule_IsEnabled(t *testing.T) { - var rule events.DescribeRuleOutput + var v1, v2, v3 events.DescribeRuleOutput rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_cloudwatch_event_rule.test" @@ -296,7 +439,7 @@ func TestAccAWSCloudWatchEventRule_IsEnabled(t *testing.T) { { Config: testAccAWSCloudWatchEventRuleConfigIsEnabled(rName, false), Check: resource.ComposeTestCheckFunc( - testAccCheckCloudWatchEventRuleExists(resourceName, &rule), + testAccCheckCloudWatchEventRuleExists(resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "is_enabled", "false"), testAccCheckCloudWatchEventRuleEnabled(resourceName, "DISABLED"), ), @@ -309,7 +452,7 @@ func TestAccAWSCloudWatchEventRule_IsEnabled(t *testing.T) { { Config: testAccAWSCloudWatchEventRuleConfigIsEnabled(rName, true), Check: resource.ComposeTestCheckFunc( - testAccCheckCloudWatchEventRuleExists(resourceName, &rule), + testAccCheckCloudWatchEventRuleExists(resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "is_enabled", "true"), testAccCheckCloudWatchEventRuleEnabled(resourceName, "ENABLED"), ), @@ -317,7 +460,7 @@ func TestAccAWSCloudWatchEventRule_IsEnabled(t *testing.T) { { Config: testAccAWSCloudWatchEventRuleConfigIsEnabled(rName, false), Check: resource.ComposeTestCheckFunc( - testAccCheckCloudWatchEventRuleExists(resourceName, &rule), + testAccCheckCloudWatchEventRuleExists(resourceName, &v3), resource.TestCheckResourceAttr(resourceName, "is_enabled", "false"), testAccCheckCloudWatchEventRuleEnabled(resourceName, "DISABLED"), ), @@ -334,15 +477,13 @@ func testAccCheckCloudWatchEventRuleExists(n string, rule *events.DescribeRuleOu } conn := testAccProvider.Meta().(*AWSClient).cloudwatcheventsconn - params := events.DescribeRuleInput{ - Name: aws.String(rs.Primary.ID), - } - resp, err := conn.DescribeRule(¶ms) + + resp, err := finder.RuleByID(conn, rs.Primary.ID) if err != nil { return err } if resp == nil { - return fmt.Errorf("Rule not found") + return fmt.Errorf("CloudWatch Events rule (%s) not found", rs.Primary.ID) } *rule = *resp @@ -359,16 +500,13 @@ func testAccCheckCloudWatchEventRuleEnabled(n string, desired string) resource.T } conn := testAccProvider.Meta().(*AWSClient).cloudwatcheventsconn - params := events.DescribeRuleInput{ - Name: aws.String(rs.Primary.ID), - } - resp, err := conn.DescribeRule(¶ms) + resp, err := finder.RuleByID(conn, rs.Primary.ID) if err != nil { return err } - if *resp.State != desired { - return fmt.Errorf("Expected state %q, given %q", desired, *resp.State) + if aws.StringValue(resp.State) != desired { + return fmt.Errorf("Expected state %q, given %q", desired, aws.StringValue(resp.State)) } return nil @@ -383,19 +521,42 @@ func testAccCheckAWSCloudWatchEventRuleDestroy(s *terraform.State) error { continue } - params := events.DescribeRuleInput{ - Name: aws.String(rs.Primary.ID), + resp, err := finder.RuleByID(conn, rs.Primary.ID) + if err == nil { + return fmt.Errorf("CloudWatch Events Rule (%s) still exists: %s", rs.Primary.ID, resp) } + } - resp, err := conn.DescribeRule(¶ms) + return nil +} - if err == nil { - return fmt.Errorf("CloudWatch Events Rule %q still exists: %s", - rs.Primary.ID, resp) +func testAccCheckCloudWatchEventRuleRecreated(i, j *events.DescribeRuleOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + if aws.StringValue(i.Arn) == aws.StringValue(j.Arn) { + return fmt.Errorf("CloudWatch Events rule not recreated, but expected it to be") } + return nil } +} - return nil +func testAccCheckCloudWatchEventRuleNotRecreated(i, j *events.DescribeRuleOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + if aws.StringValue(i.Arn) != aws.StringValue(j.Arn) { + return fmt.Errorf("CloudWatch Events rule recreated, but expected it to not be") + } + return nil + } +} + +func testAccAWSCloudWatchEventRuleNoBusNameImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("Not found: %s", resourceName) + } + + return rs.Primary.Attributes["name"], nil + } } func testAccAWSCloudWatchEventRuleConfig(name string) string { @@ -407,8 +568,50 @@ resource "aws_cloudwatch_event_rule" "test" { `, name) } +func testAccAWSCloudWatchEventRuleConfigDefaultEventBusName(name string) string { + return fmt.Sprintf(` +resource "aws_cloudwatch_event_rule" "test" { + name = %[1]q + schedule_expression = "rate(1 hour)" + event_bus_name = "default" +} +`, name) +} + +func testAccAWSCloudWatchEventRuleConfigEventBusName(name, eventBusName, description string) string { + return fmt.Sprintf(` +resource "aws_cloudwatch_event_rule" "test" { + name = %[1]q + event_bus_name = aws_cloudwatch_event_bus.test.name + description = %[2]q + event_pattern = <