Project to deploy an API Gateway with Lambda integration locally with localstack and Pulumi. We'll fully develop and test API Gateway REST endpoints and Lambdas locally before ever pushing to AWS.
- Deploy containers on Docker Desktop for Localstack and PostgreSQL
- Deploy AWS services onto Localstack using Pulumi, some code you'd use to deploy to AWS
- Run Jest tests. Tests create DB schema/tables, hit APIGWs
- Tests assert data in DB is correct
This BluePrint is done on a Mac so some of the instructions are Mac / Linux specific. It should be doable on a Windows machine also somehow.
- Install Homebrew on your Mac. https://docs.brew.sh/Installation
- Install Docker Desktop. https://www.docker.com/products/docker-desktop
- Install NVM (node version manager). If you're on Windows, install the latest stable Node version. https://github.com/nvm-sh/nvm
- Run these commands to install the latest node and use it
nvm install node nvm use node
- Install Yarn.
npm install --global yarn
- Install AWS CLI. https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html
- Create the
.env
with the following variables:
export APP_NAME=bp-api
export REGION=us-east-1
export STAGE=bp.local
export STACK=${APP_NAME}-${STAGE}
export LS_VERSION=latest
To deploy to AWS just change the STAGE
value to anything that doesn't end in .local
To see more document on Pulumi backends, see this: https://www.pulumi.com/docs/intro/concepts/state/#backends
We're storing the pulumi state to the current directory you're in. A .pulumi
directory will be created.
$ pulumi login file://`pwd`
The project uses secrets. Set a pulumi project passphrase
export PULUMI_CONFIG_PASSPHRASE=<choose some passphrase to encrypt project secrets>
Install required packages
$ make setup
Start localStack and PosgreSQL docker
$ make up
Run $ make deploy
If you want to run pulumi up
from the command line, you need to source the .env file. source .env
yarn install
All test files end with test.ts
. Go find them.
yarn test
Don't do this unless you want to completely start from scratch again. This deletes your pulumi stack and you'll have to start back at make setup
.
Run $ make destroy
Delete stack, stop Docker containers, cleanup Docker.
make redo
Deploy the services
make deploy
The developer Software Development LifeCycle is the ecosystem the developer is operating in and the workflows they can perform. The better the ecosystem, the faster the developer worksflows can be run, maximizing developer velocity.
Let's assume you have the system running. Let's change a Python Lambda
- Open a Lambda function.
src/profile/lambda/python/get_profile_by_id.py
- You see that this references the Profile class that's what you really want to change to add some more error handling or something.
src/profile/lambda/python/lib42/db_util.py
- Update unit tests in
src/test/db-api.test.ts
- Run
make deploy
to deploy the new Lambda - Run
yarn test
to run all the unit tests
This Blueprint uses AWS API Gateway Version 1 because I want to be able to quickly switch between API Gateway types regional, edge, and private. Every REST operation needs
- resource to hit. We're creating a Lambda
- give APIGW permission to invoke the Lambda
- APIGW needs a resource (URI path), HTTP Method, and Integration
- good automated tests This is where to do all of that in this project.
- Let's create a Python Lambda. See the
src/profile/lambda/python/get_profile_by_id.py
file and how it defines a Lambda. Also themakefile
in that the root directory and thesrc/profile/lambda/python
directory handle packaging the Lambda into a zip file inside a Docker container targeted to the Python 3.8 runtime.
Now we're going to modify the Pulumi Typescript resource definitions in src/profile/profileApi.ts
2. Define the Lambda Function. See examples around line 105. Every resource must have a unique name.
const getProfilePythonLambda = new aws.lambda.Function(
`${NAME}-${apiName}-get-profile-python`,
{
runtime: aws.lambda.Python3d8Runtime,
code: new pulumi.asset.FileArchive("./src/profile/lambda/python/build/get_profile_by_id.zip"),
timeout: 15,
handler: "get_profile_by_id.handler",
role: lambdaRole.arn,
environment: envvars,
},
{ provider: PulumiUtil.awsProvider, }
);
- Give APIGW permission to invoke the Lambda. See example around line 168.
let invokeGetPythonPermission = new aws.lambda.Permission(
`${NAME}-${apiName}-get-python-byid`,
{
action: "lambda:invokeFunction",
function: getProfilePythonLambda.name,
principal: "apigateway.amazonaws.com",
sourceArn: pulumi.interpolate`${restApi.executionArn}/*/*`,
// qualifier: lambdaAlias.name,
},
{
dependsOn: [getProfilePythonLambda],
provider: PulumiUtil.awsProvider, }
);
- Add resource path, method, and integration. See example around line 284.
// ------------------ Start GetProfile Python Setup --------------
//
// GetProfile APIGW config
// PATH: /profile/python
const getProfilePythonResourceBase = new aws.apigateway.Resource(
`${NAME}-${apiName}-get-python-base-profile`,
{
restApi: restApi,
pathPart: 'python',
parentId: createProfileResource.id,
},
{ provider: PulumiUtil.awsProvider, }
);
// PATH: /profile/python/{profileId}
const getProfilePythonResource = new aws.apigateway.Resource(
`${NAME}-${apiName}-get-python-profile`,
{
restApi: restApi,
pathPart: `{profileId}`,
parentId: getProfilePythonResourceBase.id,
},
{ provider: PulumiUtil.awsProvider, }
);
//
// GetProfile Python Method
//
const getProfilePythonMethod = new aws.apigateway.Method(
`${NAME}-${apiName}-get-python-profile-get`,
{
restApi: restApi,
resourceId: getProfilePythonResource.id,
httpMethod: "GET",
authorization: "NONE",
},
{ provider: PulumiUtil.awsProvider, }
);
//
// GetProfile Python RestApi Lambda Integration
//
const getProfilePythonInteg = new aws.apigateway.Integration(
`${NAME}-${apiName}-get-python-profile`,
{
restApi: restApi,
resourceId: getProfilePythonResource.id,
httpMethod: getProfilePythonMethod.httpMethod,
type: "AWS_PROXY",
integrationHttpMethod: "POST",
passthroughBehavior: "WHEN_NO_MATCH",
uri: pulumi.interpolate`arn:aws:apigateway:${AwsUtil.region}:lambda:path/2015-03-31/functions/${getProfilePythonLambda.arn}/invocations`
},
{
dependsOn: [getProfilePythonMethod],
provider: PulumiUtil.awsProvider,
}
);
- Create tests that hit the APIGW endpoint and assert results.
See
src/test/db-api.test.ts
Runyarn test
until your tests pass.
The Docker containers are not running and you've lost all your Localstack deployed resources.
Login to Pulumi backend.
pulumi login file://`pwd`
Set your Pulumi project passphrase. The one you use for this project.
export PULUMI_CONFIG_PASSPHRASE=<choose some passphrase to encrypt project secrets>
Wipe out the pulumi state and start the Docker containers
make redo
Deploy your resources again
make deploy
If you want to get onto the PostgreSQL docker instance, you can do the following.
docker-compose run database bash
Export some fake AWS Credentials
source testcreds.sh
Get Lambda names
aws --endpoint-url=http://localhost:4566 lambda list-functions
Tail Lambda Log in real-time
aws --endpoint-url=http://localhost:4566 logs tail /aws/lambda/<Lambda name> --follow
There's also a convenient make target to tail the createProfile Lambda log
$ make lambda-log
And
$ make lambda-log-get