diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index ff737bfb6..3b7bb79f6 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -122,6 +122,7 @@ func Provider() terraform.ResourceProvider { "aws_cloudformation_stack": resourceAwsCloudFormationStack(), "aws_cloudtrail": resourceAwsCloudTrail(), "aws_cloudwatch_event_rule": resourceAwsCloudWatchEventRule(), + "aws_cloudwatch_event_target": resourceAwsCloudWatchEventTarget(), "aws_cloudwatch_log_group": resourceAwsCloudWatchLogGroup(), "aws_autoscaling_lifecycle_hook": resourceAwsAutoscalingLifecycleHook(), "aws_cloudwatch_metric_alarm": resourceAwsCloudWatchMetricAlarm(), diff --git a/builtin/providers/aws/resource_aws_cloudwatch_event_target.go b/builtin/providers/aws/resource_aws_cloudwatch_event_target.go new file mode 100644 index 000000000..727a17192 --- /dev/null +++ b/builtin/providers/aws/resource_aws_cloudwatch_event_target.go @@ -0,0 +1,186 @@ +package aws + +import ( + "fmt" + "log" + "regexp" + + "github.com/hashicorp/terraform/helper/schema" + + "github.com/aws/aws-sdk-go/aws" + events "github.com/aws/aws-sdk-go/service/cloudwatchevents" +) + +func resourceAwsCloudWatchEventTarget() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsCloudWatchEventTargetCreate, + Read: resourceAwsCloudWatchEventTargetRead, + Update: resourceAwsCloudWatchEventTargetUpdate, + Delete: resourceAwsCloudWatchEventTargetDelete, + + Schema: map[string]*schema.Schema{ + "rule": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateCloudWatchEventRuleName, + }, + + "target_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateCloudWatchEventTargetId, + }, + + "arn": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "input": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"input_path"}, + // We could be normalizing the JSON here, + // but for built-in targets input may not be JSON + }, + + "input_path": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"input"}, + }, + }, + } +} + +func resourceAwsCloudWatchEventTargetCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloudwatcheventsconn + + rule := d.Get("rule").(string) + targetId := d.Get("target_id").(string) + + id := rule + "-" + targetId + d.SetId(id) + + input := buildPutTargetInputStruct(d) + log.Printf("[DEBUG] Creating CloudWatch Event Target: %s", input) + out, err := conn.PutTargets(input) + if err != nil { + return fmt.Errorf("Creating CloudWatch Event Target failed: %s", err) + } + + if len(out.FailedEntries) > 0 { + return fmt.Errorf("Creating CloudWatch Event Target failed: %s", + out.FailedEntries) + } + + log.Printf("[INFO] CloudWatch Event Target %q created", d.Id()) + + return resourceAwsCloudWatchEventTargetRead(d, meta) +} + +func resourceAwsCloudWatchEventTargetRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloudwatcheventsconn + + t, err := findEventTargetById( + d.Get("target_id").(string), + d.Get("rule").(string), + nil, conn) + if err != nil { + if regexp.MustCompile(" not found$").MatchString(err.Error()) { + log.Printf("[WARN] Removing CloudWatch Event Target %q because it's gone.", d.Id()) + d.SetId("") + return nil + } + return err + } + log.Printf("[DEBUG] Found Event Target: %s", t) + + d.Set("arn", t.Arn) + d.Set("target_id", t.Id) + d.Set("input", t.Input) + d.Set("input_path", t.InputPath) + + return nil +} + +func findEventTargetById(id, rule string, nextToken *string, conn *events.CloudWatchEvents) ( + *events.Target, error) { + input := events.ListTargetsByRuleInput{ + Rule: aws.String(rule), + NextToken: nextToken, + Limit: aws.Int64(100), // Set limit to allowed maximum to prevent API throttling + } + log.Printf("[DEBUG] Reading CloudWatch Event Target: %s", input) + out, err := conn.ListTargetsByRule(&input) + if err != nil { + return nil, err + } + + for _, t := range out.Targets { + if *t.Id == id { + return t, nil + } + } + + if out.NextToken != nil { + return findEventTargetById(id, rule, nextToken, conn) + } + + return nil, fmt.Errorf("CloudWatch Event Target %q (%q) not found", id, rule) +} + +func resourceAwsCloudWatchEventTargetUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloudwatcheventsconn + + input := buildPutTargetInputStruct(d) + log.Printf("[DEBUG] Updating CloudWatch Event Target: %s", input) + _, err := conn.PutTargets(input) + if err != nil { + return fmt.Errorf("Updating CloudWatch Event Target failed: %s", err) + } + + return resourceAwsCloudWatchEventTargetRead(d, meta) +} + +func resourceAwsCloudWatchEventTargetDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloudwatcheventsconn + + input := events.RemoveTargetsInput{ + Ids: []*string{aws.String(d.Get("target_id").(string))}, + Rule: aws.String(d.Get("rule").(string)), + } + log.Printf("[INFO] Deleting CloudWatch Event Target: %s", input) + _, err := conn.RemoveTargets(&input) + if err != nil { + return fmt.Errorf("Error deleting CloudWatch Event Target: %s", err) + } + log.Println("[INFO] CloudWatch Event Target deleted") + + d.SetId("") + + return nil +} + +func buildPutTargetInputStruct(d *schema.ResourceData) *events.PutTargetsInput { + e := &events.Target{ + Arn: aws.String(d.Get("arn").(string)), + Id: aws.String(d.Get("target_id").(string)), + } + + if v, ok := d.GetOk("input"); ok { + e.Input = aws.String(v.(string)) + } + if v, ok := d.GetOk("input_path"); ok { + e.InputPath = aws.String(v.(string)) + } + + input := events.PutTargetsInput{ + Rule: aws.String(d.Get("rule").(string)), + Targets: []*events.Target{e}, + } + + return &input +} diff --git a/builtin/providers/aws/validators.go b/builtin/providers/aws/validators.go index 84c5c5403..faa07d82d 100644 --- a/builtin/providers/aws/validators.go +++ b/builtin/providers/aws/validators.go @@ -165,3 +165,21 @@ func validateMaxLength(length int) schema.SchemaValidateFunc { return } } + +func validateCloudWatchEventTargetId(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if len(value) > 64 { + errors = append(errors, fmt.Errorf( + "%q cannot be longer than 64 characters: %q", k, value)) + } + + // http://docs.aws.amazon.com/AmazonCloudWatchEvents/latest/APIReference/API_Target.html + pattern := `^[\.\-_A-Za-z0-9]+$` + if !regexp.MustCompile(pattern).MatchString(value) { + errors = append(errors, fmt.Errorf( + "%q doesn't comply with restrictions (%q): %q", + k, pattern, value)) + } + + return +}