Am trying to use Terraform to deploy multiple storage account with variable storage containers. But I only want to call the nodule once, so the storage account variable is a map of objects. The storage container(s) is a nested map of objects inside the storage account map.
It looks like this:
variables.tf
variable "location" {
type = string
description = "Location for storage account resource."
}
variable "resource_group_name" {
type = string
description = "Resource group that will contain the resource."
}
variable "storage_account" {
type = map(object({
name = string
access_tier = string
account_kind = string
account_replication_type = string
account_tier = string
public_network_access_enabled = bool
containers = optional(map(object({
name = string
container_access_type = optional(string, "private")
})))
network_rules = list(object({
default_action = string
bypass = optional(set(string))
ip_rules = optional(set(string))
virtual_network_subnet_ids = optional(set(string))
}))
}))
}
The resource module code looks like this:
main.tf
resource "azurerm_storage_account" "storage" {
for_each = var.storage_account
name = each.value.name
resource_group_name = var.resource_group_name
location = var.location
account_tier = each.value.account_tier
account_replication_type = each.value.account_replication_type
account_kind = each.value.account_kind
access_tier = each.value.access_tier
public_network_access_enabled = each.value.public_network_access_enabled != null ? each.value.public_network_access_enabled : false
}
locals {
containers_list = flatten([
for key, value in var.storage_account : [
for container in value.containers : {
name = container
storage_account_name = azurerm_storage_account.storage[key].name
}
]
]...)
}
resource "azurerm_storage_container" "storage" {
#for_each = { for container in local.containers_list : container.name.name => container }
for_each = tomap({
for container in local.containers_list : container.name.name => container
})
name = each.value.container.name
storage_account_name = each.value.storage_account_name
}
I call the module using this:
main.tf
module "storage_account" {
source = "./modules/azurerm_storage_account"
resource_group_name = "rg-temp"
location = "uksouth"
tags = {}
storage_account = {
"storage01" = {
name = "storagerandom0001"
account_kind = "StorageV2"
account_tier = "Standard"
account_replication_type = "LRS"
access_tier = "Hot"
public_network_access_enabled = true
network_rules = []
containers = {
"container-aa" = {
name = "random01"
container_access_type = "private"
},
"container-ab" = {
name = "random02"
container_access_type = "private"
}
}
},
"storage02" = {
name = "storagerandom0002"
account_kind = "StorageV2"
account_tier = "Standard"
account_replication_type = "LRS"
access_tier = "Hot"
public_network_access_enabled = true
network_rules = []
containers = {
"container-01" = {
name = "container01"
container_access_type = "private"
},
"container-02" = {
name = "container02"
container_access_type = "private"
}
}
}
}
}
When I run this I get the error:
│ Error: Too many function arguments
│
│ on modules/azurerm_storage_account/main.tf line 17, in locals:
│ 17: containers_list = flatten([
│ 18: for key, value in var.storage_account : [
│ 19: for container in value.containers : {
│ 20: name = container
│ 21: storage_account_name = azurerm_storage_account.storage[key].name
│ 22: }
│ 23: ]
│ 24: ]...)
│ ├────────────────
│ │ while calling flatten(list)
│ │ azurerm_storage_account.storage is object with 2 attributes
│ │ var.storage_account is map of object with 2 elements
│
│ Function "flatten" expects only 1 argument(s).
From this question (which looks very similar to what I am trying to do):
How to access a sequence in a map inside a for_each loop in Terraform
I wonder do I maybe need to merge rather than flatten?
But if I change to merge
I get this error:
╷
│ Error: Error in function call
│
│ on modules/azurerm_storage_account/main.tf line 17, in locals:
│ 17: containers_list = merge([
│ 18: for key, value in var.storage_account : [
│ 19: for container in value.containers : {
│ 20: name = container
│ 21: storage_account_name = azurerm_storage_account.storage[key].name
│ 22: }
│ 23: ]
│ 24: ]...)
│ ├────────────────
│ │ while calling merge(maps...)
│ │ azurerm_storage_account.storage is object with 2 attributes
│ │ var.storage_account is map of object with 2 elements
│
│ Call to function "merge" failed: arguments must be maps or objects, got "tuple".
╵
This is my first time having to nest a loop (or use a merge or a flatten) so I'm not doing very well.
Can any help and also try to explain in simple terms what I need to do/am doing wrong please?
Thanks.
The flatten function is expecting a single argument as the error is saying while calling flatten(list) [...] Function "flatten" expects only 1 argument(s)
. The error arise because of the ...
operator you're adding to the generated list of lists. Just removing it should be fine.
You should then change your local value to:
containers_list = flatten([
for key, value in var.storage_account : [
for _, container in value.containers : {
name = container
storage_account_name = azurerm_storage_account.storage[key].name
}
]
])
Also, since containers is optional, and may be null, I'd provide it with an empty map by default:
variable "storage_account" {
type = map(object({
name = string
access_tier = string
account_kind = string
account_replication_type = string
account_tier = string
public_network_access_enabled = bool
containers = optional(map(object({
name = string
container_access_type = optional(string, "private")
})), {})
network_rules = list(object({
default_action = string
bypass = optional(set(string))
ip_rules = optional(set(string))
virtual_network_subnet_ids = optional(set(string))
}))
}))
}