Dynamic blocks in Terraform 0.12.x

Some time ago I wrote about how to make dynamic blocks in Terraform 0.11.x, that although it solved the problem, it generated others because it wasn’t an official solution and the interpretation by Terraform was not consistent.
The workaround essentially consisted of defining the blocks dynamically using an assignment to a map list. That is, instead of doing this:
resource "aws_codebuild_project" "codebuild" {
artifacts{
type = "${var.codebuild_artifacts_type}"
location = "${var.codebuild_artifacts_location}"
name = "${var.codebuild_artifacts_name}"
namespace_type = "${var.codebuild_artifacts_namespace_type}"
packaging = "${var.codebuild_artifacts_packaging}"
path = "${var.codebuild_artifacts_path}"
}
...
}
It was replaced by this other:
resource "aws_codebuild_project" "codebuild" {
artifacts = ["${local.codebuild_artifacts}"]
...
}
Where local.codebuild_artifacts built the block from a map list, which depending on the type defined in the variable codebuild_artifacts_type it created a different block:
locals {
codebuild_artifacts_def = {
"S3" = [{
type = "${var.codebuild_artifacts_type}"
location = "${var.codebuild_artifacts_location}"
name = "${var.codebuild_artifacts_name}"
namespace_type = "${var.codebuild_artifacts_namespace_type}"
packaging = "${var.codebuild_artifacts_packaging}"
path = "${var.codebuild_artifacts_path}"
encryption_disabled = var.codebuild_artifacts_encryption_disabled
}]
"CODEPIPELINE" = [{
type Some time ago I wrote about [how to make dynamic blocks in Terraform 0.11.x] (https://lgallardo.com/en/2018/07/30/dynamic-configuration-blocks-in-terraform/) {: target = "_blank "}, that although it solved the problem generated others because it was not an official solution and the interpretation by Terraform was not consistent. = "${var.codebuild_artifacts_type}"
name = "${var.codebuild_artifacts_name}"
encryption_disabled = var.codebuild_artifacts_encryption_disabled
}]
"NO_ARTIFACTS" = [{
type = "${var.codebuild_artifacts_type}"
}]
}
# Retuned map
codebuild_artifacts = "${local.codebuild_artifacts_def[var.codebuild_artifacts_type]}"
}
Dynamic blocks in Terraform 0.12.x
Terraform 0.12.x proposes dynamic blocks to solve this and other cases related to blocks. To use dynamic blocks you have to use dynamic and for-each. For example, for the above definition:
dynamic "artifacts" {
for_each = [local.codebuild_artifacts]
content {
type = artifacts.value.type
encryption_disabled = lookup(artifacts.value, "encryption_disabled", null)
location = lookup(artifacts.value, "location", null)
name = lookup(artifacts.value, "name", null)
namespace_type = lookup(artifacts.value, "namespace_type", null)
packaging = lookup(artifacts.value, "packaging", null)
path = lookup(artifacts.value, "path", null)
}
}
This way the local.codebuild_artifacts definition is simplify:
locals {
codebuild_artifacts = {
type = var.codebuild_artifacts_type
location = var.codebuild_artifacts_location
name = var.codebuild_artifacts_name
namespace_type = var.codebuild_artifacts_namespace_type
packaging = var.codebuild_artifacts_packaging
path = var.codebuild_artifacts_path
encryption_disabled = var.codebuild_artifacts_encryption_disabled
}
}
Where is the magic?
First, in the null data type, which tells Terraform to ignore the attribute if the lookup function doesn’t find the index on the map. Let’s take a look at the the first lines in the content block, which is what is replaced in the artifacts block in each iteration:
content {
type = artifacts.value.type
encryption_disabled = lookup(artifacts.value, "encryption_disabled", null)
location = lookup(artifacts.value, "location", null)
...
Here you can see that:
- The attribute
typeis always defined according to the value ofartifacts.value.type, so it’s not taken from the map. - The attribute
encryption_disabledis assigned if something likeartifacts.value ['encryption_disabled']is found in the map. If the index doesn’t exist, it’s assigned tonulland therefore it won’t be taken into account by Terraform. - And so on for the rest of the attributes, such as
location.
This allows having definitions of dynamic blocks, but you have to take into account that some values must be defined together. For example, for the S3 type you must define the following variables:
codebuild_artifacts_type
codebuild_artifacts_location
codebuild_artifacts_name
codebuild_artifacts_namespace_type
codebuild_artifacts_packaging
codebuild_artifacts_path
codebuild_artifacts_encryption_disabled
While for NO_ARTIFACTS just define this one:
codebuild_artifacts_type
On the other hand, the magic is completed by the fact that we are not using an assignment for the a artifacs block but rather the dynamic block definition. In other words, instead of this:
artifacts = ["${local.codebuild_artifacts}"]
The dynamic block definition is used:
dynamic "artifacts" {
for_each = [local.codebuild_artifacts]
content {
...
}