Forem

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

Image of Datadog

The Future of AI, LLMs, and Observability on Google Cloud

Datadog sat down with Google’s Director of AI to discuss the current and future states of AI, ML, and LLMs on Google Cloud. Discover 7 key insights for technical leaders, covering everything from upskilling teams to observability best practices

Learn More

Top comments (0)

Image of Datadog

The Essential Toolkit for Front-end Developers

Take a user-centric approach to front-end monitoring that evolves alongside increasingly complex frameworks and single-page applications.

Get The Kit

👋 Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay