Heuristic to know when to use equals in HCL blocks/objects?

Is there a heuristic y’all follow when trying to figure out whether or not you should use an equals sign in your HCL? That is, when to use

foo {
  bar {
    whatever = true
  }
}

vs

foo {
  bar = {
    whatever = true
  }
}

It seems like I get this wrong about half the time. I’m hoping for a way to intuitively know when to use what without having to refer to the docs every time.

One is an argument (with a map as the value) and the other is a block.

something = { ... } is an argument with a map as the value, similar to something = [ ... ] or something = "...".

something { ... } is a block called “something”, which can often be repeated:

something { ... }
something { ... }
something { ... }

Which one to use totally depends on what the provider has decided, so you’d need to check the docs, but if there is something which can be repeated (for example a rule or something) it will often be a block.

Hi @dpkirchner,

This ultimately comes down to how the provider developer wrote the schema. The rough guidance for provider developers is that nested blocks should typically represent separate-but-related objects that are in some sense “contained inside” the parent, whereas arguments (the name = expression syntax) is for representing a configuration setting for the current object. As long as provider developers follow that guidance, and as long as I have an agreeable mental model of what is a nested object vs. a property of the current object, my “heuristic” is to imagine what I’m describing in the remote system and think about whether it’s describing a new object or an existing one. Of course, it’s ultimately up to the provider design to decide how to use these, so there are situations where designers chose a different model out of pragmatism.

By this point XML is not so popular anymore and so it’s not always a helpful analogy for everyone, but if XML is familiar to you then it might help to think of a nested block as being like a nested element while an argument as being like an attribute of an existing element.

It is unfortunate that the syntax for object constructors uses the same punctuation as the delimiters for blocks, since that means that an argument with an inline object or map assigned to it looks more related to the block syntax than it really is. The two are not really related at all; in any case where you see name = expression you can write any arbitrary expression on the right hand side, as long as it produces a value that meets the provider’s type and validation constraints.

One specific rough edge in most existing providers today is that the legacy Terraform SDK that the long-standing providers are written with – which was originally designed for Terraform v0.11 and earlier – doesn’t offer any way to actually use object-typed arguments in a resource type schema. That type kind just didn’t exist at all in Terraform v0.11 and earlier. That means that sometimes provider developers use singleton nested blocks in situations that would ideally have been single arguments of an object type, just to work around a limitation of the SDK’s modelling of the type system. Thankfully the new framework isn’t constrained in that way, so new providers written with that framework – and existing providers as they gradually adopt it – will hopefully be more consistent in how they use arguments vs. nested blocks.

OK, thanks folks. I’ll try to think about whether or not the value feels like it’d be definable entirely with variables – if so it’s an object, if not it might be a block.