Javier Lopez Tomas
Javier Lopez Tomas

Reputation: 2362

How to loop through locals and list at the same time to generate resources

I have the following tf file:

locals {
  schemas = {
    "ODS" = {
      usage_roles = ["TRANSFORMER"]
    }
    "EXT" = {
      usage_roles = []
    }
    "INT" = {
      usage_roles = ["REPORTER"]
    }
    "DW" = {
      usage_roles = ["LOADER"]
    }
  }
}

resource "snowflake_schema" "schema" {
  for_each = local.schemas
  name = each.key
  database = ???????
  usage_roles = each.value.usage_roles
}

I want to maintain the locals as it is (different usage_roles for each schema and hardcoded here) while having several values as database for each schema. In pseudo-code it would be:

for database in ['db_1', 'db_2', 'db_3']:
    resource "snowflake_schema" "schema" {
      for_each = local.schemas
      name = each.key
      database = database
      usage_roles = each.value.usage_roles
    }

So that we have the same schema resource in the three different databases. I have read some articles that point me to the belief that it is possible to make this loop but pre assigning all the values, meaning that I would have to put usage_roles in a list or something instead of hardcoding here in locals, which I think is less readable. For instance: Terraform - how to use for_each loop on a list of objects to create resources

Is even possible what I am asking for? If so, how? Thank you very much in advance

Upvotes: 2

Views: 5136

Answers (1)

Martin Atkins
Martin Atkins

Reputation: 74674

The main requirement for for_each is that the map you provide must have one element per instance of the resource you want to create. In your case, I think that means you need a map with one element per every combination of database and schema.

The operation of finding every combination of values in two sets is formally known as the cartesian product, and Terraform has the setproduct function to perform that operation. In your case, the two sets to apply it to are the set of database names and the set of keys in your schemas map, like this:

locals {
  databases = toset(["db_1", "db_2", "db_3"])

  database_schemas = [
    for pair in setproduct(local.databases, keys(local.schemas)) : {
      database_name = pair[0]
      schema_name   = pair[1]
      usage_roles   = local.schemas[pair[1]].usage_roles
    }
  ]
}

The local.database_schemas value would then contain an object for each combination, like this:

[
  {
    database_name = "db_1"
    schema_name   = "ODS"
    usage_roles   = ["TRANSFORMER"]
  },
  {
    database_name = "db_1"
    schema_name   = "EXT"
    usage_roles   = []
  },
  # ...
  {
    database_name = "db_2"
    schema_name   = "ODS"
    usage_roles   = ["TRANSFORMER"]
  },
  {
    database_name = "db_2"
    schema_name   = "EXT"
    usage_roles   = []
  },
  # ...
  {
    database_name = "db_3"
    schema_name   = "ODS"
    usage_roles   = ["TRANSFORMER"]
  },
  {
    database_name = "db_3"
    schema_name   = "EXT"
    usage_roles   = []
  },
  # ...
]

This meets the requirement of having one element per instance you want to create, but we still need to convert it to a map with a unique key per element to give Terraform a unique tracking key for each instance, so we can do one more for projection in the for_each argument:

resource "snowflake_schema" "schema" {
  for_each = {
    for s in local.database_schemas :
    "${s.database_name}:${s.schema_name}" => s
  }

  name        = each.value.schema_name
  database    = each.value.database_name
  usage_roles = each.value.usage_roles
}

Terraform will track these instances with addresses like this:

  • snowflake_schema.schema["db_1:ODS"]
  • snowflake_schema.schema["db_1:EXT"]
  • ...
  • snowflake_schema.schema["db_2:ODS"]
  • snowflake_schema.schema["db_2:EXT"]
  • ...
  • snowflake_schema.schema["db_3:ODS"]
  • snowflake_schema.schema["db_3:EXT"]
  • ...

Upvotes: 6

Related Questions