Serverless Architecture with Lambda

First, create a Terraform module for a Lambda function and its pipeline using CodeBuild:

modules/lambda-pipeline/main.tf

# create a role for the function to assume # see the docs: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role resource "aws_iam_role" "iam_for_lambda" { name = "iam_for_lambda_${var.function_name}" assume_role_policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Action": "sts:AssumeRole", "Principal": { "Service": "lambda.amazonaws.com" }, "Effect": "Allow", "Sid": "" } ] } EOF } locals { environment_map = var.env_vars == null ? [] : [var.env_vars] artifact_key = "artifact.zip" } # create a Lambda function # see the docs: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function resource "aws_lambda_function" "lambda_func" { s3_bucket = var.s3_bucket s3_key = "${var.s3_key_prefix}/${local.artifact_key}" function_name = var.function_name role = aws_iam_role.iam_for_lambda.arn handler = var.handler dynamic "environment" { for_each = local.environment_map content { variables = environment.value } } # see all available runtimes here: https://docs.aws.amazon.com/lambda/latest/dg/API_CreateFunction.html#SSS-CreateFunction-request-Runtime runtime = var.runtime } # create a policy for publishing logs to CloudWatch # and reading messages from SQS resource "aws_iam_policy" "lambda_logging" { name = "lambda_logging_${var.function_name}" description = "IAM policy for logging from a lambda and receiving SQS messages" policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "arn:aws:logs:*:*:*", "Effect": "Allow" } ] } EOF } # attach the above policy to the function role # see the docs: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment resource "aws_iam_role_policy_attachment" "lambda_logs" { role = aws_iam_role.iam_for_lambda.name policy_arn = aws_iam_policy.lambda_logging.arn } ########## CodeBuild ######### # create a CodeBuild project resource "aws_codebuild_project" "project" { name = "build-${var.function_name}" build_timeout = "20" service_role = aws_iam_role.cbuild_role.arn artifacts { type = "NO_ARTIFACTS" } environment { compute_type = "BUILD_GENERAL1_SMALL" image = "aws/codebuild/standard:6.0" type = "LINUX_CONTAINER" environment_variable { name = "BUCKET_NAME" value = var.s3_bucket } environment_variable { name = "KEY_PREFIX" value = var.s3_key_prefix } environment_variable { name = "ARTIFACT_KEY" value = local.artifact_key } environment_variable { name = "FUNC_NAME" value = var.function_name } } logs_config { cloudwatch_logs { group_name = "build-${var.function_name}" } } # set the source to our repo source { type = "GITHUB" location = var.repo_address git_clone_depth = 1 } # set the branch name for the pipeline source_version = var.branch_name } # create a role for CodeBuild service to assume resource "aws_iam_role" "cbuild_role" { name = "role-build-${var.function_name}" assume_role_policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "codebuild.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOF } # attach an IAM policy to CodeBuild role # for accessing the S3 bucket resource "aws_iam_role_policy" "this" { role = aws_iam_role.cbuild_role.name policy = <<POLICY { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Resource": [ "*" ], "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ] }, { "Effect": "Allow", "Action": [ "s3:*" ], "Resource": ["${var.s3_bucket_arn}", "${var.s3_bucket_arn}/*"] }, { "Effect": "Allow", "Action": [ "lambda:UpdateFunctionCode" ], "Resource": ["${aws_lambda_function.lambda_func.arn}"] } ] } POLICY } # create a webhook so the projects can detect # changes in the repo and start a new build # note that you need to remove this section (or comment out) # and build whatever's above this resource first # you then need to change the source section of the # CodeBuild project to connect to your GitHub repo # after that, you can create this resource resource "aws_codebuild_webhook" "this" { project_name = aws_codebuild_project.project.name build_type = "BUILD" filter_group { filter { type = "EVENT" pattern = "PUSH" } filter { type = "HEAD_REF" pattern = var.branch_name } } }

modules/lambda-pipeline/variables.tf

variable "function_name" { type = string } variable "s3_bucket" { type = string } variable "s3_bucket_arn" { type = string } variable "s3_key_prefix" { type = string default = "artifact.zip" } variable "handler" { type = string default = "main.handler" } variable "runtime" { type = string default = "python3.8" } variable "env_vars" { type = map(any) default = null } variable "repo_address" { type = string } variable "branch_name" { type = string default = "main" }

Then in the root module:

main.tf

resource "aws_s3_bucket" "name" { } module "python_lambda" { source = "./modules/lambda-pipeline" function_name = "python-test" s3_bucket = aws_s3_bucket.name.bucket s3_bucket_arn = aws_s3_bucket.name.arn s3_key_prefix = "python-test" repo_address = "<REPO>" } module "go_lambda" { source = "./modules/lambda-pipeline" function_name = "go-test" s3_bucket = aws_s3_bucket.name.bucket s3_bucket_arn = aws_s3_bucket.name.arn s3_key_prefix = "go-test" repo_address = "<REPO>" runtime = "go1.x" handler = "main" }

CodeBuild Configs

For a Python Lambda function without any dependencies:

buildspec.yaml

version: 0.2 phases: install: runtime-versions: python: latest commands: - echo "installing..." build: commands: - echo "building..." post_build: commands: - echo "updating function $FUNC_NAME" - zip $ARTIFACT_KEY *.py - aws s3 cp $ARTIFACT_KEY s3://$BUCKET_NAME/$KEY_PREFIX/$ARTIFACT_KEY - aws lambda update-function-code --function-name $FUNC_NAME --s3-bucket $BUCKET_NAME --s3-key $KEY_PREFIX/$ARTIFACT_KEY

And for a Go Lambda function with or without dependencies:

buildspec.yaml

version: 0.2 phases: install: runtime-versions: golang: latest commands: - echo "installing..." build: commands: - echo "building..." - export GOOS=linux - export GOARCH=amd64 - go build -o main . post_build: commands: - echo "updating function $FUNC_NAME" - zip $ARTIFACT_KEY main - aws s3 cp $ARTIFACT_KEY s3://$BUCKET_NAME/$KEY_PREFIX/$ARTIFACT_KEY - aws lambda update-function-code --function-name $FUNC_NAME --s3-bucket $BUCKET_NAME --s3-key $KEY_PREFIX/$ARTIFACT_KEY