two for_each in terraform resource module - amazon-web-services

I am new to tf. Trying to use two for_each in a resource module
resource "aws_cloudwatch_event_rule" "event_rule" {
name = "${local.service_name}-trigger"
description = "Schedule for synthetic tests lambda function"
for_each = var.cron_schedule
schedule_expression = each.value.cron_schedule
for_each = var.enable_event_schedule_trigger
is_enabled = each.value.enable_event_schedule_trigger
}
resource "aws_cloudwatch_event_target" "target_lambda" {
target_id = "${local.service_name}"
arn = aws_lambda_function.synthetictests.arn
rule = aws_cloudwatch_event_rule.event_rule.name
}
but getting following error.
The argument "for_each" was already set at lambda_trigers.tf:4,3-11. Each
argument may be set only once.
Variables
variable "cron_schedule" {
type = list(string)
description = "Cron schedule of lambda trigger via event bridge"
default = []
}
variable "enable_event_schedule_trigger" {
type = list(bool)
default = ["false"]
description = "flag to enable or disable event trigger. Disabled by
default"
}
Please suggest.

You can use zipmap:
resource "aws_cloudwatch_event_rule" "schedule_rule" {
name = "${local.service_name}-trigger"
description = "Schedule for synthetic tests lambda function"
for_each = zipmap(var.cron_schedule, var.enable_event_schedule_trigger)
schedule_expression = each.key
is_enabled = each.value
}
Update
Since aws_cloudwatch_event_rule uses, aws_cloudwatch_event_target should probably also use it:
resource "aws_cloudwatch_event_target" "target_lambda" {
for_each = aws_cloudwatch_event_rule.event_rule
target_id = "${local.service_name}"
arn = aws_lambda_function.synthetictests.arn
rule = each.value.name
}

Related

Way to implement Multiple AWS lambda integrate with single API Gateway using Terraform

I want to execute multiple lambdas in a single API gateway using terraform module. I define my variable for lambdas like
variable "lambdas" {
description = "Map of Lambda function names and API gateway resource paths."
type = map(string)
default = {
"name" = "get-lambda-function",
"name" = "post-lambda-function",
"name" = "put-lambda-function",
"name" = "qr-lambda-function"
}
}
And my lambda.tf looks like
resource "aws_lambda_function" "lambda_functions" {
for_each = var.lambdas
function_name = each.value.name
filename = data.archive_file.lambda.output_path
source_code_hash = filebase64sha256(data.archive_file.lambda.output_path)
handler = var.handler
runtime = var.runtime
depends_on = [
#aws_iam_role_policy_attachment.lambda_logs
#aws_iam_role_policy_attachment.lambda_vpc,
aws_cloudwatch_log_group.lambda_fun
]
role = aws_iam_role.lambda_role.arn
}
Here for Lambda integration with API, invoking multiple lambdas with [each.key] URL looks like :
resource "aws_api_gateway_integration" "lambda" {
for_each = aws_api_gateway_method.proxyMethod
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 = aws_lambda_function.lambda_functions[each.key].invoke_arn
}
So is it possible to integrate multiple lambdas in single api gateway using terraform?

Terraform AWS Lambda function with terraform

I am new to Terraform, I am having difficulty in configuring vpc_config in Lambda function.
main.tf
resource "aws_lambda_function" "lambda" {
function_name = var.function_name
s3_bucket = var.s3_bucket_name
s3_key = var.s3_key
vpc_config {
security_group_ids = ["${var.lambda_security_group_id}"]
subnet_ids = ["${split(",", var.lambda_subnet_id)}"]
}
#source_code_hash = data.archive_file.zip.output_base64sha256
role = aws_iam_role.iam_for_lambda.arn
handler = "welcome.lambda_handler"
runtime = "python3.9"
timeout = var.timeout
memory_size = var.lambda_mem_size
}
variables.tf
variable "lambda_security_group_id" {
type = list(string)
}
variable "lambda_subnet_id" {
type = list(string)
}
terraform.tfvars
lambda_security_group_id = ["sg-0aabcef7795c7e092", "sg-0f218ddc9fb47341d"]
lambda_subnet_id = ["subnet-0d786711ca50ab0f7", "subnet-06341798f99bc9849"]
Please guide me from here.
I think you need something like this, since the variables are already a list of strings. It could be a good idea to rename your variables to the plural form since they are lists: lambda_security_group_ids and lambda_subnet_ids.
vpc_config {
security_group_ids = var.lambda_security_group_id
subnet_ids = var.lambda_subnet_id
}
More info can be found here.

How to create a dynamic map in Terraform?

I have created 2 modules. One for SQS and another for SSM. My SQS creates 4 queues and i am trying to create corresponding entries in the parameter store for their url and arn. I am importing the SSM module inside my SQS module such that it creates the parameters right after SQS creation is done.
This is what my SQS module looks like :-
resource "aws_sqs_queue" "FirstStandardSQSQueue" {
name = "sqs-${var.stage}-one"
message_retention_seconds = var.SQSMsgRetention
redrive_policy = jsonencode({
deadLetterTargetArn = aws_sqs_queue.FirstDeadLetterQueue.arn
maxReceiveCount = 2
})
}
resource "aws_sqs_queue" "FirstDeadLetterQueue" {
name = "sqs-dead-letter-${var.stage}-one"
receive_wait_time_seconds = var.SQSRecvMsgWaitTime
}
resource "aws_sqs_queue" "SecondStandardSQSQueue" {
name = "sqs-${var.stage}-two"
message_retention_seconds = var.SQSMsgRetention
redrive_policy = jsonencode({
deadLetterTargetArn = aws_sqs_queue.SecondDeadLetterQueue.arn
maxReceiveCount = 3
})
}
resource "aws_sqs_queue" "SecondDeadLetterQueue" {
name = "sqs-dead-letter-${var.stage}-two"
receive_wait_time_seconds = var.SQSRecvMsgWaitTime
}
module "ssm" {
source = "../ssm/"
// How do i create a dynamic map???
for_each = var.Queues
name = "/new/${var.stage}/${each.key}"
type = "SecureString"
value = "${each.value}"
}
My SSM module looks like this :-
resource "aws_ssm_parameter" "main" {
name = var.name
type = var.type
value = var.value
}
I am trying to create either a map or somehow dynamically be able to pass values in my ssm module using for_each? I tried setting up something like this :-
variable "Queues" {
type = map
default = {
"FirstStandardSQSQueueUrl" = aws_sqs_queue.FirstStandardSQSQueue.url
"FirstStandardSQSQueueArn" = aws_sqs_queue.FirstStandardSQSQueue.arn
"SecondStandardSQSQueueUrl" = aws_sqs_queue.SecondStandardSQSQueue.url
"SecondStandardSQSQueueArn" = aws_sqs_queue.SecondStandardSQSQueue.arn
}
}
But this is invalid because i keep running into
Error: Variables not allowed line 40: Variables may not be used here.
Can someone suggest a better/right way to do it? Thank you.
As the error msg writes, you can't have dynamic variables. locals should be used instead.
locals {
Queues = {
"FirstStandardSQSQueueUrl" = aws_sqs_queue.FirstStandardSQSQueue.url
"FirstStandardSQSQueueArn" = aws_sqs_queue.FirstStandardSQSQueue.arn
"SecondStandardSQSQueueUrl" = aws_sqs_queue.SecondStandardSQSQueue.url
"SecondStandardSQSQueueArn" = aws_sqs_queue.SecondStandardSQSQueue.arn
}
}
Then you refer to the value as local.Queues in other parts of your code.

Why isn't my Event Bridge rule executing my Lambda function?

I am trying to create an Event Bridge rule that will run my Lambda function every 30 mins.
I based my code on this answer I found here on SO Use terraform to set up a lambda function triggered by a scheduled event source
Here is my terraform code:
monitoring/main.tf:
...
module "cloudwatch_event_rule" {
source = "./cloudwatch_event_rule"
extra_tags = local.extra_tags
}
module "lambda_function" {
source = "./lambda_functions"
extra_tags = local.extra_tags
alb_names = var.alb_names
slack_webhook_url = var.slack_webhook_url
environment_tag = local.environment_tag
}
module "cloudwatch_event_target" {
source = "./cloudwatch_event_target"
lambda_function_arn = module.lambda_function.detect_bad_rejects_on_alb_lambda_arn
cloudwatch_event_rule_name = module.cloudwatch_event_rule.cloudwatch_event_rule_name
extra_tags = local.extra_tags
}
monitoring/lambda_functions/main.tf:
resource "aws_lambda_function" "detect_bad_rejects_on_alb" {
filename = var.filename
function_name = var.function_name
role = aws_iam_role.detect_bad_reject_on_alb.arn
handler = var.handler
source_code_hash = filebase64sha256(var.filename)
runtime = var.runtime
timeout = var.timeout
environment {
...
}
}
monitoring/cloudwatch_event_rule/main.tf
resource "aws_cloudwatch_event_rule" "event_rule" {
name = var.rule_name
description = var.description
schedule_expression = var.schedule_expression
tags = ...
}
monitoring/cloudwatch_event_rule/variables.tf
...
variable "schedule_expression" {
type = string
default = "rate(30 minutes)"
}
...
monitoring/cloudwatch_event_target/main.tf
resource "aws_cloudwatch_event_target" "event_target" {
arn = var.lambda_function_arn
rule = var.cloudwatch_event_rule_name
input = var.input
}
This ends up creating the lambda function and the event bridge rule with my lambda function as its target with the schedule expression "rate(30 minutes)" but the lambda function is never executed? What am I doing wrong?
From what you posted is seems that you are not adding permissions for invocations. Your code does not show creation of aws_lambda_permission with proper rules. So you should add such permissions so that EventBridge can invoke your function (example):
resource "aws_lambda_permission" "event-invoke" {
statement_id = "AllowExecutionFromCloudWatch"
action = "lambda:InvokeFunction"
function_name = var.function_name
principal = "events.amazonaws.com"
source_arn = module.cloudwatch_event_rule.cloudwatch_event_rule_arn
}
Make sure source_arn correctly points to the ARN of your event rule.

terraform .12 - add multiple inline policies

I am wondering if there is an easy way to add multiple json file policies
mypolicy1.json,
mypolicy2.json,
mypolicy3.json
as of now this is my code.. works great for one policy
variable "iam_policy_path" {
default = "./mypolicy.json"
}
resource "aws_iam_role_policy" "role" {
name = var.name
role = var.role
policy = file(var.iam_policy_path)
}
module "aws_iam_role_policy" {
source = "../modules/mypolicypolicy/"
name = "mypolicy"
role = module.myrole.myroleout
iam_policy_path = "new_policy_path.json"
}
One way would be to use for_each and having your iam_policy_path being a list of paths.
For example:
variable "iam_policy_path" {
default = ["./mypolicy.json", "./mypolicy2.json"]
}
resource "aws_iam_role_policy" "role" {
for_each = toset(var.iam_policy_path) # for each requires set.
name = var.name
role = var.role
policy = file(each.key)
}
Then when using the module:
module "aws_iam_role_policy" {
source = "../modules/mypolicypolicy/"
name = "mypolicy"
role = module.myrole.myroleout
iam_policy_path = ["new_policy_path.json", "new_policy_path2.json"]
}
Based on the extra info. The complete solution may require also using aws_iam_role_policy_attachment which attaches a managed policy to a role.