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:
lambda.tf
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
}
api_gateway.tf
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
}
api_gateway_body_mapping.template
{
"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:
- Is there anything happening in the background when I re-add my Function name ? (Except the policy thing)
- What to change in my code to be able to call api gateway right after terraform apply?
aws_apigatewayv2_apiresource and I found out that the issue is simply that I didn't have thetargetargument on it.target = aws_lambda_function.lambda.arnis the LIFE SAVER! Source here.