Search code examples
terraformhcl

Terraform dynamically generate attributes (not blocks)


I am trying to generate attributes dynamically in terraform 13. I've read through the docs but I can't seem to get this to work:

Given the following terraform:

#main.tf

locals {
  secrets = {
    secret1 = [
      {
        name  = "user",
        value = "secret"
      },
      {
        name  = "password",
        value = "password123"
      }
    ],
    secret2 = [
      {
        name  = "token",
        value = "secret"
      }
    ]
  }
}

resource "kubernetes_secret" "secrets" {
  for_each = local.secret

  metadata {
    name = each.key
  }

  data = {
    [for name, value in each.value : name = value]
  }
}

I would expect the following resources to be rendered:

resource "kubernetes_secret" "secrets[secret1]" {
  metadata {
    name = "secret1"
  }

  data = {
    user     = "secret"
    password = "password123"
  }
}

resource "kubernetes_secret" "secrets[secret2]" {
  metadata {
    name = "secret2"
  }

  data = {
    token = "secret"
  }
}

However I just get the following error:

Error: Invalid 'for' expression

  on ../../main.tf line 96, in resource "kubernetes_secret" "secrets":
  96:     [for name, value in each.value : name = value]

Extra characters after the end of the 'for' expression.

Does anybody know how to make this work?


Solution

  • The correct syntax for generating a mapping using a for expression is the following:

      data = {
        for name, value in each.value : name => value
      }
    

    The above would actually be totally redundant, because it would produce the same value as each.value. However, because your local value has a list of objects with name and value attributes instead of maps from name to value, so to get a working result we'd either need to change the input to already be a map, like this:

    locals {
      secrets = {
        secret1 = {
          user     = "secret"
          password = "password123"
        }
        secret2 = {
          token    = "secret"
        }
      }
    }
    
    resource "kubernetes_secret" "secrets" {
      for_each = local.secrets
    
      metadata {
        name = each.key
      }
    
      # each.value is already a map of a suitable shape
      data = each.value
    }
    

    or, if the input being a list of objects is important for some reason, you can project from the list of objects to the mapping like this:

    locals {
      secrets = {
        secret1 = [
          {
            name  = "user",
            value = "secret"
          },
          {
            name  = "password",
            value = "password123"
          }
        ],
        secret2 = [
          {
            name  = "token",
            value = "secret"
          }
        ]
      }
    }
    
    resource "kubernetes_secret" "secrets" {
      for_each = local.secrets
    
      metadata {
        name = each.key
      }
    
      data = {
        for obj in each.value : obj.name => obj.value
      }
    }
    

    Both of these should produce the same result, so which to choose will depend on what shape of local value data structure you find most readable or most convenient.