I am facing a problem with for_each looping in terraform.
I have a azure resource for managed_keys as follow:
resource "azurerm_storage_account_customer_managed_key" "storage-managed-key" {
for_each = toset(var.key-name)
key_name = "Key-Client-${each.value}"
key_vault_id = azurerm_key_vault.tenantsnbshared.id
key_version = azurerm_key_vault_key.client-key[each.value].version
storage_account_id = azurerm_storage_account.storage-foreach[each.value].identity.0.principal_id
depends_on = [azurerm_key_vault_access_policy.storage]
}
I have a variable named key-name
and a storage-account storage-foreach
, both of them have a list(string)
with some values.
My aim is to be able to loop through those 2 variables and encrypt the storage account with the respective key.
but if I run my code, I get this error:
Error: Invalid index
on main.tf line 173, in resource "azurerm_storage_account_customer_managed_key" "storage-managed-key":
173: storage_account_id = azurerm_storage_account.storage-foreach[each.value].identity.0.principal_id
|----------------
| azurerm_storage_account.storage-foreach is object with 4 attributes
| each.value is "key-name"
The given key does not identify an element in this collection value.
EDIT:
resource "azurerm_storage_account" "storage-foreach" {
for_each = toset(var.storage-foreach)
access_tier = "Hot"
account_kind = "StorageV2"
account_replication_type = "LRS"
account_tier = "Standard"
location = var.location
name = each.value
resource_group_name = azurerm_resource_group.tenant-testing-hamza.name
identity {
type = "SystemAssigned"
}
lifecycle {
prevent_destroy = false
}
}
Key vault access policies:
resource "azurerm_key_vault_access_policy" "storage" {
for_each = var.storage-foreach
key_vault_id = azurerm_key_vault.tenantsnbshared.id
tenant_id = "<tenant-id>"
object_id = azurerm_storage_account.storage-foreach[each.value].identity.0.principal_id
key_permissions = ["get", "Create", "List", "Restore", "Recover", "Unwrapkey", "Wrapkey", "Purge", "Encrypt", "Decrypt", "Sign", "Verify", "Delete"]
secret_permissions = ["get", "set", "list", "delete", "recover"]
depends_on = [azurerm_key_vault.tenantsnbshared]
}
variable "storage-foreach" {
type = map(string)
default = { "<name1>" = "storage1", "<name2>" = "storage2", "<name3>" = "storage3", "<name4>" = "storage4"}
}
variable "key-name" {
type = map(string)
default = {"<name1>" = "key1", "<name2>" = "<key2>", "name3" = "<key3>", "<name4>" = "key4"}
}
this error get repeated for each element I have in my key-name variable.
I tried the some thing but using a count
instead of a for_each
and it works just fine, but the problem I had with that, was if I wanted to delete the first storage account and the first key, it automatically destroy all the element coming after to then recreate them, and is not something I wanted to do.
Is there anyone who can help me to understand this error and how to fix it please?
I'm assuming the lists with keys and storage accounts are of the same length, and that you want to encrypt storage account number i with key number i. You can either use "old style", index-based loops or zip the two lists into a single list of tuples, and then iterate over the zipped list.
This solution does not use for_each
but the meta-argument count
. This way of iterating over resources in Terraform predates the newer for_each
style.
resource "azurerm_storage_account_customer_managed_key" "storage-managed-key" {
count = length(var.key-name)
key_name = azurerm_key_vault_key.client-key[var.key-name[count.index]].name
key_vault_id = azurerm_key_vault_key.client-key[var.key-name[count.index]].key_vault_id
key_version = azurerm_key_vault_key.client-key[var.key-name[count.index]].version
storage_account_id = azurerm_storage_account.storage-foreach[var.storage-foreach[count.index]].identity.0.principal_id
depends_on = [azurerm_key_vault_access_policy.storage]
}
I've taken the liberty to replace the explicit key name and key vault ID by references to the attributes of your key resource.
Here, the idea is to create a structure that you can iterate over, and of which the elements combine key name and the storage account names. There's multiple ways of doing this. The easiest way is probably to "misuse" a map and treat it as a list of tuples, as you can then simply use zipmap
.
resource "azurerm_storage_account_customer_managed_key" "storage-managed-key" {
for_each = zipmap(var.storage-foreach, var.key-name)
key_name = azurerm_key_vault_key.client-key[each.value].name
key_vault_id = azurerm_key_vault_key.client-key[each.value].key_vault_id
key_version = azurerm_key_vault_key.client-key[each.value].version
storage_account_id = azurerm_storage_account.storage-foreach[each.key].identity.0.principal_id
depends_on = [azurerm_key_vault_access_policy.storage]
}
Note that I, perhaps confusingly, chose var.storage-foreach
to be the keys of the object. Picking the keys as the map keys would make it impossible to use the same key to encrypt multiple storage accounts. Since storage-foreach is already used to index Terraform resources, I also already know these adhere to the Terraform naming restrictions.
In solution 1, your azurerm_storage_account_customer_managed_key
resource names are integer-based. Re-ordering elements in the lists may cause Terraform to destroyed and re-create resources. This is not the case for solution 2, which is why I usually prefer solution 2. However, in this case solution 1 may have the advantage of being more straight-forward.
If possible, I would suggest to re-evaluate how you define your variables. It likely makes more sense to ask for list of objects combining key name and storage accounts in the first place; then you can basically use solution 2 without the call to zipmap
. It's almost never a good idea to have an implicit dependency between two variables like this - these lists have to have the same length, and implicitly, the contents of the lists are connected by virtue of having the same index.