Skip to main content
Knowledge Base

for_each in generate block

Answer

Having a hard time trying to make this work. I'm trying to use the generate block to add a for_each and run the module multiple times depending on how much entries I have in the variable s3, but having lots of trouble making it right. This are the important parts of the terragrunt.hcl file ``` locals { s3 = { "companies-data-service" = { enable_cloudfront = true cloudfront_allowed_paths = ["logos/*"] cors_rules = { "local" = { methods = ["POST", "HEAD", "PUT", "GET"] origins = ["http://localhost:*", "https://*.test.io"] } } } } } generate "cdn" { path = "cdn.tf" if_exists = "overwrite" contents = <<EOF module "cdn" { source = "git::git@github.com:my-org/terraform-modules.git//s3?ref=v0.0.21" for_each = { for $${k}, $${v} in "${local.s3}" : $${k} => $${v} } name = "${local.s3}" != {} ? $${each.value} : "" environment = "${local.parent.inputs.core_env_name}" cors_rules = try($${each.value.cors_rules}, {}) enable_cloudfront = try($${each.value.enable_cloudfront}, false) enable_versioning = try($${each.value.enable_versioning}, false) cloudfront_allowed_paths = try($${each.value.cloudfront_allowed_paths}, []) acm_certificate_arn = "${dependency.acm.outputs.acm_certificate_arn}" aliases = ["$${each.key}-static.test.com"] enable_lifecycle_rules = try($${each.value.enable_lifecycle_rules}, false) s3_lifecycle_rules = try($${each.value.lifecycle_rules}, []) bucket_policies = try($${each.value.bucket_policies}, {}) } EOF } ``` Having these errors ``` local.s3 as object with 1 attribute "companies-data-service" ERRO[0010] . ERRO[0010] Cannot include the given value in a string template: string required. ERRO[0010] Error: Invalid template interpolation value ``` Also tried the local.s3 variable like this: ``` s3 = { companies = { name = "companies" enable_cloudfront = true cloudfront_allowed_paths = ["logos/*"] cors_rules = { "local" = { methods = ["POST", "HEAD", "PUT", "GET"] origins = ["http://localhost:*", "https://*.test.io"] } } }, companies2 = { name = "companies-2" enable_cloudfront = true cloudfront_allowed_paths = ["logos/*"] cors_rules = { "local" = { methods = ["POST", "HEAD", "PUT", "GET"] origins = ["http://localhost:*", "https://*.test.io"] } } } } ``` And block generate like this: ``` generate "cdn" { path = "cdn.tf" if_exists = "overwrite" contents = <<EOF module "cdn" { source = "git::git@github.com:my-org/terraform-modules.git//s3?ref=v0.0.21" for_each = "${local.s3}" name = $${each.value.name} environment = "${local.parent.inputs.core_env_name}" cors_rules = try($${each.value.cors_rules}, {}) enable_cloudfront = try($${each.value.enable_cloudfront}, false) enable_versioning = try($${each.value.enable_versioning}, false) cloudfront_allowed_paths = try($${each.value.cloudfront_allowed_paths}, []) acm_certificate_arn = "${dependency.acm.outputs.acm_certificate_arn}" aliases = ["$${each.value.name}-static.test.com"] enable_lifecycle_rules = try($${each.value.enable_lifecycle_rules}, false) s3_lifecycle_rules = try($${each.value.lifecycle_rules}, []) bucket_policies = try($${each.value.bucket_policies}, {}) } EOF } ``` To validate my changes I tried to pass the locals inside the generate block like pure terraform code, that way it works. ``` generate "cdn" { path = "cdn.tf" if_exists = "overwrite" contents = <<EOF locals { s3 = { companies = { name = "companies" enable_cloudfront = true cloudfront_allowed_paths = ["logos/*"] cors_rules = { "local" = { methods = ["POST", "HEAD", "PUT", "GET"] origins = ["http://localhost:*", "https://*.test.io"] } } }, companies2 = { name = "companies-2" enable_cloudfront = true cloudfront_allowed_paths = ["logos/*"] cors_rules = { "local" = { methods = ["POST", "HEAD", "PUT", "GET"] origins = ["http://localhost:*", "https://*.test.io"] } } } } } module "cdn" { source = "git::git@github.com:my-org/terraform-modules.git//s3?ref=v0.0.21" for_each = local.s3 name = each.value.name environment = "${local.parent.inputs.core_env_name}" cors_rules = try(each.value.cors_rules, {}) enable_cloudfront = try(each.value.enable_cloudfront, false) enable_versioning = try(each.value.enable_versioning, false) cloudfront_allowed_paths = try(each.value.cloudfront_allowed_paths, []) acm_certificate_arn = "${dependency.acm.outputs.acm_certificate_arn}" aliases = ["$${each.value.name}-static.test.com"] enable_lifecycle_rules = try(each.value.enable_lifecycle_rules, false) s3_lifecycle_rules = try(each.value.lifecycle_rules, []) bucket_policies = try(each.value.bucket_policies, {}) } EOF } ``` So the issue is clearly with how terragrunt is passing the variable to the generate block, but I don't what is happening because terragrunt don't output anything I can use to debug that variable. Any help will be much appreciated.

I solved similar issue and came up with following solution which based on `%{ ... }` [directives](https://developer.hashicorp.com/terraform/language/expressions/strings#directives). ``` locals { [...] conf = yamldecode(file(find_in_parent_folders("conf.yaml"))) } # Generate AWS providers generate "aws_providers" { path = "aws_providers.tf" if_exists = "overwrite_terragrunt" contents = <<EOF terraform { required_version = ">= 1.0.0, < 2.0.0" } %{for item in slice(local.conf["workload_aws_providers"][local.env], 1, length(local.conf["workload_aws_providers"][local.env]))~} provider "aws" { alias = "${keys(item)[0]}_${replace(values(item)[0], "-", "_")}" region = "${values(item)[0]}" assume_role { role_arn = "${local.aws_provider_roles[keys(item)[0]]}" } default_tags { tags = merge(var.tags, { "workload" = "${local.workload}", "env" = "${local.env}", "workload_component" = "${local.workload_component}" }) } } %{endfor~} EOF } ```