Let's try: Terraform part 8 - Conditions and Repetition
We can use if-else and for-loop in Terraform with these syntax.
In Terraform, one resource block basically means one object. However, like other programming languages, Terraform allow us to create resources in dynamic and flexible way. Yes, I’m talking about conditions and repetition.
Count
In some cases, we are going to create multiple identical instances.
count
1 is a meta-argument that allows you to create multiple instances of a resource based on a numeric value. This is useful when you want to create a fixed number of resources.
This is rare for me to use count
because I usually have to create different instances.
Syntax
1
2
3
4
5
6
7
resource "<resource_type>" "<resource_name>" {
count = <number>
attribute_1 = "<value>"
attribute_2 = "<value>"
attribute_3 = "${count.index}"
...
}
If needed, we can use count.index
to get the index of each instance, starting from 0.
Example
1
2
3
4
5
6
resource "google_storage_bucket_object" "object" {
count = 2
name = uuid()
bucket = google_storage_bucket.bucket.name
content = "This is a test object in the bucket."
}
This resource is an object in Google Cloud Storage. I want to make the object by 2 (count = 2
) and give the name by auto-generated UUID (name = uuid()
).
After we apply
it, there will be an array of google_storage_bucket_object
according to the resource block we defined with count
.
1
2
3
4
5
$ tf state list
google_storage_bucket.bucket
google_storage_bucket_object.object[0]
google_storage_bucket_object.object[1]
As above, we created object[0]
and object[1]
from count = 2
.
Condition
Sometimes we need if-else. Terraform supports ternary operator2 (?:
). I used to use it with locals
block to define new variable based on specific conditions.
Read more about locals
block in part 7 - Locals, Data, and Output.
Syntax
We can use locals
to store new values from conditions.
1
2
3
4
5
6
7
8
9
10
locals {
new_variable = <condition> ? "<true_value>" : "<false_value>"
}
resource "<resource_type>" "<resource_name>" {
attribute_1 = local.new_variable.value_1
attribute_2 = local.new_variable.value_2
...
}
Or just assign values after conditions one by one.
1
2
3
4
5
resource "<resource_type>" "<resource_name>" {
attribute_1 = <condition> ? "<true_value_1>" : "<false_value_1>"
attribute_2 = <condition> ? "<true_value_2>" : "<false_value_2>"
...
}
Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
variable "object_spec" {
type = object({
name = string
content = string
})
default = null
}
locals {
target_file = var.object_spec == null ? { name = "default.txt", content = "Default content" } : var.object_spec
}
resource "google_storage_bucket_object" "object" {
name = local.target_file.name
content = local.target_file.content
bucket = google_storage_bucket.bucket.name
}
resource "google_storage_bucket_object" "object_2" {
name = var.object_spec == null ? "special-file.txt" : var.object_spec.name
content = var.object_spec == null ? "This is a special file" : var.object_spec.content
bucket = google_storage_bucket.bucket.name
}
At line 6, we defined default value of the variable as null
.
At line 10 in resource “object”, when the variable’s value is null
then we assign local.target_file
with new populated values.
At line 20-21 of resource “object_2”, when the variable is null
then we assign the attributes one by one.
When we run terraform plan
without any variables, we will have the resource with that populated values.
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
# google_storage_bucket_object.object will be created
+ resource "google_storage_bucket_object" "object" {
+ bucket = "bluebirz-test-bucket"
+ content = (sensitive value)
+ content_type = (known after apply)
+ crc32c = (known after apply)
+ detect_md5hash = "different hash"
+ id = (known after apply)
+ kms_key_name = (known after apply)
+ md5hash = (known after apply)
+ media_link = (known after apply)
+ name = "default.txt"
+ output_name = (known after apply)
+ self_link = (known after apply)
+ storage_class = (known after apply)
}
# google_storage_bucket_object.object_2 will be created
+ resource "google_storage_bucket_object" "object_2" {
+ bucket = "bluebirz-test-bucket"
+ content = (sensitive value)
+ content_type = (known after apply)
+ crc32c = (known after apply)
+ detect_md5hash = "different hash"
+ id = (known after apply)
+ kms_key_name = (known after apply)
+ md5hash = (known after apply)
+ media_link = (known after apply)
+ name = "special-file.txt"
+ output_name = (known after apply)
+ self_link = (known after apply)
+ storage_class = (known after apply)
}
Look at the output above, line 12 shows the filename of “object” as “default.txt”. And line 29 shows “object_2” filename as “special-file.txt”.
For-each
When it comes to multiple instances, for_each
3 is the meta-argument I use very often. Because of the similarity to dictionary in Python, we just access it via each.key
and each.value
.
Syntax
1
2
3
4
5
6
resource "<resource_type>" "<resource_name>" {
for_each = "<variable in set or map>"
attribute_1 = each.key
attribute_2 = each.value
...
}
If the variable is a set (e.g. [ 1, 2, 3 ]
), we can access the element via either each.key
or each.value
because both are the same in value.
If the variable is a map (e.g. [ first = {name = "1"}, second = {name = "2"} ]
), we can access the key via each.key
and we will get first
and second
. And the value can access via each.value
then we can get {name = "1"}
and {name = "2"}
respectively.
Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
variable "object_spec_foreach" {
type = map(object({
name = string
content = string
}))
default = {
file1 = {
name = "file1.txt"
content = "This is file 1"
},
file2 = {
name = "file2.txt"
content = "This is file 2"
}
}
}
resource "google_storage_bucket_object" "object" {
for_each = var.object_spec_foreach
name = each.value.name
bucket = google_storage_bucket.bucket.name
content = each.value.content
}
As above, we have default value by 2 elements; file1
(line 7) and file2
(line 11). When we apply for_each
over it, we will have 2 instances in an array of google_storage_bucket_object.object
having keys file1
and file2
from the key name in that map.
After we run terraform apply
and check the state, we will see:
1
2
3
4
5
$ tf state list
google_storage_bucket.bucket
google_storage_bucket_object.object["file1"]
google_storage_bucket_object.object["file2"]
We can see that google_storage_bucket_object.object
has key names according to each.key
we previously defined in the variable block.
Repo
Here I saved the scripts in one place.