Iterate over map of objects in Terraform - amazon-web-services

I working on a module, provided below, to manage AWS KMS keys via Terraform and I'm using the flatten function but the output I'm getting is empty when I call this module.
Any thought why I'm getting empty output?
module
main.tf
locals {
kms_keys = flatten([
for key, kms_key in var.kms_key_list : [
for index in range(kms_key.key_id) : {
key_id = index
aws_kms_alias = kms_key.alias
is_rotating = kms_key.enable_key_rotation
deletion_window_in_days = kms_key.deletion_window_in_days
is_enabled = kms_key.is_enabled
description = kms_key.description
policy = kms_key.policy
}
]
])
}
resource "aws_kms_key" "main" {
for_each = {
for k, v in local.kms_keys: k => v if v.key_id > 0
}
deletion_window_in_days = each.value.deletion_window_in_days
is_enabled = each.value.is_enabled
enable_key_rotation = each.value.enable_key_rotation
description = each.value.description
policy = each.value.policy
tags = merge({
Name = each.value.aws_kms_alias
}, var.common_tags)
}
resource "aws_kms_alias" "alias" {
for_each = aws_kms_key.main
name = "alias/${each.value.tags.Name}"
target_key_id = each.value.key_id
}
variables.tf
variable "kms_key_list" {
type = map(object({
key_id = number
deletion_window_in_days = number
is_enabled = bool
enable_key_rotation = bool
description = string
policy = string
key_usage = string
customer_master_key_spec = string
alias = string
}))
}
calling the module in main.tf
module "kms_keys" {
source = "../module/kms"
kms_key_list = local.kms_keys
}
kms_keys.tf
locals {
kms_keys = {
name_1 = {
key_id = 1
deletion_window_in_days = 7
is_enabled = true
enable_key_rotation = true
description = "description_1"
policy = ""
key_usage = "ENCRYPT_DECRYPT"
customer_master_key_spec = "SYMMETRIC_DEFAULT"
alias = "alias_1"
}
}
}
TF Plan Output looks like this:
Changes to Outputs:
+ kms_info = {
+ kms_key = {}
}

This seems odd:
for index in range(kms_key.key_id)
This is going to loop through all values from 0 to the key_id value; is that really what you want? To add an entry into kms_keys for each value from 0 to key_id?
I doubt it, because the way you have this coded, if your var.kms_key_list contains a key config with key_id = 10, it's going to create 10 different KMS keys, all with the same configuration values.
Essentially, I'm not understanding the purpose of the nested for loop.
If you can provide samples of:
The input variable, but with a key_id > 1
The output that you expect to see
Then we might be able to help. Also, I don't see any output declared either in the module or in the parent file, so those must be missing; please include them.

Related

how to enable dynamic block conditionally for creating GCS buckets

Im trying to add retention policy but I want to enable it conditionally, as you can see from the code
buckets.tf
locals {
team_buckets = {
arc = { app_id = "20390", num_buckets = 2, retention_period = null }
ana = { app_id = "25402", num_buckets = 2, retention_period = 631139040 }
cha = { app_id = "20391", num_buckets = 2, retention_period = 631139040 } #20 year
}
}
module "team_bucket" {
source = "../../../../modules/gcs_bucket"
for_each = {
for bucket in flatten([
for product_name, bucket_info in local.team_buckets : [
for i in range(bucket_info.num_buckets) : {
name = format("%s-%02d", product_name, i + 1)
team = "ei_${product_name}"
app_id = bucket_info.app_id
retention_period = bucket_info.retention_period
}
]
]) : bucket.name => bucket
}
project_id = var.project
name = "teambucket-${each.value.name}"
app_id = each.value.app_id
team = each.value.team
retention_period = each.value.retention_period
}
root module is defined as follows
main.tf
resource "google_storage_bucket" "bucket" {
project = var.project_id
name = "${var.project_id}-${var.name}"
location = var.location
labels = {
app_id = var.app_id
ei_team = var.team
cost_center = var.cost_center
}
uniform_bucket_level_access = var.uniform_bucket_level_access
dynamic "retention_policy" {
for_each = var.retention_policy == null ? [] : [var.retention_period]
content {
retention_period = var.retention_period
}
}
}
but I can't seem to make the code pick up the value,
for example as you see below the value doesn't get implemented
~ resource "google_storage_bucket" "bucket" {
id = "teambucket-cha-02"
name = "teambucket-cha-02"
# (11 unchanged attributes hidden)
- retention_policy {
- is_locked = false -> null
- retention_period = 3155760000 -> null
}
}
variables.tf for retention policy is as follows
variable "retention_policy" {
description = "Configuation of the bucket's data retention policy for how long objects in the bucket should be retained"
type = any
default = null
}
variable "retention_period" {
default = null
}
Your var.retention_policy is always null, as its default value. You are not changing the default value at all. Probably you wanted the following:
for_each = var.retention_period == null ? [] : [var.retention_period]
instead of
for_each = var.retention_policy == null ? [] : [var.retention_period]

Nested for_each in terraform, attempting to self-merge map

i'm trying to account for an annoying case when creating my own private hosted zones where the kinesis-streams endpoint has several non-standard DNS records. I am aiming to create a custom map of objects for each endpoint with information that I need (below):
locals {
endpoint_service_names = {
ec2 = {
name = "com.amazonaws.${data.aws_region.current.name}.ec2"
type = "Interface"
private_dns = false
phz_names = ["ec2.${data.aws_region.current.name}.amazonaws.com"]
phz_wildcard = false
}
"ecr.dkr" = {
name = "com.amazonaws.${data.aws_region.current.name}.ecr.dkr"
type = "Interface"
private_dns = false
phz_names = ["dkr.ecr.${data.aws_region.current.name}.amazonaws.com"]
phz_wildcard = true
}
"ecr.api" = {
name = "com.amazonaws.${data.aws_region.current.name}.ecr.api"
type = "Interface"
private_dns = false
phz_names = ["api.ecr.${data.aws_region.current.name}.amazonaws.com"]
phz_wildcard = false
}
kinesis-streams = {
name = "com.amazonaws.${data.aws_region.current.name}.kinesis-streams"
type = "Interface"
private_dns = false
phz_names = [
"kinesis.${data.aws_region.current.name}.amazonaws.com",
"data-kinesis.${data.aws_region.current.name}.amazonaws.com",
"control-kinesis.${data.aws_region.current.name}.amazonaws.com"
]
phz_wildcard = true
}
}
In order to use this in my impl, however, I would need to perform a nested for_each which Terraform does not directly allow, so I need to merge my map info into a new map with the service and DNS names in the one object. My problem is similar to: Looping in for_each nested resources with terraform
except I do not have a list in each map element, only on the phz_names. I can't figure out how to get the syntax right to produce something like this:
endpoint_service_dns_list = {
kinesis-streams = [
"kinesis.${data.aws_region.current.name}.amazonaws.com",
"data-kinesis.${data.aws_region.current.name}.amazonaws.com",
"control-kinesis.${data.aws_region.current.name}.amazonaws.com"
]
[...]
}
}
My attempt:
endpoint_service_dns_list = merge([
for servicename, service in local.endpoint_service_names : {
for phz_name in service[0].phz_names :
"${servicename}-${phz_name}" => {
service_name = servicename
phz_name = phz.phz_names
}
}
])
but the syntax highlighting/attempt to indexing obviously fails because I do not have a list for each service, but I am not sure what to replace it with.
Maybe I have missed the point but based on your comments and your kind of expected result in the question you could have something like this. I have added a format that maps then service name to all its urls as a list. Or incase you wanted a mapping of each url to its service name I added an additional local var.
locals {
endpoint_service_names = {
ec2 = {
name = "com.amazonaws.${data.aws_region.current.name}.ec2"
type = "Interface"
private_dns = false
phz_names = ["ec2.${data.aws_region.current.name}.amazonaws.com"]
phz_wildcard = false
}
"ecr.dkr" = {
name = "com.amazonaws.${data.aws_region.current.name}.ecr.dkr"
type = "Interface"
private_dns = false
phz_names = ["dkr.ecr.${data.aws_region.current.name}.amazonaws.com"]
phz_wildcard = true
}
"ecr.api" = {
name = "com.amazonaws.${data.aws_region.current.name}.ecr.api"
type = "Interface"
private_dns = false
phz_names = ["api.ecr.${data.aws_region.current.name}.amazonaws.com"]
phz_wildcard = false
}
kinesis-streams = {
name = "com.amazonaws.${data.aws_region.current.name}.kinesis-streams"
type = "Interface"
private_dns = false
phz_names = [
"kinesis.${data.aws_region.current.name}.amazonaws.com",
"data-kinesis.${data.aws_region.current.name}.amazonaws.com",
"control-kinesis.${data.aws_region.current.name}.amazonaws.com"
]
phz_wildcard = true
}
}
endpoint_service_dns_list = {for service, attrs in local.endpoint_service_names : service => attrs.phz_names}
endpoint_dns_service_list = merge([for service, attrs in local.endpoint_service_names : {for url in attrs["phz_names"] : url => service}]...)
}
data "aws_region" "current" {}
output "endpoint_service_dns_list" {
value = local.endpoint_service_dns_list
}
output "endpoint_dns_service_list" {
value = local.endpoint_dns_service_list
}
This would map the phz_names with the service as seen in the output, or map each url to its service
endpoint_dns_service_list = {
"api.ecr.eu-west-2.amazonaws.com" = "ecr.api"
"control-kinesis.eu-west-2.amazonaws.com" = "kinesis-streams"
"data-kinesis.eu-west-2.amazonaws.com" = "kinesis-streams"
"dkr.ecr.eu-west-2.amazonaws.com" = "ecr.dkr"
"ec2.eu-west-2.amazonaws.com" = "ec2"
"kinesis.eu-west-2.amazonaws.com" = "kinesis-streams"
}
endpoint_service_dns_list = {
"ec2" = [
"ec2.eu-west-2.amazonaws.com",
]
"ecr.api" = [
"api.ecr.eu-west-2.amazonaws.com",
]
"ecr.dkr" = [
"dkr.ecr.eu-west-2.amazonaws.com",
]
"kinesis-streams" = [
"kinesis.eu-west-2.amazonaws.com",
"data-kinesis.eu-west-2.amazonaws.com",
"control-kinesis.eu-west-2.amazonaws.com",
]
}
Hopefully one of these is the kind of thing you were looking to do

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]
}
}

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

Terragrunt for_each value, can't retrieve data in other resources

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
}
}
}