I've started using terraform a few days ago, so I am a beginner in this topic. I want to create an API Gateway and a Lambda with a Layer as a custom runtime to run R script. Here are my Terraform files:
resource "aws_lambda_function" "testlambda" {
function_name = "rlambda"
s3_bucket = "mybucket"
s3_key = "rlambda/v1.0.3/rlambda.zip"
handler = "main.main"
runtime = "provided"
memory_size = 512
timeout = 30
layers = [aws_lambda_layer_version.r_layer.arn]
role = aws_iam_role.lambda_role.arn
}
# IAM role for lambda
resource "aws_iam_role" "lambda_role" {
name = "lambda_role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
# Lambda Policy
resource "aws_lambda_permission" "apigw" {
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.testlambda.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.apigateway.execution_arn}/*/POST/dev"
depends_on = [
aws_api_gateway_rest_api.apigateway,
aws_api_gateway_resource.apiresource,
]
}
# Lambda Layer
resource "aws_lambda_layer_version" "r_layer" {
layer_name = "rlayer"
s3_bucket = "mybucket"
s3_key = "lambdalayer/v1.0.3/rlayer.zip"
}
# Cloudwatch Logging for Lambda
resource "aws_iam_policy" "lambda_logging" {
name = "lambda_logging"
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_role.name
policy_arn = aws_iam_policy.lambda_logging.arn
}
resource "aws_api_gateway_rest_api" "apigateway" {
name = "ApiGatewayTest"
description = "Terraform Created Api Gateway"
binary_media_types = ["multipart/form-data", "application/octet-stream"]
}
resource "aws_api_gateway_resource" "apiresource" {
rest_api_id = aws_api_gateway_rest_api.apigateway.id
parent_id = aws_api_gateway_rest_api.apigateway.root_resource_id
path_part = "dev"
}
# Method
resource "aws_api_gateway_method" "method" {
rest_api_id = aws_api_gateway_rest_api.apigateway.id
resource_id = aws_api_gateway_resource.apiresource.id
http_method = "POST"
authorization = "NONE"
}
# Integration
resource "aws_api_gateway_integration" "integration" {
rest_api_id = aws_api_gateway_rest_api.apigateway.id
resource_id = aws_api_gateway_method.method.resource_id
http_method = aws_api_gateway_method.method.http_method
integration_http_method = "POST"
type = "AWS"
uri = aws_lambda_function.testlambda.invoke_arn
passthrough_behavior = "WHEN_NO_TEMPLATES"
request_templates = {
"multipart/form-data" = file("api_gateway_body_mapping.template")
}
depends_on = [aws_api_gateway_method.method]
}
# Method Response
resource "aws_api_gateway_method_response" "methodresponse" {
rest_api_id = aws_api_gateway_rest_api.apigateway.id
resource_id = aws_api_gateway_resource.apiresource.id
http_method = aws_api_gateway_method.method.http_method
status_code = "200"
response_parameters = {
"method.response.header.Access-Control-Allow-Origin" = true,
"method.response.header.Access-Control-Expose-Headers" = true,
"method.response.header.Content-Disposition" = true,
"method.response.header.Content-Type" = true
}
}
# Integration Response
resource "aws_api_gateway_integration_response" "integrationresponse" {
rest_api_id = aws_api_gateway_rest_api.apigateway.id
resource_id = aws_api_gateway_resource.apiresource.id
http_method = aws_api_gateway_method.method.http_method
status_code = aws_api_gateway_method_response.methodresponse.status_code
content_handling = "CONVERT_TO_BINARY"
response_parameters = {
"method.response.header.Access-Control-Allow-Origin" = "'*'",
"method.response.header.Access-Control-Expose-Headers" = "'Content-Disposition'",
"method.response.header.Content-Disposition" = "'attachment'",
"method.response.header.Content-Type" = "'application/octet-stream'"
}
depends_on = [aws_api_gateway_integration.integration]
}
# CORS Method
resource "aws_api_gateway_method" "cors_method" {
rest_api_id = aws_api_gateway_rest_api.apigateway.id
resource_id = aws_api_gateway_resource.apiresource.id
http_method = "OPTIONS"
authorization = "NONE"
}
# CORS Integration
resource "aws_api_gateway_integration" "cors_integration" {
rest_api_id = aws_api_gateway_rest_api.apigateway.id
resource_id = aws_api_gateway_resource.apiresource.id
http_method = aws_api_gateway_method.cors_method.http_method
type = "MOCK"
request_templates = {
"application/json" = <<EOF
{"statusCode": 200}
EOF
}
depends_on = [aws_api_gateway_method.cors_method]
}
# CORS Method Response
resource "aws_api_gateway_method_response" "cors_methodresponse" {
rest_api_id = aws_api_gateway_rest_api.apigateway.id
resource_id = aws_api_gateway_resource.apiresource.id
http_method = aws_api_gateway_method.cors_method.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
}
depends_on = [aws_api_gateway_method.cors_method]
}
# CORS Integration Response
resource "aws_api_gateway_integration_response" "cors_integrationresponse" {
rest_api_id = aws_api_gateway_rest_api.apigateway.id
resource_id = aws_api_gateway_resource.apiresource.id
http_method = aws_api_gateway_method.cors_method.http_method
status_code = aws_api_gateway_method_response.cors_methodresponse.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" = "'OPTIONS,POST'",
"method.response.header.Access-Control-Allow-Origin" = "'*'"
}
response_templates = {
"application/json" = <<EOF
EOF
}
depends_on = [
aws_api_gateway_method_response.cors_methodresponse,
aws_api_gateway_integration.cors_integration,
]
}
# Deployment
resource "aws_api_gateway_deployment" "apideployment" {
depends_on = [
aws_api_gateway_integration.integration,
aws_api_gateway_integration.cors_integration
]
rest_api_id = aws_api_gateway_rest_api.apigateway.id
stage_name = "dev"
}
output "base_url" {
value = aws_api_gateway_deployment.apideployment.invoke_url
}
{
"body": "$input.body",
"headers": {
#foreach($param in $input.params().header.keySet())
"$param": "$util.escapeJavaScript($input.params().header.get($param))"
#if($foreach.hasNext),#end
#end
}
}
So terraform init and apply completes, but API Gateway shows Internal Server Error. (Calling it from postman, with this url: https://(randomchars).execute-api.eu-central-1.amazonaws.com/dev/dev)
"Execution failed due to configuration error: Unable to transform request" - This is the error from the api's cloudwatch log.
If I go to the Integration Request of the POST method execution on the AWS console, and I re-add the Function name (rlambda) it adds another policy to the lambda (Exactly the same as it was before, with different Sid of course) and I re-deploy my API, then it works completely. (It does not work if I only do the deploy API part.)
So my questions are:
Okay so after 5 days of suffering I realized what is the problem.
On the AWS console you are not able to set the Integration Request's content_handling and it is only an Optional parameter in Terraform as well.
When you are re-assigning your lambda's name on the console, not only the lambda's policy got updated, but also the integration request's content_handling got set to CONVERT_TO_TEXT. I don't know why it is changing and why to this value. Maybe it's based on the lambda - there is no information about this.
So I added this row to the aws_api_gateway_integration:
content_handling = "CONVERT_TO_TEXT"
And it solved my problem. Hope nobody will run into this again.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With