From the Terraform docs - https://www.terraform.io/docs/providers/aws/r/cloudwatch_event_target.html
I don't see an option to map multiple targets to the same Cloudwatch rule. It only takes an arn field which accepts one resource. I'm trying to map 5 Lambdas to the same Cloudwatch rule. Does Terraform support this?
EDIT: How can I attach only 5 lambdas? If I've created 15 lambdas, I want to attach 5 each to 3 cloudwatch rules.
Got it working! I had to divide the count of the rules by 5 when I assigned targets to rules. This is roughly what it looks like:
resource "aws_cloudwatch_event_rule" "arule" {
count = "${ceil(length(var.lambda_arns) / 5.0)}" // Needs to be 5.0 to force float computation
name = "${var.rule_name}${format("-%d", count.index)}"
is_enabled = true
}
resource "aws_cloudwatch_event_target" "atarget" {
depends_on = ["aws_cloudwatch_event_rule.arule"]
count = "${length(var.lambda_arns)}"
rule = "${aws_cloudwatch_event_rule.arule.*.name[count.index / 5]}"
arn = "${var.lambda_arns[count.index]}"
}
I created the event rules based on the number of lambdas (i.e., if there are 10 lambdas, 2 rules are created).
I created the targets based on number of lambdas (i.e., if there are 10 lambdas, 10 targets are created).
I assigned the targets proportionally among the rules by dividing the count.index by 5 (the same logic used to determine the count of rules).
Assuming you created all your lambas using the same terraform resource with count, you can use count on this as well:
resource "aws_cloudwatch_event_target" "cw_target" {
count = length(aws_lambda_function.my_lambdas)
rule = "${aws_cloudwatch_event_rule.my_rule.name}"
arn = "${aws_lambda_function.my_lambdas.*.arn[count.index]}"
}
Here is what I did. I ignore the "target_id" of resource "aws_cloudwatch_event_target" (very important), and use local variables (define your local vars, this example: "targets"), and looping for the local vars, and creating multiple aws_cloudwatch_event_target, and multiple assessment templates.
locals {
stack_name_prefix = "Inspector"
rules_package_arn_cis = "arn:aws:inspector:ap-southeast-2:454640832652:rulespackage/0-Vkd2Vxjq"
default_target = {
rules : [local.rules_package_arn_cis],
duration : 3600
}
targets = [
merge(local.default_target, {
name : "data_indexer",
tags : {
Function = "LR - DX"
},
}),
merge(local.default_target, {
name : "ai_engine",
tags : {
Function = "LR - AIE"
},
}),
merge(local.default_target, {
name : "data_processor",
tags : {
Function = "LR - Data Processor"
},
}),
merge(local.default_target, {
name : "platform_manager",
tags : {
Function = "LR - PM"
},
})
]
}
resource "aws_inspector_assessment_template" "assessment_template" {
count = length(local.targets)
name = "${local.stack_name_prefix}_${local.targets[count.index]["name"]}_assessment_template"
target_arn = aws_inspector_assessment_target.assessment[count.index].arn
duration = local.default_target.duration
rules_package_arns = [local.rules_package_arn_cis]
}
resource "aws_cloudwatch_event_target" "event_target_for_inspector_assessment_template" {
count = length(local.targets)
rule = aws_cloudwatch_event_rule.event_rule_for_inspector_assessment_template.name
// target_id = "amazon_inspector_assessment" ## Don't USE target_id, it will mess up the cloudwatch event target, and only generated one target instead of 4
arn = aws_inspector_assessment_template.assessment_template[count.index].arn
role_arn = aws_iam_role.inspector_assessment_template.arn
}
module "eventbridgetarget" {
for_each = var.rule_and_target_details
source = "git::ssh://git#bitbucket.int.ally.com/tf/terraform-modules-aws-eventbridge.git//modules/target?ref=v1"
rule_arn = module.eventbridgerule.rule.arn
name = each.value.name
namespace = module.namespace.lower_short_name
tags = module.namespace.tags
#arn = module.lambda.arn
arn = each.value.arn
}
Now in the tfvars pass the value like below:
rule_and_target_details = {
"firsttarget" = {
name = "getentities"
arn = "arn:aws:execute-api:us-east-1:2XXXXXXX:92mchkioeh/api/GET/getEntities"
}
"secondtarget" = {
name = "getactivitylog"
arn = "arn:aws:execute-api:us-east-1:2XXXXXX:92mchkioeh/api/GET/getActivityLog"
}
"thirdtarget" = {
name = "searchactivitylog"
arn = "arn:aws:execute-api:us-east-1:XXXXXX:92mchkioeh/api/GET/searchActivityLog"
}
}
Related
I have a terraform code like below.
locals {
org_sub_accounts = [
"111111111111",
"222222222222,
"333333333333",
]
role_arns = [
"arn:aws:iam::111111111111:role/DataConnector1",
"arn:aws:iam::222222222222:role/DataConnector2",
"arn:aws:iam::333333333333:role/DataConnector3",
]
}
resource "aws_cloudformation_stack_set_instance" "stack" {
count = length(local.org_sub_accounts)
account_id = local.org_sub_accounts[count.index]
region = "ap-east-1"
parameter_overrides = {
RoleName = local.role_arns[count.index]
}
stack_set_name = aws_cloudformation_stack_set.stackset.name
}
My problem is my RoleName should be DataConnector potion (after /) but not the entire ARN in the aws_cloudformation_stack_set_instance. How can I pass the RoleName DataConnector* within each index?
Note, here I defined the variables in the locals to show my use case. But actually those comes from other resource outputs.
This can be achieved by using the split built-in function:
locals {
role_names = [for arn in local.role_arns : split("/", arn)[1]]
}
With this part split("/", arn)[1] you are splitting the IAM role ARN into two parts (before and after the /) and with the index [1] you are effectively getting the second part of that list. Then, you would have to change the code to reflect that with:
resource "aws_cloudformation_stack_set_instance" "stack" {
count = length(local.org_sub_accounts)
account_id = local.org_sub_accounts[count.index]
region = "ap-east-1"
parameter_overrides = {
RoleName = local.role_names[count.index]
}
stack_set_name = aws_cloudformation_stack_set.stackset.name
}
[1] https://developer.hashicorp.com/terraform/language/functions/split
This is an example of an official document,
https://registry.terraform.io/modules/terraform-aws-modules/eventbridge/aws/1.14.2
One of the use cases looks like this
EventBridge with schedule rule and Lambda target
module "eventbridge" {
source = "terraform-aws-modules/eventbridge/aws"
create_bus = false
rules = {
crons = {
description = "Trigger for a Lambda"
schedule_expression = "rate(5 minutes)"
}
}
targets = {
crons = [
{
name = "lambda-loves-cron"
arn = "arn:aws:lambda:ap-southeast-1:135367859851:function:resolved-penguin-lambda"
input = jsonencode({"job": "cron-by-rate"})
}
]
}
}
What does "input" above mean and what needs to be input?
The input value is passed to the Lambda function when it's invoked.
Its value is up to you (and could be empty); it depends what your Lambda function does.
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.
I have a lambda that I trigger with an EventBridge.
I have allowed_triggers in my lambda_function:
allowed_triggers = {
"RunDaily" = {
principal = "events.amazonaws.com"
source_arn = module.eventbridge.eventbridge_rule_arns["crons"]
}
}
And I have an eventbridge module:
module "eventbridge" {
source = "terraform-aws-modules/eventbridge/aws"
version = "1.14.0"
create_bus = false
create_role = false
create_rules = true
rules = {
crons = {
description = "deafault"
schedule_expression = "rate(1 day)"
}
}
targets = {
crons = [
{
arn = module.lambda_function.lambda_function_arn
input = jsonencode({ "job" : "crons" })
}
]
}
}
Now, this works great, as the rule is created and attached properly.
But when I want to change the name of the rule along with its description, terraform pickups only the description change:
module "eventbridge" {
...
rules = {
crons = {
description = "My custom cron rule"
schedule_expression = "rate(1 day)"
}
}
targets = {
crons = [
{
name = "my-custom-cron-rule-name"
arn = module.lambda_function.lambda_function_arn
input = jsonencode({ "job" : "crons" })
}
]
}
}
Plan:
Terraform will perform the following actions:
# module.eventbridge.aws_cloudwatch_event_rule.this["crons"] will be updated in-place
~ resource "aws_cloudwatch_event_rule" "this" {
~ description = "deafault" -> "My custom cron rule"
id = "crons-rule"
name = "crons-rule"
tags = {
"Name" = "crons-rule"
}
# (5 unchanged attributes hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
Question: How do I change the name attribute for an eventbridge rule?
As per the module definition [1], the aws_cloudwatch_event_rule name is derived from value of the key of the rules block, i.e.:
rules = {
crons = {
description = "My custom cron rule"
schedule_expression = "rate(1 day)"
}
}
Based on the lines from the GitHub repo, the name is formed with:
locals {
eventbridge_rules = flatten([
for index, rule in var.rules :
merge(rule, {
"name" = index
"Name" = "${replace(index, "_", "-")}-rule"
})
])
... # rest of locals goes here
}
If you take a look at your definition and this part of code, you can see that the name will be crons-rule, which is visible in both the name and tags.Name arguments:
name = "crons-rule"
tags = {
"Name" = "crons-rule"
}
So in order to change the name of the rule, you would have to change the key of the rules block, i.e.:
rules = {
very-nice-new-crons = { # <----- here is where the change should be made
description = "My custom cron rule"
schedule_expression = "rate(1 day)"
}
}
You can verify this by looking at [2]:
resource "aws_cloudwatch_event_rule" "this" {
for_each = { for k, v in local.eventbridge_rules : v.name => v if var.create && var.create_rules }
name = each.value.Name
...
tags = merge(var.tags, {
Name = each.value.Name
})
}
EDIT: As pointed out, there are two more changes that need to be made after the name is changed:
The allowed_triggers of the Lambda function should now use the new key to reference the event rule that is allowed to trigger it. It has to be changed from
source_arn = module.eventbridge.eventbridge_rule_arns["crons"]
to
source_arn = module.eventbridge.eventbridge_rule_arns["very-nice-new-crons"]
The same name change has to be used in the targets block as well, i.e., the crons key in the targets has to be replaced with the same key name as in the rules block:
targets = {
very-nice-new-crons = [
{
name = "my-custom-cron-rule-name"
arn = module.lambda_function.lambda_function_arn
input = jsonencode({ "job" : "crons" })
}
]
}
[1] https://github.com/terraform-aws-modules/terraform-aws-eventbridge/blob/master/main.tf#L2-L6
[2] https://github.com/terraform-aws-modules/terraform-aws-eventbridge/blob/master/main.tf#L44
I'm trying to terraform WAF ACL and associated rules. The terraform stack I'm working on is identical in DEV, QA , and PROD, differences are all handled using different variables. So my idea is to store a list of CIDRs in a variable, and automatically create ALLOW rules for each. My limited knowledge is slowing me down though. It creates the ipsets perfectly, but the rules and ACL complain,
variable cloud_allowed_cidr_list = {type="list" default=["1.2.3.4/32","4.3.2.1/32"]}
resource "aws_waf_ipset" "ipset" {
count = "${length(var.cloud_allowed_cidr_list)}"
name = "ipset-${count.index}"
ip_set_descriptors {
type = "IPV4"
value = "${element(var.cloud_allowed_cidr_list, count.index)}"
}
}
resource "aws_waf_rule" "matchIPrule" {
count = "${length(var.cloud_allowed_cidr_list)}"
depends_on = ["aws_waf_ipset.ipset"]
name = "matchMancIPrule${count.index}"
metric_name = "matchMancIPrule${count.index}"
predicates {
data_id = "${aws_waf_ipset.ipset.*.id}"
negated = false
type = "IPMatch"
}
}
resource "aws_waf_web_acl" "waf_acl" {
depends_on = ["aws_waf_ipset.ipset", "aws_waf_rule.matchIPrule"]
name = "mancACL${count.index}"
metric_name = "mancACL${count.index}"
default_action {
type = "BLOCK"
}
rules {
action {
type = "ALLOW"
}
priority = "${count.index}"
rule_id = "${aws_waf_rule.matchIPrule.id}"
type = "REGULAR"
}
}
It fell apart when I realised that rules have multiple predicates, and the ACL has multiple rules .....how do you create that dynamically ? If anyone has any examples of doing something similar I'd be very grateful.
Since the release of 0.12 you can now do this using dynamic blocks.
No need to use count to iterate over your array.
resource "aws_waf_ipset" "ipset" {
name = "youripset"
dynamic "ip_set_descriptors" {
iterator = ip
for_each = var.cloud_allowed_cidr_list
content {
type = "IPV4"
value = ip.value
}
}
}