Search code examples
terraforminfrastructure-as-code

Terraform Dynamic Object Generation


I have a scaffolding module that I have created to auto-generate some of the files that we need across our environments and projects - it automatically creates the backend file, providers file and variables file.

I have this config that generates a variables file:

resource "local_file" "generated_variables" {
  for_each = { for kv in local.distinct_envs_and_regions : "${kv.env}.${kv.region}" => kv }
  content  = <<EOF
variable "supported_regions" { default = ${jsonencode(distinct([
      for region in var.regions : 
        "rg-${each.value.env}-${var.project}-${region.region_short} = ${region.region_name}"
      
    ]
  ))
} }
variable "region_info" { default = "${each.value.region}" }
variable "project" { default = "${var.project}" }
variable "environment" { default = "${each.value.env}" }
variable "tenant_id" { default = "${var.tenant_id}" }
variable "subscription_id" { default = "${var.subscription_id}" }

variable "common_tags" {
  default = {
    environment = "${each.value.env}"
    project     = "${var.project}"
    managed_by  = "terraform"
  }
}
 EOF
  filename = "../${var.project}-iac/environments/category/${var.environment_category}/${each.value.env}/core/variables_generated.tf"
}

However, I cannot get the supported_regions variable in the format that I need - I need it to look something like this:

variable "supported_regions" { 
  default = { 
    rg-prd-adm-suk = "uksouth",
    rg-prd-adm-wuk = "ukwest" 
  }
}

Instead the output looks something like this:

variable "supported_regions" { default = ["rg-prd-adm-suk = uksouth","rg-prd-adm-wuk = ukwest"] }

I am sure I am missing something in the for loop but cannot work out what that is... Any ideas?


Solution

  • EDIT A better solution using string templates

      content = <<EOF
    variable "supported_regions" {
      default = {
        %{ for region in var.regions ~}
           rg-${each.value.env}-${var.project}-${region.region_short} = "${region.region_name}"
        %{ endfor ~}
    }
    EOF
    

    Output:

    variable "supported_regions" {
      default = {
               rg-dev-prd-adm-suk = "uksouth"
               rg-dev-prd-adm-wuk = "ukwest"
        }
    }
    

    Full working main.tf:

    # providers
    terraform {
      required_providers {
        local = {
          source = "hashicorp/local"
          version = "2.4.0"
        }
      }
    }
    
    
    locals {
      distinct_envs_and_regions = [
        {
          env    = "dev",
          region = "us-east-1",
        },
        {
          env    = "dev",
          region = "us-west-2",
        },
        {
          env    = "prod",
          region = "us-west-2",
        },
        {
          env    = "test",
          region = "us-east-1",
        },
        # Add more environments and regions as needed
      ]
    }
    
    
    resource "local_file" "generated_variables" {
      for_each = {
        for kv in local.distinct_envs_and_regions: "${kv.env}.${kv.region}" => kv
      }
      content = <<EOF
    variable "supported_regions" {
      default = {
        %{ for region in var.regions ~}
           rg-${each.value.env}-${var.project}-${region.region_short} = "${region.region_name}"
        %{ endfor ~}
    }
    EOF
      filename = "variables_generated.tf"
    }
    
    
    variable "project" {
        default = "prd-adm"
    }
    variable "regions" {
      type = list(object({
        env          = string
        region_short = string
        region_name  = string
      }))
      default = [
        {
        env          = "dev"
        region_short = "suk"
        region_name  = "uksouth"
        },
        {
        env          = "dev"
        region_short = "wuk"
        region_name  = "ukwest"
        }
      ]
    }

    This should also do it (note that I had to substitute "=" to ":" in the output of jsonencode (which is a string):

    content = <<EOF
    variable "supported_regions" {
      default = ${
        replace(
          jsonencode({
            for region in var.regions :
              "rg-${each.value.env}-${var.project}-${region.region_short}" => region.region_name
          }),
          ":", "="
        )
      }
    }
    EOF
    

    Output:

    variable "supported_regions" {
        default = {"rg-prod-prd-adm-suk"="uksouth","rg-prod-prd-adm-wuk"="ukwest"}
      }