Go AWS Lambda: Where is event? - amazon-web-services

Most Lambda runtimes have the following handler signature, which allows accessing both the event and context objects passed into the Lambda:
lambdaHandler(event, context){}
However the documentation for Go Lambda handlers does not follow this convention as shown here: https://docs.aws.amazon.com/lambda/latest/dg/golang-handler.html
Question: How does one access the event object when using the Go Lambda runtime, i.e., when trying to determine the repository URL in an AWS CodeCommit Lambda Trigger (https://docs.aws.amazon.com/codecommit/latest/userguide/how-to-notify-lambda.html)?

Your expected event is of type events.CodeCommitEvent
func handler(ctx context.Context, codeEvent events.CodeCommitEvent) {
for _, record := range codeEvent.Records {
// do you magic here.
}
}

Related

Getting requester's IP address in AWS lambda function

I understand, from the answer to this question, that I can get the IP address of the caller of a node.js lambda function by doing this:
events['headers']['X-Forwarded-For']
It seems like, for Go, this information should be inside the context.Context for lambda signatures that take it. However, looking at the documentation, I don't see any mention of request headers. Is there a way to get the same information in a Go lambda function?
After doing some research on my own, I figured out that I needed to put my lambda function behind an API Gateway instance to get the information I was interested in. After doing that, I could modify the handler like this:
package main
import (
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
func LambdaHandler(ctx context.Context, request events.APIGatewayV2HTTPRequest) (int, error) {
// some code
addr := request.RequestContext.HTTP.SourceIP
// some other code
return 0
}
func main() {
lambda.Start(LambdaHandler)
}
By doing this, the API gateway will populate all the request metadata I need and pass that on to my lambda function. The request body is now contained in request.Body so I can extract that data using JSON deserialization or whatever other method my data is encoded as.

Retrieve the SQS Queue URL dynamically from the lambda event or context

I have a single lambda function which responds to two different SQS queues invoking it (the second one is a back-off queue) and so I want to dynamically determine which queue the message came from, so that I can remove it once it has been processed.
I am able to create the EventSourceMapping to trigger the lambda from both queues, but I cannot see how I cleanly retrieve the QueueURL I need in order to perform a DeleteMessage call. I am using the AWS Golang SDK.
There is the EventSourceArn in the body of the event messages and it seems I could use GetQueueURL but that would be an additional API call.
The only way to avoid that (I can think of) is to pass both queue URLs in as environment variables and use the queue name from EventSourceArn to find it.
Is there a better way that this? Is there something hiding in the context that isn't documented?
If you are using lambda, SQS and an event-source-mapping, then you don't have to manually delete objects from sqs: When your function successfully processes a batch, Lambda deletes its messages from the queue
Just return a success code, and the message will automatically be deleted from the relevant SQS queue
You can rebuild the queue URL from its ARN which you have in the event as EventSourceARN.
package main
import (
"context"
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sqs"
)
var client *sqs.SQS
func main() {
client = sqs.New(session.Must(session.NewSessionWithOptions(session.Options{
SharedConfigState: session.SharedConfigEnable,
})))
lambda.Start(Handle)
}
func Handle(ctx context.Context, event events.SQSEvent) {
for _, record := range event.Records {
// Process message
fmt.Printf(record.Body)
// Rebuild Queue URL from ARN
queueArn, _ := arn.Parse(record.EventSourceARN)
queueUrl := fmt.Sprintf("https://sqs.%v.amazonaws.com/%v/%v", queueArn.Region, queueArn.AccountID, queueArn.Resource)
// Delete message from queue
client.DeleteMessage(&sqs.DeleteMessageInput{
QueueUrl: &queueUrl,
ReceiptHandle: &record.ReceiptHandle,
})
}
}
If you are using a non-standard AWS partition (e.g. China) you will also need to change the root domain of the URL to match.

Can a lambda that uses vendia serverless-express be a step in a state machine?

I have a lambda setup that uses vendia serveless-express as a handler. It has been working well to serve REST APIs from the single lambda function with multiple routes.
I now have a new requirement, where the same lambda needs to be part of a step function's state machine. But that doesn't seem to be allowed by vendia app, as it always throws the error: "Unable to determine event source based on event" as it expects the event to be api gateway / alb only.
So, based on this, it looks like I will need a separate lambda for step, which makes me have duplicate code in multiple lambdas.
Is it possible for the lambda to handle the inputs from step and still be a vendia express app? Please let me know if I am trying something that doesn't make sense at all.
If I were you I would just implement my own converter that transforms a StepFunction event into an API Gateway event and then calling the express-serverless in your lambda.
The package aws-lambda contains definitions in TypeScript of many AWS events including those ones, then try to generate a mock API Gateway event from your own step function event value. From the sources (4.3.9) we have the function:
function getEventSourceNameBasedOnEvent ({
event
}) {
if (event.requestContext && event.requestContext.elb) return 'AWS_ALB'
if (event.Records) return 'AWS_LAMBDA_EDGE'
if (event.requestContext) {
return event.version === '2.0' ? 'AWS_API_GATEWAY_V2' : 'AWS_API_GATEWAY_V1'
}
throw new Error('Unable to determine event source based on event.')
}
So probably to make it work correctly, you have to define a RequestContext mock value in your mock event and it should be enough.

Is there any ways to set values to system fields of CloudWatch Logs?

I want to set values to the fields #requestId in lambda executions because logs outputted explicitly inside the source code does not contain the #requestId field value.
I've read an article and tried to output logs like the below but the #requestId did not to be filled.
fmt.Print(`{"requestId":"come on!!"}`)
Neither the below code.
fmt.Print(`{"#requestId":"come on!!"}`)
Are system fields protected?
When an AWS Lambda function is triggered, a context element is passed to the function.
From AWS Lambda Function Handler in Go - AWS Lambda:
func HandleRequest(ctx context.Context, name MyEvent) (string, error) {
return fmt.Sprintf("Hello %s!", name.Name ), nil
}
From AWS Lambda Context Object in Go - AWS Lambda:
Context Properties
AwsRequestID – The identifier of the invocation request.
Therefore, you can retrieve the Request ID from the context. Then, anything that your function prints will be captured in CloudWatch Logs. So, include the Request ID in your print statement.

Api Gateway: AWS Subdomain for Lambda Integration

I'm attempting to integrate my lambda function, which must run async because it takes too long, with API gateway. I believe I must, instead of choosing the "Lambda" integration type, choose "AWS Service" and specify Lambda. (e.g. this and this seem to imply that.)
However, I get the message "AWS ARN for integration must contain path or action" when I attempt to set the AWS Subdomain to the ARN of my Lambda function. If I set the subdomain to just the name of my Lambda function, when attempting to deploy I get "AWS ARN for integration contains invalid path".
What is the proper AWS Subdomain for this type of integration?
Note that I could also take the advice of this post and set up a Kinesis stream, but that seems excessive for my simple use case. If that's the proper way to resolve my problem, happy to try that.
Edit: Included screen shot
Edit: Please see comment below for an incomplete resolution.
So it's pretty annoying to set up, but here are two ways:
Set up a regular Lambda integration and then add the InvocationType header described here http://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html. The value should be 'Event'.
This is annoying because the console won't let you add headers when you have a Lambda function as the Integration type. You'll have to use the SDK or the CLI, or use Swagger where you can add the header easily.
Set the whole thing up as an AWS integration in the console (this is what you're doing in the question), just so you can set the InvocationType header in the console
Leave subdomain blank
"Use path override" and set it to /2015-03-31/functions/<FunctionARN>/invocations where <FunctionARN> is the full ARN of your lambda function
HTTP method is POST
Add a static header X-Amz-Invocation-Type with value 'Event'
http://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html
The other option, which I did, was to still use the Lambda configuration and use two lambdas. The first (code below) runs in under a second and returns immediately. But, what it really does is fire off a second lambda (your primary one) that can be long running (up to the 15 minute limit) as an Event. I found this more straightforward.
/**
* Note: Step Functions, which are called out in many answers online, do NOT actually work in this case. The reason
* being that if you use Sequential or even Parallel steps they both require everything to complete before a response
* is sent. That means that this one will execute quickly but Step Functions will still wait on the other one to
* complete, thus defeating the purpose.
*
* #param {Object} event The Event from Lambda
*/
exports.handler = async (event) => {
let params = {
FunctionName: "<YOUR FUNCTION NAME OR ARN>",
InvocationType: "Event", // <--- This is KEY as it tells Lambda to start execution but immediately return / not wait.
Payload: JSON.stringify( event )
};
// we have to wait for it to at least be submitted. Otherwise Lambda runs too fast and will return before
// the Lambda can be submitted to the backend queue for execution
await new Promise((resolve, reject) => {
Lambda.invoke(params, function(err, data) {
if (err) {
reject(err, err.stack);
}
else {
resolve('Lambda invoked: '+data) ;
}
});
});
// Always return 200 not matter what
return {
statusCode : 200,
body: "Event Handled"
};
};