DEV Community

Cover image for ✨ Porting Lambda Functions to AWS SAM
Gianluca Fabrizi
Gianluca Fabrizi

Posted on

✨ Porting Lambda Functions to AWS SAM

Two weeks ago I attended to JsDay 2023 (from Verona, Italy 🇮🇹).
One talk hit me in particular: "Production-ready lambdas with Node.js" by Luciano Mammino.
He explained some trick, tips and best practices to work with AWS Lambda in a production environment.
One best practice is this:

"Stop creating resources manually on your AWS account, like right now! If you are doing this, please STOP"

Everyone loves meme

Ok, when he said this i felt reaaallly really guilty.

AWS SAM

There are many IAC tools that can be used with AWS. Maybe the logic option (or the one that seems to fit better) is using AWS SAM.
The AWS Serverless Application Model (SAM) is a framework for building serverless applications. It's open-source and all the configuration can be written in YAML files.
It consists of a cli tool to be installed (it's separate from aws-cli); you can use it to deploy your infrastructure, deploy (and sync) your application, for local test of your code and much much more.
I confess that i never used it (really never even heard of it...) so let's learn it by porting a previous project in SAM.

THE PROJECT

The project is my previous "Lambda Inception Architectural Pattern" (quite a mouthful, right? 🤭):
https://dev.to/gfabrizi/lambda-inception-architectural-pattern-f67

In that post I wrote blocks and blocks of code to describe roles and policies of the infrastructure... how naive!
So let's start by looking at the SAM documentation:
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-getting-started-hello-world.html
After digging the examples and the documentation I started writing my template.yaml

THE CODE

First thing first: the roles and policies.
The simplest role to be ported in SAM is the LambdaInceptionWorker:

LambdaInceptionWorker:
  Type: AWS::IAM::Role
  Properties:
    RoleName: LambdaInceptionWorker
    AssumeRolePolicyDocument:
      Version: '2012-10-17'
      Statement:
        - Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
          Action: sts:AssumeRole
Enter fullscreen mode Exit fullscreen mode

just some boilerplate code were we define a role (Type: AWS::IAM::Role) and attach a AssumeRolePolicyDocument that says that every Lambda functions can assume this role. As we saw in the previous post, the LambdaInceptionWorker role is empty, so there's nothing more to add here.

Next is the LambdaInceptionManager:

LambdaInceptionManager:
  Type: AWS::IAM::Role
  Properties:
    RoleName: LambdaInceptionManager
    AssumeRolePolicyDocument:
      Version: '2012-10-17'
      Statement:
        - Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
          Action: sts:AssumeRole
    ManagedPolicyArns:
      - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
    Policies:
      - PolicyName: LambdaInceptionPassRole
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - iam:PassRole
              Resource: !Sub arn:aws:iam::${AWS::AccountId}:role/LambdaInceptionWorker
      - PolicyName: LambdaInceptionCreateFunction
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - lambda:CreateFunction
              Resource: !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:*
      - PolicyName: LambdaInceptionDeleteFunction
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - lambda:DeleteFunction
              Resource: !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:*
      - PolicyName: LambdaInvokeFunction
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - lambda:InvokeFunction
              Resource: !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:*

Enter fullscreen mode Exit fullscreen mode

Let's analyze the code section by section:

ManagedPolicyArns:
  - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Enter fullscreen mode Exit fullscreen mode

with this we are attaching an AWS managed policy to the role (the basic execution role, needed by Lambda Function URL).
Then we started adding inline policies to the role; we see just the first policy:

- PolicyName: LambdaInceptionPassRole
  PolicyDocument:
    Version: '2012-10-17'
    Statement:
      - Effect: Allow
        Action:
          - iam:PassRole
        Resource: !Sub arn:aws:iam::${AWS::AccountId}:role/LambdaInceptionWorker
Enter fullscreen mode Exit fullscreen mode

here we are creating a new inline policy called LambdaInceptionPassRole; this policy allows the iam:PassRole action only to the specified resource.
In the Resource line we specify the LambdaInceptionWorker role by passing it's ARN. We are using a builtin variable to specify the account id (${AWS::AccountId}). The keyword !Sub at the beginning of the line indicates that the string contains a variable to be replaced with it's value. Another useful builtin variable is ${AWS::Region}.
The others 3 policies have the same structure, so we skip them.

Then we create the IAM user that will invoke the Inception manager function from the command line:

LambdaInceptionInvoker:
  Type: AWS::IAM::User
  Properties:
    UserName: lambda-inception-invoker
    Policies:
      - PolicyName: LambdaInceptionInvoke
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - lambda:InvokeFunctionUrl
              Resource: !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:lambda-inception
Enter fullscreen mode Exit fullscreen mode

The syntax of this block is the same as the previous, nothing new.

Finally we can write the definition of the Inception Manager function:

LambdaInceptionManagerFunction:
  Type: AWS::Serverless::Function
  Properties:
    FunctionName: "lambda-inception"
    CodeUri: manager/
    Handler: manager.handler
    Runtime: nodejs18.x
    Architectures:
      - x86_64
    MemorySize: 512
    Timeout: 30
    Role: !GetAtt LambdaInceptionManager.Arn
    FunctionUrlConfig:
      AuthType: AWS_IAM
    Environment:
      Variables:
        LAMBDA_INCEPTION_WORKER_ROLE: !GetAtt LambdaInceptionWorker.Arn
Enter fullscreen mode Exit fullscreen mode

we are using the type AWS::Serverless::Function to specify that we are defining a Lambda Function.
We gave it a name, specify the path where the code lies, the handler and some more common Lambda configuration.

Then we assign a role to the function. !GetAtt is another keyword that returns an attribute; in this case it returns the ARN of the LambdaInceptionManager role seen previously.

With the 3 last lines we pass a variable to the function handler. We can access this variable from the js code with process.env.LAMBDA_INCEPTION_WORKER_ROLE.

DEPLOY

The sam build command is used to processes the AWS SAM template file, application code, and any applicable language-specific files and dependencies (i.e. npm install).

Then we can launch sam deploy to deploy the infrastructure and code on AWS

SAM succesfully deployed

FINAL NOTES

It was quite a journey 😅
We saw how we can start using an IAC tool to define and manage a cloud infrastructure.
The infrastructure as defined here is far from perfect, this is just a learn-by-doing exercise.

The updated code can be downloaded from:
https://github.com/gfabrizi/lambda-inception-sam

Leave a comment for questions or issues with the code
Thank for reading! 👋

Top comments (0)