Terragrunt for_each value, can't retrieve data in other resources - amazon-web-services

I have an issue with my terragrunt/terraform code as below.
I don't know the right way to retrieve my both crawlers created by my for_each loop.
Normally I create it with for and count.
I can't retrieve the correct values in my action triggers (main.tf).
terragrunt file (input):
inputs = {
glue_crawler = {
crawler = {
crawler_name = "test",
description = "test crawler"
},
crawler1 = {
crawler_name = "test2",
description = "test2 crawler"
}
}
}
main.tf
#crawler declaration
resource "aws_glue_crawler" "default" {
for_each = var.glue_crawler
database_name = aws_glue_catalog_database.database.name
name = "Crawler_${each.value.crawler_name}"
description = each.value.description
role = aws_iam_role.svc-glue-crawler.id
table_prefix = "raw_"
tags = var.tags
s3_target {
path = "${var.s3_glue_name}/${each.value.crawler_name}"
}
configuration = jsonencode(var.crawler_configuration)
}
...
#trigger
resource "aws_glue_trigger" "my_trigger" {
name = var.trigger_name
schedule = "cron(00 01 * * ? *)"
type = "SCHEDULED"
enabled = "false"
tags = var.tags
actions {
job_name = aws_glue_crawler.default[0].name
}
actions {
job_name = aws_glue_crawler.default[1].name
}
variable.tf
variable "glue_crawler" {
type = map(object({
crawler_name = string
description = string
}))
default = {}
description = "glue crawler definitions."
}
When i run this code i have the following errors:
Error: Invalid index
on main.tf line 294, in resource "aws_glue_trigger" "my_trigger": 294: job_name = aws_glue_crawler.default[0].name
|----------------
| aws_glue_crawler.default is object with 2 attributes
The given key does not identify an element in this collection value.
Error: Invalid index
on main.tf line 298, in resource "aws_glue_trigger" "my_trigger": 298: job_name = aws_glue_crawler.default[1].name
|----------------
| aws_glue_crawler.default is object with 2 attributes
The given key does not identify an element in this collection value.

When you use for_each instead of count you need to access the specific element with the key and not the index. So this will be crawler and crawler1 instead of 0 and 1 in your example:
resource "aws_glue_crawler" "default" {
for_each = var.glue_crawler
database_name = aws_glue_catalog_database.database.name
name = "Crawler_${each.value.crawler_name}"
description = each.value.description
role = aws_iam_role.svc-glue-crawler.id
table_prefix = "raw_"
tags = var.tags
s3_target {
path = "${var.s3_glue_name}/${each.value.crawler_name}"
}
configuration = jsonencode(var.crawler_configuration)
}
...
#trigger
resource "aws_glue_trigger" "my_trigger" {
name = var.trigger_name
schedule = "cron(00 01 * * ? *)"
type = "SCHEDULED"
enabled = "false"
tags = var.tags
actions {
job_name = aws_glue_crawler.default["crawler"].name
}
actions {
job_name = aws_glue_crawler.default["crawler1"].name
}
}
But of course that only works that specific input. Instead you should consider making the actions parameter dynamic and using for_each over the crawlers here too:
resource "aws_glue_trigger" "my_trigger" {
name = var.trigger_name
schedule = "cron(00 01 * * ? *)"
type = "SCHEDULED"
enabled = "false"
tags = var.tags
dynamic "actions" {
for_each = aws_glue_crawler.default
content {
job_name = actions.name
}
}
}

Related

How to create multiple glue crawlers from terraform at the same time

I am deploying an array of crawlers from terraform, I want to have a list of 2 or more crawlers to deploy at the same time. I am using a foreach but for the dynamodb_target I can't find how to associate it to a specific crawler, that is, crawler 1 has table_name 1 as dynamodb_target an so on.
main.tf
resource "aws_glue_crawler" "example" {
for_each = var.crawlerList
database_name = each.value.database_name
name = each.value.crawler_name
role = each.value.role
dynamodb_target {
path = var.table_name
}
}
variables.tf
variable "table_name" {
type = string
description = "The name of the DynamoDB table to crawler."
}
variable "crawlerList" {
type = map(object({
database_name = string
crawler_name = string
role = string
}))
}
var.tfvars
table_name = "ref_master_files"
crawlerList = {
fisrt = ({
database_name = "dm_web"
crawler_name = "example_one"
role = "xxxx"
}),
second = ({
database_name = "dm_web"
crawler_name = "example_two"
role = "xxxx"
}),
third = ({
database_name = "dm_web"
crawler_name = "example_third"
role = "xxxx"
}),
}
Result
It's little difficult to get what you want to achieve so I have two options.
You want to assign every crawler different table_name. Then - expand your variable for another value:
variable "crawlerList" {
type = map(object({
database_name = string
crawler_name = string
role = string
table_name = string
}))
}
You have some table_names variable of multiple variables which you didn't attach. Then what you need to is somehow match them.
It could be something like this:
tables = ["table1", "table2"]
matches = {"table1": "first", "table2":"second"}
Then you would iterate like this:
resource "aws_glue_crawler" "example" {
for_each = var.matches
database_name = var.crawler_list[each.value].database_name
name = var.crawler_list[each.value].crawler_name
role = var.crawler_list[each.value].role
dynamodb_target {
path = var.tables[each.key]
}
}

Terraform: how to create resource from loop through a objects with inner list

I am working with Terraform and I need to create a Glue Workflow. My target is the following schema:
I don't understand how I can use the "nested loops" to create the resouce from a variable object with a list of string:
My main.tf file is:
provider "aws" {
region = "eu-west-1"
profile = "<MY-STAGE>"
}
locals {
workflow_name = "my_example"
first_job = "Job_start"
my_map = [
{
flow = ["JOB-A1", "JOB-A2", "JOB-A3"]
},
{
flow = ["JOB-B1", "JOB-B2", "JOB-B3"]
}
]
}
resource "aws_glue_workflow" "example" {
name = "example"
}
resource "aws_glue_trigger" "example-start" {
name = "trigger-start"
type = "ON_DEMAND"
workflow_name = local.workflow_name
actions {
job_name = "${replace(lower(local.first_job), "_", "-")}"
}
}
resource "aws_glue_trigger" "this" {
for_each = toset(local.my_map)
name = "trigger-inner--${lower(element(each.key, index(local.my_map, each.key)))}"
type = "CONDITIONAL"
workflow_name = aws_glue_workflow.example.name
predicate {
conditions {
job_name = "${replace(lower(element(each.key, index(local.my_map, each.key))), "_", "-")}"
state = "SUCCEEDED"
}
}
actions {
job_name = "${replace(lower(element(each.key, index(local.my_map, each.key) + 1)), "_", "-")}"
}
}
When I try to do the "plan" I get this error:
| each.key is a string, known only after apply
| local.my_map is tuple with 2 elements
| Call to function "element" failed: cannot read elements from string.
Then, how can I get all the rows of the object and scroll through the list elements?
Any help or pointers would be much appreciated!

Use attributes in a tfvars file

I have a file fsx.tf with 2 resource blocks and a file prod.tfvars:
My fsx.tf looks like:
resource "aws_fsx_ontap_storage_virtual_machine" "fsx_svm" {
for_each = var.fsx_svm
file_system_id = aws_fsx_ontap_file_system.fsx.id
name = each.value.name
}
resource "aws_fsx_ontap_volume" "fsx_volumes" {
for_each = var.fsx_volumes
name = var.name
storage_virtual_machine_id = each.value.storage_virtual_machine_id
My prod.tfvars looks like:
fsx_svm = {
svm01 = {
name = "svm01-single-az"
}
}
fsx_volumes = {
vol01 = {
name = "FS"
storage_virtual_machine_id = fsx_svm.svm01.id
}
}
I get the following error:
Error: expected length of storage_virtual_machine_id to be in the range (21 - 21), got fsx_svm.svm01.id
OR
Variables not allowed here
How to set the attribute id of resource aws_fsx_ontap_storage_virtual_machine in the variable of fsx_volumes ? My goal is to be able to reuse the resource block for other .tfvars files.
In this case the best way to do this may be resource chaining with for_each [1]. This means instead of having to rely on variables you can just do this:
resource "aws_fsx_ontap_storage_virtual_machine" "fsx_svm" {
for_each = var.fsx_svm
file_system_id = aws_fsx_ontap_file_system.fsx.id
name = each.value.name
}
resource "aws_fsx_ontap_volume" "fsx_volumes" {
for_each = aws_fsx_ontap_storage_virtual_machine.fsx_svm
name = "${each.key}-${var.name}"
storage_virtual_machine_id = each.value.id
}
[1] https://developer.hashicorp.com/terraform/language/meta-arguments/for_each#chaining-for_each-between-resources

Change eventbridge cron rule name in terraform

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

Terraform count within for_each loop

I'm trying to create GCP SQL DBs by iterating a list of string using Terraform's for_each and count parameter and the other loop is for the map keys (maindb & replicadb).
Unfortunately, I get the error that appears below.
Is it possible to do this is Terraform?
variables.tf
variable "sql_var" {
default = {
"maindb" = {
"db_list" = ["firstdb", "secondsdb", "thirddb"],
"disk_size" = "20",
},
"replicadb" = {
"db_list" = ["firstdb"],
"disk_size" = "",
}
}
}
main.tf
resource "google_sql_database_instance" "master_sql_instance" {
...
}
resource "google_sql_database" "database" {
for_each = var.sql_var
name = "${element(each.value.db_list, count.index)}"
instance = "${google_sql_database_instance.master_sql_instance[each.key].name}"
count = "${length(each.value.db_list)}"
}
Error Message
Error: Invalid combination of "count" and "for_each"
on ../main.tf line 43, in resource
"google_sql_database" "database": 43: for_each =
var.sql_var
The "count" and "for_each" meta-arguments are mutually-exclusive, only
one should be used to be explicit about the number of resources to be
created.
What the error message tells you is that you cannot use count and for_each together. It looks like you are trying to create 3 main databases and 1 replica database am I correct? What I would do is create your 2 master instances and then transform your map variable to create the databases.
terraform {
required_version = ">=0.13.3"
required_providers {
google = ">=3.36.0"
}
}
variable "sql_instances" {
default = {
"main_instance" = {
"db_list" = ["first_db", "second_db", "third_db"],
"disk_size" = "20",
},
"replica_instance" = {
"db_list" = ["first_db"],
"disk_size" = "20",
}
}
}
locals {
databases = flatten([
for key, value in var.sql_instances : [
for item in value.db_list : {
name = item
instance = key
}
]
])
sql_databases = {
for item in local.databases :
uuid() => item
}
}
resource "google_sql_database_instance" "sql_instance" {
for_each = var.sql_instances
name = each.key
settings {
disk_size = each.value.disk_size
tier = "db-f1-micro"
}
}
resource "google_sql_database" "sql_database" {
for_each = local.sql_databases
name = each.value.name
instance = each.value.instance
depends_on = [
google_sql_database_instance.sql_instance,
]
}
Then, first run terraform apply -target=google_sql_database_instance.sql_instance and after this run terraform apply.