-
Notifications
You must be signed in to change notification settings - Fork 9.6k
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
provider/aws: Add support for CloudWatch Events #4986
Changes from all commits
65ac864
ab89e5e
b5039dd
61afc6d
30082a4
e288b16
30e5ec7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
package aws | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"regexp" | ||
"time" | ||
|
||
"github.com/aws/aws-sdk-go/aws/awserr" | ||
"github.com/hashicorp/terraform/helper/resource" | ||
"github.com/hashicorp/terraform/helper/schema" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
events "github.com/aws/aws-sdk-go/service/cloudwatchevents" | ||
) | ||
|
||
func resourceAwsCloudWatchEventRule() *schema.Resource { | ||
return &schema.Resource{ | ||
Create: resourceAwsCloudWatchEventRuleCreate, | ||
Read: resourceAwsCloudWatchEventRuleRead, | ||
Update: resourceAwsCloudWatchEventRuleUpdate, | ||
Delete: resourceAwsCloudWatchEventRuleDelete, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
"name": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
ValidateFunc: validateCloudWatchEventRuleName, | ||
}, | ||
"schedule_expression": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ValidateFunc: validateMaxLength(256), | ||
}, | ||
"event_pattern": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ValidateFunc: validateMaxLength(2048), | ||
StateFunc: normalizeJson, | ||
}, | ||
"description": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ValidateFunc: validateMaxLength(512), | ||
}, | ||
"role_arn": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ValidateFunc: validateMaxLength(1600), | ||
}, | ||
"is_enabled": &schema.Schema{ | ||
Type: schema.TypeBool, | ||
Optional: true, | ||
Default: true, | ||
}, | ||
"arn": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func resourceAwsCloudWatchEventRuleCreate(d *schema.ResourceData, meta interface{}) error { | ||
conn := meta.(*AWSClient).cloudwatcheventsconn | ||
|
||
input := buildPutRuleInputStruct(d) | ||
log.Printf("[DEBUG] Creating CloudWatch Event Rule: %s", input) | ||
|
||
// IAM Roles take some time to propagate | ||
var out *events.PutRuleOutput | ||
err := resource.Retry(30*time.Second, func() error { | ||
var err error | ||
out, err = conn.PutRule(input) | ||
pattern := regexp.MustCompile("cannot be assumed by principal '[a-z]+\\.amazonaws\\.com'\\.$") | ||
if err != nil { | ||
if awsErr, ok := err.(awserr.Error); ok { | ||
if awsErr.Code() == "ValidationException" && pattern.MatchString(awsErr.Message()) { | ||
log.Printf("[DEBUG] Retrying creation of CloudWatch Event Rule %q", *input.Name) | ||
return err | ||
} | ||
} | ||
return &resource.RetryError{ | ||
Err: err, | ||
} | ||
} | ||
return nil | ||
}) | ||
if err != nil { | ||
return fmt.Errorf("Creating CloudWatch Event Rule failed: %s", err) | ||
} | ||
|
||
d.Set("arn", out.RuleArn) | ||
d.SetId(d.Get("name").(string)) | ||
|
||
log.Printf("[INFO] CloudWatch Event Rule %q created", *out.RuleArn) | ||
|
||
return resourceAwsCloudWatchEventRuleUpdate(d, meta) | ||
} | ||
|
||
func resourceAwsCloudWatchEventRuleRead(d *schema.ResourceData, meta interface{}) error { | ||
conn := meta.(*AWSClient).cloudwatcheventsconn | ||
|
||
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() == "ResourceNotFoundException" { | ||
log.Printf("[WARN] Removing CloudWatch Event Rule %q because it's gone.", d.Id()) | ||
d.SetId("") | ||
return nil | ||
} | ||
} | ||
if err != nil { | ||
return err | ||
} | ||
log.Printf("[DEBUG] Found Event Rule: %s", out) | ||
|
||
d.Set("arn", out.Arn) | ||
d.Set("description", out.Description) | ||
if out.EventPattern != nil { | ||
d.Set("event_pattern", normalizeJson(*out.EventPattern)) | ||
} | ||
d.Set("name", out.Name) | ||
d.Set("role_arn", out.RoleArn) | ||
d.Set("schedule_expression", out.ScheduleExpression) | ||
|
||
boolState, err := getBooleanStateFromString(*out.State) | ||
if err != nil { | ||
return err | ||
} | ||
log.Printf("[DEBUG] Setting boolean state: %t", boolState) | ||
d.Set("is_enabled", boolState) | ||
|
||
return nil | ||
} | ||
|
||
func resourceAwsCloudWatchEventRuleUpdate(d *schema.ResourceData, meta interface{}) error { | ||
conn := meta.(*AWSClient).cloudwatcheventsconn | ||
|
||
if d.HasChange("is_enabled") && d.Get("is_enabled").(bool) { | ||
log.Printf("[DEBUG] Enabling CloudWatch Event Rule %q", d.Id()) | ||
_, err := conn.EnableRule(&events.EnableRuleInput{ | ||
Name: aws.String(d.Id()), | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
log.Printf("[DEBUG] CloudWatch Event Rule (%q) enabled", d.Id()) | ||
} | ||
|
||
input := buildPutRuleInputStruct(d) | ||
log.Printf("[DEBUG] Updating CloudWatch Event Rule: %s", input) | ||
|
||
// IAM Roles take some time to propagate | ||
var out *events.PutRuleOutput | ||
err := resource.Retry(30*time.Second, func() error { | ||
var err error | ||
out, err = conn.PutRule(input) | ||
pattern := regexp.MustCompile("cannot be assumed by principal '[a-z]+\\.amazonaws\\.com'\\.$") | ||
if err != nil { | ||
if awsErr, ok := err.(awserr.Error); ok { | ||
if awsErr.Code() == "ValidationException" && pattern.MatchString(awsErr.Message()) { | ||
log.Printf("[DEBUG] Retrying update of CloudWatch Event Rule %q", *input.Name) | ||
return err | ||
} | ||
} | ||
return &resource.RetryError{ | ||
Err: err, | ||
} | ||
} | ||
return nil | ||
}) | ||
if err != nil { | ||
return fmt.Errorf("Updating CloudWatch Event Rule failed: %s", err) | ||
} | ||
|
||
if d.HasChange("is_enabled") && !d.Get("is_enabled").(bool) { | ||
log.Printf("[DEBUG] Disabling CloudWatch Event Rule %q", d.Id()) | ||
_, err := conn.DisableRule(&events.DisableRuleInput{ | ||
Name: aws.String(d.Id()), | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
log.Printf("[DEBUG] CloudWatch Event Rule (%q) disabled", d.Id()) | ||
} | ||
|
||
return resourceAwsCloudWatchEventRuleRead(d, meta) | ||
} | ||
|
||
func resourceAwsCloudWatchEventRuleDelete(d *schema.ResourceData, meta interface{}) error { | ||
conn := meta.(*AWSClient).cloudwatcheventsconn | ||
|
||
log.Printf("[INFO] Deleting CloudWatch Event Rule: %s", d.Id()) | ||
_, err := conn.DeleteRule(&events.DeleteRuleInput{ | ||
Name: aws.String(d.Id()), | ||
}) | ||
if err != nil { | ||
return fmt.Errorf("Error deleting CloudWatch Event Rule: %s", err) | ||
} | ||
log.Println("[INFO] CloudWatch Event Rule deleted") | ||
|
||
d.SetId("") | ||
|
||
return nil | ||
} | ||
|
||
func buildPutRuleInputStruct(d *schema.ResourceData) *events.PutRuleInput { | ||
input := events.PutRuleInput{ | ||
Name: aws.String(d.Get("name").(string)), | ||
} | ||
if v, ok := d.GetOk("description"); ok { | ||
input.Description = aws.String(v.(string)) | ||
} | ||
if v, ok := d.GetOk("event_pattern"); ok { | ||
input.EventPattern = aws.String(v.(string)) | ||
} | ||
if v, ok := d.GetOk("role_arn"); ok { | ||
input.RoleArn = aws.String(v.(string)) | ||
} | ||
if v, ok := d.GetOk("schedule_expression"); ok { | ||
input.ScheduleExpression = aws.String(v.(string)) | ||
} | ||
|
||
input.State = aws.String(getStringStateFromBoolean(d.Get("is_enabled").(bool))) | ||
|
||
return &input | ||
} | ||
|
||
// State is represented as (ENABLED|DISABLED) in the API | ||
func getBooleanStateFromString(state string) (bool, error) { | ||
if state == "ENABLED" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. are we positive we will always get back uppercase? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, it is documented like that in AWS docs: http://docs.aws.amazon.com/AmazonCloudWatchEvents/latest/APIReference/API_PutRule.html#API_PutRule_RequestSyntax but I agree that trusting the docs may not always be good idea and tracking down such bug here might not be easy with this I will add error reporting for unexpected values. |
||
return true, nil | ||
} else if state == "DISABLED" { | ||
return false, nil | ||
} | ||
// We don't just blindly trust AWS as they tend to return | ||
// unexpected values in similar cases (different casing etc.) | ||
return false, fmt.Errorf("Failed converting state %q into boolean", state) | ||
} | ||
|
||
// State is represented as (ENABLED|DISABLED) in the API | ||
func getStringStateFromBoolean(isEnabled bool) string { | ||
if isEnabled { | ||
return "ENABLED" | ||
} | ||
return "DISABLED" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love this little helper! This will be able to be used elsewhere :)