0
votes

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?

1
Can you edit your question to also include how you define the azurerm_storage_account resource as well please?ydaetskcoR
@ydaetskcoR done mate, thank you so much!Nayden Van
I don't understand why I cannot loop on 2 resources at the same time. In the resource managed keys, I am looping on key-name and looping on storage account id. In my IDE I can check that the loops are set correctly and point at the right values, but when I do a terraform plan, the value "each.value" get filled only with the values in key-name variable(for storage account_id as well) and that is the reason of the error. Any workaround?Nayden Van
This sounds so much like you want a map type and not a list type for your iterator value. That would probably fix your issue and make this a lot easier.Matt Schuchard
I was considering the idea of using a map. But can I implement the map only on the storage account? Or I have to set a map for the keys too?Nayden Van

1 Answers

2
votes

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.

Solution 1: using index-based iteration

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.

Solution 2: using combined structure

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.

What's the difference?

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.

A suggestion...

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.