For_each and multiple loops

Using terraform 0.13.0 we have a scenario we where we want to create 1 to many keyvaults with 1 to many keyvault access policy objects and some of these object IDs are known up front eg: 1 to many Azure AD groups/users, and some are dynamic eg: MSI from 1 to many app services,

keyvaults = [
“kv1”,“kv2”,“kv3”]
keyvault_accesspolicyobjects = [“12345678-1234-abcd-df12-abc45678901x”,“12345678-5678-efgh-ui67-def45678901y”,“12345678-9101-ijkl-7g3f-hij45678901z”]
dynamic MSI’s

Using a single for_each we can loop through list “keyvaults” and create those no problem,

resource “azurerm_key_vault” “azure” {
for_each = toset(var.keyvaults)
name = each.key
location = azurerm_resource_group.azure.location
resource_group_name = azurerm_resource_group.azure.name
enabled_for_disk_encryption = true
tenant_id = data.azurerm_client_config.current.tenant_id
soft_delete_enabled = false
sku_name = “standard”
tags = var.tags
network_acls {
default_action = “Allow”
bypass = “AzureServices”
}
}

The next bit we want to apply is the keyvault access policy objects.
We loop through the key_vault_id and if the “object_id” is a single string this works without issue. However we cannot pass a list and as we cannot have more than 1 for_each we are not able to loop through either a static or dynamic list of object ids.
We’ve looked at setproduct() and tried using for_each in a locals block but that is also unsupported.
We’ve moved the for_each back to the parent module level but that also introduces other issues for us.
What other options should we consider?

resource “azurerm_key_vault_access_policy” “azure” {
for_each = toset(var.keyvaults)
key_vault_id = azurerm_key_vault.azure[each.key].id
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = SECOND LOOP REQUIRED
key_permissions = [
“backup”, “create”, “decrypt”, “delete”, “encrypt”,
]
secret_permissions = [
“backup”, “delete”, “get”, “list”, “purge”, “recover”, “restore”, “set”
]
certificate_permissions = [
“get”,
]
}

Hi @mattduguid,

The general rule for for_each is that the value you provide to it should have one element per instance you want to create, so the approach here will be to construct a new collection that contains one element for each pair of key vault and object id.

The two most common ways to do that are flatten (if you have a heirarchical sort of structure where one object “belongs to” another), and setproduct (if you have two separate sets and want to find every combination of values from them both).

You mentioned you already tried setproduct and that it didn’t work; if you can show what you tried and what happened when you tried it, I might be able to explain what happened and offer a slightly different variant.

we tried listA which was outputs of keyvault ID’s retrieved using a remote state data source (only available after keyvault creation and required to set keyvault access policy) and listB which was a static list of object_ID’s from var file, we were attempting to use setproduct on them in a locals block to produce a map which we could then do a single for_each on in the subsequent resource block and access via each.key & each.value, I’ll wire it back up and test again and post the outcome, to get us going for now we’ve added the many object_id’s to an single AD group and use the groups object_id in the policy

create a module and pass in Id for the KV.
Worked for me!

module:
image

variables:
image

resource (main.tf under modules)
image

Load module:
image