Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add eventbus to cloudwatch event rule #15727

Merged
merged 14 commits into from
Oct 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions aws/internal/service/cloudwatchevents/finder/finder.go
Original file line number Diff line number Diff line change
@@ -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)
}
29 changes: 29 additions & 0 deletions aws/internal/service/cloudwatchevents/id.go
Original file line number Diff line number Diff line change
@@ -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 <event-bus-name>"+ruleIDSeparator+"<rule-name> or <rule-name>", id)
}
131 changes: 74 additions & 57 deletions aws/resource_aws_cloudwatch_event_rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
Expand All @@ -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 {
Expand All @@ -175,31 +185,34 @@ 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
}

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 {
Expand All @@ -212,15 +225,15 @@ 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)
if d.HasChange("tags") {
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)
}
}

Expand All @@ -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") {
Expand All @@ -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
Expand All @@ -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)
}
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand Down
Loading