Terraform Optional Object Type Attributes

DevOps / Cloud Engineer โ๏ธ Terraform Evangelist ๐ Docker & Kubernetes Enthusiast ๐ณ Python Main ๐
Search for a command to run...

DevOps / Cloud Engineer โ๏ธ Terraform Evangelist ๐ Docker & Kubernetes Enthusiast ๐ณ Python Main ๐
No comments yet. Be the first to comment.
Iโve accidentally ran into Spacelift and Iโm so happy I did. To be honest, Iโve never been a big fan of tools on top of IaC, but this is something else. As they state on their website, Spacelift is a sophisticated CI/CD platform for a variety of tool...

As Iโve stated in other posts Iโve done here, I am a big fan of Github Actions. I really like the simplicity of it and how easily you can build pipelines for your Terraform code. Here, I want to show you what pipelines I am building for my terraform ...
I wonโt lie, I havenโt studied at all for this certification because I presumed my six years of experience with Terraform should be enough. One day, I just decided to take the certification and fortunately, my experience was enough to pass it. My fee...

When Hashicorp introduced Terraform Cloud, I was already accustomed to Oracleโs Resource Manager offering and even though I was a not a big fan of this managed service approach, I still gave it a chance. There were some things that I really liked, th...

If you read my previous posts, you already know that I am big fan of using for_each and object variables when I'm building my modules.
For quite some time, I've been waiting for a particular feature to exit beta and this is the optional object type attribute. The Optional Object type attribute, was in beta for quite some time since Terraform 0.14 and now in Terraform 1.3 (released end of September 2022) it's GA. So to get this straight, this is not a new feature, but now it is 100% ready to be used in production use cases.
When you are building a generic module and you want to offer a lot of possibilities for the people that are going to use it, you will use objects.
Nevertheless, this created a big problem in the past: all the attributes had to be provided by the person using that module and of course, no one will ever need to configure everything a module offers. This meant that you had to use an any type, but if you like to generate documentation with tfdocs, the variables part wouldn't be very helpful. The module code was also pretty ugly, with a lot of lookups to set default values and whatnot.
There are two things that you can actually do with this feature when you are using object variables:
You can build better modules, with less code and the documentation will be astonishing when you generate it with tfdocs, making you aware of all of the configurable parameters.
I will show you how I've changed a Terraform Module and what are the differences on the main.tf and variables.tf files.
resource "helm_release" "this" {
for_each = var.helm
name = each.value.name
chart = each.value.chart
repository = lookup(each.value, "repository", null)
version = lookup(each.value, "version", null)
namespace = lookup(each.value, "namespace", null)
create_namespace = lookup(each.value, "create_namespace", false) ? true : false
values = [for yaml_file in lookup(each.value, "values", []) : file(yaml_file)]
dynamic "set" {
for_each = lookup(each.value, "set", [])
content {
name = set.value.name
value = set.value.value
type = lookup(set.value, "type", "auto")
}
}
dynamic "set_sensitive" {
for_each = lookup(each.value, "set_sensitive", [])
content {
name = set_sensitive.value.name
value = set_sensitive.value.value
type = lookup(set_sensitive.value, "type", "auto")
}
}
}
As you see, in the old version of the code, I had a lot of lookups and I had to provide the default values on the main.tf version.
resource "helm_release" "this" {
for_each = var.helm
name = each.value.name
chart = each.value.chart
repository = each.value.repository
version = each.value.version
namespace = each.value.namespace
create_namespace = each.value.create_namespace
values = [for yaml_file in each.value.values : file(yaml_file)]
dynamic "set" {
for_each = each.value.set
content {
name = set.value.name
value = set.value.value
type = set.value.type
}
}
dynamic "set_sensitive" {
for_each = each.value.set_sensitive
content {
name = set_sensitive.value.name
value = set_sensitive.value.value
type = set_sensitive.value.type
}
}
}
0 lookups, less code, easier to read and understand.
variable "helm" {
type = any
description = <<-EOT
name = string
chart = string
repository = string
version = string
repository = string
namespace = string
create_namespace = bool
values = list(string)
set = list(object({
name = string
value = string
type = string
}))
set_sensitive = list(object({
name = string
value = string
type = string
}))
EOT
}
In the above variables.tf, I've provided the parameters to the description of the variable, but of course, I did this to help users understand the attributes of my variable. This is not how it's supposed to be done, but I wanted to make tfdocs generate a somewhat readable documentation.
variable "helm" {
type = map(object({
name = string
chart = string
repository = optional(string, null)
version = optional(string, null)
namespace = optional(string, null)
create_namespace = optional(bool, false)
values = optional(list(string), [])
set = optional(list(object({
name = string
value = string
type = optional(string, "auto")
})), [])
set_sensitive = optional(list(object({
name = string
value = string
type = optional(string, "auto")
})), [])
}))
description = "Helm release parameters"
default = {}
}
In the new example, we are using the powerful optional attribute. The same thing that was done by a lookup in the resource code, we can now do with this keyword.
This is the Optional syntax: optional(parameter_type, default_value).
The simplicity of it, is exactly what we needed in order to speed up module development and to keep up with the maintenance for them.
I am very thrilled with this feature and I totally recommend checking it out.