Exploring AWS Lambda Function URL using .NET Container - Part 1
Tags: dotnet, docker, aws, serverless
Published: 2022-04-30 05:35:33

## Preparation

You will need several tools, like

1. .NET SDK. Please download [here](https://dotnet.microsoft.com/en-us/)
2. AWS Lambda Templates for .NET. You may check [here](https://docs.aws.amazon.com/lambda/latest/dg/csharp-package-cli.html). Basically you need this [template](https://www.nuget.org/packages/Amazon.Lambda.Templates), using this command: `dotnet new --install Amazon.Lambda.Templates::6.1.0`
3. Dotnet deployment tools (optional). Using this command: `dotnet tool install -g Amazon.Lambda.Tools`.
4. Terraform CLI (optional). I will use Github Action and Terraform Cloud for the deployment of infrastructure.

## Generate AWS Lambda Project from templates

There are many options that you may use and explore. In this article, I will use Web API with Container Image.

![Template list](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/61zlmu020mhsl21pmqeb.png)

You may use this command to generate the project:

```bash
dotnet new serverless.image.AspNetCoreWebAPI --name Demo
```

After that, please copy the folder/directory inside Demo to the root directory of the project. Previously you will have files like this:

```
Demo/src/Demo
Demo/test/Demo.Test
```

Please move it, so become like this:

```
src/Demo
test/Demo.Test
```

Well done! Let's move on to the next step.

## Terraform

I use Terraform to manage my Lambda setup. Unfortunately, I still create/manage Amazon ECR manually, so I will not include the set up of ECR here. My configuration will be like this:

{% collapsible main.tf %}
```terraform
resource "aws_iam_role" "iam_for_lambda" {
  name = "iam_for_lambda"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}

resource "aws_lambda_function" "lambda_container_demo" {
  function_name = "lambda_container_demo"
  role          = aws_iam_role.iam_for_lambda.arn
  image_uri     = "<your image location in here>" # please update your image inside the quotes
  publish       = true
  package_type  = "Image"

  tags = {
    "env" = "dev"
  }
}

resource "aws_lambda_function_url" "lambda_container_demo_dev" {
  function_name      = aws_lambda_function.lambda_container_demo.function_name
  authorization_type = "NONE"
  cors {
    allow_credentials = true
    allow_origins     = ["*"]
    allow_methods     = ["GET", "POST", "PUT", "DELETE"]
    allow_headers     = ["date", "keep-alive"]
    expose_headers    = ["keep-alive", "date"]
    max_age           = 300
  }
}

resource "aws_lambda_function_url" "lambda_container_demo_prod" {
  function_name      = aws_lambda_function.lambda_container_demo.function_name
  authorization_type = "AWS_IAM"

  cors {
    allow_credentials = true
    allow_origins     = ["*"]
    allow_methods     = ["*"]
    allow_headers     = ["date", "keep-alive"]
    expose_headers    = ["keep-alive", "date"]
    max_age           = 86400
  }
}
```
{% endcollapsible %}

You may use that configuration or customize it. Please change the `image_uri` before you use it.

* provider.tf

```terraform
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
  cloud {
    organization = "<org>" # change with your organization

    workspaces {
      name = "<workspace-name>" # please update the workspace name inside the quotes
    }
  }
}

# Configure the AWS Provider
provider "aws" {
  region = "ap-southeast-1" # you may use another region
}
```

I store the Terraform scripts inside terraform directory. So it will look like this:

```
src/Demo
test/Demo.Test
terraform/
```

## Prepare your Amazon ECR and Github Action

I will cover the Github Action only. If you are curious to prepare the Amazon ECR, please visit this post.

{% embed https://dev.to/berviantoleo/deploy-net-6-web-api-docker-image-to-amazon-ecr-using-circleci-e5h %}

{% collapsible "github/workflows/deploy.yml" %}

```yml
name: Deploy
on:
  push:
    branches: [ main ]
    tags:
      - v*
  pull_request:
env:
  IMAGE_NAME: 'lambda-sharp'
  PROJECT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
jobs:
  build-push-docker:
    runs-on: ubuntu-20.04
    steps:
    - uses: actions/checkout@v3
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.DEMO_ECR_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.DEMO_ECR_SECRET_KEY }}
        aws-region: ap-southeast-1
    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v1
      with:
        registries: ${{ secrets.AWS_ACCOUNT_ID }}
    - name: Extract metadata (tags, labels) for Docker
      id: meta
      uses: docker/metadata-action@v3
      with:
        images: ${{ env.ECR_REGISTRY }}/${{ env.IMAGE_NAME }}
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
    - name: Build and push Docker images
      uses: docker/build-push-action@v2
      with:
        context: src/SimpleAPI
        push: true
        tags: ${{ steps.meta.outputs.tags }}
        labels: ${{ steps.meta.outputs.labels }}
  terraform:
    name: 'Terraform'
    needs: build-push-docker
    runs-on: ubuntu-20.04
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
      - name: Terraform Format
        id: fmt
        run: terraform fmt -check
        working-directory: ./terraform
      - name: Terraform Init
        id: init
        run: terraform init
        working-directory: ./terraform
      - name: Terraform Validate
        id: validate
        run: terraform validate -no-color
        working-directory: ./terraform
      - name: Terraform Plan
        id: plan
        if: github.event_name == 'pull_request'
        run: terraform plan -no-color
        continue-on-error: true
        working-directory: ./terraform
      - name: Update Pull Request
        uses: actions/github-script@v6.0.0
        if: github.event_name == 'pull_request'
        env:
          PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
            #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
            #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
            #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
            <details><summary>Show Plan</summary>
            \`\`\`\n
            ${process.env.PLAN}
            \`\`\`
            </details>
            *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`;
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: output
            })
      - name: Terraform Plan Status
        if: steps.plan.outcome == 'failure'
        run: exit 1
      - name: Terraform Apply
        if: github.ref == 'refs/heads/main' && github.event_name == 'push'
        working-directory: ./terraform
        run: terraform apply -auto-approve
```

{% endcollapsible %}

So, I use two different jobs to deploy my Lambda. 1) I will deploy my Docker Image to ECR. 2) Provision and sync my AWS Lambda with terraform. Please check my architecture here:

![Arch](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/73yfe9poi4xruv7ncy11.jpg)

## Push to Github!

Yeah, if you have set up all of them. You may push it to Github. Note: Please set up the Terraform Cloud with `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`. In the Github, you will need those credentials and Terraform API Key/Token (to store/check state in the Terraform Cloud.

![Configuration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3eg7wk0lx3p9posrdt7k.png)

You may check my repository.

{% github bervProject/lambda-sharp %}

## Test our Lambda using Function URL

You may check the Lambda Function URL in Lambda Menu.

![Lambda](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nk0vt2ncko11nume9xp2.png)

Next, please visit the URL, and you may find it like this.

![Root URL](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1rnreiduv0ugl5oryhak.png)
 
After that, we may check another URL.

![another API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ezgrzsyn10uw7iczp810.png)

## Other Resources

You might want to know more about AWS Lambda Functions and Terraform. I suggest visiting an article [How to Manage AWS Lambda Functions with Terraform](https://spacelift.io/blog/terraform-aws-lambda).

## Thank you

If you have some questions, please don't hesitate to ask me on Twitter or directly comment here.

* https://twitter.com/berviantoleo
 
Thank you.

![WOW GIF](https://media.giphy.com/media/b8RfbQFaOs1rO10ren/giphy.gif)

## What's Next?

I will try to cover Lambda to connect with Amazon RDS (Relational Database Service). After that, I will cover Lambda to be called by any Frontend(s), and maybe I will use React. Stay tune!