When creating Lambda functions and deploy the functions using CloudFormation, the python code can be inline or packaged in a zip file. When no third party packages are required or tests are created, inline works fine. For very simple use cases this is ok. If you want to be able to test the python function locally, it should be a separate file (and not part of the CloudFormation template). There are many ways to package Lambda functions, and this is one of the easiest ways. It’s just a single command, and only requires Docker on your machine.
CloudFormation
Consider the following CloudFormation template. You will find out the Version parameter is pretty important. If you update the code, but don’t change anything in the template, the function will not be replaced (the deployment even fails). In this example I just use an auto increment bash script to generate a new zip file and use this prefix when uploading. CloudFormation now thinks the function is new, will deploy the template and replace the function.
Parameters:
Version:
content: Lambdas are written in a version directory, which is auto incremented on upload
Type: String
Resources:
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
Handler: lambda_function.lambda_handler
Timeout: 30
Role: !GetAtt 'LambdaBasicExecutionRole.Arn'
Runtime: python3.6
Code:
S3Bucket: "mybucketname"
S3Key: !Sub "${Version}/<lambda_name>.zip"
LambdaBasicExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
This is the file structure you should consider using. The .gitignore is important to prevent saving the build artifacts ending up in your git repo (it’s a zip file and can grow quite big). You can also add custom dependencies in this folder.
└── src
└── <lambda_name>
├── lambda_function.py
├── readme.md
└── requirements.txt
└── ...
└── build
└── <lambda_name>.zip
└── ...
└── .gitignore
└── version.txt
└── template.yml
Now run the command to package all functions, including the version trick, and upload them to an S3 bucket.
docker run -v $(pwd)/src:/src \
-v $(pwd)/build:/build \
binxio/python-lambda-packager
version=`cat version.txt`
version=$[$version+1]
echo $version > version.txt
aws s3 cp --recursive \
--acl public-read \
./build s3://mybucketname/stack/${version}
Now you can deploy the CloudFormation stack using the uploaded Lambdas and example template, using the following command.
version=`cat version.txt`
aws cloudformation deploy \
--stack-name <stack name> \
--template-file template.yml \
--capabilities CAPABILITY_IAM \
--parameter-overrides version=${version}
Conclusion
It has become really easy to package your Lambda functions including their dependencies.