Code snippet: a terraform module for lambda + api gateway

//See https://learn.hashicorp.com/tutorials/terraform/lambda-api-gateway
//===============================//

variable "code_bucket_name" {
}

//a path
variable "dummy_code_file" {
}

//used to access s3 bucket
variable "code_bucket_user_arn" {
}

variable "function_name" {
}

variable "function_handler" {
  
}

variable "lambda_runtime" {
  
}

variable "exec_role_name" {}

locals {
  code_file_bucket_key = "lambda-code.zip"
}


resource "aws_s3_bucket" "code_bucket" {
  bucket = var.code_bucket_name
  policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "user-and-bucket",
      "Effect": "Allow",
      "Principal": {
        "AWS": "${var.code_bucket_user_arn}"
      },
      "Action": ["s3:ListBucket"],
       "Resource": "arn:aws:s3:::${var.code_bucket_name}"
    },

    {
      "Sid": "user-and-objects",
      "Effect": "Allow",
      "Principal": {
        "AWS": "${var.code_bucket_user_arn}"
      },
      "Action": [
        "s3:GetObject",
        "s3:DeleteObject",
        "s3:PutObject"
      ],
      "Resource": "arn:aws:s3:::${var.code_bucket_name}/*"
    }
 ]
}
POLICY
}

//You have to put a dummy code file into S3 bucket
//  otherwise lambda creation will fail with "Error occurred while GetObject. S3 Error Code: NoSuchKey"
//When you rerun terraform, the dummy code file won't replace the read code file.
//  Don't worry. https://stackoverflow.com/a/56120462
resource "aws_s3_bucket_object" "dummy_code" {
  bucket = aws_s3_bucket.code_bucket.bucket
  key = local.code_file_bucket_key
  source = var.dummy_code_file
}


resource "aws_lambda_function" "lambda_function" {
  depends_on = [
    aws_s3_bucket_object.dummy_code,
    aws_iam_role_policy_attachment.lambda_logs,
    aws_cloudwatch_log_group.lambda_log_group
  ]

  function_name = var.function_name


  s3_bucket = aws_s3_bucket.code_bucket.bucket
  s3_key = "lambda-code.zip"


  handler = var.function_handler
  runtime = var.lambda_runtime

  timeout = 30
  memory_size = 256

  role = aws_iam_role.lambda_exec_role.arn
}

# IAM role which dictates what other AWS services the Lambda function may access.
resource "aws_iam_role" "lambda_exec_role" {
  name = var.exec_role_name

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

}

//the gateway
resource "aws_api_gateway_rest_api" "gateway_api" {
  name = "${aws_lambda_function.lambda_function.function_name}-rest-api"
}

resource "aws_api_gateway_resource" "gateway_resource" {
  rest_api_id = aws_api_gateway_rest_api.gateway_api.id
  parent_id = aws_api_gateway_rest_api.gateway_api.root_resource_id
  path_part = "{proxy+}"
}

resource "aws_api_gateway_method" "gateway_method" {
  rest_api_id = aws_api_gateway_rest_api.gateway_api.id
  resource_id = aws_api_gateway_resource.gateway_resource.id
  http_method = "ANY"
  authorization = "NONE"
}

////this is stupid, but you have to do it according to the document
resource "aws_api_gateway_method" "gateway_method_for_root" {
  rest_api_id = aws_api_gateway_rest_api.gateway_api.id
  resource_id = aws_api_gateway_rest_api.gateway_api.root_resource_id
  http_method = "ANY"
  authorization = "NONE"
}

//the integration of gateway and function
resource "aws_api_gateway_integration" "gateway_integration" {
  rest_api_id = aws_api_gateway_rest_api.gateway_api.id
  resource_id = aws_api_gateway_method.gateway_method.resource_id
  http_method = aws_api_gateway_method.gateway_method.http_method

  integration_http_method = "POST"
  type = "AWS_PROXY"
  uri = aws_lambda_function.lambda_function.invoke_arn
}

////this is stupid, but you have to do it according to the document
resource "aws_api_gateway_integration" "gateway_integration_for_root" {
  rest_api_id = aws_api_gateway_rest_api.gateway_api.id
  resource_id = aws_api_gateway_method.gateway_method_for_root.resource_id
  http_method = aws_api_gateway_method.gateway_method_for_root.http_method

  integration_http_method = "POST"
  type = "AWS_PROXY"
  uri = aws_lambda_function.lambda_function.invoke_arn
}

//deployment
resource "aws_api_gateway_deployment" "gateway_deployment" {
  depends_on = [
    aws_api_gateway_integration.gateway_integration,
    aws_api_gateway_integration.gateway_integration_for_root,
  ]

  rest_api_id = aws_api_gateway_rest_api.gateway_api.id
  stage_name = "default"
}

//permission
resource "aws_lambda_permission" "lambda_permission" {
  statement_id = "AllowAPIGatewayInvoke"
  action = "lambda:InvokeFunction"
  function_name = aws_lambda_function.lambda_function.function_name
  principal = "apigateway.amazonaws.com"

  # The "/*/*" portion grants access from any method on any resource
  # within the API Gateway REST API.
  source_arn = "${aws_api_gateway_rest_api.gateway_api.execution_arn}/*/*"
}




# See https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function#cloudwatch-logging-and-permissions

resource "aws_cloudwatch_log_group" "lambda_log_group" {
  name              = "/aws/lambda/${var.function_name}"
  retention_in_days = 14
}

resource "aws_iam_policy" "lambda_logging_policy" {
  name        = "lambda-logging-${var.function_name}"
  path        = "/"
  description = "IAM policy for logging from a lambda"

  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*",
      "Effect": "Allow"
    }
  ]
}
EOF
}

resource "aws_iam_role_policy_attachment" "lambda_logs" {
  role       = aws_iam_role.lambda_exec_role.name
  policy_arn = aws_iam_policy.lambda_logging_policy.arn
}

To call such a module,

module "some-system" {
  source = "./modules/lambda_and_api_gateway"
  code_bucket_name = "some-bucket-name"   # will create it for you
  dummy_code_file = "path/to/the/dummy/code/file"  
  function_name = "some-function-name"
  function_handler = "some-function-handler"
  lambda_runtime = "some-runtime-such-as-nodejs"
  code_bucket_user_arn = "some-iam-user-arn"
  exec_role_name = "some-exec-role-name"  #Please create it in advance
}

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.