Trigger Lambda Using CloudFormation

Last updated on November 6th, 2024 at 08:27 am

In AWS CloudFormation there is a way to run Lambda function as target of a Custom Resource. To get started we need a Service token which is used to invoke Lambda function.

To trigger a Lambda function from CloudFormation, you can leverage a Custom Resource in AWS CloudFormation. This enables the inclusion of custom provisioning logic in your templates, allowing CloudFormation to execute it during stack operations. To proceed, acquire a Service token for invoking the Lambda function. This method proves valuable for provisioning workflows that cannot be expressed using CloudFormation’s built-in resource types.

To start lets go through the steps below

Step 1 Create Custom Resource Definition

The service token represents Amazon Resource Name (ARN) of the function that AWS CloudFormation triggers when creating, updating, or deleting the stack, and allows for the inclusion of extra properties such as FunctionName, which AWS CloudFormation directly passes to your function.

Resources:
  LambdaCustomResource:
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !GetAtt 'RunMyLambda.Arn'
      ParameterOne: "Print me when it is ready"

Step 2 Specify Lambda Function as target

Within your CloudFormation template, you have the option to designate a Lambda function as the intended recipient of a unique resource. By utilizing the ZipFile attribute to designate the function’s source code, you are able to incorporate the cfn-response module, enabling the dispatch of responses from your Lambda function to the specified custom resource.

Note: The cfn-response module is available only when you use the ZipFile property to write your source code.

For Lambda code in S3 buckets, you must write your own functions to send responses
  RunMyLambda:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import json
          import cfnresponse
          def lambda_handler(event, context):
           print("log -- Event: %s " % json.dumps(event))
           responseData = {'Message': 'Hello_From_Lambda'}
           cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData,"CustomResourcePhysicalID")
      Handler: index.lambda_handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Runtime: python3.9
      Timeout: '120'
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: arn:aws:logs:*:*:*
              - Effect: Allow
                Action:
                  - ec2:DescribeImages
                Resource: '*'

Explanation of the code above

1] Defined a Lambda function code using zipfile under resource named AWS::Lambda::Function

2] Inside the LambdaExecutionRole we provided permission to create a log group when the Lambda gets executed and put the logs inside that log group. This will help us verify the Lambda logs once the CloudFormation is executed successfully.

3] In the role I have also provided ec2:DescribeImages permission for Lambda. This is just to show that if your Lambda function need to call specific API’s you can add them there.

Step 3 Get Lambda function response in CloudFormation Outputs

In order to get the function response all you have to do is add this

Outputs:
  MyOutput:
    Description: Lambda Output
    Value: !GetAtt LambdaCustomResource.Message

In this case LambdaCustomResource is the Service token we created initially in Step 1. The message is thrown from this line of code

responseData = {'Message': 'Hello_From_Lambda'}

You can change Message to any key as per your requirement.

Step 4 CloudFormtion output and Lambda logs in CloudWatch

Before we delve into the CloudFormation details, here is the complete YAML file used for this tutorial

AWSTemplateFormatVersion: '2010-09-09'
Description: 'AWS CloudFormation template for testing Lambda trigger.'
Resources:
  LambdaCustomResource:
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !GetAtt 'AMIInfoFunction.Arn'
      ParameterOne: "Print me when it is ready"
  AMIInfoFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
           import json
           import cfnresponse
           def lambda_handler(event, context):
            print("log -- Event: %s " % json.dumps(event))
            responseData = {'Message': 'Hello_From_Lambda'}
            cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData,"CustomResourcePhysicalID")
      Handler: "index.lambda_handler"
      Role: !GetAtt LambdaExecutionRole.Arn
      Runtime: python3.9
      Timeout: '120'
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: arn:aws:logs:*:*:*
              - Effect: Allow
                Action:
                  - ec2:DescribeImages
                Resource: '*'
Outputs:
  MyOutput:
    Description: Lambda Output
    Value: !GetAtt LambdaCustomResource.Message

After running the CloudFormation template, you will receive the following output

Once you see the above CloudFormation output, the next step is to navigate to CloudWatch where the Lambda logs reside.

As you can see we have a ResponseURL section with presigned S3 bucket that receives responses from the custom resource provider to AWS CloudFormation, more details can be found here. It also have the parameter we passed as part of the ServiceToken.

The above screenshot has the final response from our Lambda function that got pushed to CloudFormation as its output.