Terraform: Invalid value for “v” parameter: cannot convert object to set of any single type

2.3k views Asked by At

I’m trying out Terraform, and I’m trying to setup an environment from scratch to play around with it.

I’ve seen some ideas around using modules, so I’m trying to modulise both my vnet and snets, based on my tfvars file.

I’ve hit a snag and wondered if someone could either direct me to where I can get the answer, or point out the likely obvious mistake I’ve made.

My modules look like this:

Modules:

vnet main.tf:

resource “azurerm_virtual_network” “vnet” {
    name = var.name
    location = var.location
    resource_group_name = var.resource_group_name
    address_space = var.address_space
    dynamic “subnet” {
    for_each = toset(var.subnets)
    content {
             name = subnet.value.name
             address_prefix = subnet.value.address_prefix
             security_group = subnet.value.security_group
            }
   }
}
snet main.tf:


resource “azurerm_subnet” “subnet” {
    for_each = var.subnets
    name = each.value.name
    resource_group_name = var.resource_group_name
    virtual_network_name = var.virtual_network_name
    address_prefixes = each.value.address_prefixes
    service_endpoints = each.value.service_endpoints
    dynamic “delegation” {
                          for_each = toset(each.value.delegation)
                         content {
                                 name = delegation.value.name
                                 service_delegation {
                                                     name = delegation.value.service_delegation
                                                     actions = delegation.value.service_delegation_actions
                                                    }
                                 }
                          }
}

Main.tf

And the main.tf where I use the modules looks like:

module “vnet_uks” {
    source = “./modules/vnet”
    name = var.vnetuksname
    location = var.vnetukslocation
    resource_group_name = var.vnetuksrg
    address_space = var.uksaddress_space
    subnets = var.subnets
}



module “subnets” {
    source = “./modules/subnets”
    subnets = var.subnets
    resource_group_name = var.resource_group_name
    virtual_network_name = module.vnet_uks.name
    depends_on = [ module.vnet_uks ]
}

and the tfvars file section for this looks like:

#Virtual Networks and Subnets for UKS
vnetukslocation = “uksouth”
vnetuksrg = “rg-uks-compute”
uksaddress_space = [“10.0.0.0/16”, “10.1.0.0/24”]
vnetuksname = “vnet-uks-01”
subnets = {
          uks_vms = {name = “snet_uks_vms”, address_prefix = [“10.1.1.0/24”], enforce_private_link_endpoint_policies = false, delegations = , service_endpoints = [“Microsoft.AzureActiveDirectory”, “Microsoft.KeyVault”, “Microsoft.Sql”, “Microsoft.Storage”, “Microsoft.Web”] }
}

When I run a tfplan, I get the following error:

Error: Invalid function argument

on modules\vnet\main.tf line 9, in resource “azurerm_virtual_network” “vnet”:
9: for_each = toset(var.subnets)
├────────────────
│ while calling toset(v)
│ var.subnets is object with 5 attributes`

Invalid value for “v” parameter: cannot convert object to set of any single type.

I’m not sure what else to do - I’ve tried replacing the toset with tomap, but that wants every variable declaring separately.

I'm trying to create this so that the subnets will be added to a virtual network each time an environment is created - I've got about 3-5 of these to create, so I'm trying to automate it so that I don't have to manually add each subnet and vnet into the main.tf separately.

Could someone help point me in the right direction? Or a cleaner way of doing this?

Thanks

1

There are 1 answers

0
Vinay B On BEST ANSWER

I’m trying to modulize both my vnet and snets, based on my tfvars file using terraform I was able to provision the requirement successfully.

Your issue arises from the way you're trying to iterate over the subnets object. The toset() function is for converting lists to sets, and var.subnets is an object (map). We should use for_each directly with maps. However, since you're using nested map structures, we need to make some adjustments in Vnet, Subnet and tfvars files.

My file structure:

terraform-setup/
│
├── main.tf
├── variables.tf
├── terraform.tfvars
│
└── modules/
    ├── vnet/
    │   └── main.tf
    │
    └── subnets/
        └── main.tf

main.tf:

provider "azurerm" {
    features {}
}

module "azure_vnet" {
    source              = "./modules/vnet"
    name                = var.vnet_name
    location            = var.location
    resource_group_name = var.resource_group_name
    vnet_address_space =  var.vnet_address_space
    subnets             = var.subnets
}

module "azure_subnets" {
    source              = "./modules/subnets"
    subnets             = var.subnets
    resource_group_name = var.resource_group_name
    virtual_network_name = module.azure_vnet.vnet_name
    depends_on          = [ module.azure_vnet ]
}

variables.tf:

variable "location" { 
    description = "Azure location" 
}

variable "resource_group_name" { 
    description = "Resource group name"
}

variable "vnet_name" { 
    description = "Virtual Network name"
}

variable "vnet_address_space" { 
    description = "Address space for VNet" 
    type = list(string) 
}

variable "subnets" { 
    description = "Subnets configurations" 
    type = map(any) 
}

terraform.tfvars:

#Virtual Networks and Subnets for UKS
location = "east us"
resource_group_name = "v-sakavya"
vnet_address_space = ["10.0.0.0/16", "10.1.0.0/24"]
vnet_name = "vnet-uks-01"
subnets = {
  uks_vms = {
    name = "sm_uks_vkvms", 
    address_prefix = ["10.0.2.0/24"], 
    enforce_private_link_endpoint_policies = false, 
    service_endpoints = ["Microsoft.Containerinstance/containerGroups", "Microsoft.KeyVault", "Microsoft.Sql", "Microsoft.Storage", "Microsoft.Web"]
    }
}

modules/subnets/main.tf:

resource "azurerm_subnet" "subnet" {
        for_each             = var.subnets
        name                 = each.value.name
        resource_group_name  = var.resource_group_name
        virtual_network_name = var.virtual_network_name
        address_prefixes     = each.value.address_prefix
    
        service_endpoints    = each.value.service_endpoints
    
     
   
    dynamic "delegation" {
        for_each = each.value.delegations ? [each.value.delegations] : []
        content {
            name    = delegation.value.name
            service_delegation {
                name    = delegation.value.service_delegation
                actions = delegation.value.service_delegation_actions
           }
       }
   }
}

modules/vnet/main.tf:

     resource "azurerm_virtual_network" "vnet" {
        name                = var.name
        location            = var.location
        resource_group_name = var.resource_group_name
        address_space       = var.vnet_address_space
    
        dynamic "subnet" {
            for_each = var.subnets
            content {
                name                 = subnet.value.name
                address_prefix       = subnet.value.address_prefix[0] # assuming each subnet has a single address prefix
                //security_group       = subnet.value.security_group
            }
        }
    }
     output "vnet_name" {
        description = "The name of the virtual network."
        value       = azurerm_virtual_network.vnet.name
    }

modules/vnet/variable.tf:

variable "name" {
  description = "The name of the virtual network."
  type        = string
}

variable "location" {
  description = "The Azure Region in which the virtual network should exist."
  type        = string
}

variable "resource_group_name" {
  description = "The name of the resource group in which the virtual network should be created."
  type        = string
}

variable "vnet_address_space" {
  description = "The address space that is used by the virtual network. This should be a list of address prefixes."
  type        = list(string)
}

variable "subnets" {
  description = "The configurations for the subnets to be created within this virtual network."
  type        = map(any)
}

modules/subnets/variable.tf:

variable "subnets" {
  description = "The configurations for the subnets to be created."
  type        = map(any)
}

variable "resource_group_name" {
  description = "The name of the resource group in which the subnets should be created."
  type        = string
}

variable "virtual_network_name" {
  description = "The name of the virtual network in which the subnets should be created."
  type        = string
}

Output:

enter image description here

enter image description here