Skip to content

Provide an adaptor for localstack deployment #574

@tyge68

Description

@tyge68

Expected Behaviour

Deployment of the generated zip should be compatible with localstack development (which allow to run AWS locally)

Actual Behaviour

Current "lambda" handler is not working while running in localstack due to few differences in the event object structure.
It returns a message like "Request with GET/HEAD method cannot have body" for a simple GET request.

Reproduce Scenario (including but not limited to)

a bit complex, you need to use localstack in docker, and setup a few services (api gateway, s3, secresmanager etc.. as it would run in a real AWS)

version: '3.1'

services:

  localstack:
    image: localstack/localstack:latest
    environment:
      - AWS_DEFAULT_REGION=us-east-1
      - SERVICES=s3,apigateway,sqs,lambda,secretsmanager
    ports:
      - '4566:4566'
      - '4567:4567'
    volumes:
      - "${TEMPDIR:-/tmp/localstack}:/tmp/localstack"
      - ./test/aws:/etc/localstack/init/ready.d
      - ./dist:/dist
      - "/var/run/docker.sock:/var/run/docker.sock"

define a bootstrap script in test/aws/apigateway.sh (you will also need some for s3 and secretsmanager) but that one is the most important

#!/usr/bin/env bash
API_NAME=myapp
REGION=us-east-1
STAGE=dev

function fail() {
    echo $2
    exit $1
}

awslocal lambda create-function \
    --region ${REGION} \
    --function-name ${API_NAME} \
    --runtime nodejs16.x \
    --handler index.lambda \
    --memory-size 1024 \
    --zip-file fileb:///dist/helix-services/yourservice@x.y.z.zip \
    --role arn:aws:iam::123456123456: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 LAMBDA_ARN=${LAMBDA_ARN}

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 "endpoint"

[ $? == 0 ] || fail 2 "Failed: AWS / apigateway / create-resource /endpoint"

RESOURCE_ID=$(awslocal apigateway get-resources --rest-api-id ${API_ID} --query 'items[?path==`/endpoint`].id' --output text --region ${REGION})

awslocal apigateway put-method \
    --region ${REGION} \
    --rest-api-id ${API_ID} \
    --resource-id ${RESOURCE_ID} \
    --http-method ANY \
    --authorization-type "NONE"

[ $? == 0 ] || fail 4 "Failed: AWS / apigateway / put-method"

awslocal apigateway put-integration \
    --region ${REGION} \
    --rest-api-id ${API_ID} \
    --resource-id ${RESOURCE_ID} \
    --http-method ANY \
    --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"

awslocal apigateway create-deployment \
    --region ${REGION} \
    --rest-api-id ${API_ID} \
    --stage-name ${STAGE} \

[ $? == 0 ] || fail 6 "Failed: AWS / apigateway / create-deployment"

ENDPOINT=http://localhost:4566/restapis/${API_ID}/${STAGE}/_user_request_/endpoint
echo "API available at: ${ENDPOINT}"

Steps to Reproduce

Once you setup to make the generated zip (dist/helix-services/yourservice@x.y.z.zip) as the lambda function to be used by the apigateway endpoint of your choice. Call the service url .i.e http://localhost:4566/restapis/hrbmpo1ifh/dev/_user_request_/endpoint, it will returns a 500 with "Request with GET/HEAD method cannot have body"

Platform and Version

AWS via Localstack

Sample Code that illustrates the problem

Logs taken while reproducing problem

The main reason seems that the main function expect some specific structure for the requestContent, which is not 100% the same in the localstack

main differences is that there is no event.requestContent.http.method, instead it is under event.requestContent.httpMethod
and second main differences is that event.body = "" instead of being null in case of "GET" request.

one fix could be to create a small adapter that fix the event object to be like the default "lambda" adapter expect (mainly copy what is needed under event.requestContext.http and fix the body value to be null in get of "GET|HEAD" request.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions