AWS CloudFormation script for Lambda and SNS integration

AWS CloudFormation is a service that helps us to set up your Amazon Web Services. We just need to create a template that describes all the AWS resources that we want (like SNS, Lambda), and AWS CloudFormation takes care of provisioning and configuring those resources for us. We don't need to individually create and configure AWS resources and figure out what's dependent on what.

This post decribes how to write cloud formation script for creating a Lambda function, SNS topic and integrating those two to trigger the Lambda function from SNS.

We can write cloud formation scripts in Visual studio directly if we installed AWS Toolkit for Visual studio.
Once you installed the AWS toolkit, open Visualstudio -> Click on Solution -> Add -> New Project -> AWS -> Select AWS Cloud Formation Project.



Select "Create with Empty Template" in the next screen. It will create a project with empty template. The template will look like as follows
{
 "AWSTemplateFormatVersion" : "2010-09-09",

 "Description" : "",

 "Parameters" : {
 },

 "Resources" : {
 },

 "Outputs" : {
 }
}
It contains four sections
  1. Description: Decription about the cloud formation template
  2. Parameters: Any dynamic values requires while creating the resources like environment name, existing resources ars etc.
  3. Resources: AWS resourcs which needs to be created
  4. Outputs: Output values like the resource arn etc.

Now the first step for our cloud formation template is we need to create a Exectuiont role for Lambda. 

Lambda Execution Role

Before we can build a Lambda Function, we need to create some permissions for it to assume at runtime. The below role is a minimal role suitable for a basic Lambda Function with no external integration points. Additional permissions (e.g. reading from an S3 Bucket) can be added to the list of Statements in the PolicyDocument. 
"LambdaExecutionRole" : {
 "Type" : "AWS::IAM::Role",
 "Properties" : {
  "AssumeRolePolicyDocument" : {
   "Version" : "2012-10-17",
   "Statement" : [
    {
     "Action" : [
      "sts:AssumeRole"
     ],
     "Effect" : "Allow",
     "Principal" : {
      "Service" : [
       "lambda.amazonaws.com"
      ]
     }
    }
   ]
  },
  "Policies"                 : [
   {
    "PolicyName" : "lambda-test-policy",
    "PolicyDocument" : {
     "Version" : "2012-10-17",
     "Statement" : [
      {
       "Effect" : "Allow",
       "Action" : [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:GetLogEvents",
        "logs:PutLogEvents"
       ],
       "Resource" : "*"
      }
     ]
    }
   }
  ]
 }
}

Lambda Function

Following is the template for creating a lambda function. My lambda function will get the code from S3 bucket.
"Lambda"              : {
 "Type" : "AWS::Lambda::Function",
 "Properties" : {
  "FunctionName" : {
   "Fn::Sub" : "lbd-example-${Environment}"
  },
  "Description"  : "Lambda function triggered by SNS",
  "Handler"      : "Lambda.Test::Lambda.Test.Function::FunctionHandler",
  "Role"         : {
   "Fn::GetAtt" : [
    "LambdaExecutionRole",
    "Arn"
   ]
  },
  "Runtime"      : "dotnetcore2.1",
  "MemorySize"   : 256,
  "Timeout"      : 600,
  "Tags"         : [
   {
    "Key" : {
     "Fn::Sub" : "${Environment}"
    },
    "Value" : ""
   }
  ],
  "Code"         : {
   "S3Bucket" : "my-bucket",
   "S3Key"    : "deployments/dev/LambdaTest.zip"
  }
 },
 "DependsOn"  : [
  "LambdaExecutionRole"
 ]
}
Note: ${Environment} is reference to my parameter

SNS Topic

Following is the template for creating a SNS Topic. SNS Topic is very simple, just the topic name and it subscriptions.
"SNSTopic" : {
 "Type" : "AWS::SNS::Topic",
 "Properties" : {
  "DisplayName" : {
   "Fn::Sub" : "test-sns-${Environment}"
  },
  "TopicName"   : {
   "Fn::Sub" : "test-sns-${Environment}"
  },
  "Subscription" : [
   {
    "Endpoint" : {
     "Fn::GetAtt" : [
      "Lambda",
      "Arn"
     ]
    },
    "Protocol" : "lambda"
   }
  ]
 },
 "DependsOn"  : "Lambda"
}

Lambda invoke Permission for the Topic

The important thing here is to give Lambda invoke permission to SNS.In cloud formation, creating Lambda, SNS topic resource will not work. We need to grant permission to SNS topic to invoke Lambda function directly. Following is my template for lambda permission.
"LambdaInvokePermission" : {
 "Type" : "AWS::Lambda::Permission",
 "Properties" : {
  "Action" : "lambda:InvokeFunction",
  "Principal" : "sns.amazonaws.com",
  "SourceArn" : {
   "Ref" : "SNSTopic"
  },
  "FunctionName" : {
   "Fn::GetAtt" : [
    "Lambda",
    "Arn"
   ]
  }
 }
}
Note: Here "SourceArn" property refer to the SNS Topic we created. Visual studio shows "SNSTopic is invalid type for this reference" for this property. But no worries. It is in the correct format and will deploy. 

Following is my complete cloud formation template
{
    "AWSTemplateFormatVersion" : "2010-09-09",
    "Description"              : "Test Lambda SNS integration",
    "Parameters"               : {
        "Environment" : {
            "Type" : "String",
            "Default" : "dev",
            "Description" : "Environment name for which the components are deploying",
            "AllowedPattern" : "^[a-z]*$"
        }
    },
    "Resources"                : {
        "LambdaExecutionRole" : {
            "Type" : "AWS::IAM::Role",
            "Properties" : {
                "AssumeRolePolicyDocument" : {
                    "Version" : "2012-10-17",
                    "Statement" : [
                        {
                            "Action" : [
                                "sts:AssumeRole"
                            ],
                            "Effect" : "Allow",
                            "Principal" : {
                                "Service" : [
                                    "lambda.amazonaws.com"
                                ]
                            }
                        }
                    ]
                },
                "Policies"                 : [
                    {
                        "PolicyName" : "lambda-test-policy",
                        "PolicyDocument" : {
                            "Version" : "2012-10-17",
                            "Statement" : [
                                {
                                    "Effect" : "Allow",
                                    "Action" : [
                                        "logs:CreateLogGroup",
                                        "logs:CreateLogStream",
                                        "logs:GetLogEvents",
                                        "logs:PutLogEvents"
                                    ],
                                    "Resource" : "*"
                                }
                            ]
                        }
                    }
                ]
            }
        },
        "Lambda"              : {
            "Type" : "AWS::Lambda::Function",
            "Properties" : {
                "FunctionName" : {
                    "Fn::Sub" : "lbd-example-${Environment}"
                },
                "Description"  : "Lambda function triggered by SNS",
                "Handler"      : "Lambda.Test::Lambda.Test.Function::FunctionHandler",
                "Role"         : {
                    "Fn::GetAtt" : [
                        "LambdaExecutionRole",
                        "Arn"
                    ]
                },
                "Runtime"      : "dotnetcore2.1",
                "MemorySize"   : 256,
                "Timeout"      : 600,
                "Tags"         : [
                    {
                        "Key" : {
                            "Fn::Sub" : "${Environment}"
                        },
                        "Value" : ""
                    }
                ],
                "Code"         : {
                    "S3Bucket" : "my-bucket",
                    "S3Key"    : "deployments/dev/LambdaTest.zip"
                }
            },
            "DependsOn"  : [
                "LambdaExecutionRole"
            ]
        },
        "SNSTopic"            : {
            "Type" : "AWS::SNS::Topic",
            "Properties" : {
                "DisplayName" : {
                    "Fn::Sub" : "test-sns-${Environment}"
                },
                "TopicName"   : {
                    "Fn::Sub" : "test-sns-${Environment}"
                },
                "Subscription" : [
                    {
                        "Endpoint" : {
                            "Fn::GetAtt" : [
                                "Lambda",
                                "Arn"
                            ]
                        },
                        "Protocol" : "lambda"
                    }
                ]
            },
            "DependsOn"  : "Lambda"
        },
        "LambdaInvokePermission" : {
            "Type" : "AWS::Lambda::Permission",
            "Properties" : {
                "Action" : "lambda:InvokeFunction",
                "Principal" : "sns.amazonaws.com",
                "SourceArn" : {
                    "Ref" : "SNSTopic"
                },
                "FunctionName" : {
                    "Fn::GetAtt" : [
                        "Lambda",
                        "Arn"
                    ]
                }
            }
        }
    },
    "Outputs"                  : {
        "Topic" : {
            "Description" : "Test Topic",
            "Value"       : {
                "Ref" : "SNSTopic"
            }
        },
        "Lambda" : {
            "Description" : "Test Lambda",
            "Value"       : {
                "Fn::GetAtt" : [
                    "Lambda",
                    "Arn"
                ]
            }
        }
    }
}
Happy Coding 😊!

Gopikrishna

    Blogger Comment
    Facebook Comment

0 comments:

Post a Comment