Hi @terrag,
When I tried what you shared, I saw Terraform produce a few different error messages, including the one you mentioned:
╷
│ Error: Invalid type specification
│
│ on unsuitable-value.tf line 15, in variable "systems":
│ 15: lom = { ip = string },
│
│ A type specification is either a primitive type keyword (bool, number, string) or a complex type constructor call, like list(string).
╵
╷
│ Error: Invalid type specification
│
│ on unsuitable-value.tf line 16, in variable "systems":
│ 16: management = {
│ 17: device = string
│ 18: ip = string
│ 19: netmask = string
│ 20: gateway = string
│ 21: mac = string
│ 22: },
│
│ A type specification is either a primitive type keyword (bool, number, string) or a complex type constructor call, like list(string).
╵
╷
│ Error: Invalid type specification
│
│ on unsuitable-value.tf line 23, in variable "systems":
│ 23: stable = {
│ 24: device = string
│ 25: ip = string
│ 26: }
│
│ A type specification is either a primitive type keyword (bool, number, string) or a complex type constructor call, like list(string).
╵
╷
│ Error: Missing argument separator
│
│ on unsuitable-value.tf line 34, in variable "systems":
│ 3: type = map(object(
│ 4: {
│ 5: interfaces = map(object(
│ 6: {
│ 7: leafs = list(object(
│ 8: {
│ 9: device = string
│ 10: ip = string
│ 11: netmask = string
│ 12: gateway = string
│ 13: }
│ 14: )),
│ 15: lom = { ip = string },
│ 16: management = {
│ 17: device = string
│ 18: ip = string
│ 19: netmask = string
│ 20: gateway = string
│ 21: mac = string
│ 22: },
│ 23: stable = {
│ 24: device = string
│ 25: ip = string
│ 26: }
│ 27: }
│ 28: )
│ 29: ),
│ 30: disks = list(string)
│ 31: }
│ 32: )
│ 33: }
│
│ A comma is required to separate each function argument from the next.
╵
Unfortunately when there are syntax errors Terraform can only make a best effort to guess what you were trying to achieve and so the error messages can sometimes be confusing or misleading – in a sense, Terraform itself is “confused”, so its explanation of the problem doesn’t always make sense.
So I copied the text into my test editor and added newlines and indentations manually in all of the places I’d typically put them when following idiomatic style, which led met to this:
variable systems {
type = map(
object({
interfaces = map(
object({
leafs = list(
object({
device = string
ip = string
netmask = string
gateway = string
})
),
),
lom = {
ip = string
},
management = {
device = string
ip = string
netmask = string
gateway = string
mac = string
},
stable = {
device = string
ip = string
}
})
),
disks = list(string)
})
}
Although I can’t show it this way in the forum, my editor highlights the parenthesis at the end of type = map(
as not having a closing )
, which led me to notice that your final attribute disks
is not nested inside the object({ ... })
declaration, but Terraform didn’t notice that correctly because the closing )
for map(
is incorrectly written as })
instead, which confused both Terraform and my human eyes when trying to spot the problem just from reading the input.
The same problem seems to be true for the nested interfaces
map of objects too, which I think was a few too many syntax errors for the Terraform parser’s recovery behavior to be able to figure out, and so it just reported whatever it parsed next as being an error.
With all of that said then, I’ve tried to rework this to match what I think you were intending to write, and terraform fmt
now accepts it:
variable "systems" {
type = map(
object({
interfaces = map(
object({
leafs = list(
object({
device = string
ip = string
netmask = string
gateway = string
})
)
lom = {
ip = string
}
management = {
device = string
ip = string
netmask = string
gateway = string
mac = string
}
stable = {
device = string
ip = string
}
})
)
disks = list(string)
})
)
}
Again, because I don’t know about the domain model you are trying to represent here I had to guess which of the attributes belong to “leafs”, which to “interfaces”, and which to whole “systems”, but hopefully I got this close enough that you can see how to move around the individual attribute definitions into different object types if you need to.
You’ll probably also notice that I adopted a style here where each type constraint or attribute value is on a line of its own, and so I represented lists of object types like this:
map(
object({
# ...
})
)
…instead of like this:
map(object({
# ...
}))
Both of these are reasonable idiomatic style, and I would often use the second compact form for relatively simple type constraints, but I chose to reflect the nesting more directly in the indentation here just because this is a pretty complicated type constraint and combining the collection type and the object type into a single line – and therefore having to do the same for the closing delimiters }))
, was making this harder rather than easier to read.
For completeness though, here’s the more vertically-compact form of the same type constraint:
variable "systems" {
type = map(object({
interfaces = map(object({
leafs = list(object({
device = string
ip = string
netmask = string
gateway = string
}))
lom = {
ip = string
}
management = {
device = string
ip = string
netmask = string
gateway = string
mac = string
}
stable = {
device = string
ip = string
}
}))
disks = list(string)
}))
}
It’s tough to say objectively which of these is “better” in this case. I find the second example less readable because it’s harder to visually separate the collection wrapper map
or list
from the object type inside it, but of course that’s just how my brain prefers to work, and you might have different preferences. I would not recommend trying to be any more compact than the most recent example above though.