sokolata
sokolata

Reputation: 549

How to use output ids in terraform as variable?

I use terraform to build my infrastructure in the cloud (I'm new to terraform).

I also create some firewall rules with terraform that I need to assign to multiple servers. For that I created a field called firewall in my model.

Variables.tf

variable "hosts" {
    type = map(object({
        name                    = string
        serverType              = string
        serverImage             = string
        serverLocation          = string
        serverKeepDisk          = bool
        serverBackup            = bool 
        ip                      = string
        volume                  = bool
        volumeName              = string
        volumeSize              = number
        volumeFormat            = string
        volumeAutomount         = bool
        volumeDeleteProtection  = bool
        floating                = bool
        firewall                = list(string) <--- this thing here!
      }))
    }

Terraform.tfvars

"myServer01"     = {
            name                    = "s01"
            serverType              = "cx11"
            serverImage             = "ubuntu-20.04"
            serverLocation          = "fsn1"
            serverKeepDisk          = false
            serverBackup            = false
            ip                      = "192.168.0.12"
            volume                  = true
            volumeName              = "data"
            volumeSize              = 100
            volumeFormat            = "ext4"
            volumeAutomount         = false
            volumeDeleteProtection  = false
            floating                = false
            firewall                = [hcloud_firewall.basic.id, hcloud_firewall.ssh.id, hcloud_firewall.webserver.id] <-- I define here variables of the firewalls I need to assign to this specific server
        },

main.tf

resource "hcloud_server" "default" {
  for_each      = var.hosts
  name          = each.value.name
  server_type   = each.value.serverType
  image         = each.value.serverImage
  location      = each.value.serverLocation
  user_data     = file("userdata.yml")
  keep_disk     = each.value.serverKeepDisk
  backups       = each.value.serverBackup
  ssh_keys      = [hcloud_ssh_key.default.id]
  firewall_ids  = each.value.firewall

Error:

│ Error: Variables not allowed
│ 
│   on terraform.tfvars line 71:
│   71:             firewall                = [hcloud_firewall.basic.id, hcloud_firewall.ssh.id, hcloud_firewall.webserver.id]
│ 
│ Variables may not be used here.

How can I define multiple firewall rules by ID/ as variable in Terraform?

Upvotes: 0

Views: 2329

Answers (2)

Martin Atkins
Martin Atkins

Reputation: 74259

Definitions for root module input variables effectively exist outside of the root module, in the same sense that when you pass values to variables of a child module you define them in the context of the caller, and so they don't have direct access to anything defined in the called module.

Because there's no module outside of the root module to evaluate expressions in, root module input variables must always have entirely constant values. There is no way for root module variable definitions to directly refer to objects declared inside the root module.

However, you can get a similar effect to what you intended here by adding a level of indirection: rather than having the hosts variable directly include the firewall ids, it could instead include some symbolic names that have meaning only in this configuration, each of which refers to one of your declared firewalls.

variable "hosts" {
  type = map(object({
    name                    = string
    serverType              = string
    serverImage             = string
    serverLocation          = string
    serverKeepDisk          = bool
    serverBackup            = bool 
    ip                      = string
    volume                  = bool
    volumeName              = string
    volumeSize              = number
    volumeFormat            = string
    volumeAutomount         = bool
    volumeDeleteProtection  = bool
    floating                = bool
    firewall_names          = set(string)
  }))
}

locals {
  firewall_ids = {
    basic     = hcloud_firewall.basic.id
    ssh       = hcloud_firewall.ssh.id
    webserver = hcloud_firewall.webserver.id
  }
}

resource "hcloud_server" "default" {
  for_each = var.hosts

  name          = each.value.name
  server_type   = each.value.serverType
  image         = each.value.serverImage
  location      = each.value.serverLocation
  user_data     = file("userdata.yml")
  keep_disk     = each.value.serverKeepDisk
  backups       = each.value.serverBackup
  ssh_keys      = [hcloud_ssh_key.default.id]
  firewall_ids  = [
    for name in each.value.firewall_names : local.firewall_ids[name]
  ]
}

In your terraform.tfvars file you can then specify these indirectly using the symbolic names, instead of by referring directly to the objects inside the module:

hosts = {
  "myServer01" = {
    name                    = "s01"
    serverType              = "cx11"
    serverImage             = "ubuntu-20.04"
    serverLocation          = "fsn1"
    serverKeepDisk          = false
    serverBackup            = false
    ip                      = "192.168.0.12"
    volume                  = true
    volumeName              = "data"
    volumeSize              = 100
    volumeFormat            = "ext4"
    volumeAutomount         = false
    volumeDeleteProtection  = false
    floating                = false
    firewall_names          = ["basic", "ssh", "webserver"]
  }
}

Please note that idiomatic Terraform style is to use attribute names that are all lowercase with underscores separating words, like server_type instead of serverType. Terraform will of course accept both, but using the conventional style may make your module less surprising to people with prior Terraform experience.

Upvotes: 0

Marcin
Marcin

Reputation: 238249

You can't create dynamic variables. So its not possible to do what you want to do. Instead, you should create a local variable and use that instead:

locals {
  firewall = [hcloud_firewall.basic.id, hcloud_firewall.ssh.id, hcloud_firewall.webserver.id]
}

Then you use local.firewall in place of var.firewall in your code.

Upvotes: 1

Related Questions