Terraform HCL Intro 8: For In and Removing Duplication
In the last post, we provided an introduction to the for in
construct. This post provides more examples, shows local assignment, and covers how to set default attribute values. Remember, for
is useful for data structure manipulation. Sometimes you may not get the data in the structure that is easy for you to work with, so understanding how to manipulate the data structure in Terraform can be quite useful.
Simple Functions Like Lookup, Keys, Contains
Have seen folks use the lookup function to check if Map elements in a List contain the key they’re looking. Here’s an example:
locals {
list = [{a = 1}, {b = 2}, {a = 3}]
}
output "list" {
value = [for m in local.list : m if lookup(m, "a", null) != null ]
}
Results in:
$ terraform apply
Outputs:
list = [{"a" = 1}, {"a" = 3}]
We can also achieve the same result with contains and keys.
locals {
list = [{a = 1}, {b = 2}, {a = 3}]
}
output "list" {
value = [for m in local.list2 : m if contains(keys(m), "a") ]
}
Also results in:
$ terraform apply
Outputs:
list = [{"a" = 1}, {"a" = 3}]
Local to Local Conversion
It’s worth pointing out that you can use one local variable to set another local variable. In a normal procedural programming language this is obvious. But with Terraform being declarative, it can throw some people off. Constructing a new local variable can sometimes clean up your code. Let’s say we have this data structure that holds:
locals {
groups = {
example0 = {
description = "sg description 0"
rules = [{
description = "rule description 0",
},{
description = "rule description 1",
}]
},
example1 = {
description = "sg description 1"
rules = [{
description = "rule description 0",
},{
description = "rule description 1",
}]
}
}
}
output "groups" {
value = local.groups
}
And we needed to get all the rules
as a Map with the same groups
keys. We can do that with a simple for
expression and assigning the result to another local variable.
locals {
groups = {
example0 = {
# ...
}
}
rules = { for k,v in local.groups : k => v.rules }
}
output "rules" {
value = local.rules
}
That results in:
$ terraform apply
Outputs:
rules = {
"example0" = [
{
"description" = "rule description 0"
},
{
"description" = "rule description 1"
},
]
"example1" = [
{
"description" = "rule description 0"
},
{
"description" = "rule description 1"
},
]
}
Now we’re able to use a more straightforward flatter structure local.rules
instead of local.groups
.
Default Values and Removing Duplication
Back in the Terraform Intro 6: Nested Loops post, we showed you a direct assignment approach which rid the code of an extra inner loop. It would also tell Terraform to remove ingress rules properly. It came with a cost, though: duplication. We had to set these extra attributes like ipv6_cidr_blocks
and prefix_list_ids
.
Combining our gained knowledge from the previous posts on for_each
and for
loops, we are now posed to remove that duplication. Here’s the code:
variable "groups" {
default = {
example0 = {
description = "sg description 0"
},
example1 = {
description = "sg description 1"
}
}
}
variable "rules" {
default = {
example0 = [{
description = "rule description 0"
to_port = 80
from_port = 80
cidr_blocks = ["10.0.0.0/16"]
},{
description = "rule description 1"
to_port = 80
from_port = 80
cidr_blocks = ["10.1.0.0/16"]
}]
example1 = [] # empty Array removes rules
}
}
locals {
rules = {
for k,v in var.rules:
k => [
for i in v:
merge({
ipv6_cidr_blocks = null
prefix_list_ids = null
security_groups = null
protocol = "tcp"
self = null
}, i)
]
}
}
resource "aws_security_group" "this" {
for_each = var.groups
name = each.key # top-level key is security group name
description = each.value.description
ingress = contains(keys(local.rules), each.key) ? local.rules[each.key] : [] # List of Maps with rule attributes
}
output "rules" {
value = local.rules
}
Expand to see terraform plan results.
$ terraform apply
Outputs:
rules = {
"example0" = [
{
"cidr_blocks" = [
"10.0.0.0/16",
]
"description" = "rule description 0"
"from_port" = 80
"protocol" = "tcp"
"to_port" = 80
},
{
"cidr_blocks" = [
"10.1.0.0/16",
]
"description" = "rule description 1"
"from_port" = 80
"protocol" = "tcp"
"to_port" = 80
},
]
"example1" = []
}
The user would just set the rules
variable. The code uses 2 for
loops to set default values for the Maps within the rules
structure. The merge function is used to set the default values. The custom user-defined values are passed through, making this code quite flexible.
Note, one tricky thing is if you try to see the default values for rules
with terraform output
, you will not set the null
values. Terraform output does not display null values.
Other Examples Worth Pointing Out
- VPC and Subnets & setproduct: This is a good example from the terraform docs that flattens a hierarchy data structure.
Cleanup
Note, remember to clean up the resources:
terraform destroy
Summary
This post covered more examples with the for
loop construct, which is useful for data structure manipulation. We used our combined knowledge from previous to set default values. We’ve covered a fair amount of Terraform HCL fundamentals.
The source code for these examples is available for BoltOps Learn subscribers: terraform-hcl-tutorials/8-for-in-loop-examples
Want It to be Easier to Work with Terraform?
Check out Terraspace: The Terraform Framework.
The Terraform HCL Language Intro Tutorials
Thanks for reading this far. If you found this article useful, I'd really appreciate it if you share this article so others can find it too! Thanks 😁 Also follow me on Twitter.
Got questions? Check out BoltOps.
You might also like
More tools:
-
Kubes
Kubes: Kubernetes Deployment Tool
Kubes is a Kubernetes Deployment Tool. It builds the docker image, creates the Kubernetes YAML, and runs kubectl apply. It automates the deployment process and saves you precious finger-typing energy.
-
Jets
Jets: The Ruby Serverless Framework
Ruby on Jets allows you to create and deploy serverless services with ease, and to seamlessly glue AWS services together with the most beautiful dynamic language: Ruby. It includes everything you need to build an API and deploy it to AWS Lambda. Jets leverages the power of Ruby to make serverless joyful for everyone.
-
Lono
Lono: The CloudFormation Framework
Building infrastructure-as-code is challenging. Lono makes it much easier and fun. It includes everything you need to manage and deploy infrastructure-as-code.