Let's try: Terraform part 9 END - Dynamic
There is a situation we need to create a resource with complex nested blocks.
There is a situation we need to create a resource with complex nested blocks then we can consider this block type to facilitate the flexibility of our code.
It’s dynamic block.
dynamic block
dynamic block is used to generate nested blocks within a resource or module based on a collection of values. It allows you to create complex configurations dynamically, making your Terraform code more flexible and reusable.
However, using too many dynamic blocks can cause readability problems from its multi-levels and iterations. So use it when necessary.
Syntax
dynamic block must have content block inside with for_each.
Read more about for_each in part 8 - Conditions and Repetition.
1
2
3
4
5
6
7
8
9
10
11
12
13
resource "<resource_type>" "<resource_name>" {
...
dynamic "<block_type>" {
for_each = <expression>
iterator = <iterator_name> # optional, default is "<block_type>"
content {
attribute_1 = "<value>"
attribute_2 = "<value>"
...
}
}
...
}
And of course, dynamic block can be nested.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
resource "<resource_type>" "<resource_name>" {
...
dynamic "<block_type>" {
for_each = <expression>
content {
attribute_1 = "<value>"
attribute_2 = "<value>"
dynamic "<nested_block_type>" {
for_each = <expression>
content {
attribute_1 = "<value>"
attribute_2 = "<value>"
...
}
}
}
...
}
Example 1: with single variables
This is more flexible way to maintain attributes per resource but it could be more redundant and repetitive if there are many resources with similar attributes.
I prepared two buckets. One with
lifecycle_ruleto delete file older than 30 days controlled by variabledelete_after_days, and another without.1 2 3 4 5 6 7 8 9
buckets = { test1 = { name = "bluebirz-tf9-test-1" }, test2 = { name = "bluebirz-tf9-test-2" delete_after_days = 30 }, }
We just need only
nameanddelete_after_dayswhich is optional.1 2 3 4 5 6
variable "buckets" { type = map(object({ name = string delete_after_days = optional(number) })) }
In
dynamicblock, I evaluatedelete_after_daysto an empty array if it’s null and this loop will not iterate. Otherwise, it will be an array with a single value to iterate once.After that, I assign the value at line #12 by
lifecycle_rule.valueaslifecycle_ruleis as same asdynamictype name because it’s the default iterator name.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
resource "google_storage_bucket" "bucket" { for_each = var.buckets name = each.value.name location = "US" dynamic "lifecycle_rule" { for_each = each.value.delete_after_days != null ? [each.value.delete_after_days] : [] content { action { type = "Delete" } condition { age = lifecycle_rule.value } } } }
Look at
google_storage_bucket.bucket["test2"]that haslifecycle_ruleat line #30.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
Terraform will perform the following actions: # google_storage_bucket.bucket["test1"] will be created + resource "google_storage_bucket" "bucket" { + bucket_policy_only = (known after apply) + force_destroy = false + id = (known after apply) + location = "US" + name = "bluebirz-tf9-test-1" + project = (known after apply) + self_link = (known after apply) + storage_class = "STANDARD" + uniform_bucket_level_access = (known after apply) + url = (known after apply) } # google_storage_bucket.bucket["test2"] will be created + resource "google_storage_bucket" "bucket" { + bucket_policy_only = (known after apply) + force_destroy = false + id = (known after apply) + location = "US" + name = "bluebirz-tf9-test-2" + project = (known after apply) + self_link = (known after apply) + storage_class = "STANDARD" + uniform_bucket_level_access = (known after apply) + url = (known after apply) + lifecycle_rule { + action { + type = "Delete" # (1 unchanged attribute hidden) } + condition { + age = 30 + matches_storage_class = [] + with_state = (known after apply) # (3 unchanged attributes hidden) } } } Plan: 2 to add, 0 to change, 0 to destroy.
This example can be drawn to diagram below. Each arrow shows how the value flows from tfvars to variables and at last to resources.
---
config:
layout:
---
stateDiagram-v2
direction LR
state "main.tf" as main {
state "google_storage_bucket" as gcs1 {
state "name" as gcs1name
state "location = "US"" as gcs1location
}
state "google_storage_bucket" as gcs2 {
state "name" as gcs2name
state "location = "US"" as gcs2location
state "lifecycle_rule" as gcs2lc {
state "condition.age" as gcs2condition
state "action.type = "Delete"" as gcs2action
}
}
}
state "variables.tf" as vartf {
state "bucket" as varbucket1 {
state "name" as varbucket1name
}
state "bucket" as varbucket2 {
state "name" as varbucket2name
state "delete_after_days" as varbucket2del
}
}
state "vars.tfvars" as tfvars {
state "bucket.test1" as valbuckettest1 {
state "name =<br/>"bluebirz-tf9-test-1"" as valbuckettest1name
}
state "bucket.test2" as valbuckettest2 {
state "name =<br/>"bluebirz-tf9-test-2"" as valbuckettest2name
state "delete_after_days<br/>= 30" as valbuckettest2del
}
}
valbuckettest1name --> varbucket1name
valbuckettest2name --> varbucket2name
valbuckettest2del --> varbucket2del
varbucket1name --> gcs1name
varbucket2name --> gcs2name
varbucket2del --> gcs2condition
Example 2: with multiple variables
We can decouple variables for resources and dynamic block in some cases, like making shared variables.
So the example design could look like this.
From the variable file, we can maintain buckets as a map and separately keep
lifecycle_rulesas a single object.1 2 3 4 5 6 7 8 9 10 11 12 13
buckets = { test1 = { name = "bluebirz-tf9-test-1" }, test2 = { name = "bluebirz-tf9-test-2" }, } bucket_lifecycle_rules = { age = 30 action = "Delete" }
Here we maintain buckets and
lifecycle_rulesseparately.1 2 3 4 5 6 7 8 9 10 11 12 13
variable "buckets" { type = map(object({ name = string })) } variable "bucket_lifecycle_rules" { default = null type = object({ age = number action = string }) }
For this example, I assign a separated variable
var.bucket_lifecycle_rulesto maintaindynamicblock forlifecycle_ruleAfter
for_eachis evaluated (line #6), it would be an array havingvar.bucket_lifecycle_rulesitself or nothing.And we refer the variable in the
content(line #9 & #12) rather thaneach.valuefrom the iteration as in the previous example.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
resource "google_storage_bucket" "bucket" { for_each = var.buckets name = each.value.name location = "US" dynamic "lifecycle_rule" { for_each = var.bucket_lifecycle_rules == null ? [] : [var.bucket_lifecycle_rules] content { action { type = var.bucket_lifecycle_rules.action } condition { age = var.bucket_lifecycle_rules.age } } } }
According to the Terraform script, our buckets of both will have the same
lifecycle_rule(line #16 & #43) that is to delete objects after 30 days.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
Terraform will perform the following actions: # google_storage_bucket.bucket["test1"] will be created + resource "google_storage_bucket" "bucket" { + bucket_policy_only = (known after apply) + force_destroy = false + id = (known after apply) + location = "US" + name = "bluebirz-tf9-test-1" + project = (known after apply) + self_link = (known after apply) + storage_class = "STANDARD" + uniform_bucket_level_access = (known after apply) + url = (known after apply) + lifecycle_rule { + action { + type = "Delete" # (1 unchanged attribute hidden) } + condition { + age = 30 + matches_storage_class = [] + with_state = (known after apply) # (3 unchanged attributes hidden) } } } # google_storage_bucket.bucket["test2"] will be created + resource "google_storage_bucket" "bucket" { + bucket_policy_only = (known after apply) + force_destroy = false + id = (known after apply) + location = "US" + name = "bluebirz-tf9-test-2" + project = (known after apply) + self_link = (known after apply) + storage_class = "STANDARD" + uniform_bucket_level_access = (known after apply) + url = (known after apply) + lifecycle_rule { + action { + type = "Delete" # (1 unchanged attribute hidden) } + condition { + age = 30 + matches_storage_class = [] + with_state = (known after apply) # (3 unchanged attributes hidden) } } } Plan: 2 to add, 0 to change, 0 to destroy.
Here is the diagram showing how values flow for this example.
---
config:
layout:
---
stateDiagram-v2
direction LR
state "main.tf" as main {
state "google_storage_bucket" as gcs1 {
state "name" as gcs1name
state "location = "US"" as gcs1location
state "lifecycle_rule" as gcs1lc {
state "condition.age" as gcs1condition
state "action.type = "Delete"" as gcs1action
}
}
state "google_storage_bucket" as gcs2 {
state "lifecycle_rule" as gcs2lc {
state "condition.age" as gcs2condition
state "action.type = "Delete"" as gcs2action
}
state "location = "US"" as gcs2location
state "name" as gcs2name
}
}
state "variables.tf" as vartf {
state "bucket" as varbucket1 {
state "name" as varbucket1name
}
state "bucket" as varbucket2 {
state "name" as varbucket2name
}
state "bucket_lifecycle_rules" as varlc {
state "age" as varage
state "action" as varaction
}
}
state "vars.tfvars" as tfvars {
state "bucket.test1" as valbuckettest1 {
state "name =<br/>"bluebirz-tf9-test-1"" as valbuckettest1name
}
state "bucket.test2" as valbuckettest2 {
state "name =<br/>"bluebirz-tf9-test-2"" as valbuckettest2name
}
state "bucket_lifecycle_rules" as vallc {
state "age = 30" as valage
state "action = "Delete"" as valact
}
}
%% vardel --> condition
%% varname --> name
%% test1_name --> varname
%% test2_name --> varname
%% test2_del --> vardel
valbuckettest1name --> varbucket1name
valbuckettest2name --> varbucket2name
varbucket1name --> gcs1name
varbucket2name --> gcs2name
valage --> varage
valact --> varaction
varage --> gcs1condition
varaction --> gcs1action
varage --> gcs2condition
varaction --> gcs2action
dynamic block is useful when we need some flexibility but we also need to leverage with simplicity in the time we are fixing it.
I decided to finish the series here. However, I will write more Terraform blogs as soon as I have some interesting stuffs.
