Showing posts with label Lambda. Show all posts
Showing posts with label Lambda. Show all posts

Creating a Lambda function in .netCore with Visual Studio and AWS Visual Studio Toolkit

AWS allows .net Core framework also while creating Lambda functions. But the AWS console doesn't have any editor for .Net Core framework. It just has the option to upload the code in a zip file. We need to create the Lambda function in our local machine and upload the package file. We can create the Lambda function easily in Visual Studio with the template given in AWS Toolkit for Visual Studio. AWS Toolkit for Visual Studio have some templates for creating AWS Lambda functions easily using .NET Core. This post will guide you to create a Lambda function in .Net core using Visual Studio

Prerequisites

Following are the prerequisites for creating and deploying Lambda functions in Visual Studio

Create a .NET Core Lambda in Visual Studio

Following are the steps to create a Lambda function in Visual Studio
1) Open Visual Studio -> File menu -> New -> Project.
2) In the New Project dialog box, Select "AWS Lambda" under Installed -> Visual C#

     It will show you two types of project.
a) AWS Lambda project: These templates are for creating a project to develop and deploy an                 individual Lambda function.
b) AWS Serverless Application: These templates are for creating Lambda functions with a server-less AWS CloudFormation template. AWS server-less applications enable you to define more than just the function. For example, you can simultaneously create a database, add IAM roles, etc., with server-less deployment. AWS server-less applications also enable you to deploy multiple functions at one time.

3) Select AWS Lambda Project (.NET Core - C#) template.
4) Enter Name and Location for the project.
5) In the next screen, select Blue print for the Lambda you want to develop. For this example, I am selecting Empty Template.
6) It will create the project with the following structure 
Here, we need to examine two files
  • Function.cs
  • aws-lambda-tools-defaults.json

Function.cs 

Following is the sample code which was created automatically in Function.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using Amazon.Lambda.Core;

// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]

namespace LambdaSample
{
    public class Function
    {
        
        /// 
        /// A simple function that takes a string and does a ToUpper
        /// 
        /// 
        /// 
        /// 
        public string FunctionHandler(string input, ILambdaContext context)
        {
            return input?.ToUpper();
        }
    }
}
Here FunctionHandler is the function where we need to write out Lambda function code. The above code will take a string as parameter and will return the same in UPPER Case. Here ILambdaContext object provides properties with information about the invocation, function, and execution environment(Read more about ILambdaContext here). For the time being, I am not writing any logic in the function, but I just added some logs. My FunctionHandler is as below after adding some logs to it
public string FunctionHandler(string input, ILambdaContext context)
{
 context.Logger.LogLine($"Input string:{input}");
 var upperCaseValue = input?.ToUpper();
 context.Logger.LogLine($"Upper case value:{upperCaseValue}");
 return upperCaseValue;
}

aws-lambda-tools-defaults.json

This is the file from where the Lambda function creator reads the default values while deploying the lambda function. You can set the default values like, framework, run time, memory size, timeout, function-handler etc parameters required for Lambda. Here function-handler defines the starting functionof the Lambda. For our sample it is "LambdaSample::LambdaSample.Function::FunctionHandler". In this, first part indicates the NameSpace(LambdaSample), second part indicates the ClassName with namespace(LambdaSample.Function) and third part indicates the Function name in the class(FunctionHandler).

Following is the aws-lambda-tools-defaults.json file created automatically by Visual Studio
{
  "Information" : [
    "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.",
    "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.",

    "dotnet lambda help",

    "All the command line options for the Lambda command can be specified in this file."
  ],

  "profile":"default",
  "region" : "us-east-1",
  "configuration" : "Release",
  "framework" : "netcoreapp2.1",
  "function-runtime":"dotnetcore2.1",
  "function-memory-size" : 256,
  "function-timeout" : 30,
  "function-handler" : "LambdaSample::LambdaSample.Function::FunctionHandler"
}

Note: If you change the class name or function name, you need to update function-handler in the above file

Publish the Lambda Function

1) In Solution Explorer, right-click the project, and then choose Publish to AWS Lambda.

2) On the Upload Lambda Function page, in Function Name, type a name for the function or select a previously published function to republish -> Choose Next.

3) In the Advanced Function Details page,
  • Select Existing role: Select any role assoicated with your role. The role is used to provide credentials for any AWS service calls made by the code in the function. Your account should have IAM:ListPolicies action, or the Role Name list will be empty and you will be unable to continue.
  • Change Memory & Timeout values if required. 
  • Assign VPC and its Subnets if those are required by your Lambda
  • Add any Environment variable that your Lambda function needs

4) Click on Upload
5) This will open Uploading function page as shown below and the screen will automatically closes after Lambda function uploaded to AWS account

6) After function uploaded successfully, it will opens the following screen which allows you to execute the lambda function, change the Configuration,Event Sources and check the logs from Visual Studio. 

Note: You can also open the above screen for any existing Lambda function by Opening "AWS Explorer" -> AWS Lambda -> Double click on the required function.

Invoke the Function from Visual Studio

We can invoke the Lambda function from Visual Studio directly by the screen shown in the above image

1) Select any Example Request to choose any predefined request. But for our sample, it is just accepting a string as input, I have given "gopiportal_test" in the Request box


2) Click on "Invoke" button
3) This will execute the Lambda function automatically and will show the Response and the function logs

That's it...
Happy Coding !😊

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 😊!