AWS recently released Versioning and Aliases for Lambda Functions. I'm going to outline how I've taken advantage of this to provide environmentally-aware Lambda function configurations in Python.
AWS Lambda doesn't currently support environment variables, so 12-factor-style configuration isn't an option. I'll be using seprate config files for each environment.
We're making two assumptions for this article:
- I've already created an AWS Lambda function with the following aliases:
- Dev
- Test
- Staging
- Production
- I've put together a deployment routine that will create and deploy a Python Deployment Package
My (simplified) project folder structure looks like this:
config/
|-- Dev-config.py
|-- Test-config.py
|-- Staging-config.py
|-- Production-config.py
lambda_utils.py
myfunction.py
- My Lambda function handler is in
myfunction.py
- The
config
directory contains specific*-config.py
files for each Lambda function alias/environment - The
lambda_utils.py
module contains the code used to inject environment-specific values into the Lambda function handler
As an example, here's what my Dev-config.py
file looks like:
class MyName:
first_name = "Bandit"
last_name = "Brandit"
I'm using a Python Function Decorator to inject a config
argument into my Lambda function handler.
Let's start with a basic handler in myfunction.py
:
def myfunction_handler(event, context):
message = 'Hello {} {}!'.format(event['first_name'],
event['last_name'])
return {
'message' : message
}
I'm going to add the @import_config
decorator that injects the environment-specific configuration:
from lambda_utils import *
@import_config
def myfunction_handler(event, context, config):
message = 'Hello {} {}!'.format(config.MyName.first_name,
config.MyName.last_name)
return {
'message' : message
}
Here's the @import_config
decorator code in lambda_utils.py
:
def get_env(context):
# get the Alias name from the Lambda Function ARN
split_arn = context.invoked_function_arn.split(':')
env = split_arn[len(split_arn) - 1]
return env
def import_config(f):
def wrapper(*args, **kwargs):
context = args[1]
env = get_env(context)
config = __import__(env + '-config')
args += (config,)
return f(*args, **kwargs)
return wrapper
That's it! Put your *-config.py
and lambda_utils.py
files into your deployment package and you're good to go.
In the real world, I've used this pattern to integrate with environment-specific DynamoDB tables. It is also quite handy when defining configurations for local development (i.e. each developer has their own config). More on local Lambda function development to come...
Hey Patrick,
Can u tell me how did u put together a deployment routine for lambda functions and from where would u push the updates?
Thank u!