I made a small lambda function without SAM. This is the document of it.
Background
I usually use AWS SAM to create a serverless application because it provides everything like infrastructure definition, application code, deployment, and local development environment with docker. However, I felt it would be overspec because I wanted to create a small function which has 30-lines python code and just bridges a request to another endpoint. I tested the small code line in my jupter notebook and looked for the place to execute the code snippets. Then I challenged to create Lambda env with Terraform.
What I did
I checked the official docs by Terraform first. The docs is quite old. So I looked for other articles.
Then I googled the way to do with Terraform and Lambda and I decided to use the article as a reference.
The reason why I chose it was because I didn’t create a docker image. I knew I could the same thing with docker but I didn’t want to use it because my code was just 30-lines and docker was too much for the purpose. However, if you don’t use docker for this purpose, you can’t use pip package. In my case, I could manage what I wanted to do with packages provided by Python itself. But it could be a blocker for the way to do without docker image.
This is about the way to do with Python. This is pretty much about what I did.
Actually, I did almost same as the reference article and mixed the python way into it. But I changed the settings for CORS. In the reference, the CORS check uses OPTION method before sending GET or POST method request. So I explicitly created another aws_api_gateway_method_response
for the OPTION method. I got the code snippets from the following article.
Then the my final output looks like below. I skipped the code lines for cognito and route53 because they are same as the reference.
- For lambda
resource "aws_lambda_function" "my_lambda_name" {
function_name = "my_lambda_name_function_name"
filename = "lambda_function.zip"
source_code_hash = data.archive_file.python_lambda_package.output_base64sha256
handler = "lambda_function.lambda_handler"
runtime = "python3.11"
role = aws_iam_role.my_exec_iam.arn
}
data "archive_file" "python_lambda_package" {
type = "zip"
source_file = "${path.module}/lambda/lambda_function.py"
output_path = "lambda_function.zip"
}
resource "aws_iam_role" "my_exec_iam" {
name = "my_exec_iam_name"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
data "aws_iam_policy_document" "assume_role" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
actions = ["sts:AssumeRole"]
}
}
resource "aws_iam_role_policy_attachment" "lambda_basic" {
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
role = aws_iam_role.my_exec_iam.name
}
resource "aws_lambda_permission" "apigw_lambda" {
statement_id = "AllowExecutionFromAPIGateway"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.my_lambda_name.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.my_lambda_name.execution_arn}/*/*/*"
}
- For API Gateway
resource "aws_api_gateway_rest_api" "my_gateway" {
name = "my_gateway_name"
description = "My gateway description"
}
resource "aws_api_gateway_resource" "my_gateway" {
rest_api_id = aws_api_gateway_rest_api.my_gateway.id
parent_id = aws_api_gateway_rest_api.my_gateway.root_resource_id
path_part = "my_gateway"
}
resource "aws_api_gateway_method" "options_method_prod" {
rest_api_id = aws_api_gateway_rest_api.my_gateway.id
resource_id = aws_api_gateway_resource.my_gateway.id
http_method = "OPTIONS"
authorization = "NONE"
}
resource "aws_api_gateway_method" "get_method_prod" {
rest_api_id = aws_api_gateway_rest_api.my_gateway.id
resource_id = aws_api_gateway_resource.my_gateway.id
http_method = "GET"
authorization = "NONE"
request_parameters = {
"method.request.querystring.id" = true
"method.request.querystring.key" = true
}
}
resource "aws_api_gateway_integration" "lambda_prod" {
rest_api_id = aws_api_gateway_rest_api.my_gateway.id
resource_id = aws_api_gateway_resource.my_gateway.id
http_method = aws_api_gateway_method.get_method_prod.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.my_gateway.invoke_arn
}
resource "aws_api_gateway_method_response" "response_200_prod" {
rest_api_id = aws_api_gateway_rest_api.my_gateway.id
resource_id = aws_api_gateway_resource.my_gateway.id
http_method = aws_api_gateway_method.get_method_prod.http_method
status_code = "200"
response_models = {
"application/json" = "Empty"
}
}
resource "aws_api_gateway_method_response" "options_200_prod" {
rest_api_id = aws_api_gateway_rest_api.my_gateway.id
resource_id = aws_api_gateway_resource.my_gateway.id
http_method = aws_api_gateway_method.options_method_prod.http_method
status_code = "200"
response_models = {
"application/json" = "Empty"
}
response_parameters = {
"method.response.header.Access-Control-Allow-Headers" = true,
"method.response.header.Access-Control-Allow-Methods" = true,
"method.response.header.Access-Control-Allow-Origin" = true
}
}
resource "aws_api_gateway_integration" "options_integration_prod" {
rest_api_id = aws_api_gateway_rest_api.my_gateway.id
resource_id = aws_api_gateway_resource.my_gateway.id
http_method = aws_api_gateway_method.options_method_prod.http_method
type = "MOCK"
request_templates = {
"application/json" = jsonencode({ statusCode = 200 })
}
}
resource "aws_api_gateway_integration_response" "options_integration_response_prod" {
rest_api_id = aws_api_gateway_rest_api.my_gateway.id
resource_id = aws_api_gateway_resource.my_gateway.id
http_method = aws_api_gateway_method.options_method_prod.http_method
status_code = aws_api_gateway_method_response.options_200_prod.status_code
response_parameters = {
"method.response.header.Access-Control-Allow-Headers" = "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
"method.response.header.Access-Control-Allow-Methods" = "'GET,OPTIONS'",
"method.response.header.Access-Control-Allow-Origin" = "'*'"
}
}
resource "aws_api_gateway_deployment" "my_gateway" {
rest_api_id = aws_api_gateway_rest_api.my_gateway.id
stage_name = "prod"
triggers = {
redeployment = sha1(jsonencode([
aws_api_gateway_resource.my_gateway.id,
aws_api_gateway_method.get_method_prod.id,
aws_api_gateway_method.options_method_prod.id,
aws_api_gateway_method_response.response_200_prod.id,
aws_api_gateway_method_response.options_200_prod.id,
aws_api_gateway_integration.lambda_prod.id,
aws_api_gateway_integration.options_integration_prod.id,
aws_api_gateway_integration_response.options_integration_response_prod.id,
]))
}
lifecycle {
create_before_destroy = true
}
}
After all, I felt SAM was convenient because it would set up all of them including API Gateway. I would say SAM is suitable even if the code lines are quite small.
That’s it!