Aws Cloudformation Embedded Python Lambda

Cloudformation stack with embedded python lambda.

Resources diagram

Resources diagram:

AWS Lambda described as a cloudformation resource:

Cloudformation

CloudWatch

Log group for streaming lambda execution logs

  LambdaLogGroup:
    Type: 'AWS::Logs::LogGroup'
    Properties:
      LogGroupName: !Sub '/aws/lambda/${Environment}-python-lambda'
      RetentionInDays: 5

IAM Role

Allow creating log stream and xray tracking

  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service:
                - 'lambda.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      # ManagedPolicyArns:
      #   - !Join
      #     - ''
      #     - - 'arn:'
      #       - !Ref 'AWS::Partition'
      #       - ':iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole'
      Policies:
        - PolicyName: !Sub '${Env}-python-lambda'
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource:
                  - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${Env}-python-lambda:*:*"
              - Effect: Allow
                Action:
                  - xray:PutTraceSegments
                  - xray:PutTelemetryRecords
                Resource: '*'
VPC and AWSLambdaVPCAccessExecutionRole

Provides minimum permissions for a Lambda function to execute while accessing a resource within a VPC - create, describe, delete network interfaces and write permissions to CloudWatch Logs.

Python lambda

  LambdaFunction:
    DependsOn:
      - LambdaRole
      - LambdaLogGroup
    Type: AWS::Lambda::Function
    Properties:
      Description: 'Python lambda'
      FunctionName: !Sub '${Env}-python-lambda'
      Handler: index.handler
      Runtime: python3.7
      MemorySize: 256
      Timeout: 30
      Environment:
        Variables:
          VAR_A: !Sub "${Env}.example.templating"
          VAR_B: "/A/B/C"
      # VpcConfig:
      #   SecurityGroupIds:
      #     - Fn::ImportValue: !Sub '${Env}-security-group'
      #   SubnetIds:
      #     - Fn::ImportValue: !Sub '${Env}-private-subnet-1'
      #     - Fn::ImportValue: !Sub '${Env}-private-subnet-2'
      TracingConfig:
        Mode: Active
      Role: !GetAtt LambdaRole.Arn
      Code:
        ZipFile: |
          import os
          import json

          VAR_A = os.environ['VAR_A']
          VAR_B = os.environ['VAR_B']

          def handler(event, context):
            print("Event: {}".format(event))
            body = {
              'a': VAR_A,
              'b': VAR_B,
            }
            result = {
              'statusCode': 200,
              'headers': {},
              'body': body,
            }
            return result          
VPC and Subnets

Configuration must be provided with security group ENI will be bound and a list of subnets lambda will be deployed to.

Template

Complete cloudformation template

AWSTemplateFormatVersion: '2010-09-09'
Description: 'Embedded python lambda.'

Parameters:
  Env:
    Type: 'String'
    Description: 'Logical environment dev|test etc'

Resources:

  LambdaLogGroup:
    Type: 'AWS::Logs::LogGroup'
    Properties:
      LogGroupName: !Sub '/aws/lambda/${Env}-python-lambda'
      RetentionInDays: 5

  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service:
                - 'lambda.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      # ManagedPolicyArns:
      #   - !Join
      #     - ''
      #     - - 'arn:'
      #       - !Ref 'AWS::Partition'
      #       - ':iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole'
      Policies:
        - PolicyName: !Sub '${Env}-python-lambda'
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource:
                  - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${Env}-python-lambda:*:*"
              - Effect: Allow
                Action:
                  - xray:PutTraceSegments
                  - xray:PutTelemetryRecords
                Resource: '*'

  LambdaFunction:
    DependsOn:
      - LambdaRole
      - LambdaLogGroup
    Type: AWS::Lambda::Function
    Properties:
      Description: 'Python lambda'
      FunctionName: !Sub '${Env}-python-lambda'
      Handler: index.handler
      Runtime: python3.7
      MemorySize: 256
      Timeout: 30
      Environment:
        Variables:
          VAR_A: !Sub "${Env}.example.templating"
          VAR_B: "/A/B/C"
      # VpcConfig:
      #   SecurityGroupIds:
      #     - Fn::ImportValue: !Sub '${Env}-security-group'
      #   SubnetIds:
      #     - Fn::ImportValue: !Sub '${Env}-private-subnet-1'
      #     - Fn::ImportValue: !Sub '${Env}-private-subnet-2'
      TracingConfig:
        Mode: Active
      Role: !GetAtt LambdaRole.Arn
      Code:
        ZipFile: |
          import os
          import json

          VAR_A = os.environ['VAR_A']
          VAR_B = os.environ['VAR_B']

          def handler(event, context):
            print("Event: {}".format(event))
            body = {
              'a': VAR_A,
              'b': VAR_B,
            }
            result = {
              'statusCode': 200,
              'headers': {},
              'body': body,
            }
            return result          
Notes

Handy thing when lambda is used as a glue point between services and can simply be part of infrastructure configuration.