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

Is there a cloudwatch events example. #51

Closed
btsteve opened this issue Mar 1, 2018 · 11 comments · Fixed by #296
Closed

Is there a cloudwatch events example. #51

btsteve opened this issue Mar 1, 2018 · 11 comments · Fixed by #296
Labels

Comments

@btsteve
Copy link

btsteve commented Mar 1, 2018

I see the cloudwatch_events.go file in the repo but there is no example for this. I have tried using the type but the import does not seem to work. My problem seems to be getting the details of the message.

Thanks, I am new to go so it could be my problem, but I think I am using thing correctly.
My use case is I am trying to use a cloudwatch_event to trigger my lambda.

@asuresh8
Copy link

asuresh8 commented Mar 6, 2018

The details are stored in a json.RawMessage type. See https://golang.org/pkg/encoding/json/#RawMessage for more details about that type

From the documentation it looks like json.RawMessage is really just byte[] so you should be able to use it just like:

json.Unmarshal(some_json_raw_message, &some_struct)

Let me know if that helps

@darakian
Copy link

darakian commented Mar 25, 2019

I would also like some docs on this. I simply want to export the event as json. In particular is there a way to get the CloudWatch event into a byte[]?

@darakian
Copy link

darakian commented Mar 29, 2019

@btsteve Here's my example decode of the CloudWatch Event that I got working after some struggling

func handler(event events.CloudWatchEvent) (s string, err error){
	some_url := "https://something.com"
	payload := []byte(`{"category": "AWS CloudWatchEvent", "summary": "AWS event parsing in aws* fields" `+
        `, "details": {`+
        `"aws-version":`+ "\""+event.Version+"\""+
        `, "aws-id":`+"\""+event.ID+"\""+
        `, "aws-detail-type":`+"\""+event.DetailType+"\""+
        `, "aws-source":`+"\""+event.Source+"\""+
        `, "aws-account":`+"\""+event.AccountID+"\""+
        `, "aws-time":`+"\""+event.Time.String()+"\""+
        `, "aws-region":`+"\""+event.Region+"\""+
        `, "aws-resources":`+"\""+strings.Join(event.Resources, " ")+"\""+
        `, "aws-message":`+string(event.Detail)+
        `}}`)
	req, err := http.Post(some_url, "application/json", bytes.NewBuffer(payload))
	return fmt.Sprintf("%s", req), err
}

Works for getting CloudWatch events out to a generic http endpoint.

@xiy
Copy link

xiy commented Apr 1, 2019

I've been grappling with there being almost zero docs on this too, so here's an example of unmarshaling the Detail section of an RDS Snapshot event in CloudWatch:

// CloudWatchRdsSnapshotEventDetail represnts an AWS CloudWatch event triggered
// by a new RDS snapshot being created.
type CloudWatchRdsSnapshotEventDetail struct {
	// "EventCategories": [
	// 	"creation"
	// ],
	// "SourceType": "SNAPSHOT",
	// "SourceArn": "arn:aws:rds:us-east-1:123456789012:db:rds:mysql-instance-2018-10-06-12-24",
	// "Date": "2018-10-06T12:26:13.882Z",
	// "SourceIdentifier": "rds:mysql-instance-2018-10-06-12-24",
	// "Message": "Automated snapshot created"
	EventCategories  []string `json:"EventCategories`
	SourceType       string   `json:"SourceType"`
	SourceArn        string   `json:"SourceArn"`
	Date             string   `json:"Date"`
	SourceIdentifier string   `json:"SourceIdentifier"`
	Message          string   `json:"Message"`
}

You can use it like this:

var snapshotEventDetail CloudWatchRdsSnapshotEventDetail
json.Unmarshal(snapshotCloudwatchEvent.Detail, &snapshotEventDetail)

...where snapshotCloudwatchEvent is a github.com/aws/aws-lambda-go/events/CloudWatchEvent.

There's nothing crazy happening here, it's just standard Go JSON unmarshalling into a struct type. It looks like you'll need to create your own structs for messages for now, as far as I can see anyway. Hope this helps.

@patalwell
Copy link

patalwell commented Jun 12, 2019

Hey guys! I'm in the process of converting my python over to Go and was struggling with this a bit myself :( Considering the CloudWatchEvent.Details field doesn't cover all of the events one could possibly handle/ unmarshall, you may want to consider using a map[string]interface{} to unmarshall the event details. This makes it easy to capture the elements from the resulting map that you may need as a local var. Here's my example for capturing details for an AWS API Event Via Cloud Trail and displaying them to STDOUT.

P.S @xiy has the best method for unmarshalling structured json, but some of these events are huge and change based on certain parameters, ergo my current usage of type map. Maybe there is a better way?

You'd also have to be mindful of the types you are pulling from the map, so the nested events could be of type Struct. This is a helpful tool:https://mholt.github.io/json-to-go/

Update: I just wound up making a custom struct for the elements I needed. Here is my updated code:

And I totally agree, having more concrete examples would help the newbies like myself 👍

/**
This function listens for a cloudWatch event that filters for AWS API calls via
CloudTrail. Moreover, it appends a series of reference vars and instance IDs to a dictionary in order to tag ec2
instances with a predefined set of key pair values.

Note: In order to attain the proper event level details you'll need to log the
event level metadata to the cloudwatch logs and capture the json for testing
purposes. e.g.

You'll also need to build your goPackage and compress the executable in the working package directory

This command will produce a binary file called "main" -> GOOS=linux GOARCH=amd64 go build -o main autoTagEc2Instances.go

Hey guys, was struggling with this a bit as well considering the CloudWatchEvent Struct type doesn't cover all of the events one could possibly handle/ unmarshall via lambda. While @xiy has the best method here, you may want to consider using a map of type map[string]interface{} to unmarshall the details. This also makes it easy to capture the elements from the resulting map that you may need as a local var. Here's my example for capturing details for an AWS API Event Via Cloud Trail:

To check the event level from cloudWatch logs navigate to cloudWatch, Logs,
the appropriate log group, e.g./aws/lambda/EC2-AutoTag-EC2..., and the verison
of the lambda function you are testing
*/
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
	"github.com/patalwell/awsLambdaGoLangAutoTagEc2/CloudTrailApiEvent"
	"log"
)

/*

Sample Event Type we need to unmarshall

type CloudWatchEvent struct {
	Version    string          `json:"version"`
	ID         string          `json:"id"`
	DetailType string          `json:"detail-type"`
	Source     string          `json:"source"`
	AccountID  string          `json:"account"`
	Time       time.Time       `json:"time"`
	Region     string          `json:"region"`
	Resources  []string        `json:"resources"`
	Detail     json.RawMessage `json:"detail"`
}
 */

 var eventDetail map[string]interface{}

 var eventDetails CustomAwsLambdaEvent.CloudWatchEventDetails


//HandleRequest is a required function for Lambda and GoLang
func HandleRequest(ctx context.Context, jsonEvent events.CloudWatchEvent ) {


	//Unmarshall the CloudWatchEvent Struct Details
	err := json.Unmarshal(jsonEvent.Detail, &eventDetails)
	if err != nil {
		log.Fatal("Could not unmarshal scheduled event: ", err)
		fmt.Println("Could not unmarshal scheduled event: ", err)
	}


	outputJSON, err := json.Marshal(eventDetails)
	if err != nil {
		log.Fatal("Could not unmarshal scheduled event: ", err)
		fmt.Println("Could not unmarshal scheduled event: ", err)
	}

	fmt.Println("This is the JSON for event details", string(outputJSON))



	version := jsonEvent.Version
	id := jsonEvent.ID
	detailType := jsonEvent.DetailType
	source := jsonEvent.Source
	accountId := jsonEvent.AccountID
	eventTime := jsonEvent.Time
	region := jsonEvent.Region
	resources := jsonEvent.Resources

	//eventname = detail['eventName']
	eventName := eventDetails.EventName

	//arn = detail['userIdentity']['arn']
	arn := eventDetails.UserIdentity.Arn

	//principal = detail['userIdentity']['principalId']
	principal := eventDetails.UserIdentity.PrincipalID

	//userType = detail['userIdentity']['type']
	userType := eventDetails.UserIdentity.Type

	//user = detail['userIdentity']['userName']
	user := eventDetails.UserIdentity.UserName

	//date = detail['eventTime']
	date := eventDetails.EventTime

	//items = detail['responseElements']['instancesSet']['items'][0]['instanceId']
	instanceId := eventDetails.ResponseElements.InstancesSet.Items[0].InstanceID


	fmt.Println("This is the version: ",version)
	fmt.Println("This is the id: ",id)
	fmt.Println("This is the detailType: ",detailType)
	fmt.Println("This is the Source: ",source)
	fmt.Println("This is the accountId: ",accountId)
	fmt.Println("This is the eventTime: ", eventTime)
	fmt.Println("This is the region: ",region)
	fmt.Println("This is the resource: ",resources)

	fmt.Println("Here is the eventName: ", eventName)
	fmt.Println("Here is the date: ", date)
	fmt.Println("Here is the arn: ", arn)
	fmt.Println("Here is the principalId: ", principal)
	fmt.Println("Here is the userType: ", userType)
	fmt.Println("Here is the user: ", user)
	fmt.Println("Here is the instanceId: ", instanceId)

}

func main() {
	lambda.Start(HandleRequest)
}

@patalwell
Copy link

patalwell commented Jun 12, 2019

Wound up making my own Struct for the event in question, which is quite the chore without https://mholt.github.io/json-to-go/. Again, the map[string]interface{} works but will break for nested Types...e.g. Time, Date, etc

You can trim the struct to match the fields you need:

@xiy has the best answer IMO, the map[string]interface{} is ok for testing.

package CustomAwsLambdaEvent

import "time"

type CloudWatchEventDetails struct {
	EventVersion     string    `json:"eventVersion"`
	EventID          string    `json:"eventID"`
	EventTime        time.Time `json:"eventTime"`
	EventType        string    `json:"eventType"`
	ResponseElements struct {
		OwnerID  string `json:"ownerId"`
		InstancesSet  struct {
			Items []struct {
				InstanceID     string `json:"instanceId"`
			} `json:"items"`
		} `json:"instancesSet"`
	} `json:"responseElements"`
	AwsRegion    string `json:"awsRegion"`
	EventName    string `json:"eventName"`
	UserIdentity struct {
		UserName       string `json:"userName"`
		PrincipalID    string `json:"principalId"`
		AccessKeyID    string `json:"accessKeyId"`
		InvokedBy      string `json:"invokedBy"`
		Type      string `json:"type"`
		Arn       string `json:"arn"`
		AccountID string `json:"accountId"`
	} `json:"userIdentity"`
	EventSource     string `json:"eventSource"`
}

@ghayman
Copy link

ghayman commented Aug 21, 2019

Here is an EventsDetails struct I built for monitoring MediaConvert events in CloudWatch. It contains entries for all (most) Status events posted by AWS Elemental Media Convert.

  type MediaConvertEventDetail struct {
	Timestamp int
	AccountId string
	Queue string
	JobId string
	Status string
	ErrorCode int
	ErrorMessage string
	FramesDecoded int
	JobProgress struct {
		PhaseProgress struct {
			PROBING struct {
				Status string
				PercentComplete int
			}
			TRANSCODING struct {
				Status string
				PercentComplete int
			}
			UPLOADING struct {
				Status string
				PercentComplete int
			}
		}
		JobPercentComplete int
		CurrentPhase string
		RetryCount int
	}
	OutputGroupDetails []struct {
		OutputDetails []struct {
			OutputFilePaths []string
			DurationInMs    int
			VideoDetails    struct {
				WidthInPx  int
				HeightInPx int
			}
		}
		PlaylistFilePaths []string
		Type              string
	}
} 

func Handler(ctx context.Context, event events.CloudWatchEvent) {
	var eventDetails MediaConvertEventDetail

	fmt.Printf("The string : %s\n", string(event.Detail[:]))
	_ = json.Unmarshal(event.Detail, &eventDetails)

	switch eventDetails.Status {
	case "STATUS_UPDATE":
		fmt.Printf("Updating, %d percent complete", eventDetails.JobProgress.JobPercentComplete)
		break

	case "PROGRESSING":
		fmt.Printf("Starting up job id %s\n", eventDetails.JobId)
		break

	case "ERROR":
		fmt.Printf("Error! %s\n", eventDetails.ErrorMessage)
		break

	case "COMPLETE":
		fmt.Printf("Complete: %d output file paths\n", len(eventDetails.OutputGroupDetails))
		break
	}
}

func main() {
	lambda.Start(Handler)
}

@HeathNaylor
Copy link

None of the above works for me. I had created a CloudWatch Event Rule. Configure input was Matched event.

Here is the output from CloudWatch Log Group for my Lambda function:

05:09:57
START RequestId: ac7b74f9-9401-4d83-ae13-bd0db9c3cf09 Version: $LATEST

05:09:57
Detail = {}

05:09:57
END RequestId: ac7b74f9-9401-4d83-ae13-bd0db9c3cf09

05:09:57
REPORT RequestId: ac7b74f9-9401-4d83-ae13-bd0db9c3cf09	Duration: 0.49 ms	Billed Duration: 100 ms	Memory Size: 512 MB	Max Memory Used: 25 MB	

At a loss since this contradicts the documentation here

Here is my code:

package main

import (
	"context"
	"fmt"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
)

func main() {
	lambda.Start(handleRequest)
}

func handleRequest(ctx context.Context, event events.CloudWatchEvent) {
	fmt.Printf("Detail = %s\n", event.Detail)
}

@patalwell
Copy link

@HeathNaylor it looks like you have an empty payload or you aren't deserializing the event which is a byte array. What does the details portion of your event look like? You can grab sample data from CloudTrail API logs OR attempt to find examples online. I would suggest going to look at the event in question in the CloudTrail logs so you have something to test against.

Here's an example of what I mean by needing details:

{
  "version": "0",
  "id": "6a7e8feb-b491-4cf7-a9f1-bf3703467718",
  "detail-type": "EC2 Instance State-change Notification",
  "source": "aws.ec2",
  "account": "111122223333",
  "time": "2017-12-22T18:43:48Z",
  "region": "us-west-1",
  "resources": [
    "arn:aws:ec2:us-west-1:123456789012:instance/ i-1234567890abcdef0"
  ],
  "detail": {
    "instance-id": " i-1234567890abcdef0",
    "state": "terminated"
  }
}

It seems like your event or test payload is using something like this or you simply aren't deserializing the event when you go to handle the request.

{
  "version": "0",
  "id": "6a7e8feb-b491-4cf7-a9f1-bf3703467718",
  "detail-type": "EC2 Instance State-change Notification",
  "source": "aws.ec2",
  "account": "111122223333",
  "time": "2017-12-22T18:43:48Z",
  "region": "us-west-1",
  "resources": [
    "arn:aws:ec2:us-west-1:123456789012:instance/ i-1234567890abcdef0"
  ],
  "detail": {}
}

event.Detail is typically serialized as a byte array; so you'll more than likely need to deserialize the event.Detail using the Json SERDE lib or defaulting to a map[string]interface{}.

I've replicated this on my end with :
Payload with Details: https://play.golang.org/p/Ru-vj1AKx8I
Payload without Details: https://play.golang.org/p/R4VMtU69ZlR

@ppai-plivo
Copy link
Contributor

I ran into the same issue as @HeathNaylor

In my case, I wanted CloudWatch Events to pass constant JSON to lambda as input as shown below:

cw

Turns out, when Constant (JSON text) is being used as input, the events.CloudWatchEvent struct I get in lambda handler is empty. The Detail field of the struct also is empty.

Handling it is simple. Either of these can be used:

type Request {
    CountryISO string `json:"country_iso"`
}

func handleRequest(ctx context.Context, req Request) {
}

or

func handleRequest(ctx context.Context, b json.RawMessage) {
    // json.RawMessage is basically []byte i.e raw json 
    // that can be unmarshalled
    // json.RawMessage can be used to check what's being passed
}

@davidsteed
Copy link

If you go to eventbridge schema registry and select schema on the left hand side menu, there is a library of schemas. You can download code bindings for them (but not in go). However, if you download the openapi spec there are tools online eg. https://github.com/OpenAPITools/openapi-generator that will generate the structures you require in go.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants