Skip to content

Instantly share code, notes, and snippets.

@andreif
Last active February 27, 2024 19:39
Show Gist options
  • Save andreif/4650ddf8ba311fa3127066a4cc6cbf73 to your computer and use it in GitHub Desktop.
Save andreif/4650ddf8ba311fa3127066a4cc6cbf73 to your computer and use it in GitHub Desktop.
#go #aws #lambda

Example of Go AWS Lambda using stdlib only

Source

Create a main.go file:

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
)

func main() {
	runtime_api := os.Getenv("AWS_LAMBDA_RUNTIME_API")
	base_url := "http://" + runtime_api + "/2018-06-01/runtime/invocation/"

	for {
		resp, err := http.Get(base_url + "next")
		if err != nil {
			log.Fatalf("Failed to get next invocation: %v", err)
		}
		requestID := resp.Header.Get("Lambda-Runtime-Aws-Request-Id")
		var event map[string]interface{}

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.Fatalf("Failed ioutil.ReadAll: %v", err)
		}
		err = json.Unmarshal(body, &event)
		if err != nil {
			log.Fatalf("Failed json.Unmarshal: %v", err)
		}
		resp.Body.Close()

		response, err := handler(event)
		if err != nil {
			log.Fatalf("Failed json.Marshal: %v", err)
		}

		responseURL := base_url + requestID + "/response"
		_, err = http.Post(responseURL, "application/json", response)
		if err != nil {
			log.Fatalf("Failed to post response: %v", err)
		}
	}
}

func handler(event map[string]interface{}) (*bytes.Buffer, error) {
	response := map[string]interface{}{
		"message": fmt.Sprintf("Hello %s!", event["name"]),
	}
	responseBody, err := json.Marshal(response)
	if err != nil {
		return nil, err
	}
	return bytes.NewBuffer(responseBody), nil
}

Build

Compile it to bootstrap binary as expected by AWS Lambda:

docker run --rm -ti -w /home -v "${PWD}":/home \
	-e GOOS=linux -e GOARCH=arm64 -e CGO_ENABLED=0 \
	golang:1.22 go build -o bootstrap main.go

Now zip it as AWS requires:

zip lambda.zip bootstrap

Deploy

Use the following commands from AWS Cloud Shell or from local terminal assuming you have an active AWS session:

export FUNC_NAME=andrei-test
export ROLE_NAME="${FUNC_NAME}-role"

export ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"

aws iam create-role --role-name "${ROLE_NAME}" --assume-role-policy-document '{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"Service": "lambda.amazonaws.com"},
    "Action": "sts:AssumeRole"
  }]
}' | jq

aws iam attach-role-policy \
    --role-name "${ROLE_NAME}" \
    --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

aws lambda create-function \
    --function-name "${FUNC_NAME}" \
    --role "arn:aws:iam::${ACCOUNT_ID}:role/${ROLE_NAME}" \
    --runtime provided.al2023 \
    --architectures arm64 \
    --handler whatever \
    --zip-file fileb://lambda.zip | jq

Now test invoking your function

aws lambda invoke \
    --function-name "${FUNC_NAME}" \
    --cli-binary-format raw-in-base64-out \
    --payload '{"name":"test"}' \
    _.json | jq && cat _.json | jq && rm _.json

Should see the output

{
  "StatusCode": 200,
  "ExecutedVersion": "$LATEST"
}
{
  "message": "Hello test!"
}

When you need to update your function:

aws lambda update-function-code \
    --function-name "${FUNC_NAME}" \
    --zip-file fileb://lambda.zip | jq

Tear down

After you done with testing, don't forget to remove the function, the role and an auto-created log group:

aws lambda delete-function --function-name "${FUNC_NAME}"

# detach all role policies
for ARN in $(aws iam list-attached-role-policies --role-name "${ROLE_NAME}" \
--query 'AttachedPolicies[].PolicyArn' --output text); do 
aws iam detach-role-policy --role-name "${ROLE_NAME}" --policy-arn "${ARN}"; done

aws iam delete-role --role-name "${ROLE_NAME}"

aws logs delete-log-group --log-group-name "/aws/lambda/${FUNC_NAME}"
@andreif
Copy link
Author

andreif commented Feb 26, 2024

Here is example of main.go using aws-lambda-go package:

package main

import (
	"context"
	"fmt"
	"github.com/aws/aws-lambda-go/lambda"
)

type MyEvent struct {
	Name string `json:"name"`
}

func HandleRequest(ctx context.Context, event *MyEvent) (*string, error) {
	if event == nil {
		return nil, fmt.Errorf("received nil event")
	}
	message := fmt.Sprintf("Hello %s!", event.Name)
	return &message, nil
}

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

Run inside the container:

go mod init example/mymodule
go get github.com/aws/aws-lambda-go/lambda
go mod download && go mod verify

and then build as in the original example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment