Want to make sure all the variables declared in a terraform module are actually used in the code?

This code lists all variables used in each of the sub-directories containing terraform code.

It started off as a one-liner but, as usual, the code to make it look pretty is bigger than the main functional code!

#!/usr/bin/env bash

set -euo pipefail


main() {

print_underlined () {
  local text="$1" ; shift
  local ul_char
  if [[ -n ${1:-} ]] ; then
    ul_char="$1" ; shift
  printf '%s\n%s\n' "$text" "${text//?/$ul_char}"

process() {
  # loop over all directories
  while read -r dir ; do
    pushd "$dir" >/dev/null
    print_underlined "$dir" 
    # get a unique list of variables used in all .tf files in this directory
    sort -u < <(
      perl -ne 'print "$1\n" while /var\.([\w-]+)/g' ./*.tf
    popd > /dev/null
  done < <(
    # get a unique list of directories containing terraform files
    # starting in the present working directory
    sort -u < <(
      find . -name '*.tf' -exec dirname {} \;

main "$@"

Updated 2017-12-06: complete re-write to support nested braces in variable description
Updated 2017-12-05: support minimal variable declaration, eg. variable "foo" {}

When developing terraform code, it is easy to end up with a bunch of variable definitions that are listed in no particular order.

Here's a bit of python code that will sort terraform variable definitions. Use it as a filter from inside vim, or as a standalone tool if you have all your variable definitions in one file.


tf_sort < variables.tf > variables.tf.sorted
mv variables.tf.sorted variables.tf

Here's the code:

#!/usr/bin/env python
# sort terraform variables

import sys

import regex

# this regex matches terraform variable definitions
# we capture the variable name so we can sort on it
pattern = regex.compile('''
(                   # capturing group #1
    variable        # literal text
    \s*             # white space (optional)
    "               # literal quote character
(                   # capturing group #2 (variable name)
    [^"]+           # anything except another quote
(                   # capturing group #3
    "               # literal quote character
    \s*             # white space (optional)
(?<rec>             # capturing group named "rec"
    {               # literal left brace
    (?:             # non-capturing group
        [^{}]++     # anything but braces one or more times with no backtracking
        |           # or
        (?&rec)     # recursive substitution of group "rec"
    )*              # this group can appear 0 or more times
    }               # literal right brace
''', regex.VERBOSE)

def process(content):
    # sort the content (a list of tuples) on the second item of the tuple
    # (which is the variable name)
    matches = sorted(regex.findall(pattern, content), key=lambda x: x[1])

    # iterate over the sorted list and output them
    for match in matches:
        print(''.join(map(str, match)))

        # don't print the newline on the last item
        if match != matches[-1]:

# check if we're reading from stdin
if not sys.stdin.isatty():
    stdin = sys.stdin.read()
    if stdin:

# process any filenames on the command line
for filename in sys.argv[1:]:
    with open(filename) as f:

If you manually delete a resource that is being managed by terraform, it it not removed from the state file and becomes "orphaned".

You many see errors like this when running terraform:

1 error(s) occurred:
* aws_iam_role.s3_readonly (destroy): 1 error(s) occurred:
* aws_iam_role.s3_readonly (deposed #0): 1 error(s) occurred:
* aws_iam_role.s3_readonly (deposed #0): Error listing Profiles for IAM Role (s3_readonly) when trying to delete: NoSuchEntity: The role with name s3_readonly cannot be found.

This prevents terraform from running, even if you don't care about the missing resource such as when you're trying to delete everything, ie. running terraform destroy.

Fortunately, terraform has a command for exactly this situation, to remove a resource from the state file: terraform state rm <name of resource>

In the example above, the command would be terraform state rm aws_iam_role.s3_readonly