Event Source - CloudWatch Events

In this section we’ll walkthrough how to trigger your lambda function in response to different types of CloudWatch Events. This overview is based on the SpartaApplication sample code if you’d rather jump to the end result.

Goal

Assume that we’re supposed to write a simple “HelloWorld” CloudWatch event function that has two requirements:

  • Run every 5 minutes to provide a heartbeat notification to our alerting system via a logfile entry
  • Log EC2-related events for later processing

Getting Started

Our lambda function is relatively small:

func echoCloudWatchEvent(event *json.RawMessage,
                        context *sparta.LambdaContext,
                        w http.ResponseWriter,
                        logger *logrus.Logger) {

	logger.WithFields(logrus.Fields{
		"RequestID": context.AWSRequestID,
	}).Info("Request received")

	config, _ := sparta.Discover()
	logger.WithFields(logrus.Fields{
		"RequestID":     context.AWSRequestID,
		"Event":         string(*event),
		"Configuration": config,
	}).Info("Request received")
	fmt.Fprintf(w, "Hello World!")
}

Our lambda function doesn’t need to do much with the event other than log it.

Sparta Integration

With echoCloudWatchEvent() implemented, the next step is to integrate the Go function with Sparta. This is done by the appendCloudWatchEventHandler in the SpartaApplication application.go source.

Our lambda function only needs logfile write privileges, and since these are enabled by default, we can use an empty sparta.IAMRoleDefinition value:

func appendCloudWatchEventHandler(api *sparta.API,
                                  lambdaFunctions []*sparta.LambdaAWSInfo) []*sparta.LambdaAWSInfo {

  lambdaFn := sparta.NewLambda(sparta.IAMRoleDefinition{}, echoCloudWatchEvent, nil)

The next step is to add a CloudWatchEventsPermission value that includes the two rule triggers.

cloudWatchEventsPermission := sparta.CloudWatchEventsPermission{}
cloudWatchEventsPermission.Rules = make(map[string]sparta.CloudWatchEventsRule, 0)

Our two rules will be inserted into the Rules map in the next steps.

Cron Expression

Our first requirement is that the lambda function write a heartbeat to the logfile every 5 mins. This can be configured by adding a scheduled event:

cloudWatchEventsPermission.Rules["Rate5Mins"] = sparta.CloudWatchEventsRule{
  ScheduleExpression: "rate(5 minutes)",
}

The ScheduleExpression value can either be a rate or a cron expression. The map keyname is used when adding the rule during stack provisioning.

Event Pattern

The other requirement is that our lambda function be notified when matching EC2 events are created. To support this, we’ll add a second Rule:

cloudWatchEventsPermission.Rules["EC2Activity"] = sparta.CloudWatchEventsRule{
  EventPattern: map[string]interface{}{
    "source":      []string{"aws.ec2"},
    "detail-type": []string{"EC2 Instance State-change Notification"},
  },
}

The EC2 event pattern is the Go JSON-compatible representation of the event pattern that CloudWatch Events will use to trigger our lambda function. This structured value will be marshaled to a String during CloudFormation Template marshaling.

Validity Checks

Sparta does NOT attempt to validate either ScheduleExpression or EventPattern values prior to calling CloudFormation. Syntax errors in either value will be detected during provisioning when the Sparta CloudFormation CustomResource calls putRule to add the lambda target. This error will cause the CloudFormation operation to fail. Any API errors will be logged & are viewable in the CloudFormation Logs Console.

Add Permission

With the two rules configured, the final step is to add the sparta.CloudWatchPermission to our sparta.LambdaAWSInfo value:

lambdaFn.Permissions = append(lambdaFn.Permissions, cloudWatchEventsPermission)
return append(lambdaFunctions, lambdaFn)

Our entire function is therefore:

func appendCloudWatchEventHandler(api *sparta.API,
                                  lambdaFunctions []*sparta.LambdaAWSInfo) []*sparta.LambdaAWSInfo {

  lambdaFn := sparta.NewLambda(sparta.IAMRoleDefinition{}, echoCloudWatchEvent, nil)

  cloudWatchEventsPermission := sparta.CloudWatchEventsPermission{}
  cloudWatchEventsPermission.Rules = make(map[string]sparta.CloudWatchEventsRule, 0)
  cloudWatchEventsPermission.Rules["Rate5Mins"] = sparta.CloudWatchEventsRule{
    ScheduleExpression: "rate(5 minutes)",
  }
  cloudWatchEventsPermission.Rules["EC2Activity"] = sparta.CloudWatchEventsRule{
    EventPattern: map[string]interface{}{
    	"source":      []string{"aws.ec2"},
    	"detail-type": []string{"EC2 Instance State-change Notification"},
    },
  }
  lambdaFn.Permissions = append(lambdaFn.Permissions, cloudWatchEventsPermission)

  return append(lambdaFunctions, lambdaFn)
}

Wrapping Up

With the lambdaFn fully defined, we can provide it to sparta.Main() and deploy our service. The workflow below is shared by all CloudWatch Events-triggered lambda functions:

  • Define the lambda function (echoCloudWatchEvent).
  • If needed, create the required IAMRoleDefinition with appropriate privileges.
  • Provide the lambda function & IAMRoleDefinition to sparta.NewLambda()
  • Create a CloudWatchEventsPermission value.
  • Add one or more CloudWatchEventsRules to the CloudWatchEventsPermission.Rules map that define your lambda function’s trigger condition:
  • Append the CloudWatchEventsPermission value to the lambda function’s Permissions slice.
  • Include the reference in the call to sparta.Main().

Other Resources