Terraform Associate: Top 5 Learnings
Tags: terraform, certification, learning, devops
Published: 2024-04-13 00:57:14

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](https://marketplace.visualstudio.com/items?itemName=HashiCorp.terraform) 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`?
* did you know `terraform destroy` [is actually just an alias](https://developer.hashicorp.com/terraform/cli/commands/destroy#usage) for `terraform apply -destroy`? I didn't either!
* you can also use `-auto-approve` here but ["I sure hope you know what you're doing"](https://www.reddit.com/r/ProgrammerHumor/comments/rjx1y5/i_hope_you_know_what_youre_doing/)

#### `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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0wphlm24i2fbek9vz5v0.png)

#### String Functions
##### [format](https://developer.hashicorp.com/terraform/language/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:
```hcl
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"
```

##### [trimprefix](https://developer.hashicorp.com/terraform/language/functions/trimprefix)
Super simple command that allows you remove part of the beginning of a string. Say we have the following variable:
```hcl
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🥧"
```

##### [trimsuffix](https://developer.hashicorp.com/terraform/language/functions/trimsuffix)
Same as `trimprefix`, but at the end rather than the beginning!
```hcl
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"
```

##### [replace](https://developer.hashicorp.com/terraform/language/functions/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!
```hcl
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🍰"
```

#### Collection Functions
##### [keys](https://developer.hashicorp.com/terraform/language/functions/keys)
Helps get the keys of a map. Can really be useful in a cinch - notice the keys are [_sorted in lexicographical order_](https://developer.hashicorp.com/terraform/language/functions/keys), which is probably different than the order they're created in!
```hcl
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",
  "🍰",
  "🥧",
])
```

##### [lookup](https://developer.hashicorp.com/terraform/language/functions/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:
```hcl
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, "👾", "🥧")
"🥧"
```
When a key isn't present inside of `var.map_of_pies`, we elect to always return a pie emoji!
![OMG Pie meme](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h0f1oba56ubqw4p6xohy.png)
*I like that default value more, tastes better*

##### [concat](https://developer.hashicorp.com/terraform/language/functions/concat)
Simply take two lists and make them one!
```hcl
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",
  "🥧",
  "🎂",
  "🍰",
  "🍥",
])
```

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

```hcl
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",
])
```

##### [formatlist](https://developer.hashicorp.com/terraform/language/functions/formatlist)
Just like with the `format` command, `formatlist` can apply its specification to a whole list of strings!
```hcl
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!",
])
```

### 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
```hcl
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([
  "🍰",
  "🍪",
  "🍦",
  "🍩",
  "🧁",
])
```
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:
```hcl
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
    }
  }
}
```
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`:
```hcl
variable "key_vault_contact" {
  type = object({
    email = string
    name = string
  })
  default = null
}
```

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

![Why meme](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/41cfuh6p8tpq177arlvl.gif)


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](https://developer.hashicorp.com/terraform/language/expressions/splat)!

### 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](https://developer.hashicorp.com/terraform/language/modules/develop/refactoring), 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/97scfmul77ganhjlq3vb.png)


-- 🗺️ 🧠 🤩 Happy Terraforming

---

I'd also like to shout out to a few awesome resources that helped me study along the way:
* Free: [HashiCorp Terraform Associate Certification Course (003) - Pass the Exam!](https://www.youtube.com/watch?v=SPcwo0Gq9T8)
* Paid: [HashiCorp Certified: Terraform Associate Practice Exam 2024](https://www.udemy.com/course/terraform-associate-practice-exam/)