Cannot pass complex variable type on command line

My understanding is that this should work to pass complex types:

$ terraform plan -var 'mylist=["a"]'
$ terraform plan -var 'mymap={"a":"b"}'

but it seems all terraform gets are strings:

var.mymap is "{\"a\":\"b\"}"
...
...arguments must be maps or objects, got "string".

This is on linux, terraform 1.2.2 (but happens with 1.3.7 too).
Am I doing something wrong?

Hi @mt2022,

Terraform CLI decides how to interpret your -var arguments (and also the similar TF_VAR_-prefixed environment variables) based on the type constraint you specified for the input variable.

Because root module input variables are most commonly strings, by default Terraform CLI will just treat the value as a raw string to make it easier to set those without needing to include extra quotes escaped by the shell.

If you change your variable block to declare the variable as expecting a map or list type then Terraform CLI will instead parse the string you provided as Terraform expression syntax, which I think will achieve the result you wanted:

variable "mylist" {
  type = list(string)
}

variable "mymap" {
  type = map(string)
}

This is a special rule just for root module input variables set directly on the command line and in environment variables, because in this case there’s some ambiguity about how a value should be interpreted. This is behavior implemented by Terraform CLI as it decodes the command line and environment, rather than behavior of the Terraform Language itself. The type constraint doesn’t affect the parsing of variable values in any other context.

1 Like

Thanks. I see a different result (again, it’s possible I misunderstood). Minimal reproducer:

terraform {
  required_providers {
    vsphere = {
      source  = "hashicorp/vsphere"
      version = "2.1.1"
    }
  }
}

variable "vsphere_config_defaults" {
  type = object({
    username = string,
    host = string,
    password = string,
  })
  default = {
    username = ""
    host = ""
    password = ""
  }
}

variable "local_vsphere_config" {}

locals {
  vsphere_config = merge(
    var.vsphere_config_defaults, 
    var.local_vsphere_config, 
  )
}

provider "vsphere" {
  vsphere_server       = local.vsphere_config["host"]
  user                 = local.vsphere_config["username"]
  password             = local.vsphere_config["password"]
  allow_unverified_ssl = true
}

Invoke with:

$ terraform plan -var 'local_vsphere_config={"username":"foo","host":"foo","password":"foo"}'
β•·
β”‚ Error: Error in function call
β”‚ 
β”‚   on main.tf line 26, in locals:
β”‚   26:   vsphere_config = merge(
β”‚   27:     var.vsphere_config_defaults, 
β”‚   28:     var.local_vsphere_config, 
β”‚   29:   )
β”‚     β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚     β”‚ var.local_vsphere_config is "{\"username\":\"foo\",\"host\":\"foo\",\"password\":\"foo\"}"
β”‚     β”‚ var.vsphere_config_defaults is object with 3 attributes
β”‚ 
β”‚ Call to function "merge" failed: arguments must be maps or objects, got "string".

Ok, I see where I went wrong. It’s the local_vsphere_config variable that has to be explicitly typed in the manifest (whereas I used just {}). It works now. Thanks!