Skip to content

Instantly share code, notes, and snippets.

@patrickbrandt
Last active November 6, 2022 10:14
Show Gist options
  • Save patrickbrandt/21fc41459fe6a6a19e31 to your computer and use it in GitHub Desktop.
Save patrickbrandt/21fc41459fe6a6a19e31 to your computer and use it in GitHub Desktop.
A simple approach to multi-environment configurations for AWS Lambda functions

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.

Pregame

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

The configuration files

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"

The configuration code

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

Conclusion

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...

@bobby259
Copy link

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment