How to loop nested map of maps in terraform

222 views Asked by At

I am trying to create Volumes in NetApp Account - Pools.

Below picture is my locals maps of objects, where I want to retrieve the volumes information for each Netapp pool to create azurerm_netapp_volume resource.

enter image description here

Below is my terraform resources where I am get the Resource Group, Virtual Network, Subnet information using terraform data block and trying to create NetApp Account , NetApp Pool and NetApp Volumes. I am able to create NetApp Account and NetApp Pool but there are failures for NetApp Volume creation.

locals :

locals {
  netapps = {
    tiers = {
      app = {
        netapp_account = "app-netappaccount"
        netapp_pool_volume = {
          pool_1 = {
            pool_name  = "app_pool_1"
            size_in_tb = "3"
            volume_1 = {
              volume_name     = "app_pool_1_vol_1"
              rule_index      = "1"
              allowed_clients = ["1.0.3.0/27"]

            }
            volume_2 = {
              volume_name     = "app_db_pool_1_vol_2"
              rule_index      = "2"
              allowed_clients = ["1.0.4.0/27"]

            }
          }
        }
      },
      db = {
        netapp_account = "db-netappaccount"
        netapp_pool_volume = {
          pool_1 = {
            pool_name  = "db_pool_1"
            size_in_tb = "4"
            volume_1 = {
              volume_name     = "db_pool_1_vol_1"
              rule_index      = "1"
              allowed_clients = ["1.0.2.0/27"]
              prevent_destroy = true

            }
            volume_2 = {
              volume_name     = "db_pool_1_vol_2"
              rule_index      = "2"
              allowed_clients = ["1.0.5.0/27"]
              prevent_destroy = false

            }
          },
          pool_2 = {
            pool_name  = "db_pool_2"
            size_in_tb = "2"
            volume_1 = {
              volume_name     = "db_pool_2_vol_1"
              rule_index      = "1"
              allowed_clients = ["1.0.6.0/27"]
              prevent_destroy = true

            }
            volume_2 = {
              volume_name     = "db_pool_2_vol_2"
              rule_index      = "2"
              allowed_clients = ["0.0.0.0/0"]
              prevent_destroy = false
            }
          }
        }
      }
    }
  }
}

Data Block to get the Resource Group, Virtual Network, Subnet details

data "azurerm_resource_group" "example" {
  name = "pluto-rg"
}

data "azurerm_virtual_network" "example" {
  name                = "pluto-virtualnetwork"
  resource_group_name = "pluto-rg"
}

data "azurerm_subnet" "example" {
  name                 = "netapp-subnet"
  virtual_network_name = "pluto-virtualnetwork"
  resource_group_name  = "pluto-rg"
}

Resources Creation for NetApp Accounts, NetApp Pools and NetApp Volumes

    resource "azurerm_netapp_account" "example" {
      for_each            =  local.netapps.tiers
      name                = each.value.netapp_account
      location            = data.azurerm_resource_group.example.location
      resource_group_name = data.azurerm_resource_group.example.name
    }
    
    resource "azurerm_netapp_pool" "example" {
      depends_on = [azurerm_netapp_account.example]
      for_each = {
        for account in flatten([
          for tiers_k, account in local.netapps.tiers : [
            for pool_k, pool_v in account.netapp_pool_volume : {
              netapp_account = account.netapp_account,
              pool_name      = pool_v.pool_name
              size_in_tb     = pool_v.size_in_tb
              tiers_k        = tiers_k,
              pool_k         = pool_k
            }
          ]
          ]
        ) : "${account.tiers_k}-${account.pool_k}" => account
      }
      name                = each.value.pool_name
      location            = data.azurerm_resource_group.example.location
      resource_group_name = data.azurerm_resource_group.example.name
      account_name        = each.value.netapp_account
      service_level       = "Premium"
      size_in_tb          = each.value.size_in_tb
      qos_type            = "Manual"
    }
    
    resource "azurerm_netapp_volume" "example" {
      depends_on = [azurerm_netapp_pool.example]
      for_each = {
        for account in flatten([
          for tiers_k, account in local.netapps.tiers : [
            for pool_k, pool_v in account.netapp_pool_volume : {
              netapp_account  = account.netapp_account,
              pool_name       = pool_v.pool_name
              size_in_tb      = pool_v.size_in_tb
              volume_name     = pool_v.volume_name
              allowed_clients = pool_v.allowed_clients
              prevent_destroy = pool_v.prevent_destroy
              tiers_k         = tiers_k,
              pool_k          = pool_k
            }
          ]
          ]
        ) : "${account.tiers_k}-${account.pool_k}" => account
      }
    
      lifecycle {
        prevent_destroy = false
      }
    
      name                       = each.value.volume_name
      location                   = data.azurerm_resource_group.example.location
      resource_group_name        = data.azurerm_resource_group.example.name
      account_name               = each.value.netapp_account
      pool_name                  = each.value.pool_name
      volume_path                = "tmp"
      service_level              = "Standard"
      subnet_id                  = data.azurerm_subnet.example.id
      network_features           = "Standard"
      protocols                  = ["NFSv4.1"]
      security_style             = "unix"
      storage_quota_in_gb        = 100
      snapshot_directory_visible = false
      throughput_in_mibps        = "25"
    
      export_policy_rule {
        rule_index          = 1
        allowed_clients     = each.value.allowed_clients
        unix_read_write     = true
        root_access_enabled = true
        protocols_enabled   = ["NFSv4.1"]
  }
}

errors :

│ Error: Unsupported attribute
│
│   on main.tf line 175, in resource "azurerm_netapp_volume" "example":
│  175:           volume_name     = pool_v.volume_name
│
│ This object does not have an attribute named "volume_name".

│ Error: Unsupported attribute
│
│   on main.tf line 176, in resource "azurerm_netapp_volume" "example":
│  176:           allowed_clients = pool_v.allowed_clients
│
│ This object does not have an attribute named "allowed_clients".

Could someone throw someone light on how to loops the nested map of maps ? Thank you in advance.

1

There are 1 answers

0
Vinay B On BEST ANSWER

I tried to use a loop nested map of maps in Terraform and I was able to provision the requirement successfully.

The error messages indicate that pool_v.volume_name and pool_v.allowed_clients are not valid attributes. This is because pool_v refers to each netapp_pool_volume object, which itself contains multiple volumes (like volume_1, volume_2, etc.), rather than directly containing the volume_name and allowed_clients attributes.

To overcome this blocker modify the for_each in the azurerm_netapp_volume resource to correctly loop through each volume in each pool & I used flattened_pools & flatten for volumn definition.

Since we are passing the dynamic parameters we need to replace volume path in azurerm_netapp_volume module.

My Updated terraform configuration:

provider "azurerm" {
    features {}
}

resource "azurerm_resource_group" "example" {
  name     = "pluto-rg"
  location = "East US2"
}

resource "azurerm_virtual_network" "example" {
  name                = "pluto-virtualnetwork"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
}

resource "azurerm_subnet" "example" {
  name                 = "netapp-subnet"
  resource_group_name  = azurerm_resource_group.example.name
  virtual_network_name = azurerm_virtual_network.example.name
  address_prefixes     = ["10.0.1.0/24"]

  delegation {
    name = "netappdelegation"

    service_delegation {
      name = "Microsoft.Netapp/volumes"
      actions = [
        "Microsoft.Network/networkinterfaces/*",
        "Microsoft.Network/virtualNetworks/subnets/join/action"
      ]
    }
  }
}

locals {
  netapps = {
    tiers = {
      app = {
        netapp_account = "app-netappaccount"
        pools = {
          my_app_pool_1 = {
            size_in_tb = "3"
            volumes = {
              app_pool_1_vol_1 = {
                rule_index       = "1"
                allowed_clients  = ["1.0.3.0/27"]
              }
              app_pool_1_vol_2 = {
                rule_index       = "2"
                allowed_clients  = ["1.0.4.0/27"]
              }
            }
          }
        }
      }
      db = {
        netapp_account = "db-netappaccount"
        pools = {
          my_db_pool_1 = {
            size_in_tb = "4"
            volumes = {
              db_pool_1_vol_1 = {
                rule_index       = "1"
                allowed_clients  = ["1.0.2.0/27"]
                prevent_destroy  = true
              }
              db_pool_1_vol_2 = {
                rule_index       = "2"
                allowed_clients  = ["1.0.5.0/27"]
              }
            }
          }
          my_db_pool_2 = {
            size_in_tb = "2"
            volumes = {
              db_pool_2_vol_1 = {
                rule_index       = "1"
                allowed_clients  = ["1.0.6.0/27"]
                prevent_destroy  = true
              }
              db_pool_2_vol_2 = {
                rule_index       = "2"
                allowed_clients  = ["0.0.0.0/0"]
              }
            }
          }
        }
      }
    }
  }

 flattened_pools = merge([
    for tier_name, tier in local.netapps.tiers : {
      for pool_name, pool in tier.pools : "${tier_name}-${pool_name}" => {
        tier_name = tier_name,
        pool_name = pool_name,
        size_in_tb = pool.size_in_tb
      }
    }
  ]...)

  volume_definitions = flatten([
    for tier_name, tier in local.netapps.tiers : [
      for pool_name, pool in tier.pools : [
        for volume_name, volume in pool.volumes : {
          tier_name      = tier_name,
          pool_name      = pool_name,
          volume_name    = volume_name,
          volume_details = volume
        }
      ]
    ]
  ])
}



resource "azurerm_netapp_account" "accounts" {
  for_each = local.netapps.tiers

  name                = each.value.netapp_account
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
}

resource "azurerm_netapp_pool" "pools" {
  for_each = local.flattened_pools

  name                = each.value.pool_name
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
  account_name        = azurerm_netapp_account.accounts[each.value.tier_name].name
  service_level       = "Premium"
  size_in_tb          = each.value.size_in_tb
  qos_type            = "Manual"

  depends_on = [ azurerm_netapp_account.accounts ]
}

resource "azurerm_netapp_volume" "example" {
  depends_on = [ azurerm_netapp_pool.pools ]
  for_each = { for v in local.volume_definitions : v.volume_name => v }

  name                  = each.value.volume_name
  location              = azurerm_resource_group.example.location
  resource_group_name   = azurerm_resource_group.example.name
  account_name          = azurerm_netapp_account.accounts[each.value.tier_name].name
  pool_name             = each.value.pool_name
  
 volume_path           = substr(replace("${each.value.pool_name}-${each.value.volume_name}", "_", "-"), 0, min(length("${each.value.pool_name}-${each.value.volume_name}"), 80))

  service_level         = "Premium"
  subnet_id             = azurerm_subnet.example.id
  network_features      = "Standard"
  protocols             = ["NFSv4.1"]
  security_style        = "unix"
  storage_quota_in_gb   = 100  // Adjust if necessary
  snapshot_directory_visible = false
  throughput_in_mibps   = "10"  // Adjust if necessary


  export_policy_rule {
    rule_index        = each.value.volume_details.rule_index
    allowed_clients   = each.value.volume_details.allowed_clients
    unix_read_write   = true
    root_access_enabled = true
    protocols_enabled = ["NFSv4.1"]
  }

}

Output:

enter image description here

enter image description here

enter image description here

enter image description here