How to extend terraform with direnv

Anton
2 min readFeb 7, 2023

--

In previous article I was writing about how we’re using terraform without wrappers in multi-region, multi-account environments. I got a few questions about “how we’re using direnv exactly?”. Here you will find the whole snippet we created.

Our main .envrc file looks like this:

#Before copy-pasting this code please go to https://medium.com/@senior-devops/how-to-extend-terraform-with-direnv-a4a3fef092c5
#and press "clapp" button at the bottom three times! ;)

source_up

has aws
has jq

BASE_PWD=$PWD
# figures the current working directory without relying on PWD (which will change when direnv is done)
get_cwd() {
p="$(expand_path "$1")"
local t="${p%${p##*/}}"
echo "${t%/}"
}

# Needed for nested .envrc files
use_terraform_env() {
local CWD
CWD="$(get_cwd "$1")"

ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
echo Current account ID: ${ACCOUNT_ID:-"UNDEFINED!!!"}, Region: ${AWS_REGION:-"UNDEFINED!!!"}
echo Current PWD: $CWD

#Environment name is just a concatenation of two nested folders (i.e. project-environment)
ENVIRONMENT="$(basename ${CWD%/*})-$(basename $CWD)"

#Check if tfvars files exist:
for file in ${BASE_PWD}/vars/global.tfvars \
${BASE_PWD}/vars/global.etfvars \
${BASE_PWD}/vars/${ENVIRONMENT}.tfvars \
${BASE_PWD}/vars/${ENVIRONMENT}.etfvars
do
if [[ -f ${file} ]]; then
TF_VAR_FILES="${TF_VAR_FILES:-} -var-file=${file}"
fi
done

TF_COMMON_ARGS="-compact-warnings -input=false -lock-timeout=120s ${TF_VAR_FILES}"
export TF_CLI_ARGS_plan="${TF_COMMON_ARGS}"
export TF_CLI_ARGS_apply="${TF_COMMON_ARGS}"
export TF_CLI_ARGS_import="${TF_COMMON_ARGS}"
export TF_CLI_ARGS_destroy="${TF_COMMON_ARGS}"
export TF_CLI_ARGS_console="${TF_VAR_FILES}"
export TF_CLI_ARGS_refresh="${TF_COMMON_ARGS}"
export TF_CLI_ARGS_init="-backend=true -get=true -backend-config="bucket=terraform-${ACCOUNT_ID}" -backend-config="key=$ENVIRONMENT.tfstate" -backend-config="workspace_key_prefix=$AWS_REGION/$ENVIRONMENT""
export TF_PLUGIN_CACHE_DIR="${TF_PLUGIN_CACHE_DIR:=${BASE_PWD}/.plugin_cache}"
export TF_VAR_region=${AWS_REGION}
}

Each terraform project has similar .direnv file which references the main .direnv:

source_up
#Use current PWD in upper .envrc
use terraform_env "$1"

Function get_cwd is needed to keep the right working directory of the current project and not the path to the main .direnv

The only drawback we have with such approach is warnings from terraform regarding unused variables. There were pretty hot discussion when hashicorp introduced a warning for undeclared variables followed by feature deprecation notice. At the end they replied: “Yes, you read that right, we’ve heard you loud and clear.”, hope it will stay this way.

Beside of that with direnv we’re getting following advantages:

  • An ability to use global and custom variables per environment completely transparently
  • An ability to use secret variables from encrypted .etfvars files
  • Easily switch AWS accounts with aws-vault and reuse same code-base
  • Similar naming conventions for state files across all environments

Anton Babenko is this reading worth to mention on weekly.tf? :)

--

--