Search code examples
terraformterraform-modules

Terraform dynamicly attach disks and network when vps is counted


I can't figure out how to dynamically connect disks and networks to virtual machines if they are counted I expect i will pass variables like this:

module "vps-test" {
  source            = "../module"
  count             = 3
  server_name       = "vpstest"
  server_image      = "debian-11"
  server_type       = "cx21"
  server_datacenter = "fsn1-dc14"
  labels            = { groups = "test_offline.test_vps" }
  server_network = {
    backend_network = {
      subnet_id = (data.terraform_remote_state.htz_network.outputs.main-subnet-id)
      ip        = "" #DHCP
    }
    custom_network = {
      subnet_id = "1867414"
      ip        = ""
    }
  }
  volumes = {
    firts_volume = {
      name = "volume1"
      size = "20"
    }
    second_volume = {
      name = "volume1"
      size = "20"
    }
  }
  hetzner_cloud_token  = var.hetzner_cloud_offline_main_api_token
  cloud_init_file_path = "../module/scripts/user.yaml"

}

and the module will create 3 identical VMs, where each will have 2 disks and 2 networks

It's Hetznere cloud provider, here is my simple code:

resource "hcloud_server" "vps" {
  count       = var.server_count
  name        = var.server_count > 1 ? "${var.server_name}-${count.index}" : var.server_name
  image       = var.server_image
  server_type = var.server_type
  datacenter  = var.server_datacenter
  user_data   = data.template_file.ansible_user_data.rendered
  labels      = var.labels
}

resource "hcloud_volume" "volume" {
  for_each  = var.volumes
  name      = tostring(each.value["name"])
  size      = tonumber(each.value["size"])
  server_id = hcloud_server.vps.id
  automount = true
  format    = var.volume_filesystem
}

resource "hcloud_server_network" "network" {
  for_each  = var.server_network
  server_id = hcloud_server.vps.id
  subnet_id = each.value["subnet_id"]
  ip        = tostring(each.value["ip"])
}

Errors:

│ Error: Missing resource instance key
│ 
│   on ../module/resource.tf line 15, in resource "hcloud_volume" "volume":
│   15:   server_id = hcloud_server.vps.id
│ 
│ Because hcloud_server.vps has "count" set, its attributes must be accessed on specific instances.
│ 
│ For example, to correlate with indices of a referring resource, use:
│     hcloud_server.vps[count.index]
╵
╷
│ Error: Missing resource instance key
│ 
│   on ../module/resource.tf line 22, in resource "hcloud_server_network" "network":
│   22:   server_id = hcloud_server.vps.id
│ 
│ Because hcloud_server.vps has "count" set, its attributes must be accessed on specific instances.
│ 
│ For example, to correlate with indices of a referring resource, use:
│     hcloud_server.vps[count.index]

if using recommends from error log

│ Error: Reference to "count" in non-counted context
│ 
│   on ../module/resource.tf line 15, in resource "hcloud_volume" "volume":
│   15:   server_id = hcloud_server.vps[count.index].id
│ 
│ The "count" object can only be used in "module", "resource", and "data" blocks, and only when the "count" argument is set.
╵
╷
│ Error: Reference to "count" in non-counted context
│ 
│   on ../module/resource.tf line 22, in resource "hcloud_server_network" "network":
│   22:   server_id = hcloud_server.vps[count.index].id
│ 
│ The "count" object can only be used in "module", "resource", and "data" blocks, and only when the "count" argument is set.

but if server_id = hcloud_server.vps[0].id (or any specific index) - working

where is the correct way


Solution

  • Since you are using both count and for_each you need to iterate over both of them. Basically it means you need a double for loop. One way to do it in TF is with the help of setproduct:

    resource "hcloud_volume" "volume" {
      for_each  = {for idx, val in setproduct(range(var.server_count), keys(var.volumes)): idx => val}
      name      = tostring(var.volume[each.value[1]].name)
      size      = tonumber(var.volume[each.value[1]].size)
      server_id = hcloud_server.vps[each.value[0]].id
      automount = true
      format    = var.volume_filesystem
    }
    
    resource "hcloud_server_network" "network" {
      for_each  = {for idx, val in setproduct(range(var.server_count), keys(var.server_network)): idx => val}  
      server_id = hcloud_server.vps[each.value[0]].id
      subnet_id = var.server_network[each.value[1]].subnet_id  
      ip        = tostring(var.server_network[each.value[1]].ip)
    }