In this example we are going to create an AWS Lambda Terraform module for Golang. We will also create an AWS CloudWatch log groups dedicated to this Lambda function.


Your Terraform user will require ec2.*, iam:*, lambda:*, logs.* AWS permissions using aws_iam_policy_document. You should actually just pick what exactly is required rather than allowing everything.


Structure


blog
├── .gitignore
├── cmd
│   └── greeter
│   └── main.go
├── go.mod
├── go.sum
└── terraform
├── development
│   └── lambda.tf
└── modules
└── lambda_go
├── main.tf
└── variables.tf

.gitignore


bin/
tmp/

.terraform/
terraform.tfstate*

main.go


package main

import (
"context"
"fmt"
"os"

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

type Request struct {
Name string `json:"name"`
}

type Response struct {
Greeting string `json:"greeting"`
}

func HandleRequest(ctx context.Context, req Request) (Response, error) {
fmt.Printf("Request (%s - %s): %+v\n", os.Getenv("ENV"), os.Getenv("VER"), req)

return Response{Greeting: fmt.Sprintf("Hello %s!", req.Name)}, nil
}

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

lambda.tf


provider "aws" {
region = "eu-west-1"
profile = "development"
}

module "greeter_lambda" {
source = "../modules/lambda_go"

aws_region = "eu-west-1"
aws_profile = "development"

app_repository = "blog"

lambda_timeout = 5
lambda_memory_size = 128
lambda_function_name = "greeter"
lambda_environment_vars = {
ENV = "dev"
VER = "v0.0.1"
}

go_source_path = "${path.module}/../../cmd/greeter/..."
go_binary_path = "${path.module}/../../bin/greeter"
go_zip_path = "${path.module}/../../tmp/greeter.zip"

log_retention = 30
}

variables.tf


variable "aws_region" {
description = "AWS region where the resources are provisioned."
type = string
}

variable "aws_profile" {
description = "AWS profile where the application is provisioned."
type = string
}

variable "app_repository" {
description = "The application repository name."
type = string
}

variable "lambda_timeout" {
description = "Amount of time the Lambda function can run in seconds."
type = number
}

variable "lambda_memory_size" {
description = "Amount of memory the Lambda function can use at runtime in MB."
type = number
}

variable "lambda_function_name" {
description = "The name of the Lambda function."
type = string
}

variable "lambda_environment_vars" {
description = "A list of optional Lambda environment variables."
type = map(string)
}

variable "go_source_path" {
description = "The source path for the Golang code."
type = string
}

variable "go_binary_path" {
description = "The binary path for the Golang code."
type = string
}

variable "go_zip_path" {
description = "The zip path for the Lambda function."
type = string
}

variable "log_retention" {
description = "The number of days to retain log events in the log group."
type = number
}

main.go


terraform {
required_version = "~> 1.4.4"

required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.30.0"
}

archive = {
source = "hashicorp/archive"
version = "~> 2.3.0"
}

null = {
source = "hashicorp/null"
version = "~> 3.2.1"
}
}
}

# LOCALS -----------------------------------------------------------------------

locals {
lambda_environment_vars = var.lambda_environment_vars[*]
}

# FUNCTION ---------------------------------------------------------------------

resource "null_resource" "go_binary_file" {
provisioner "local-exec" {
command = "GOOS=linux GOARCH=amd64 CGO_ENABLED=0 GOFLAGS=-trimpath go build -mod=readonly -ldflags='-s -w' -o ${var.go_binary_path} ${var.go_source_path}"
}
}

data "archive_file" "go_zip_file" {
type = "zip"
source_file = var.go_binary_path
output_path = var.go_zip_path

depends_on = [
null_resource.go_binary_file,
]
}

resource "aws_lambda_function" "go" {
function_name = "${var.app_repository}-${var.lambda_function_name}"
handler = var.lambda_function_name
filename = var.go_zip_path
package_type = "Zip"
runtime = "go1.x"
timeout = var.lambda_timeout
memory_size = var.lambda_memory_size

role = aws_iam_role.lambda_execution.arn
source_code_hash = data.archive_file.go_zip_file.output_base64sha256

dynamic "environment" {
for_each = local.lambda_environment_vars
content {
variables = environment.value
}
}

depends_on = [
aws_iam_role_policy_attachment.lambda_logs,
aws_cloudwatch_log_group.lambda_log_group,
]
}

# POLICY DOCUMENTS -------------------------------------------------------------

data "aws_iam_policy_document" "lambda_execution" {
version = "2012-10-17"
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
}
}

data "aws_iam_policy_document" "lambda_logging" {
version = "2012-10-17"
statement {
effect = "Allow"
actions = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
]
resources = ["arn:aws:logs:*:*:*"]
}
}

# ROLE -------------------------------------------------------------------------

resource "aws_iam_role" "lambda_execution" {
name = "lambda-execution"
description = "Assume execution role when invoked."

assume_role_policy = data.aws_iam_policy_document.lambda_execution.json
}

# LOGGING ----------------------------------------------------------------------

resource "aws_iam_policy" "lambda_logging" {
name = "lambda-logging"
description = "Grant logging role permissions."

policy = data.aws_iam_policy_document.lambda_logging.json
}

resource "aws_iam_role_policy_attachment" "lambda_logs" {
role = aws_iam_role.lambda_execution.name
policy_arn = aws_iam_policy.lambda_logging.arn
}

resource "aws_cloudwatch_log_group" "lambda_log_group" {
name = "/aws/lambda/${var.app_repository}-${var.lambda_function_name}"
retention_in_days = var.log_retention
}

AWS CLI config


[default]
region = eu-west-1
output = json

[profile development]
source_profile = default
role_session_name = github-actions
role_arn = arn:aws:iam::1234567890:role/provisioner

[default]
aws_access_key_id = QWERTYU12345678
aws_secret_access_key = QWERTY123456qwertyu09876

Setup


blog/terraform/development$ terraform apply

Result