Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When is the equal sign (=) required in Terraform when assigning a map (TF 0.11)

Tags:

terraform

I wasn't able to solve this question by any other method, so I have to ask this here...

What is the logic behind using the equal sign (=) when assigning a map to a value in Terraform 0.11.

With =

resource "pseude_resource" "pseudo_name" {
  value = {
    key1 = value1
    key2 = value2
  }
}

Without =

resource "pseude_resource" "pseudo_name" {
  value {
    key1 = value1
    key2 = value2
  }
}

The = seems to be required when using arrays ([]), but isn't when using a map. What is the reason behind this and why on earth?? Can it just be omited?

like image 605
iptizer Avatar asked Jan 25 '23 23:01

iptizer


2 Answers

In the Terraform language, there are two distinct constructs that have quite similar-looking syntax in the common case.

Arguments that expect maps

An argument in Terraform is a single name/value pair where the provider dictates (in the resource type schema) what type of value it expects. You are free to create a value of that expected type any way you like, whether it be as a literal value or as a complex expression.

The argument syntax is, in general:

  name = value

If a particular argument is defined as being a map then one way you can set it is with a literal value, like this:

 tags = {
   Name = "foo bar baz"
 }

...but you can also use a reference to some other value that is compatible with the map type:

  # Tags are the same as on some other VPC object
  tags = aws_vpc.example.tags

...or you can combine maps together using built-in Terraform functions:

  tags = merge(local.default_tags, var.override_tags)

Generally speaking, you can use any expression whose result is a map with the expected element type.

In Terraform 0.11 these non-literal examples all need to be presented in the template interpolation syntax ${ ... }, but the principle is the same nonetheless.

Nested blocks

Whereas an argument sets some specific configuration setting for the object whose block it's embedded in, the nested block syntax conventionally declares the existence of another object that is related to the one the block is embedded in. Sometimes this really is a separate physical object, such as a rule associated with a security group, and other times it's a more conceptual "object", such as versioning in aws_s3_bucket which models the versioning feature as a separate "object" because the presence of it activates the feature.

The nested block syntax follows the same conventions as the syntax for the top-level resource, variable, terraform, etc blocks:

  block_type "label" {
    nested_argument = value
  }

Each block type will have a fixed number of labels it expects, which in many cases is no labels at all. Because each block represents the declaration of a separate object, the block structure is more rigid and must always follow the shape above; it's not possible to use arbitrary expressions in this case because Terraform wants to validate that each of the blocks has correct arguments inside it during its static validation phase.

Because the block syntax and the map literal syntax both use braces { }, they have a similar look in the configuration, but they mean something quite different to Terraform. With the block syntax, you can expect the content of the block to have a fixed structure with a specific set of argument names and nested block types decided by the resource type schema. With a map argument, you are free to choose whichever map keys you like (subject to any validation rules the provider or remote system might impose outside of the Terraform schema).

Recognizing the difference

Unfortunately today the documentation for providers is often vague about exactly how each argument or nested block should be used, and sometimes even omits the expected type of an argument. The major providers are very large codebases and so their documentation has variable quality due to the fact that they've been written by many different people over several years and that ongoing improvements to the documentation can only happen gradually.

With that said, the provider documentation will commonly use the words "nested block" or "block type" in the description of something that is a nested block, and will refer to some definition elsewhere on the page for exactly which arguments and nested blocks belong inside that block. If the documentation states that a particular argument is a map or implies that the keys are free-form then that suggests that it's an argument expecting a map value. Another clue is that block type names are conventionally singular nouns (because each block describes a single object) while arguments that take maps and other collections conventionally use plural nouns.

If you find specific cases where the documentation is ambiguous about whether a particular name is a nested block type or an argument, it can be helpful to open an issue for it in the provider's repository to help improve the documentation. Terraform's documentation pages have an "Edit This Page" link in the footer which you can use to propose simple (single-page-only) edits as a pull request in the appropriate repository.

The longer-form explanation of these concepts is in the documentation section Arguments and Blocks.

like image 135
Martin Atkins Avatar answered Jan 28 '23 12:01

Martin Atkins


The confusion comes from the behavior of the hcl language used by terraform. The functionality isn't very well documented in terraform but... In hcl, you can define a list by using repeating blocks, which is how resources like aws_route_table define inline routes, e.g.

resource "aws_route_table" "r" {
  vpc_id = "${aws_vpc.default.id}"

  route {
    cidr_block = "10.0.1.0/24"
    gateway_id = "${aws_internet_gateway.main.id}"
  }

  route {
    ipv6_cidr_block        = "::/0"
    egress_only_gateway_id = "${aws_egress_only_internet_gateway.foo.id}"
  }

  tags = {
    Name = "main"
  }
}

which would be equivalent to

resource "aws_route_table" "r" {
  vpc_id = "${aws_vpc.default.id}"

  route = [
    {
      cidr_block = "10.0.1.0/24"
      gateway_id = "${aws_internet_gateway.main.id}"
    },
    {
      ipv6_cidr_block        = "::/0"
      egress_only_gateway_id = "${aws_egress_only_internet_gateway.foo.id}"
    }
  ]

  tags = {
    Name = "main"
  }
}

You want to make sure you're using the = when you're assigning a value to something, and only use the repeating block syntax when you're working with lists. Also, from my experience I recommend NOT using inlines when an individual resource is available.

Some very limited documentation for hcl can be found in the readme for the repo.

like image 34
Ngenator Avatar answered Jan 28 '23 13:01

Ngenator