Merge pull request #4986 from TimeIncOSS/f-aws-cloudwatch-events

provider/aws: Add support for CloudWatch Events
This commit is contained in:
Radek Simko 2016-02-13 13:29:50 +00:00
commit 0e66f67246
23 changed files with 2531 additions and 29 deletions

5
Godeps/Godeps.json generated
View File

@ -262,6 +262,11 @@
"Comment": "v1.1.0",
"Rev": "be2ec39e520e3c4088c0c6288055bdc8184a89ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/cloudwatchevents",
"Comment": "v1.1.0",
"Rev": "be2ec39e520e3c4088c0c6288055bdc8184a89ee"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/cloudwatchlogs",
"Comment": "v1.1.0",

View File

@ -23,6 +23,7 @@ import (
"github.com/aws/aws-sdk-go/service/cloudformation"
"github.com/aws/aws-sdk-go/service/cloudtrail"
"github.com/aws/aws-sdk-go/service/cloudwatch"
"github.com/aws/aws-sdk-go/service/cloudwatchevents"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/aws/aws-sdk-go/service/codecommit"
"github.com/aws/aws-sdk-go/service/codedeploy"
@ -74,6 +75,7 @@ type AWSClient struct {
cloudtrailconn *cloudtrail.CloudTrail
cloudwatchconn *cloudwatch.CloudWatch
cloudwatchlogsconn *cloudwatchlogs.CloudWatchLogs
cloudwatcheventsconn *cloudwatchevents.CloudWatchEvents
dsconn *directoryservice.DirectoryService
dynamodbconn *dynamodb.DynamoDB
ec2conn *ec2.EC2
@ -256,6 +258,9 @@ func (c *Config) Client() (interface{}, error) {
log.Println("[INFO] Initializing CloudWatch SDK connection")
client.cloudwatchconn = cloudwatch.New(sess)
log.Println("[INFO] Initializing CloudWatch Events connection")
client.cloudwatcheventsconn = cloudwatchevents.New(sess)
log.Println("[INFO] Initializing CloudTrail connection")
client.cloudtrailconn = cloudtrail.New(sess)

View File

@ -121,6 +121,8 @@ func Provider() terraform.ResourceProvider {
"aws_autoscaling_schedule": resourceAwsAutoscalingSchedule(),
"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(),

View File

@ -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" {
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"
}

View File

@ -0,0 +1,208 @@
package aws
import (
"fmt"
"testing"
"github.com/aws/aws-sdk-go/aws"
events "github.com/aws/aws-sdk-go/service/cloudwatchevents"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAWSCloudWatchEventRule_basic(t *testing.T) {
var rule events.DescribeRuleOutput
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudWatchEventRuleDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSCloudWatchEventRuleConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudWatchEventRuleExists("aws_cloudwatch_event_rule.foo", &rule),
resource.TestCheckResourceAttr("aws_cloudwatch_event_rule.foo", "name", "tf-acc-cw-event-rule"),
),
},
resource.TestStep{
Config: testAccAWSCloudWatchEventRuleConfigModified,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudWatchEventRuleExists("aws_cloudwatch_event_rule.foo", &rule),
resource.TestCheckResourceAttr("aws_cloudwatch_event_rule.foo", "name", "tf-acc-cw-event-rule-mod"),
),
},
},
})
}
func TestAccAWSCloudWatchEventRule_full(t *testing.T) {
var rule events.DescribeRuleOutput
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudWatchEventRuleDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSCloudWatchEventRuleConfig_full,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudWatchEventRuleExists("aws_cloudwatch_event_rule.moobar", &rule),
resource.TestCheckResourceAttr("aws_cloudwatch_event_rule.moobar", "name", "tf-acc-cw-event-rule-full"),
resource.TestCheckResourceAttr("aws_cloudwatch_event_rule.moobar", "schedule_expression", "rate(5 minutes)"),
resource.TestCheckResourceAttr("aws_cloudwatch_event_rule.moobar", "event_pattern", "{\"source\":[\"aws.ec2\"]}"),
resource.TestCheckResourceAttr("aws_cloudwatch_event_rule.moobar", "description", "He's not dead, he's just resting!"),
resource.TestCheckResourceAttr("aws_cloudwatch_event_rule.moobar", "role_arn", ""),
testAccCheckCloudWatchEventRuleEnabled("aws_cloudwatch_event_rule.moobar", "DISABLED", &rule),
),
},
},
})
}
func TestAccAWSCloudWatchEventRule_enable(t *testing.T) {
var rule events.DescribeRuleOutput
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudWatchEventRuleDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSCloudWatchEventRuleConfigEnabled,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudWatchEventRuleExists("aws_cloudwatch_event_rule.moo", &rule),
testAccCheckCloudWatchEventRuleEnabled("aws_cloudwatch_event_rule.moo", "ENABLED", &rule),
),
},
resource.TestStep{
Config: testAccAWSCloudWatchEventRuleConfigDisabled,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudWatchEventRuleExists("aws_cloudwatch_event_rule.moo", &rule),
testAccCheckCloudWatchEventRuleEnabled("aws_cloudwatch_event_rule.moo", "DISABLED", &rule),
),
},
resource.TestStep{
Config: testAccAWSCloudWatchEventRuleConfigEnabled,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudWatchEventRuleExists("aws_cloudwatch_event_rule.moo", &rule),
testAccCheckCloudWatchEventRuleEnabled("aws_cloudwatch_event_rule.moo", "ENABLED", &rule),
),
},
},
})
}
func testAccCheckCloudWatchEventRuleExists(n string, rule *events.DescribeRuleOutput) 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).cloudwatcheventsconn
params := events.DescribeRuleInput{
Name: aws.String(rs.Primary.ID),
}
resp, err := conn.DescribeRule(&params)
if err != nil {
return err
}
if resp == nil {
return fmt.Errorf("Rule not found")
}
*rule = *resp
return nil
}
}
func testAccCheckCloudWatchEventRuleEnabled(n string, desired string, rule *events.DescribeRuleOutput) 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).cloudwatcheventsconn
params := events.DescribeRuleInput{
Name: aws.String(rs.Primary.ID),
}
resp, err := conn.DescribeRule(&params)
if err != nil {
return err
}
if *resp.State != desired {
return fmt.Errorf("Expected state %q, given %q", desired, *resp.State)
}
return nil
}
}
func testAccCheckAWSCloudWatchEventRuleDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).cloudwatcheventsconn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_cloudwatch_event_rule" {
continue
}
params := events.DescribeRuleInput{
Name: aws.String(rs.Primary.ID),
}
resp, err := conn.DescribeRule(&params)
if err == nil {
return fmt.Errorf("CloudWatch Event Rule %q still exists: %s",
rs.Primary.ID, resp)
}
}
return nil
}
var testAccAWSCloudWatchEventRuleConfig = `
resource "aws_cloudwatch_event_rule" "foo" {
name = "tf-acc-cw-event-rule"
schedule_expression = "rate(1 hour)"
}
`
var testAccAWSCloudWatchEventRuleConfigEnabled = `
resource "aws_cloudwatch_event_rule" "moo" {
name = "tf-acc-cw-event-rule-state"
schedule_expression = "rate(1 hour)"
}
`
var testAccAWSCloudWatchEventRuleConfigDisabled = `
resource "aws_cloudwatch_event_rule" "moo" {
name = "tf-acc-cw-event-rule-state"
schedule_expression = "rate(1 hour)"
is_enabled = false
}
`
var testAccAWSCloudWatchEventRuleConfigModified = `
resource "aws_cloudwatch_event_rule" "foo" {
name = "tf-acc-cw-event-rule-mod"
schedule_expression = "rate(1 hour)"
}
`
var testAccAWSCloudWatchEventRuleConfig_full = `
resource "aws_cloudwatch_event_rule" "moobar" {
name = "tf-acc-cw-event-rule-full"
schedule_expression = "rate(5 minutes)"
event_pattern = <<PATTERN
{ "source": ["aws.ec2"] }
PATTERN
description = "He's not dead, he's just resting!"
is_enabled = false
}
`
// TODO: Figure out example with IAM Role

View File

@ -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
}

View File

@ -0,0 +1,203 @@
package aws
import (
"fmt"
"regexp"
"testing"
events "github.com/aws/aws-sdk-go/service/cloudwatchevents"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAWSCloudWatchEventTarget_basic(t *testing.T) {
var target events.Target
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudWatchEventTargetDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSCloudWatchEventTargetConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudWatchEventTargetExists("aws_cloudwatch_event_target.moobar", &target),
resource.TestCheckResourceAttr("aws_cloudwatch_event_target.moobar", "rule", "tf-acc-cw-event-rule-basic"),
resource.TestCheckResourceAttr("aws_cloudwatch_event_target.moobar", "target_id", "tf-acc-cw-target-basic"),
resource.TestMatchResourceAttr("aws_cloudwatch_event_target.moobar", "arn",
regexp.MustCompile(":tf-acc-moon$")),
),
},
resource.TestStep{
Config: testAccAWSCloudWatchEventTargetConfigModified,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudWatchEventTargetExists("aws_cloudwatch_event_target.moobar", &target),
resource.TestCheckResourceAttr("aws_cloudwatch_event_target.moobar", "rule", "tf-acc-cw-event-rule-basic"),
resource.TestCheckResourceAttr("aws_cloudwatch_event_target.moobar", "target_id", "tf-acc-cw-target-modified"),
resource.TestMatchResourceAttr("aws_cloudwatch_event_target.moobar", "arn",
regexp.MustCompile(":tf-acc-sun$")),
),
},
},
})
}
func TestAccAWSCloudWatchEventTarget_full(t *testing.T) {
var target events.Target
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudWatchEventTargetDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSCloudWatchEventTargetConfig_full,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudWatchEventTargetExists("aws_cloudwatch_event_target.foobar", &target),
resource.TestCheckResourceAttr("aws_cloudwatch_event_target.foobar", "rule", "tf-acc-cw-event-rule-full"),
resource.TestCheckResourceAttr("aws_cloudwatch_event_target.foobar", "target_id", "tf-acc-cw-target-full"),
resource.TestMatchResourceAttr("aws_cloudwatch_event_target.foobar", "arn",
regexp.MustCompile("^arn:aws:kinesis:.*:stream/terraform-kinesis-test$")),
resource.TestCheckResourceAttr("aws_cloudwatch_event_target.foobar", "input", "{ \"source\": [\"aws.cloudtrail\"] }\n"),
resource.TestCheckResourceAttr("aws_cloudwatch_event_target.foobar", "input_path", ""),
),
},
},
})
}
func testAccCheckCloudWatchEventTargetExists(n string, rule *events.Target) 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).cloudwatcheventsconn
t, err := findEventTargetById(rs.Primary.Attributes["target_id"],
rs.Primary.Attributes["rule"], nil, conn)
if err != nil {
return fmt.Errorf("Event Target not found: %s", err)
}
*rule = *t
return nil
}
}
func testAccCheckAWSCloudWatchEventTargetDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).cloudwatcheventsconn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_cloudwatch_event_target" {
continue
}
t, err := findEventTargetById(rs.Primary.Attributes["target_id"],
rs.Primary.Attributes["rule"], nil, conn)
if err == nil {
return fmt.Errorf("CloudWatch Event Target %q still exists: %s",
rs.Primary.ID, t)
}
}
return nil
}
var testAccAWSCloudWatchEventTargetConfig = `
resource "aws_cloudwatch_event_rule" "foo" {
name = "tf-acc-cw-event-rule-basic"
schedule_expression = "rate(1 hour)"
}
resource "aws_cloudwatch_event_target" "moobar" {
rule = "${aws_cloudwatch_event_rule.foo.name}"
target_id = "tf-acc-cw-target-basic"
arn = "${aws_sns_topic.moon.arn}"
}
resource "aws_sns_topic" "moon" {
name = "tf-acc-moon"
}
`
var testAccAWSCloudWatchEventTargetConfigModified = `
resource "aws_cloudwatch_event_rule" "foo" {
name = "tf-acc-cw-event-rule-basic"
schedule_expression = "rate(1 hour)"
}
resource "aws_cloudwatch_event_target" "moobar" {
rule = "${aws_cloudwatch_event_rule.foo.name}"
target_id = "tf-acc-cw-target-modified"
arn = "${aws_sns_topic.sun.arn}"
}
resource "aws_sns_topic" "sun" {
name = "tf-acc-sun"
}
`
var testAccAWSCloudWatchEventTargetConfig_full = `
resource "aws_cloudwatch_event_rule" "foo" {
name = "tf-acc-cw-event-rule-full"
schedule_expression = "rate(1 hour)"
role_arn = "${aws_iam_role.role.arn}"
}
resource "aws_iam_role" "role" {
name = "test_role"
assume_role_policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "events.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
POLICY
}
resource "aws_iam_role_policy" "test_policy" {
name = "test_policy"
role = "${aws_iam_role.role.id}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"kinesis:PutRecord",
"kinesis:PutRecords"
],
"Resource": [
"*"
],
"Effect": "Allow"
}
]
}
EOF
}
resource "aws_cloudwatch_event_target" "foobar" {
rule = "${aws_cloudwatch_event_rule.foo.name}"
target_id = "tf-acc-cw-target-full"
input = <<INPUT
{ "source": ["aws.cloudtrail"] }
INPUT
arn = "${aws_kinesis_stream.test_stream.arn}"
}
resource "aws_kinesis_stream" "test_stream" {
name = "terraform-kinesis-test"
shard_count = 1
}
`

View File

@ -4,6 +4,8 @@ import (
"fmt"
"regexp"
"time"
"github.com/hashicorp/terraform/helper/schema"
)
func validateRdsId(v interface{}, k string) (ws []string, errors []error) {
@ -134,3 +136,50 @@ func validateEcrRepositoryName(v interface{}, k string) (ws []string, errors []e
return
}
func validateCloudWatchEventRuleName(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_PutRule.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
}
func validateMaxLength(length int) schema.SchemaValidateFunc {
return func(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if len(value) > length {
errors = append(errors, fmt.Errorf(
"%q cannot be longer than %d characters: %q", k, length, value))
}
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
}

View File

@ -43,3 +43,30 @@ func TestValidateEcrRepositoryName(t *testing.T) {
}
}
}
func TestValidateCloudWatchEventRuleName(t *testing.T) {
validNames := []string{
"HelloWorl_d",
"hello-world",
"hello.World0125",
}
for _, v := range validNames {
_, errors := validateCloudWatchEventRuleName(v, "name")
if len(errors) != 0 {
t.Fatalf("%q should be a valid CW event rule name: %q", v, errors)
}
}
invalidNames := []string{
"special@character",
"slash/in-the-middle",
// Length > 64
"TooLooooooooooooooooooooooooooooooooooooooooooooooooooooooongName",
}
for _, v := range invalidNames {
_, errors := validateCloudWatchEventRuleName(v, "name")
if len(errors) == 0 {
t.Fatalf("%q should be an invalid CW event rule name", v)
}
}
}

View File

@ -0,0 +1,14 @@
# CloudWatch Event sent to Kinesis Stream
This example sets up a CloudWatch Event Rule with a Target and IAM Role & Policy
to send all autoscaling events into Kinesis stream for further examination.
See more details about [CloudWatch Events](http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/WhatIsCloudWatchEvents.html)
in the official AWS docs.
## How to run the example
```
terraform apply \
-var=aws_region=us-west-2
```

View File

@ -0,0 +1,72 @@
provider "aws" {
region = "${var.aws_region}"
}
resource "aws_cloudwatch_event_rule" "foo" {
name = "${var.rule_name}"
event_pattern = <<PATTERN
{
"detail-type": [
"AWS API Call via CloudTrail"
],
"detail": {
"eventSource": [
"autoscaling.amazonaws.com"
]
}
}
PATTERN
role_arn = "${aws_iam_role.role.arn}"
}
resource "aws_iam_role" "role" {
name = "${var.iam_role_name}"
assume_role_policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "events.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
POLICY
}
resource "aws_iam_role_policy" "policy" {
name = "tf-example-policy"
role = "${aws_iam_role.role.id}"
policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"kinesis:PutRecord",
"kinesis:PutRecords"
],
"Resource": [
"*"
],
"Effect": "Allow"
}
]
}
POLICY
}
resource "aws_cloudwatch_event_target" "foobar" {
rule = "${aws_cloudwatch_event_rule.foo.name}"
target_id = "${var.target_name}"
arn = "${aws_kinesis_stream.foo.arn}"
}
resource "aws_kinesis_stream" "foo" {
name = "${var.stream_name}"
shard_count = 1
}

View File

@ -0,0 +1,7 @@
output "rule_arn" {
value = "${aws_cloudwatch_event_rule.foo.arn}"
}
output "kinesis_stream_arn" {
value = "${aws_kinesis_stream.foo.arn}"
}

View File

@ -0,0 +1,24 @@
variable "aws_region" {
description = "The AWS region to create resources in."
default = "us-east-1"
}
variable "rule_name" {
description = "The name of the CloudWatch Event Rule"
default = "tf-example-cloudwatch-event-rule-for-kinesis"
}
variable "iam_role_name" {
description = "The name of the IAM Role"
default = "tf-example-iam-role-for-kinesis"
}
variable "target_name" {
description = "The name of the CloudWatch Event Target"
default = "tf-example-cloudwatch-event-target-for-kinesis"
}
variable "stream_name" {
description = "The name of the Kinesis Stream to send events to"
default = "tf-example-kinesis-stream"
}

View File

@ -0,0 +1,15 @@
# CloudWatch Event sent to SNS Topic
This example sets up a CloudWatch Event Rule with a Target and SNS Topic
to send any CloudTrail API operation into that SNS topic. This allows you
to add SNS subscriptions which may notify you about suspicious activity.
See more details about [CloudWatch Events](http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/WhatIsCloudWatchEvents.html)
in the official AWS docs.
## How to run the example
```
terraform apply \
-var=aws_region=us-west-2
```

View File

@ -0,0 +1,29 @@
provider "aws" {
region = "${var.aws_region}"
}
resource "aws_cloudwatch_event_rule" "foo" {
name = "${var.rule_name}"
event_pattern = <<PATTERN
{
"detail-type": [
"AWS API Call via CloudTrail"
],
"detail": {
"eventSource": [
"cloudtrail.amazonaws.com"
]
}
}
PATTERN
}
resource "aws_cloudwatch_event_target" "bar" {
rule = "${aws_cloudwatch_event_rule.foo.name}"
target_id = "${var.target_name}"
arn = "${aws_sns_topic.foo.arn}"
}
resource "aws_sns_topic" "foo" {
name = "${var.sns_topic_name}"
}

View File

@ -0,0 +1,7 @@
output "rule_arn" {
value = "${aws_cloudwatch_event_rule.foo.arn}"
}
output "sns_topic_arn" {
value = "${aws_sns_topic.foo.arn}"
}

View File

@ -0,0 +1,19 @@
variable "aws_region" {
description = "The AWS region to create resources in."
default = "us-east-1"
}
variable "rule_name" {
description = "The name of the CloudWatch Event Rule"
default = "tf-example-cloudwatch-event-rule-for-sns"
}
variable "target_name" {
description = "The name of the CloudWatch Event Target"
default = "tf-example-cloudwatch-event-target-for-sns"
}
variable "sns_topic_name" {
description = "The name of the SNS Topic to send events to"
default = "tf-example-sns-topic"
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,62 @@
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
// Package cloudwatcheventsiface provides an interface for the Amazon CloudWatch Events.
package cloudwatcheventsiface
import (
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/cloudwatchevents"
)
// CloudWatchEventsAPI is the interface type for cloudwatchevents.CloudWatchEvents.
type CloudWatchEventsAPI interface {
DeleteRuleRequest(*cloudwatchevents.DeleteRuleInput) (*request.Request, *cloudwatchevents.DeleteRuleOutput)
DeleteRule(*cloudwatchevents.DeleteRuleInput) (*cloudwatchevents.DeleteRuleOutput, error)
DescribeRuleRequest(*cloudwatchevents.DescribeRuleInput) (*request.Request, *cloudwatchevents.DescribeRuleOutput)
DescribeRule(*cloudwatchevents.DescribeRuleInput) (*cloudwatchevents.DescribeRuleOutput, error)
DisableRuleRequest(*cloudwatchevents.DisableRuleInput) (*request.Request, *cloudwatchevents.DisableRuleOutput)
DisableRule(*cloudwatchevents.DisableRuleInput) (*cloudwatchevents.DisableRuleOutput, error)
EnableRuleRequest(*cloudwatchevents.EnableRuleInput) (*request.Request, *cloudwatchevents.EnableRuleOutput)
EnableRule(*cloudwatchevents.EnableRuleInput) (*cloudwatchevents.EnableRuleOutput, error)
ListRuleNamesByTargetRequest(*cloudwatchevents.ListRuleNamesByTargetInput) (*request.Request, *cloudwatchevents.ListRuleNamesByTargetOutput)
ListRuleNamesByTarget(*cloudwatchevents.ListRuleNamesByTargetInput) (*cloudwatchevents.ListRuleNamesByTargetOutput, error)
ListRulesRequest(*cloudwatchevents.ListRulesInput) (*request.Request, *cloudwatchevents.ListRulesOutput)
ListRules(*cloudwatchevents.ListRulesInput) (*cloudwatchevents.ListRulesOutput, error)
ListTargetsByRuleRequest(*cloudwatchevents.ListTargetsByRuleInput) (*request.Request, *cloudwatchevents.ListTargetsByRuleOutput)
ListTargetsByRule(*cloudwatchevents.ListTargetsByRuleInput) (*cloudwatchevents.ListTargetsByRuleOutput, error)
PutEventsRequest(*cloudwatchevents.PutEventsInput) (*request.Request, *cloudwatchevents.PutEventsOutput)
PutEvents(*cloudwatchevents.PutEventsInput) (*cloudwatchevents.PutEventsOutput, error)
PutRuleRequest(*cloudwatchevents.PutRuleInput) (*request.Request, *cloudwatchevents.PutRuleOutput)
PutRule(*cloudwatchevents.PutRuleInput) (*cloudwatchevents.PutRuleOutput, error)
PutTargetsRequest(*cloudwatchevents.PutTargetsInput) (*request.Request, *cloudwatchevents.PutTargetsOutput)
PutTargets(*cloudwatchevents.PutTargetsInput) (*cloudwatchevents.PutTargetsOutput, error)
RemoveTargetsRequest(*cloudwatchevents.RemoveTargetsInput) (*request.Request, *cloudwatchevents.RemoveTargetsOutput)
RemoveTargets(*cloudwatchevents.RemoveTargetsInput) (*cloudwatchevents.RemoveTargetsOutput, error)
TestEventPatternRequest(*cloudwatchevents.TestEventPatternInput) (*request.Request, *cloudwatchevents.TestEventPatternOutput)
TestEventPattern(*cloudwatchevents.TestEventPatternInput) (*cloudwatchevents.TestEventPatternOutput, error)
}
var _ CloudWatchEventsAPI = (*cloudwatchevents.CloudWatchEvents)(nil)

View File

@ -0,0 +1,101 @@
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
package cloudwatchevents
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/client/metadata"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/protocol/jsonrpc"
"github.com/aws/aws-sdk-go/private/signer/v4"
)
// Amazon CloudWatch Events helps you to respond to state changes in your AWS
// resources. When your resources change state they automatically send events
// into an event stream. You can create rules that match selected events in
// the stream and route them to targets to take action. You can also use rules
// to take action on a pre-determined schedule. For example, you can configure
// rules to:
//
// Automatically invoke an AWS Lambda function to update DNS entries when
// an event notifies you that Amazon EC2 instance enters the running state.
// Direct specific API records from CloudTrail to an Amazon Kinesis stream for
// detailed analysis of potential security or availability risks. Periodically
// invoke a built-in target to create a snapshot of an Amazon EBS volume.
// For more information about Amazon CloudWatch Events features, see the Amazon
// CloudWatch Developer Guide (http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide).
//The service client's operations are safe to be used concurrently.
// It is not safe to mutate any of the client's properties though.
type CloudWatchEvents struct {
*client.Client
}
// Used for custom client initialization logic
var initClient func(*client.Client)
// Used for custom request initialization logic
var initRequest func(*request.Request)
// A ServiceName is the name of the service the client will make API calls to.
const ServiceName = "events"
// New creates a new instance of the CloudWatchEvents client with a session.
// If additional configuration is needed for the client instance use the optional
// aws.Config parameter to add your extra config.
//
// Example:
// // Create a CloudWatchEvents client from just a session.
// svc := cloudwatchevents.New(mySession)
//
// // Create a CloudWatchEvents client with additional configuration
// svc := cloudwatchevents.New(mySession, aws.NewConfig().WithRegion("us-west-2"))
func New(p client.ConfigProvider, cfgs ...*aws.Config) *CloudWatchEvents {
c := p.ClientConfig(ServiceName, cfgs...)
return newClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion)
}
// newClient creates, initializes and returns a new service client instance.
func newClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegion string) *CloudWatchEvents {
svc := &CloudWatchEvents{
Client: client.New(
cfg,
metadata.ClientInfo{
ServiceName: ServiceName,
SigningRegion: signingRegion,
Endpoint: endpoint,
APIVersion: "2014-02-03",
JSONVersion: "1.1",
TargetPrefix: "AWSEvents",
},
handlers,
),
}
// Handlers
svc.Handlers.Sign.PushBack(v4.Sign)
svc.Handlers.Build.PushBack(jsonrpc.Build)
svc.Handlers.Unmarshal.PushBack(jsonrpc.Unmarshal)
svc.Handlers.UnmarshalMeta.PushBack(jsonrpc.UnmarshalMeta)
svc.Handlers.UnmarshalError.PushBack(jsonrpc.UnmarshalError)
// Run custom client initialization if present
if initClient != nil {
initClient(svc.Client)
}
return svc
}
// newRequest creates a new request for a CloudWatchEvents operation and runs any
// custom request initialization.
func (c *CloudWatchEvents) newRequest(op *request.Operation, params, data interface{}) *request.Request {
req := c.NewRequest(op, params, data)
// Run custom request initialization if present
if initRequest != nil {
initRequest(req)
}
return req
}

View File

@ -0,0 +1,57 @@
---
layout: "aws"
page_title: "AWS: aws_cloudwatch_event_rule"
sidebar_current: "docs-aws-resource-cloudwatch-event-rule"
description: |-
Provides a CloudWatch Event Rule resource.
---
# aws\_cloudwatch\_event\_rule
Provides a CloudWatch Event Rule resource.
## Example Usage
```
resource "aws_cloudwatch_event_rule" "console" {
name = "capture-aws-sign-in"
description = "Capture each AWS Console Sign In"
event_pattern = <<PATTERN
{
"detail-type": [
"AWS Console Sign In via CloudTrail"
]
}
PATTERN
}
resource "aws_cloudwatch_event_target" "sns" {
rule = "${aws_cloudwatch_event_rule.console.name}"
target_id = "SendToSNS"
arn = "${aws_sns_topic.aws_logins.arn}"
}
resource "aws_sns_topic" "aws_logins" {
name = "aws-console-logins"
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The rule's name.
* `schedule_expression` - (Required, if `event_pattern` isn't specified) The scheduling expression.
For example, `cron(0 20 * * ? *)` or `rate(5 minutes)`.
* `event_pattern` - (Required, if `schedule_expression` isn't specified) Event pattern
described a JSON object.
See full documentation of [CloudWatch Events and Event Patterns](http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CloudWatchEventsandEventPatterns.html) for details.
* `description` - (Optional) The description of the rule.
* `role_arn` - (Optional) The Amazon Resource Name (ARN) associated with the role that is used for target invocation.
* `is_enabled` - (Optional) Whether the rule should be enabled (defaults to `true`).
## Attributes Reference
The following attributes are exported:
* `arn` - The Amazon Resource Name (ARN) of the rule.

View File

@ -0,0 +1,57 @@
---
layout: "aws"
page_title: "AWS: aws_cloudwatch_event_target"
sidebar_current: "docs-aws-resource-cloudwatch-event-target"
description: |-
Provides a CloudWatch Event Target resource.
---
# aws\_cloudwatch\_event\_target
Provides a CloudWatch Event Target resource.
## Example Usage
```
resource "aws_cloudwatch_event_target" "yada" {
target_id = "Yada"
rule = "${aws_cloudwatch_event_rule.console.arn}"
arn = "${aws_kinesis_stream.test_stream.arn}"
}
resource "aws_cloudwatch_event_rule" "console" {
name = "capture-ec2-scaling-events"
description = "Capture all EC2 scaling events"
event_pattern = <<PATTERN
{
"source": [
"aws.autoscaling"
],
"detail-type": [
"EC2 Instance Launch Successful",
"EC2 Instance Terminate Successful",
"EC2 Instance Launch Unsuccessful",
"EC2 Instance Terminate Unsuccessful"
]
}
PATTERN
}
resource "aws_kinesis_stream" "test_stream" {
name = "terraform-kinesis-test"
shard_count = 1
}
```
## Argument Reference
-> **Note:** `input` and `input_path` are mutually exclusive options.
The following arguments are supported:
* `rule` - (Required) The name of the rule you want to add targets to.
* `target_id` - (Required) The unique target assignment ID.
* `arn` - (Required) The Amazon Resource Name (ARN) associated of the target.
* `input` - (Optional) Valid JSON text passed to the target.
* `input_path` - (Optional) The value of the [JSONPath](http://goessner.net/articles/JsonPath/)
that is used for extracting part of the matched event when passing it to the target.

View File

@ -31,6 +31,13 @@
<li<%= sidebar_current(/^docs-aws-resource-cloudwatch/) %>>
<a href="#">CloudWatch Resources</a>
<ul class="nav nav-visible">
<li<%= sidebar_current("docs-aws-resource-cloudwatch-event-rule") %>>
<a href="/docs/providers/aws/r/cloudwatch_event_rule.html">aws_cloudwatch_event_rule</a>
</li>
<li<%= sidebar_current("docs-aws-resource-cloudwatch-event-target") %>>
<a href="/docs/providers/aws/r/cloudwatch_event_target.html">aws_cloudwatch_event_target</a>
</li>
<li<%= sidebar_current("docs-aws-resource-cloudwatch-log-group") %>>
<a href="/docs/providers/aws/r/cloudwatch_log_group.html">aws_cloudwatch_log_group</a>