Terraform API Gateway Not Showing Up As Trigger For Lambda - amazon-web-services

I followed the instructions here for setting up a gateway and a lambda but it does not work. The symptoms appear to be the same as described here but the fixes suggested there did not work.
My infrastructure definition is as follows:
resource "aws_apigatewayv2_api" "lambda_api" {
name = "${upper(var.project)}-${upper(var.environment)}-${var.gateway_name}"
protocol_type = "HTTP"
}
resource "aws_apigatewayv2_stage" "lambda_default" {
name = "$default"
api_id = aws_apigatewayv2_api.lambda_api.id
auto_deploy = true
}
resource "aws_apigatewayv2_integration" "gateway_to_lambda" {
api_id = aws_apigatewayv2_api.lambda_api.id
integration_type = "AWS_PROXY"
connection_type = "INTERNET"
integration_method = "POST"
integration_uri = aws_lambda_function.executable.arn
payload_format_version = "2.0"
}
resource "aws_apigatewayv2_route" "route" {
api_id = aws_apigatewayv2_api.lambda_api.id
route_key = "GET /profile"
target = "integrations/${aws_apigatewayv2_integration.gateway_to_lambda.id}"
}
resource "aws_lambda_permission" "execution_lambda_from_gateway" {
statement_id = "AllowExecutionFromAPIGateway"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.executable.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.lambda_api.arn}/*/*"
}
On the gateway side it looks like things are created correctly: I have an integration that connects my path 'profile' to the lambda:
However, when I look on the lambda the trigger is missing:
When I try to hit the endpoint I get an "internal server error" message.
When I manually add the trigger in my lambda then it works but not under the 'profile' route key that I specified.
What am I missing here to correctly route my /profile in the API Gateway to my lambda?

Based on the comments. The solution was to modify the permissions (remove source_arn):
resource "aws_lambda_permission" "execution_lambda_from_gateway" {
statement_id = "AllowExecutionFromAPIGateway"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.executable.function_name
principal = "apigateway.amazonaws.com"
}

Related

Integrate AWS API Gateway methods with multiple Lambdas using Terraform

I have to integrate more than 50 Lambda functions with relevant api gateway method. So that I create a terraform map with Lambda function name and api gateway resource path as below.
variable "lambdas" {
description = "Map of Lambda function names and API gateway resource paths."
type = map
default = {
user = {
name = "user-lambda-function"
path = "user"
},
products= {
name = "products-lambda-function"
path = "products"
},
orders= {
name = "orders-lambda-function"
path = "orders"
},
Then I iterate lambda function creation through this map using for_each as below.
resource "aws_lambda_function" "lambda_functions" {
for_each = var.lambdas
filename = "lambda_function_code.zip"
function_name = each.value.name
role = data.aws_iam_role.lambda_execution_role.arn
handler = "index.handler"
source_code_hash = filebase64sha256("lambda_function_code.zip")
runtime = "nodejs14.x"
}
After that I start to create API Gateway, resources and methods as below,
resource "aws_api_gateway_rest_api" "api_gateway" {
name = var.api-gateway-name
}
resource "aws_api_gateway_resource" "resources" {
for_each = var.lambdas
rest_api_id = aws_api_gateway_rest_api.api_gateway.id
parent_id = aws_api_gateway_rest_api.api_gateway.root_resource_id
path_part = each.value.path
}
resource "aws_api_gateway_method" "methods" {
for_each = aws_api_gateway_resource.resources
rest_api_id = aws_api_gateway_rest_api.api_gateway.id
resource_id = each.value.id
http_method = "POST"
authorization = "NONE"
api_key_required = false
}
Then I try to integrate above API Gateway method with relevant Lambda function by iterating above above methods. But here I have to input relevant lambda function invocation uri.
resource "aws_api_gateway_integration" "integration" {
for_each = aws_api_gateway_method.methods
rest_api_id = each.value.rest_api_id
resource_id = each.value.resource_id
http_method = each.value.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = ""
}
I am struggling to input this relevant lambda function uri to integrate with relevant method.
My question is, How do I get relevant lambda function uri to input here with this iteration? Or any solution to achieve this without code every lambdas, resources and methods.
The aws_lambda_function has invoke_arn which is:
ARN to be used for invoking Lambda Function from API Gateway - to be used in aws_api_gateway_integration's uri
So you have to use that in your aws_api_gateway_integration.

An error occurred while listing api-gateway relations: Too Many Requests

I have multiple Apigateway trigger a lambda. They can invoke lambda normally but when I want to see triggers of lambda after deployed by terraform or serverless framework, AWS often throws 429 error An error occurred while listing api-gateway relations: Too Many Requests. Can anyone shed any light please how can I fix it?
Sample
Here is my config:
data "aws_lambda_function" "lambda" {
function_name = var.function_name
}
locals {
resource_path = replace(var.resource_path, "/{.*}/", "*")
}
resource "aws_api_gateway_integration" "integration" {
rest_api_id = var.api_id
resource_id = var.resource_id
http_method = aws_api_gateway_method.method.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = data.aws_lambda_function.lambda.invoke_arn
}
# Create Lambda trigger to the method
resource "aws_lambda_permission" "allow_api" {
action = "lambda:InvokeFunction"
function_name = var.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${var.api_arn}/*/${var.http_method}${local.resource_path}"
}

How to refresh AWS Lambda permission for API Gateway using Terraform?

I am deploying a REST API Gateway using Terraform. Couple of endpoints are accessing Lambda function to return response. Whenever I deploy api-gw using terraform, the Lambda permission doesn't seem to refresh and I have to manually open the api-gw portal in AWS console and again add that lambda function post which it prompts me to allow invoke action. How can I refresh the permission without having to do these manual steps ? I am using below snippet for api-gw deployment and lambda permissions:
resource "aws_api_gateway_deployment" "deploy" {
rest_api_id = aws_api_gateway_rest_api.apigw.id
stage_name = ""
variables = {
deployed_at = timestamp()
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_lambda_permission" "customers_lambda_permission" {
statement_id = "AllowDemoAPIInvokeProjectGet"
action = "lambda:InvokeFunction"
function_name = local.lambda_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.apigw.execution_arn}/*/GET/api/customers"
}
Your aws_api_gateway_deployment resource should depend on the aws_api_gateway_integration so that the lambda integration is created before deployment.
resource "aws_api_gateway_deployment" "deploy" {
...
depends_on = [
aws_api_gateway_integration.example1,
aws_api_gateway_integration.example2
]
}
or use triggers attribute:
resource "aws_api_gateway_deployment" "deploy" {
...
triggers = {
redeployment = sha1(jsonencode([
aws_api_gateway_resource.example1.id,
aws_api_gateway_method.example1.id,
aws_api_gateway_integration.example1.id,
]))
}

Terraform multiple cloudwatch events trigger same lambda function

I have setup the following in Terraform. So two event rules, start_event at 8am and stop_event at 6pm.
# Create cloudwatch event rules
resource "aws_cloudwatch_event_rule" "stop_ec2_event_rule" {
name = "stop-ec2-event-rule"
description = "Stop EC2 instance at a specified time each day"
schedule_expression = var.cloudwatch_schedule_stop
}
resource "aws_cloudwatch_event_rule" "start_ec2_event_rule" {
name = "start-ec2-event-rule"
description = "Start EC2 instance at a specified time each day"
schedule_expression = var.cloudwatch_schedule_start
}
Each event passes an action to the lambda
resource "aws_cloudwatch_event_target" "stop_ec2_event_rule_target" {
rule = aws_cloudwatch_event_rule.stop_ec2_event_rule.name
target_id = "TriggerLambdaFunction"
arn = aws_lambda_function.lambda_rscheduler.arn
input = "{\"environment\":\"${var.environment}\", \"action\":\"stop\"}"
}
resource "aws_cloudwatch_event_target" "start_ec2_event_rule_target" {
rule = aws_cloudwatch_event_rule.start_ec2_event_rule.name
target_id = "TriggerLambdaFunction"
arn = aws_lambda_function.lambda_rscheduler.arn
input = "{\"environment\":\"${var.environment}\", \"action\":\"start\"}"
}
This works
resource "aws_lambda_permission" "allow_cloudwatch" {
statement_id = "AllowExecutionFromCloudWatch"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.lambda_rscheduler.function_name
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.stop_ec2_event_rule.arn
This issue I'm facing is that I cannot get Terraform to associate the start_event with the lambda function. I go into the AWS console and I can manually add the CloudWatch start_event trigger to the lambda function.
If I have the start_event resources
resource "aws_lambda_permission" "allow_cloudwatch" {
statement_id = "AllowExecutionFromCloudWatch"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.lambda_rscheduler.function_name
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.start_ec2_event_rule.arn
It will complain that the statement_id is duplicated.
I needed something like the terraform aws_lambda_event_source_mapping but that only allows Lambda functions to get events from Kinesis, DynamoDB and SQS; and not a CloudWatch event.
How can I tell terraform to associate multiple cloudwatch events to the same lambda function; when I can manually do it from the AWS console?
statement_id is not compulsory, so you can safely omit it from your aws_lambda_permission and terraform will unique generate id for you automatically. You can also use count or for_each to save you some typing for aws_lambda_permission.
For example, using for_each you could define aws_lambda_permission to be:
resource "aws_lambda_permission" "allow_cloudwatch" {
for_each = {for idx, v in [
aws_cloudwatch_event_rule.stop_ec2_event_rule,
aws_cloudwatch_event_rule.start_ec2_event_rule
]: idx => v}
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.lambda_rscheduler.function_name
principal = "events.amazonaws.com"
source_arn = each.value.arn
}
Analogical versions could be written for aws_cloudwatch_event_rule and aws_cloudwatch_event_target so that your code is based on for_each or count without the copy-and-paste repetitions.

Terraform : S3 trigger code is failing with status-code : 400

In terraform , Trying to S3 bucket as trigger to my lambda and giving the permissions. For this use case , creating S3 resource and trying to refer that lambda function in triggering logic. But When I refer code is failing with below error.Please help me to resolve this issue .
#########################################
# Creating Lambda resource
###########################################
resource "aws_lambda_function" "test_lambda" {
filename = "output/welcome.zip"
function_name = var.function_name
role = var.role_name
handler = var.handler_name
runtime = var.run_time
}
######################################################
# Creating s3 resource for invoking to lambda function
######################################################
resource "aws_s3_bucket" "bucket" {
bucket = "source-bucktet-testing"
}
#####################################################################
# Adding S3 bucket as trigger to my lambda and giving the permissions
#####################################################################
resource "aws_s3_bucket_notification" "aws-lambda-trigger" {
bucket = aws_s3_bucket.bucket.id
lambda_function {
lambda_function_arn = aws_lambda_function.test_lambda.arn
events = ["s3:ObjectCreated:*"]
filter_prefix = "file-prefix"
filter_suffix = "file-extension"
}
}
resource "aws_lambda_permission" "test" {
statement_id = "AllowS3Invoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.test_lambda.function_name
principal = "s3.amazonaws.com"
source_arn = "arn:aws:s3:::aws_s3_bucket.bucket.id"
}
Error Message :
Error: Error putting S3 notification configuration: InvalidArgument: Unable to validate the following destination configurations
status code: 400, request id: 8D16EE1EF8FC0E63, host id: PlzqurwmHo3hDJdr0nUhOGuJKnghOBCtMImZ+8fEFX3JPjKV2M47UZuJ5Z26FalKxmoF1Xl8lag=
Your source_arn in aws_lambda_permission is incorrect. It should be:
source_arn = aws_s3_bucket.bucket.arn
At present your source_arn is literally string "arn:aws:s3:::aws_s3_bucket.bucket.id", which is incorrect.