Git Push to Deploy Lambdas using CircleCI
$ git push origin master
* branch master -> FETCH_HEAD
9e364190..0e14a1fd master -> origin/master
Updating 9e364190..0e14a1fd
$ #triggers a circleci job that deploys your serverless function
$ curl aws-myapi-gateway.com/function
"Hello world"
- Setup a repo with functions
- One-time setup of AWS and CircleCI
- Configure CircleCI
- Git push

- Dev
- Stg
- Prod
branch | environment |
---|---|
master | dev |
stg | stg |
prod | prod |
.
├── functions
| |── echo
| | |── index.js
| |── helloWorld
| |── myFunc
| | ...
├── .circleci
| |── config.yml
| |── deploy.sh
|── .git
├── .gitignore
- a git repo at the top level.
- a functions folder.
- a circleci folder (more on this later).



#common
AWS_ACCOUNT_ID
AWS_REGION
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
#dev api gateway id
AWS_DEV_REST_API_ID
#stg api gateway id
AWS_STG_REST_API_ID
#prod api gateway id
AWS_PROD_REST_API_ID
version: 2
jobs:
build_dev:
steps:
- checkout
- run:
name: Setup Environment Variables
command: |
echo 'export AWS_REST_API_ID="$AWS_DEV_REST_API_ID"' >> $BASH_ENV
- run:
name: Install dependencies
command: |
apt-get update
apt-get install -y jq zip python3-pip
...
- run:
name: Deploy functions
command: |
cd functions
../.circleci/deploy.sh echo
workflows:
version: 2
full:
jobs:
- build_dev:
filters:
branches:
only: master
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
version: 2 | |
jobs: | |
build_dev: | |
working_directory: ~/project | |
docker: | |
- image: buildpack-deps:xenial | |
environment: | |
DEPLOY_ENVIRONMENT: dev | |
steps: | |
- checkout | |
- run: | |
name: Setup Environment Variables | |
command: | | |
echo 'export AWS_REST_API_ID="$AWS_DEV_REST_API_ID"' >> $BASH_ENV | |
echo 'export GIT_SHA="$(git rev-parse --short HEAD)"' >> $BASH_ENV | |
# Better to create a docker image for this step | |
- run: | |
name: Install awscli, jq and npm | |
command: | | |
apt-get update | |
apt-get install -y jq zip python3-pip | |
pip3 install awscli --upgrade | |
curl -sL https://deb.nodesource.com/setup_8.x | bash - | |
apt-get install -y nodejs | |
npm install -g npm@5 | |
aws configure set default.region $AWS_REGION | |
- run: | |
name: Deploy Functions | |
command: | | |
cd functions | |
../.circleci/deploy.sh echo | |
../.circleci/deploy.sh hello-world | |
build_stg: | |
working_directory: ~/project | |
docker: | |
- image: buildpack-deps:xenial | |
environment: | |
DEPLOY_ENVIRONMENT: stg | |
steps: | |
- checkout | |
- run: | |
name: Setup Environment Variables | |
command: | | |
echo 'export AWS_REST_API_ID="$AWS_STG_REST_API_ID"' >> $BASH_ENV | |
echo 'export GIT_SHA="$(git rev-parse --short HEAD)"' >> $BASH_ENV | |
# Better to create a docker image for this step | |
- run: | |
name: Install awscli, jq and npm | |
command: | | |
apt-get update | |
apt-get install -y jq zip python3-pip | |
pip3 install awscli --upgrade | |
curl -sL https://deb.nodesource.com/setup_8.x | bash - | |
apt-get install -y nodejs | |
npm install -g npm@5 | |
aws configure set default.region $AWS_REGION | |
- run: | |
name: Deploy Functions | |
command: | | |
cd functions | |
../.circleci/deploy.sh echo | |
../.circleci/deploy.sh hello-world | |
build_prod: | |
working_directory: ~/project | |
docker: | |
- image: buildpack-deps:xenial | |
environment: | |
DEPLOY_ENVIRONMENT: prod | |
steps: | |
- checkout | |
- run: | |
name: Setup Environment Variables | |
command: | | |
echo 'export AWS_REST_API_ID="$AWS_PROD_REST_API_ID"' >> $BASH_ENV | |
echo 'export GIT_SHA="$(git rev-parse --short HEAD)"' >> $BASH_ENV | |
# Better to create a docker image for this step | |
- run: | |
name: Install awscli, jq and npm | |
command: | | |
apt-get update | |
apt-get install -y jq zip python3-pip | |
pip3 install awscli --upgrade | |
curl -sL https://deb.nodesource.com/setup_8.x | bash - | |
apt-get install -y nodejs | |
npm install -g npm@5 | |
aws configure set default.region $AWS_REGION | |
- run: | |
name: Deploy Functions | |
command: | | |
cd functions | |
../.circleci/deploy.sh echo | |
../.circleci/deploy.sh hello-world | |
workflows: | |
version: 2 | |
full: | |
jobs: | |
- build_dev: | |
filters: | |
branches: | |
only: master | |
- build_stg: | |
filters: | |
branches: | |
only: stg | |
- build_prod: | |
filters: | |
branches: | |
only: prod |
- Create a Lambda with the name
<functionName>_<environment>
, if doesn't exist already. - Zip the folder and upload the code to the above Lambda.
- Create an alias for the Lambda with the
GITSHA
of the commit. - Create a route in the API Gateway, if doesn't exist already.
- Link the alias with the route.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
set -o nounset | |
set -o errexit | |
set -o pipefail | |
current_function="$1" | |
current_build="${current_function}_${DEPLOY_ENVIRONMENT}" | |
cd $current_function | |
if [ -f package-lock.json ]; then | |
npm ci | |
else | |
npm i | |
fi | |
zip -r "${current_build}.zip" . | |
echo "Checking if function $current_build already exists" | |
functionArn=$(aws lambda list-functions | jq -r --arg CURRENTFUNCTION "$current_build" '.Functions[] | select(.FunctionName==$CURRENTFUNCTION) | .FunctionArn') | |
if [ -z "$functionArn" ] | |
then | |
echo "Creating function: $current_build" | |
functionArn=$(aws lambda create-function --function-name "$current_build" --runtime nodejs8.10 --role arn:aws:iam::$AWS_ACCOUNT_ID:role/lambda-basic-role --handler lambdaCtx.handler --zip-file fileb://./"${current_build}.zip" | jq -r '.FunctionArn') | |
if [ -z "$functionArn" ] | |
then | |
echo "Failed to get functionArn" | |
exit 1 | |
fi | |
fi | |
echo "Updating function: $current_build" | |
aws lambda update-function-code --function-name "$current_build" --zip-file fileb://./"${current_build}.zip" --no-publish | |
echo "Publishing version" | |
version=$(aws lambda publish-version --function-name "$current_build" | jq .Version | xargs) | |
echo "Check for alias" | |
CREATE_ALIAS_EXIT_CODE=0 | |
aws lambda get-alias --function-name "$current_build" --name $GIT_SHA || CREATE_ALIAS_EXIT_CODE=$? | |
if [ $CREATE_ALIAS_EXIT_CODE -ne 0 ] | |
then | |
echo "Creating alias" | |
aws lambda create-alias --function-name "$current_build" --description "alias for $GIT_SHA" --function-version $version --name $GIT_SHA | |
fi | |
echo "Check for API resource" | |
parentID=$(aws apigateway get-resources --rest-api-id $AWS_REST_API_ID | jq -r '.items[] | select(.path=="/") | .id') | |
resourceID=$(aws apigateway get-resources --rest-api-id $AWS_REST_API_ID | jq -r --arg CURRENTPATH "/$current_function" '.items[] | select(.path==$CURRENTPATH) | .id') | |
echo "parentID: $parentID, resourceID: $resourceID" | |
if [ -z "$resourceID" ] | |
then | |
echo "Creating resource" | |
resourceID=$(aws apigateway create-resource --rest-api-id $AWS_REST_API_ID --parent-id $parentID --path-part "$current_function" | jq -r '.id') | |
echo "Created resource with id: $resourceID" | |
fi | |
echo "Check for Resource Method" | |
GET_METHOD_EXIT_CODE=0 | |
aws apigateway get-method --rest-api-id $AWS_REST_API_ID --resource-id $resourceID --http-method POST || GET_METHOD_EXIT_CODE=$? | |
if [ $GET_METHOD_EXIT_CODE -ne 0 ] | |
then | |
echo "Creating Resource Method" | |
aws apigateway put-method --rest-api-id $AWS_REST_API_ID --resource-id $resourceID --http-method POST --authorization-type NONE | |
fi | |
echo "Check for integration" | |
GET_INTEGRATION_EXIT_CODE=0 | |
aws apigateway get-integration --rest-api-id $AWS_REST_API_ID --resource-id $resourceID --http-method POST || GET_INTEGRATION_EXIT_CODE=$? | |
if [ $GET_INTEGRATION_EXIT_CODE -ne 0 ] | |
then | |
echo "Creating Integration" | |
aws apigateway put-integration --rest-api-id $AWS_REST_API_ID --resource-id $resourceID --http-method POST --type AWS_PROXY --integration-http-method POST --uri arn:aws:apigateway:$AWS_REGION:lambda:path/2015-03-31/functions/$functionArn:$GIT_SHA/invocations | |
fi | |
aws apigateway update-integration --rest-api-id $AWS_REST_API_ID --resource-id $resourceID --http-method POST --patch-operations "[ {\"op\" : \"replace\",\"path\" : \"/uri\",\"value\" : \"arn:aws:apigateway:$AWS_REGION:lambda:path/2015-03-31/functions/$functionArn:$GIT_SHA/invocations\"} ]" | |
echo "Delete API Gateway permission if exists" | |
REMOVE_PERMISSION_EXIT_CODE=0 | |
STATEMENT_ID="${GIT_SHA}_${current_build}" | |
aws lambda remove-permission --function-name arn:aws:lambda:$AWS_REGION:$AWS_ACCOUNT_ID:function:$current_build:$GIT_SHA --statement-id $STATEMENT_ID || REMOVE_PERMISSION_EXIT_CODE=$? | |
echo "Creating API Gateway permission" | |
aws lambda add-permission --function-name arn:aws:lambda:$AWS_REGION:$AWS_ACCOUNT_ID:function:$current_build:$GIT_SHA --source-arn "arn:aws:execute-api:$AWS_REGION:$AWS_ACCOUNT_ID:$AWS_REST_API_ID/*/*/$current_function" --principal apigateway.amazonaws.com --statement-id $STATEMENT_ID --action lambda:InvokeFunction | |
echo "Creating deployment" | |
aws apigateway create-deployment --rest-api-id $AWS_REST_API_ID --stage-name default | |
echo "Finished" |
Related reading