Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Terraform By James Joyner IV · · 9 min read

Writing Bulletproof Terraform Variable Validation With AI

Use AI to draft strong Terraform variable validation blocks that fail fast at plan time, then have a human review every condition before you ever apply.

  • #terraform
  • #variables
  • #validation
  • #ai
  • #iac

The last time I let a bad variable slip past terraform plan, it cost me forty minutes and a half-provisioned VPC. Someone passed prod with a trailing space as the environment name, the apply got far enough to create a few resources, then a downstream module choked on the malformed string. I had to import, taint, and clean up by hand. The frustrating part is that the input was wrong the whole time — Terraform just never bothered to tell me until it was already mutating cloud state.

That incident pushed me to treat variable validation as a first-class part of every module. And because writing exhaustive validation blocks is tedious, I now lean on AI to draft them from a spec. But — and this matters — the model writes the first draft, I review every rule, and nothing it produces ever touches state or cloud credentials. Let me walk through how I do it.

Why plan-time validation beats apply-time failures

Terraform gives you three rough chances to catch a bad input: at plan, at apply, or at runtime once the resource exists. Each one further down that list is more expensive. A failure at plan time is free — nothing has changed. A failure at apply time can leave you with partially-created infrastructure. A failure at runtime might mean a misconfigured security group that’s quietly open to the world.

validation blocks live entirely in the plan phase. If a condition fails, Terraform refuses to even start planning. That’s the cheapest possible place to reject garbage, and it’s where I want every preventable mistake to surface.

The anatomy of a validation block

A validation block goes inside a variable and has exactly two arguments: a condition that must evaluate to true, and an error_message shown when it doesn’t. Here’s a CIDR validation using can() to swallow the error that cidrnetmask() throws on malformed input:

variable "cidr_block" {
  type        = string
  description = "Primary CIDR block for the VPC."

  validation {
    condition     = can(cidrnetmask(var.cidr_block))
    error_message = "The cidr_block must be a valid IPv4 CIDR (e.g. 10.0.0.0/16)."
  }
}

cidrnetmask() returns the netmask for a valid CIDR and errors otherwise; wrapping it in can() converts that into a clean boolean. This single block stops a malformed network range before Terraform ever calls the provider.

Constraining strings: enum lists and regex

Two patterns cover most string validation. For a fixed set of allowed values, use contains():

variable "environment" {
  type        = string
  description = "Deployment environment."

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "environment must be one of: dev, staging, prod."
  }
}

For patterned strings — naming conventions, prefixes, allowed characters — use a regex wrapped in can():

variable "project_name" {
  type        = string
  description = "Lowercase project slug used to name resources."

  validation {
    condition     = can(regex("^[a-z][a-z0-9-]{2,29}$", var.project_name))
    error_message = "project_name must be 3-30 chars, lowercase letters, digits, and hyphens, starting with a letter."
  }
}

Now a typo like Prod or an accidental trailing space fails immediately, with a message that tells the next engineer exactly what shape the value should take.

Pro Tip: Write the error_message as if you’re the tired engineer who’ll hit it at 2 a.m. Include a concrete example of a valid value. “Invalid input” wastes everyone’s time; “must be a valid IPv4 CIDR (e.g. 10.0.0.0/16)” fixes the problem in one read.

Typed objects with optional attributes

Type constraints are validation too — they reject the wrong shape before any validation block runs. The object() type plus optional() lets you define a precise structure with sensible defaults:

variable "node_pool" {
  description = "Configuration for a Kubernetes node pool."

  type = object({
    machine_type = string
    min_nodes    = number
    max_nodes    = optional(number, 5)
    disk_size_gb = optional(number, 100)
    labels       = optional(map(string), {})
    preemptible  = optional(bool, false)
  })

  validation {
    condition     = var.node_pool.min_nodes <= var.node_pool.max_nodes
    error_message = "node_pool.min_nodes must be less than or equal to node_pool.max_nodes."
  }
}

The optional(number, 5) form supplies a default when the caller omits the attribute, so consumers only specify what they care about. The validation block then enforces a relationship between two fields of the same object — min_nodes can’t exceed max_nodes — catching a contradictory config at plan time.

Cross-variable validation

Terraform now lets a validation block reference variables other than its own. This unlocks rules that span inputs. Say you want to forbid preemptible machines in production:

variable "use_preemptible" {
  type        = bool
  default     = false
  description = "Whether to use preemptible/spot instances."

  validation {
    condition     = !(var.environment == "prod" && var.use_preemptible)
    error_message = "use_preemptible must be false when environment is prod."
  }
}

This is the kind of policy that used to require a separate tool like OPA or Sentinel. For straightforward guardrails, a cross-variable validation keeps the rule next to the variables it governs, in plain HCL.

What a failure looks like

When a condition fails, terraform plan stops with the message you wrote. Pass environment = "prod" and use_preemptible = true and you get:

$ terraform plan

│ Error: Invalid value for variable

│   on variables.tf line 41, in variable "use_preemptible":
│   41:   validation {
│     ├────────────────
│     │ var.environment is "prod"
│     │ var.use_preemptible is true

│ use_preemptible must be false when environment is prod.

│ This was checked by the validation rule at variables.tf:41,3-13.

No resources planned, no state touched, nothing applied. That’s the whole point — the bad input dies at the door.

Where AI fits (and where it absolutely doesn’t)

Drafting a dozen of these blocks by hand is dull, and dullness breeds skipped validation. So I hand the model the variable spec — names, types, intended constraints — and ask it to draft the validation blocks. Tools like Claude or Cursor are good at this because the patterns are well-established. I keep a reusable prompt for it in my prompt workspace, and there are ready-made Terraform prompts in the prompt packs.

Treat the AI like a fast junior engineer: quick, eager, and occasionally confidently wrong. It will sometimes invent a function that doesn’t exist, get a regex anchor subtly wrong, or write an error_message that contradicts the condition. So:

  • Never auto-apply. The model drafts HCL. A human reads every condition and confirms it rejects what it should and accepts what it should.
  • Test the validation logic itself. Feed it a known-bad value and confirm plan fails; feed it a known-good value and confirm it passes. A validation rule that’s too loose is worse than none, because it gives false confidence.
  • Never give the model state-write access or cloud credentials. It generates text. It does not run apply, it does not hold AWS keys, it does not touch your backend. Drafting and executing are different jobs, and only one of them is safe to delegate.

Pro Tip: Run AI-drafted HCL through your normal review path — terraform fmt, validate, and a peer’s eyes — same as any other code. Wiring it into an automated code review step catches the hallucinated functions before they reach a pull request. The AI is a drafting accelerant, not a reviewer.

Conclusion

Strong variable validation is the cheapest reliability win in Terraform: it moves failures from expensive apply-time and runtime back to free plan-time. The validation block, typed object constraints with optional(), and cross-variable rules give you almost everything you need in plain HCL. AI makes writing all of it fast — as long as you remember it’s a junior drafting assistant, not an operator. You review every rule, you test that it actually fails on bad input, and you never hand it the keys to your state or your cloud. For more in this vein, browse the Terraform category or my collection of reusable prompts.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.