Hi @gowthamakanthan,
Assuming that “replica” means a different value for the database
argument, a typical approach to a problem like this would be to construct a data structure which has one element for each pair of database and extension name that you want to declare.
If you want to produce an exhaustive set of combinations of all of the database names and all of the extension names then from a set-theory perspective that’s called the cartesian product of the two sets, which in Terraform is implemented by the setproduct
function. You can use that function in combination with a for
expression to build a set of objects where each object represents one pairing of database and extension name.
locals {
database_extensions = toset([
for pair in setproduct(var.db_replicas[local.aws_account], var.pg_extensions) : {
database = pair[0]
extension = pair[1]
}
])
}
The result of this is a set of objects which will look something like this (using some example names for the “replicas”):
toset([
{ database = "replica1", extension = "pglogical" }
{ database = "replica1", extension = "pg_trgm" }
{ database = "replica2", extension = "pglogical" }
{ database = "replica2", extension = "pg_trgm" }
])
This collection now meets one of the two main requirements for for_each
: there’s one element per instance you want to declare. The second requirement is that the value be a mapping whose element keys will become the instance keys, which means that we need to give each of the elements of the set a unique key. These elements don’t have any explicit unique key, but we know that the pair of database and extension should be unique across all of them and so we can concatenate those together to produce a key, using another for
expression:
resource "postgresql_extension" "all" {
for_each = {
for de in local.database_extensions :
"${de.name}:${de.extension}" => de
}
name = each.value.extension
database = each.value.database
}
When I’m converting a set to a map purely to create unique keys for for_each
I typically prefer to perform that final conversion inline in the for_each
expression, rather than declaring another local value, but you can declare it as a named local value and refer to that value in for_each
instead if you prefer; the effect is the same either way.
Notice that unlike in my first answer for_each
now really is a map. Previously I noted that Terraform will treat a set of strings here as if it were a map from those strings to themselves, but now we have a real map the distinction between each.key
and each.value
becomes significant: each.value
is the current object, and so each.value.extension
and each.value.database
refer to the current instance’s extension name and database name respectively.
Due to the key concatenation scheme I used here, this would declare four instances of the resource with the following addresses, if we assume the same placeholder replica names I used for the set example above:
postgresql_extension.all["replica1:pglogical"]
postgresql_extension.all["replica1:pg_trgm"]
postgresql_extension.all["replica2:pglogical"]
postgresql_extension.all["replica2:pg_trgm"]
This addressing scheme will allow Terraform to understand the meaning of future changes to either var.pg_extensions
or var.db_replicas[local.aws_account]
. For example, if you were to remove pg_trgm
from the set of extensions then Terraform would see that both postgresql_extension.all["replica1:pg_trgm"]
and postgresql_extension.all["replica2:pg_trgm"]
are no longer declared, but the two with pglogical
would remain unchanged because their compound keys would still be present in the map.