« Back to Index

[Python Lambda]

View original Gist on GitHub

Tags: #aws #lambda #makefile #python #cognito

1. Python Lambda.md

The AWS Python SDK (Boto) is pre-installed with AWS Lambda, so if all we need is the requests library, then we can use the following trick to avoid needing to package up our lambda script and its dependencies…

from botocore.vendored import requests

Note: the following instructions require you to be using Pipenv for handling you Python dev environment.

To generate your dependencies and zip them (+ your lambda script) up for upload to AWS:

You need to ensure the dependencies are in the same directory as the lambda script. For example…

requests/
urllib3/
lambda_function.py

You can (if you’re not using pipenv) use --target flag to install dependencies to the current directory: pip install requests --target .

unzip -vl lambda.zip to check contents of zip

2. Makefile

SHELL := /bin/bash

build: clean
	pipenv run pip install -r <(pipenv lock -r) --target ./
	zip -r lambda.zip ./

# We use `@` to prevent Make from displaying the line that was just executed (it's just noise)
# We also use `&> /dev/null || true` to ensure errors are silenced
# e.g. trying to remove files that don't exist, would normally trigger an error in Bash
clean:
	@# use double $$ to avoid conflict with builtin $() syntax
	@rm -r $$(ls | egrep -v 'Makefile|Pipfile|lambda_function.py') &> /dev/null || true

run:
	@# env vars can be set in the AWS Lambda console
	FOO=123 BAR=456 python lambda_function.py

lambda_function.py

import os
import requests


def get_host():
    tld = 'stage.example.com'
    host = f'https://{tld}'

    webapp_stage_user = os.environ["STAGE_USN"]
    webapp_stage_pass = os.environ["STAGE_PSW"]

    creds = f'{webapp_stage_user}:{webapp_stage_pass}'
    host = f'https://{creds}@{tld}'

    return host


def authn_webapp(event):
    data = {'username': event['userName'],
            'password': event['request']['password']}

    uri = f'{get_host()}/api/login'

    return requests.post(uri, data=data)


def lambda_handler(event, context):
    """
    There are two states:

    1. authentication (but before user created in cognito)
    2. forgotten password

    For authentication we log the user into our webapp and then extract their
    details and modify the user account that is about to be created in cognito.
    
    This allows us to handle migrating users from our data store to cognito.
    """
    if event['triggerSource'] == "UserMigration_Authentication":
        login_response = authn_webapp(event)

        if login_response.status_code == 200:
            d = login_response.json()

            user_attr = {'username': d['username'],
                         'name': d['display_name'],
                         'email': d['email'],
                         'email_verified': 'true',
                         'custom:webapp_id': d['userid']}

            event['response']['userAttributes'] = user_attr
            event['response']['finalUserStatus'] = "CONFIRMED"
            event['response']['messageAction'] = "SUPPRESS"

            return event
        else:
            return None
    else:
        return None


if __name__ == "__main__":
    event = {'triggerSource': 'UserMigration_Authentication',
             'userName': 'beep',
             'request': {'password': 'boop'},
             'response': {}}

    result = lambda_handler(event, {})

    print(f'result: {result}')

# Build package:
#   make build
#       pipenv run pip install -r <(pipenv lock -r) --target ./
#       zip -r lambda.zip ./
#
# Test locally:
#   make run
#       STAGE_USN=123 STAGE_PSW=456 python lambda_function.py