How To Create AWS Lambda Functions Using Pulumi And Golang
Tags: go, devops, serverless
Published: 2020-01-29 17:02:59

---
title: How To Create AWS Lambda Functions Using Pulumi And Golang
published: true
date: 2020-01-28 00:00:00 UTC
tags: Go,DevOps,Serverless
canonical_url: https://retgits.com/2020/01/how-to-create-aws-lambda-functions-using-pulumi-and-golang/
---

I’ve looked at [Pulumi](https://pulumi.com) to do a bunch of things, including [creating subnets in a VPC](https://retgits.com/2020/01/how-to-add-subnets-to-a-vpc-in-aws-using-pulumi-and-golang/), [building EKS clusters](https://retgits.com/2020/01/how-to-create-an-aws-eks-cluster-using-pulumi-and-golang/), and [DynamoDB tables](https://retgits.com/2020/01/how-to-create-a-dynamodb-in-aws-using-pulumi-and-golang/). The one thing I hadn’t explored yet was how to deploy AWS Lambda functions using Pulumi, so that’s exactly what this blog is about.

_The complete project is available on [GitHub](https://github.com/retgits/pulumi-blogs/tree/lambda-blog)._

## My Lambda

The Lambda function for this post is a rather simple one, it says _“Hello”_ to whatever the value of an environment variable will be.

```
package main

import (
	"fmt"
	"os"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
)

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	val := os.Getenv("NAME")
	return events.APIGatewayProxyResponse{
		Body: fmt.Sprintf("Hello, %s", val),
		StatusCode: 200,
	}, nil
}

func main() {
	lambda.Start(handler)
}

```

That code is in a file called **hello-world.go** , which is in a folder called **hello-world**. The Pulumi project is inside the folder called **Pulumi** so it will be a separate folder structure and not collide with the other code of your Lambda functions. The folder structure, including all the files looks like

```
├── README.md
├── go.mod
├── go.sum
└── hello-world
│ └── main.go
├── pulumi
│ ├── Pulumi.lambdastack.yaml
│ ├── Pulumi.yaml
    └── main.go

```

## Building and uploading your Lambda code

In order to deploy a Lambda function, the code needs to be uploaded as well. While Pulumi has the concept of an Archive to create the zip file, the Go implementation has a [known issue](https://github.com/pulumi/pulumi/issues/1962) which makes it impossible to use that feature. Rather than manually building, zipping, and uploading the code, you can extend the Pulumi program a little to do all of that before the program runs.

```
const (
	shell = "sh"
	shellFlag = "-c"
	rootFolder = "/rootfolder/of/your/lambdaapp"
)

func runCmd(args string) error {
	cmd := exec.Command(shell, shellFlag, args)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = rootFolder
	return cmd.Run()
}

```

The `runCmd` method will run a specific command and will return either an error or a _nil_ object. To build, zip, and upload your Go code to S3 you can add the below snippets to your Pulumi project. It uses the above function to run the three commands you would normally script as part of your CI/CD or test framework. These commands should be pasted before `pulumi.Run()` gets invoked.

```
if err := runCmd("GOOS=linux GOARCH=amd64 go build -o hello-world/hello-world ./hello-world"); err != nil {
    fmt.Printf("Error building code: %s", err.Error())
    os.Exit(1)
}

if err := runCmd("zip -r -j ./hello-world/hello-world.zip ./hello-world/hello-world"); err != nil {
    fmt.Printf("Error creating zipfile: %s", err.Error())
    os.Exit(1)
}

if err := runCmd("aws s3 cp ./hello-world/hello-world.zip s3://<your-bucket>/hello-world.zip"); err != nil {
    fmt.Printf("Error creating zipfile: %s", err.Error())
    os.Exit(1)
}

```

If these commands fail, you’ll be able to see the output and the error message as part of the _“diagnostics”_ section in your terminal.

## Creating an IAM role

Every Lambda function, and most other AWS resources as well, need an IAM role to be able to work. The IAM role gives the resources the permissions to act on your behalf. This Lambda function doesn’t have a lot of specifics it needs to do, other than be able to run. To create an IAM role with that permission, you can use the below code. The ARN ([Amazon Resource Name](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html)), is exported so that it is visible from within the Pulumi console.

```
// The policy description of the IAM role, in this case only the sts:AssumeRole is needed
roleArgs := &iam.RoleArgs{
    AssumeRolePolicy: `{
        "Version": "2012-10-17",
        "Statement": [
        {
            "Action": "sts:AssumeRole",
            "Principal": {
                "Service": "lambda.amazonaws.com"
            },
            "Effect": "Allow",
            "Sid": ""
        }
        ]
    }`,
}

// Create a new role called HelloWorldIAMRole
role, err := iam.NewRole(ctx, "HelloWorldIAMRole", roleArgs)
if err != nil {
    fmt.Printf("role error: %s\n", err.Error())
    return err
}

// Export the role ARN as an output of the Pulumi stack
ctx.Export("Role ARN", role.Arn())

```

## Setting environment variables

Just like in CloudFormation, the Pulumi SDK allows you to create a set of environment variables. That’s a good thing, because the Lambda function relies on an environment variable called _“NAME”_ to know who to greet. The environment variables are a `map[string]interface{}` inside a `map[string]interface{}`. For this Lambda function, that would be the below snippet.

```
environment := make(map[string]interface{})
variables := make(map[string]interface{})
variables["NAME"] = "WORLD"
environment["variables"] = variables

```

## Creating the function

The last step. at least for this code, is to actually create the Lambda function. The **S3Bucket** and **S3Key** are the name of the bucket and the name of the file you’ve uploaded earlier in the process. The **role.Arn()** is the ARN of the role that was created in the previous step.

```
// The set of arguments for constructing a Function resource.
functionArgs := &lambda.FunctionArgs{
    Description: "My Lambda function",
    Runtime: "go1.x",
    Name: "HelloWorldFunction",
    MemorySize: 256,
    Timeout: 10,
    Handler: "hello-world",
    Environment: environment,
    S3Bucket: "<your-bucket>",
    S3Key: "hello-world.zip",
    Role: role.Arn(),
}

// NewFunction registers a new resource with the given unique name, arguments, and options.
function, err := lambda.NewFunction(ctx, "HelloWorldFunction", functionArgs)
if err != nil {
    fmt.Println(err.Error())
    return err
}

// Export the function ARN as an output of the Pulumi stack
ctx.Export("Function", function.Arn())

```

## Running Pulumi up

With all the code ready, it’s time to run `pulumi up` and deploy the Lambda function to AWS. If you need more details on how to create a Go project for Pulumi, check out [this](https://retgits.com/2020/01/how-to-create-a-vpc-in-aws-using-pulumi-and-golang/) post.

```
$ pulumi up
Previewing update (lambda):

     Type Name Plan Info
 + pulumi:pulumi:Stack lambda-lambda create 2 messages
 + ├─ aws:iam:Role HelloWorldIAMRole create     
 + └─ aws:lambda:Function HelloWorldFunction create     
 
Diagnostics:
  pulumi:pulumi:Stack (lambda-lambda):
    updating: hello-world/hello-world (deflated 49%)
upload: hello-world/hello-world.zip to s3://<your-bucket>/hello-world.zip
 
Resources:
    + 3 to create

Do you want to perform this update? yes
Updating (lambda):

     Type Name Status Info
 + pulumi:pulumi:Stack lambda-lambda created 2 messages
 + ├─ aws:iam:Role HelloWorldIAMRole created     
 + └─ aws:lambda:Function HelloWorldFunction created     
 
Diagnostics:
  pulumi:pulumi:Stack (lambda-lambda):
    updating: hello-world/hello-world (deflated 49%)
upload: hello-world/hello-world.zip to s3://<your-bucket>/hello-world.zip
 
Outputs:
    Function: "arn:aws:lambda:us-west-2:ACCOUNTID:function:HelloWorldFunction"
    Role ARN: "arn:aws:iam::ACCOUNTID:role/HelloWorldIAMRole-7532034"

Resources:
    + 3 created

Duration: 44s

Permalink: https://app.pulumi.com/retgits/lambda/lambda/updates/1

```

## Testing with the AWS Console

Inside the Pulumi console, you’ll be able to see the resources that have been created.

![The Pulumi console also has really useful links to the AWS console to see the resources.](https://retgits.com/images/how-to-create-lambdas-in-aws-using-pulumi-and-golang-1.png) <figcaption>
            <p>The Pulumi console also has really useful links to the AWS console to see the resources.</p>
        </figcaption>

Within the AWS Lambda console, you’ll be able to test the function and see that it indeed responds with the greeting _“Hello, WORLD”_.

![The AWS Lambda console.](https://retgits.com/images/how-to-create-lambdas-in-aws-using-pulumi-and-golang-2.png) <figcaption>
            <p>The AWS Lambda console.</p>
        </figcaption>