- Expressions
- Dynamic Blocks
dynamic
Blocks
Within top-level block constructs like resources, expressions can usually be
used only when assigning a value to an argument using the name = expression
form. This covers many uses, but some resource types include repeatable nested
blocks in their arguments, which typically represent separate objects that
are related to (or embedded within) the containing object:
resource "aws_elastic_beanstalk_environment" "tfenvtest" {
name = "tf-test-name" # can use expressions here
setting {
# but the "setting" block is always a literal block
}
}
You can dynamically construct repeatable nested blocks like setting
using a
special dynamic
block type, which is supported inside resource
, data
,
provider
, and provisioner
blocks:
resource "aws_elastic_beanstalk_environment" "tfenvtest" {
name = "tf-test-name"
application = "${aws_elastic_beanstalk_application.tftest.name}"
solution_stack_name = "64bit Amazon Linux 2018.03 v2.11.4 running Go 1.12.6"
dynamic "setting" {
for_each = var.settings
content {
namespace = setting.value["namespace"]
name = setting.value["name"]
value = setting.value["value"]
}
}
}
A dynamic
block acts much like a for
expression, but produces
nested blocks instead of a complex typed value. It iterates over a given
complex value, and generates a nested block for each element of that complex
value.
- The label of the dynamic block (
"setting"
in the example above) specifies what kind of nested block to generate. - The
for_each
argument provides the complex value to iterate over. - The
iterator
argument (optional) sets the name of a temporary variable that represents the current element of the complex value. If omitted, the name of the variable defaults to the label of thedynamic
block ("setting"
in the example above). - The
labels
argument (optional) is a list of strings that specifies the block labels, in order, to use for each generated block. You can use the temporary iterator variable in this value. - The nested
content
block defines the body of each generated block. You can use the temporary iterator variable inside this block.
Since the for_each
argument accepts any collection or structural value,
you can use a for
expression or splat expression to transform an existing
collection.
The iterator object (setting
in the example above) has two attributes:
key
is the map key or list element index for the current element. If thefor_each
expression produces a set value thenkey
is identical tovalue
and should not be used.value
is the value of the current element.
A dynamic
block can only generate arguments that belong to the resource type,
data source, provider or provisioner being configured. It is not possible
to generate meta-argument blocks such as lifecycle
and provisioner
blocks, since OpenTF must process these before it is safe to evaluate
expressions.
The for_each
value must be a collection with one element per desired
nested block. If you need to declare resource instances based on a nested
data structure or combinations of elements from multiple data structures you
can use OpenTF expressions and functions to derive a suitable value.
For some common examples of such situations, see the
flatten
and
setproduct
functions.
Multi-level Nested Block Structures
Some providers define resource types that include multiple levels of blocks
nested inside one another. You can generate these nested structures dynamically
when necessary by nesting dynamic
blocks in the content
portion of other
dynamic
blocks.
For example, a module might accept a complex data structure like the following:
variable "load_balancer_origin_groups" {
type = map(object({
origins = set(object({
hostname = string
}))
}))
}
If you were defining a resource whose type expects a block for each origin
group and then nested blocks for each origin within a group, you could ask
OpenTF to generate that dynamically using the following nested dynamic
blocks:
dynamic "origin_group" {
for_each = var.load_balancer_origin_groups
content {
name = origin_group.key
dynamic "origin" {
for_each = origin_group.value.origins
content {
hostname = origin.value.hostname
}
}
}
}
When using nested dynamic
blocks it's particularly important to pay attention
to the iterator symbol for each block. In the above example,
origin_group.value
refers to the current element of the outer block, while
origin.value
refers to the current element of the inner block.
If a particular resource type defines nested blocks that have the same type
name as one of their parents, you can use the iterator
argument in each of
dynamic
blocks to choose a different iterator symbol that makes the two
easier to distinguish.
Best Practices for dynamic
Blocks
Overuse of dynamic
blocks can make configuration hard to read and maintain, so
we recommend using them only when you need to hide details in order to build a
clean user interface for a re-usable module. Always write nested blocks out
literally where possible.
If you find yourself defining most or all of a resource
block's arguments and
nested blocks using directly-corresponding attributes from an input variable
then that might suggest that your module is not creating a useful abstraction.
It may be better for the calling module to define the resource itself then
pass information about it into your module. For more information on this design
tradeoff, see When to Write a Module
and Module Composition.