Dynamic Block

Terraform has a feature known as dynamic blocks. The dynamic blocks provide a way of dynamically generating repeatable nested blocks inside resource, data, provider, and provisioner blocks. It is particularly useful for resource types that have multiple levels for nested blocks. Dynamic blocks can make it difficult to read and manage, so they should be used only where absolutely necessary.

Dynamic block Syntax

The basic syntax of a dynamic block is:
dynamic block_name {
    for_each = <collection>
    content {
        # arguments
    }
}
Let's break this syntax apart:
Let's take an example where we want to create a CodeArtifact repository named "Common-repo" which requires three upstream repositories. Initial configuration will look like this:
resource "aws_codeartifact_repository" "codeartifact_repository" {
    repository = "Common-repo"
    description = "This is repo contains Private packages"
    domain_owner = "8363872828
    domain = "PrivateRepo
    upstream {
        repository_name = "repo1"
    }
    upstream {
        repository_name = "repo2"
    }
    upstream {
        repository_name = "repo3"
    }
}
Here, we have three repetitions of the upstream block. Whenever we need to add or remove any upstream repository, we must update it manually, which is very tedious and most of the time error prone. In order to avoid such repetition as shown in the previous example, it is possible to use dynamic blocks which generate for us dynamically the blocks for upstream. Here's the updated configuration:
variable upstream-repos {
    default = ["repo1", "repo2", "repo3"]
}
resource "aws_codeartifact_repository" "codeartifact_repository" {
    repository = "Common-repo"
    description = "This is repo contains Private packages"
    domain_owner = "8363872828
    domain = "PrivateRepo
    dynamic "upstream" {
        for_each = toset(var.upstream-repos)
        content {
            repository_name = upstream.value
        }
    }
}
This dynamic block gets rid of the repetition and gives us a compact, maintainable configuration. We will have to edit only the upstream-repos variable when we add or remove upstream repositories, and the configuration automatically adjusts.
NOTE: It is not possible to generate meta-argument blocks, such as the Lifecycle block as well as Provisioner block, using a dynamic block since Terraform needs to process the meta-argument blocks before it can evaluate the expressions. Since the Dynamic Block depends on the evaluation of an expression, it cannot be used to dynamically create these meta-argument blocks.

Using for_each with transformations

The for_each argument, in an dynamic block, accepts any collection or structural value. The two attributes available on the for_each iterator object are:
You can do transformations, conditional logic, or other operations on the data within your dynamic block using key and value.

Multi-Level Nested Block Structures

Some resource types have multiple levels of blocks nested within the resource. For example, one parent block might contain several child blocks, and each of those child blocks might in turn contain other nested blocks. To handle this level of nested structure dynamically, Terraform supports the use of nested dynamic blocks.
Nested dynamic blocks are created by placing a dynamic block inside the content part of another dynamic block. This will enable Terraform to create as many nested blocks as needed from the provided input data.
The basic syntax for Multi-Level Nested block structure is:
dynamic "block_name" {
    for_each = <collection>
    content {
        # arguments
        dynamic "block_name" {
            for_each = <collection>
            content {
                # arguments
            }
        }
    }
}
Let's consider an example where we would like to create a custom IAM policy that has permissions to decrypt encrypted data using the AWS KMS, but under certain conditions. We define here the policy document using a datasource "aws_iam_policy_document" block. Here is the configuration of the policy document:
data "aws_iam_policy_document" "example" {
    statement {
        actions = [kms:Decrypt]
        resources = ["*"]
        condition {
            test = "ForAnyValue:StringEquals"
            variable = "kms:EncryptionContext:service"
            values = ["pi"]
        }
        condition {
            test = "ForAnyValue:StringEquals"
            variable = "kms:EncryptionContext:aws:pi:service"
            values = ["rds"]
        }
    }
}
In this setup, the condition block is duplicated. This is not good, because it results in:
To avoid the mentioned above problems, we can use nested dynamic blocks for building the policy document. Here's how you can configure that:
variable "multi-blocks" {
    default = {
        kms:Decrypt = {
            resources = "*"
            conditions = {
                condition1 = {
                    test = "ForAnyValue:StringEquals"
                    variable = "kms:EncryptionContext:service"
                    values = ["pi"]
                }
                condition2 = {
                    test = "ForAnyValue:StringEquals"
                    variable = "kms:EncryptionContext:aws:pi:service"
                    values = ["rds"]
                }
            }
        }
    }
}
data "aws_iam_policy_document" "example" {
    dynamic "statement" {
        for_each = var.multi-blocks
            actions = [statement.key]
            resources = [statement.value.resources]
            content {
                dynamic "condition" {
                    for_each = statement.value.connections
                    content {
                        test = connection.value.test
                        variable = connection.value.variable
                        values = connection.value.values
                    }
              }
         }
    }
}
Here we have a new configuration in which we declare a variable named multi-blocks that holds a map of statements, where each statement has its own set of conditions. Then we use a dynamic statement block where we iterate the multi-blocks variable through the for_each argument. Inside the statement block, we use a nested dynamic condition block to iterate through the conditions of each statement. The content block inside the conditional block states the definition of test, variable, and values attributes using the syntax conditional.value.

Best Practices for Dynamic Blocks

When using dynamic blocks, follow these guidelines:

Related Pages

Feedback

Was this page helpful?