Localstack / AWS Services / Go November 20, 2020 • Fahad Siddiqui Pre-requisites Before following the article, you should have python & pip docker.io docker-compose go AWS CLI or awslocal Installed on your system. The reason why awslocal makes it a lot easier to play with AWS commands is, you don’t have to specify --endpoint-url each and everytime you need to access, invoke or create a AWS resource. Thanks to localstack.cloud for creating this utility for us. pip install awscli-local Example Project Lambda Function Let’s write Lambda function that responds with a JSON response. package main import ( "context" "encoding/json" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" "log" "time" ) type response struct { UTC time.Time `json:"utc"` } func handleRequest(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { now := time.Now() resp := &response{ UTC: now.UTC(), } body, err := json.Marshal(resp) if err != nil { return events.APIGatewayProxyResponse{}, err } return events.APIGatewayProxyResponse{Body: string(body), StatusCode: 200}, nil } func main() { lambda.Start(handleRequest) } You can probably put this code into a main.go and then build your project. With Go modules, it is even simpler i.e; you don’t have to manage GOPATH separately. mkdir mylambda/ cd mylambda/ go mod init mylambda touch main.go # and put the code above, in that file go mod download # will fetch all dependencies Localstack Install Localstack pip install localstack Run Localstack localstack start If you need services like AWS Lambda, AWS API Gateway, or may be S3 - you may pass an environment variable SERVICES=lambda,apigateway,s3 localstack start Run Localstack Using Docker Compose You can define your own docker-compose file to run localstack version: "3.3" services: mylocalstack: container_name: mylocalstack image: localstack/localstack network_mode: bridge ports: - "4566:4566" - "4571:4571" - "${PORT_WEB_UI-8080}:${PORT_WEB_UI-8080}" environment: - DATA_DIR=${DATA_DIR- } - PORT_WEB_UI=${PORT_WEB_UI- } - LAMBDA_EXECUTOR=${LAMBDA_EXECUTOR- } - KINESIS_ERROR_PROBABILITY=${KINESIS_ERROR_PROBABILITY- } - DOCKER_HOST=unix:///var/run/docker.sock - HOST_TMP_FOLDER=${TMPDIR} - LOCALSTACK_API_KEY=${LOCALSTACK_API_KEY} - DEBUG=${DEBUG- } - SERVICES=${SERVICES- } volumes: - "${TMPDIR:-/tmp/localstack}:/tmp/localstack" - "/var/run/docker.sock:/var/run/docker.sock" You can now run the container docker-compose up -d In macOS, you will have to run it using TMPDIR=/private$TMPDIR docker-compose up -d Otherwise it won’t work. Installing AWS CLI pip install awscli-local Setup AWS Infrastructure Locally #!/usr/bin/env bash GRACE_TIME="20s" REGION="us-east-1" STAGE="test" API_NAME=api_$(date +"%Y%m%d_%H%M%S") DOCKER_FILE="docker-compose.yml" ENV_FILE="./.env" LOCALSTACK_ENDPOINT="http://localhost:4566" function fail() { echo $2 exit $1 } if [[ -f $ENV_FILE ]]; then echo "Sourcing environment variables..." source $ENV_FILE else fail 9 "$ENV_FILE not present..." fi echo "Building lambda..." GOOS=linux go build -o api ./ && \ zip api.zip api && \ rm -rf api echo "Removing old containers..." docker-compose -f ${DOCKER_FILE} down --remove-orphans echo "Building new localstack environment..." docker-compose -f ${DOCKER_FILE} up -d echo "Grace time for $GRACE_TIME..." sleep $GRACE_TIME && echo "Grace time ended..." echo "Generated API name: $API_NAME" awslocal elasticache create-cache-cluster \ --cache-cluster-id "testcenters_cache" \ --engine redis \ --cache-node-type cache.m5.large \ --num-cache-nodes 1 echo "Creating lambda function..." awslocal lambda create-function \ --region ${REGION} \ --function-name ${API_NAME} \ --runtime go1.x \ --handler api \ --timeout 30 \ --memory-size 512 \ --zip-file fileb://api.zip \ --role arn:aws:iam::123456:role/irrelevant [ $? == 0 ] || fail 1 "Failed: AWS / lambda / create-function" LAMBDA_ARN=$(awslocal lambda list-functions --query "Functions[?FunctionName==\`${API_NAME}\`].FunctionArn" --output text --region ${REGION}) echo "Creating REST API..." awslocal apigateway create-rest-api \ --region ${REGION} \ --name ${API_NAME} [ $? == 0 ] || fail 2 "Failed: AWS / apigateway / create-rest-api" API_ID=$(awslocal apigateway get-rest-apis --query "items[?name==\`${API_NAME}\`].id" --output text --region ${REGION}) PARENT_RESOURCE_ID=$(awslocal apigateway get-resources --rest-api-id ${API_ID} --query 'items[?path==`/`].id' --output text --region ${REGION}) awslocal apigateway create-resource \ --region ${REGION} \ --rest-api-id ${API_ID} \ --parent-id ${PARENT_RESOURCE_ID} \ --path-part "{somethingId}" [ $? == 0 ] || fail 3 "Failed: AWS / apigateway / create-resource" RESOURCE_ID=$(awslocal apigateway get-resources --rest-api-id ${API_ID} --query 'items[?path==`/{somethingId}`].id' --output text --region ${REGION}) awslocal apigateway put-method \ --region ${REGION} \ --rest-api-id ${API_ID} \ --resource-id ${RESOURCE_ID} \ --http-method GET \ --request-parameters "method.request.path.somethingId=true" \ --authorization-type "NONE" \ [ $? == 0 ] || fail 4 "Failed: AWS / apigateway / put-method" echo "Creating REST API integration..." awslocal apigateway put-integration \ --region ${REGION} \ --rest-api-id ${API_ID} \ --resource-id ${RESOURCE_ID} \ --http-method GET \ --type AWS_PROXY \ --integration-http-method POST \ --uri arn:aws:apigateway:${REGION}:lambda:path/2015-03-31/functions/${LAMBDA_ARN}/invocations \ --passthrough-behavior WHEN_NO_MATCH \ [ $? == 0 ] || fail 5 "Failed: AWS / apigateway / put-integration" echo "Creating REST API deployment..." awslocal apigateway create-deployment \ --region ${REGION} \ --rest-api-id ${API_ID} \ --stage-name ${STAGE} \ [ $? == 0 ] || fail 6 "Failed: AWS / apigateway / create-deployment" ENDPOINT=$LOCALSTACK_ENDPOINT/restapis/${API_ID}/${STAGE}/_user_request_ echo "API endpoint:" echo ${ENDPOINT} echo -e "\nAll good..." Resources Thanks to this amazing gist Localstack’s AWS CLI AWS documentation Please enable JavaScript to view the comments powered by Disqus.