✨ Porting Lambda Functions to AWS SAM
Tags: aws, lambda, sam, serverless
Published: 2023-04-23 21:55:22

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6wwp1o1favxe7frrunt4.png)

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`:
```yaml
LambdaInceptionWorker:
  Type: AWS::IAM::Role
  Properties:
    RoleName: LambdaInceptionWorker
    AssumeRolePolicyDocument:
      Version: '2012-10-17'
      Statement:
        - Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
          Action: sts:AssumeRole
```
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`:
```yaml
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:*

```
Let's analyze the code section by section:
```yaml
ManagedPolicyArns:
  - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
```
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:
```yaml
- PolicyName: LambdaInceptionPassRole
  PolicyDocument:
    Version: '2012-10-17'
    Statement:
      - Effect: Allow
        Action:
          - iam:PassRole
        Resource: !Sub arn:aws:iam::${AWS::AccountId}:role/LambdaInceptionWorker
```
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:
```yaml
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
```
The syntax of this block is the same as the previous, nothing new.

Finally we can write the definition of the Inception Manager function:
```yaml
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
```
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dckxrpq95absrs25yxbe.png)

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