Terraform Associate: Top 5 Learnings

codycodes

Cody Antonio Gagnon

Posted on April 13, 2024

Terraform Associate: Top 5 Learnings

Unlock the power of Terraform for your career, project, or any opportunity with these essential tips and tricks from my Terraform Associate exam journey!

Overview:

  1. 🔦 Essential Commands
  2. ⌨️ Command line options for days
  3. 🧮 Functions, functions, and more functions!
  4. 🌟 Splatting it up
  5. 🧠 Getting into the terraform state of mind

1. 🔦 Essential Commands

terraform validate

  • Validates the syntax of terraform files statically (before running a plan)
  • Helps (quickly!) uncover any potential issues with syntax (e.g. typos, undeclared variables, etc.)
  • Does not determine issues that may occur at plan or apply time (e.g. circular resource dependencies in which resources depend on each other for creation, API considerations for deployment on a given cloud provider, etc.)

NOTE: ensure when running this command that you're inside the directory containing your Terraform files!! This command will output "Success!" even when no Terraform files exist in the current directory 🥲

terraform fmt

  • Ensures consistency and adherence to HashiCorp's canonical file formatting standards by automatically formatting your Terraform files
  • Using the -recursive flag will format all the Terraform files under the current directory and its subdirectories options
  • While you'll need to know this command for the exam, this functionality is built into HashiCorp's extension for VS Code and can automatically format your Terraform configuration on save!

terraform console

  • Think of this as a playground for testing out expressions. You can take the current Terraform state you have and interact with its values.
  • You can use variables in the current Terraform workspace as its input as well as other state that's been applied - this makes it super easy to evaluate expressions to see if they'll get the job done (all without needing to apply a plan!)
  • Additionally, if no default values exist for input variables, they can be initialized using -var 'name_of_defined_variable=value_of_defined_variable' on the command line or using either *.auto.tfvars or terraform.tfvars files when running terraform console

2. ⌨️ Command line options for days

Learning terraform's command line options can save you time, helping you get the most out of the tools at your disposal! Some of my favorites include:

terraform apply -auto-approve

  • skip the plan, just apply (exercising caution where necessary)

terraform apply -destroy or shall I say terraform destroy?

terraform apply -replace

  • Super helpful command to indicate to Terraform that a resource needs to be marked for replacement
  • Example could include a database that didn't get provisioned correctly when Terraform thinks it was. Just replace it!

terraform apply -refresh=false

  • Instead of refreshing state during the apply stage, Terraform will accept whatever it has as its plan, reducing the time it would otherwise take to refresh resources.
  • This can cause issues if the current state does not match the target environment, it's important to ensure that state is reconciled before using this option!

3. 🧮 Functions, functions, and more functions!

Functions functions functions meme

String Functions

format

This interesting command can come in handy when you want to output a variable in a specific way. For instance, I may have a variable pi with a value of 3.14159265358979323846264338... well using the format command I can truncate, approximate, and manipulate the value for display in other ways:

variable "big_ol_pi" {
  description = "A big 'ol pi 🥧"
  type        = number
  default     = 3.14159265358979323846264338
}

# running this in terraform console
> format("This 🥧 is approximated to %.2f", var.big_ol_pi)
"This 🥧 is approximated to 3.14"
Enter fullscreen mode Exit fullscreen mode
trimprefix

Super simple command that allows you remove part of the beginning of a string. Say we have the following variable:

variable "string_of_pi" {
  description = "A string of 🥧"
  type        = string
  default     = "🥧3.14159265358979323846264338🥧"
}

# running this in terraform console
> trimprefix(var.string_of_pi, "🥧")
"3.14159265358979323846264338🥧"
Enter fullscreen mode Exit fullscreen mode
trimsuffix

Same as trimprefix, but at the end rather than the beginning!

variable "string_of_pi" {
  description = "A string of 🥧"
  type        = string
  default     = "🥧3.14159265358979323846264338🥧"
}

# running this in terraform console
> trimsuffix(var.string_of_pi, "🥧")
"🥧3.14159265358979323846264338"
Enter fullscreen mode Exit fullscreen mode
replace

Allows you to transform your pie into cake, or rather, takes an input string and substitutes the value(s) you specify with another value!

variable "string_of_pi" {
  description = "A string of 🥧"
  type        = string
  default     = "🥧3.14159265358979323846264338🥧"
}

# running this in terraform console
> replace(var.string_of_pi, "🥧", "🍰")
"🍰3.14159265358979323846264338🍰"
Enter fullscreen mode Exit fullscreen mode

Collection Functions

keys

Helps get the keys of a map. Can really be useful in a cinch - notice the keys are sorted in lexicographical order, which is probably different than the order they're created in!

variable "map_of_pies" {
  description = "lotsa pi 🥧"
  type        = map(any)
  default = {
    "3.141592653589793238462643384" : "the number pi"
    "'3.14'" : "literal approximation of the number pi"
    "🥧" : "emoji pie"
    "🍰" : "not a pie"
  }
}

# running this in terraform console
> keys(var.map_of_pies)
tolist([
  "'3.14'",
  "3.141592653589793238462643384",
  "🍰",
  "🥧",
])
Enter fullscreen mode Exit fullscreen mode
lookup

Great little function that can help return a single value based on the input. What differentiates this from a typical map index (using var.map_of_pies["🍰"] syntax) is that it can fall back to a default value:

variable "map_of_pies" {
  description = "lotsa pi 🥧"
  type        = map(any)
  default = {
    "3.141592653589793238462643384" : "the number pi"
    "'3.14'" : "literal approximation of the number pi"
    "🥧" : "emoji pie"
    "🍰" : "not a pie"
  }
}

# running this in terraform console
> lookup(var.map_of_pies, "👾", "🥧")
"🥧"
Enter fullscreen mode Exit fullscreen mode

When a key isn't present inside of var.map_of_pies, we elect to always return a pie emoji!
OMG Pie meme
I like that default value more, tastes better

concat

Simply take two lists and make them one!

variable "list_of_pies" {
  description = "more 🥧"
  type        = list(string)
  default = [
    "'3.14'",
    "3.141592653589793238462643384",
    "🥧",
  ]
}
variable "list_of_cakes" {
  description = "definitely not 🥧"
  type        = list(string)
  default = [
    "🎂",
    "🍰",
    "🍥",
  ]
}

# running this in terraform console
> concat(var.list_of_pies, var.list_of_cakes)
concat(var.list_of_pies, var.list_of_cakes)
tolist([
  "'3.14'",
  "3.141592653589793238462643384",
  "🥧",
  "🎂",
  "🍰",
  "🍥",
])
Enter fullscreen mode Exit fullscreen mode

Additionally, we can also take other functions that return a list and use them here as well!

variable "map_of_pies" {
  description = "lotsa pi 🥧"
  type        = map(any)
  default = {
    "3.141592653589793238462643384" : "the number pi"
    "'3.14'" : "literal approximation of the number pi"
    "🥧" : "emoji pie"
    "🍰" : "not a pie"
  }
}

# running this in terraform console
> concat(keys(var.map_of_pies), values(var.map_of_pies))
tolist([
  "'3.14'",
  "3.141592653589793238462643384",
  "🍰",
  "🥧",
  "literal approximation of the number pi",
  "the number pi",
  "not a pie",
  "emoji pie",
])
Enter fullscreen mode Exit fullscreen mode
formatlist

Just like with the format command, formatlist can apply its specification to a whole list of strings!

variable "list_of_shadys" {
  description = "All the shadys"
  type = list(string)
  default = [
    "who",
    "what",
    "chka chka slim-shady!",
  ]
}

# running this in terraform console
> formatlist("hi my name is %s", var.list_of_shadys)
tolist([
  "hi my name is who",
  "hi my name is what",
  "hi my name is chka chka slim-shady!",
])
Enter fullscreen mode Exit fullscreen mode

4. 🌟 Splatting it up

Have you ever woken up and just thought: wow, splats are great? Me neither, but someday I might, because it turns out these expressions can really improve readability and make it easier to wrap your head around some loops in Terraform!

So, what is a splat expression really?
A splat is really just a shorthand for "all the things in this list."
It's a syntatical sugar that you can use to convert all types of for expressions for in Terraform to even simpler splat expressions!

Here's an example of converting a variable to a splat

variable "desserts" {
  description = "A list of favorite desserts"
  type = list(object({
    emoji = string
    name  = string
  }))
  default = [
    {
      emoji = "🍰"
      name  = "Cheesecake"
    },
    {
      emoji = "🍪"
      name  = "Chocolate Chip Cookie"
    },
    {
      emoji = "🍦"
      name  = "Ice Cream"
    },
    {
      emoji = "🍩"
      name  = "Donut"
    },
    {
      emoji = "🧁"
      name  = "Cupcake"
    }
  ]
}

# running this in terraform console
> [ for dessert in var.desserts : dessert.emoji ]
[
  "🍰",
  "🍪",
  "🍦",
  "🍩",
  "🧁",
]
> 
# is equivalent to
> var.desserts[*].emoji
tolist([
  "🍰",
  "🍪",
  "🍦",
  "🍩",
  "🧁",
])
Enter fullscreen mode Exit fullscreen mode

As we can see in the above, our for expression, while totally readable, is less concise. You know the saying though: "once you understand splat you'll never want to go back!"

Now, splats are great for replacing for expressions on lists, but they can also be used for conditional inputs on dynamic blocks. While we have focused on Terraform being able to get all elements from a list, we haven't discussed the special behavior splat expressions exhibit when applied to a single (non-collection) value. Say we have the following resource definition fo an azurerm_key_vault, which utilizes a dynamic block:

resource "azurerm_key_vault" "example" {
  name                = "examplekeyvault"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
  tenant_id           = data.azurerm_client_config.current.tenant_id
  sku_name            = "standard"

  dynamic "contact" {
    for_each = var.key_vault_contact[*]
    content {
      email = contact.value.email
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

If we look into our variable definition, we have what looks like only a single object, that can have its variable by default initialized to a default value of null:

variable "key_vault_contact" {
  type = object({
    email = string
    name = string
  })
  default = null
}
Enter fullscreen mode Exit fullscreen mode

This will plan just fine. Buy why, you may ask?

Why meme

The splat operator can actually do more than just handle lists. It can also take a single value, such as an object or a map, and convert it behind the scenes to a list of a single object! If the value the object is null, Terraform will take and convert it into an empty list. This makes it possible to for_each over more complex data types in Terraform!

Curious to splat it up even more? I recommend checking Terraform Docs!

5. 🧠 Getting into the terraform state of mind

Last but not least, where would we be without out our beloved terraform state command? While there is also now a moved block, making some state transitions even more reliable/repeatable through GitOps, for the purpose of the Terraform associate exam here are three use cases of the imperative: a still valuable command to know for your state needs!

1. Change the name of a resource/module

terraform state mv my_provider.pie my_provider.cake

  • Simple, on the tin changing of a resource's name.
  • Additionally, we can provide -dry-run as an option to view what changes would be performed before doing so.

2. Move a resource into a module

terraform state mv my_provider.pie module.pie_soft.pie

  • This can be super helpful if you are testing out the changes to a given resource, get it how you'd like it, and then want to bring those changes into a module.
  • Or if you simply didn't have the module before to bring the changes into, this can be a lifesaver!
  • Additionally, you can choose to rename the resource as you wish to fit the module's naming conventions

3. Move a module into another module

terraform state mv module.pie_soft.pie module.cake_soft.pie

  • Finally, last, but certainly not least, we have the ability to move an entire module into another module!
  • If we are at this point, we may be using the imperative command to move modules between two completely different terraform projects.
    • For this we can use the options -state=../my_dir/a -state-out=../my_dir/b, meaning that we can tell Terraform to take existing module state from other projects and bring it into our current project accordingly!

Those are my five learning takeaways from studying and taking the Terraform Associate exam!
I'm hopeful this post will serve as a valuable resource throughout your Terraform journey.

Please feel free to comment with your experience, suggestions, and anything else you'd be curious to learn or share!

HashiCorp Terraform Certified

-- 🗺️ 🧠 🤩 Happy Terraforming


I'd also like to shout out to a few awesome resources that helped me study along the way:

💖 💪 🙅 🚩
codycodes
Cody Antonio Gagnon

Posted on April 13, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Terraform Associate: Top 5 Learnings
terraform Terraform Associate: Top 5 Learnings

April 13, 2024