AAAAAH
Drifted resources coming for your soul

Fear the Side Effect

Create atomic infrastructure

Boo! It’s me, the unexplained permanent drift in your Terraform plans, the random resource recreation, your downtime when deploying a new release, or WORSE your supply chain attack.

The Problem

Sadly, not everything can be atomic, but we can do our best by ensuring things happen with as much idempotency as they can have, Terraform already helps with this by preventing drift, but with some resources it can and will be murder-y, our goal here is to prevent that, there are choices we can take that will help with this.

Cloud infrastructure can and will be messy at some point, and that drives the design of tools that manage it, the latest addition to Terraform, action blocks, or the {local|remote}-exec blocks (you should almost never ever use those).

Try to find the most deterministic way to do things, you will be grateful later

The count meta-argument seems fine right? Wrong, order is not guaranteed to be deterministic (though most of the time it is) but try removing a resource from the list that you’re iterating against and you’ll find it likely that it will try to recreate all resources created by that count (albeit, Terraform is now smarter and if it can it’ll avoid recreating it)

The Solution…? ¯\(ツ)

First, be idiomatic and follow Terraform’s best practices.

Second, I recommend using validation as much as possible, lean towards configuration objects, optional inputs with default values.

Decent variable definition example

variable "persons" {
  default     = []
  description = "A list of persons with their location and personal details"

  type = list(object(map({
      last_name = optional(string, null)
      name      = string
      office    = optional(string, "New York")
    })))

  validation {
    condition     = [for person in var.persons : contains(["New York", "California"], person.office)]
    error_message = "Person's office should be eithe New York or California."
  }
}

resource "human" "jack_sparrow" {
  phone_number = twilio_api_accounts_incoming_phone_numbers.jack_sparrow.phone_number
  office       = try(var.this_is_my_cool_object.office, "Unkown Location") # доверяй, но проверяй
}

Decent loop example

# This won't create anything if the variable isn't defined, that's good!
resource "human_email" "humans" {
  for_each = {
    for person in var.persons : person.name => person
  } # Won't work if someone has the same name, but that can't never happen...

  name = "${person.name}@ibm.com"
}

In Summary

Be conscious about your code, create modules that serve an actual purpose, that have opinions, Terraform is already basically an API wrapper with state tracking built-in, creating modules that just wrap a collection of resources and takes every resource option as an input serves no value, be opinionated, bake security standards, make something actually useful to users.

If you want to read more about this, I’d recommend this blog post by Dave Hall.