How to set a default value for a variable in a Terraform template?

For setting a default value in something like Jinja2 I would use something like

Hey {{ first_name|default(“there”, true) }}

How would I achieve this in a Terraform template file?

Hi @nabarunchatterjee,

The Terraform language doesn’t have a dedicated operator for this, but there are some different options to get a similar result:

  • You can write out an explicit conditional check. This is definitely much longer, but it makes the result clear about exactly what conditions the default value should be used in, such as if first_name is null:

    Hey ${ first_name != null ? first_name : "there" }
    
  • If you want to use the default value when the given value is either null or an empty string, you can use the coalesce function to write that more compactly:

    Hey ${ coalesce(first_name, "there") }
    

One significant difference between Terraform language and Jinja2 is in the handling of undeclared variables: IIRC Jinja2 will handle an undefined first_name reference by returning an undefined value, while Terraform considers that to be an error. Therefore in order for something like the above to work, you need to make sure that first_name is always available to your template, even if it’s explicitly set to "" or null.

1 Like

Thanks @apparentlymart. That was helpful.

Therefore in order for something like the above to work, you need to make sure that first_name is always available to your template, even if it’s explicitly set to "" or null .

That kind of defeats the point of a default value though. I have a case where a have template that I would like to have a boolean variable that indicates whether a certain section should be included in the result. 90% of the time that is false. I’d really like to avoid having to pass that through in all the cases where the argument doesn’t even really make sense.

Any of the following would solve my problem:

  • Be able to do something like lookup(vars, "my_var", "default") to look up a field in the root map
  • Be able to define a custom function that calls templatefile with the defaults I want
  • Have some syntax to declare that a variable is optional in a template file.

Haha, right after I posted that, I realized that in terraform 0.12 you can do

try(my_var, "default")

nevermind. that doesn’t work :frowning:

Modules can also have variables with default values.

variable "first_name" {
  type = string
  default = "there"
}

resource "local_file" "foo" {
    content     = templatefile(format("%s/hello.tpl", path.module), { first_name = var.first_name })
    filename = format("%s/hello.txt", path.module)
}

content of hello.tpl

Hello ${first_name}

init

$ terraform init

Initializing the backend...

Initializing provider plugins...
- Finding latest version of hashicorp/local...
- Installing hashicorp/local v2.1.0...
- Installed hashicorp/local v2.1.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

apply with default value

$ terraform apply -auto-approve
local_file.foo: Refreshing state... [id=42329929896ff1f958cb9e4013d610eadf8b6fde]
local_file.foo: Destroying... [id=42329929896ff1f958cb9e4013d610eadf8b6fde]
local_file.foo: Destruction complete after 0s
local_file.foo: Creating...
local_file.foo: Creation complete after 0s [id=cf77cea2580f552986703576b260376c818e37f0]

Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
$ cat .\hello.txt
Hello there

apply with a value set (via CLI here, but could be a .tfvars file or a variable set in the workspace when using TF cloud

$ terraform apply -auto-approve -var 'first_name=Kitty'
local_file.foo: Refreshing state... [id=cf77cea2580f552986703576b260376c818e37f0]
local_file.foo: Destroying... [id=cf77cea2580f552986703576b260376c818e37f0]
local_file.foo: Destruction complete after 0s
local_file.foo: Creating...
local_file.foo: Creation complete after 0s [id=fd9bc4b115e5c567fe0f5b18dd7fff847a10bc36]

Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
$ cat .\hello.txt
Hello Kitty

Sadly, this pattern does not work any more (>= Terraform v1.0.2).

I use merge function to process the default values.

locals {
  name = "test-server"
  write_file = {
    content     = ""
    owner       = "root:root"
    permissions = "0644"
    encoding    = "text/plain"
    append      = "false"
    defer       = "false"
  }
}

data "cloudinit_config" "api" {
  gzip          = true
  base64_encode = true

  part {
    content_type = "text/cloud-config"
    content = templatefile("./cloud-init/api.ubuntu.yml", {
      hostname : local.name
    })
  }

  part {
    content_type = "text/cloud-config"
    content      = templatefile("./cloud-init/write_file.yml", merge(local.write_file, { path = "/root/test.txt",  content = "CONTENT", permissions = "0777" }))
  }

  part {
    content_type = "text/cloud-config"
    content = templatefile("./cloud-init/write_file.yml", merge(local.write_file, {
      path     = "/usr/lib/systemd/system/test-server.service"
      content  = base64encode(templatefile("./cloud-init/test-server.service", { name = local.name })),
      encoding = "base64"
    }))
  }

}

Simplest pattern if you’re dealing with map/hash object:

Hey ${lookup(user_object, "first_name", "there")}
e.g.

Hey ${lookup(<HASH OBJECT>, <HASK KEY NAME>, <DEFAULT VALUE>)}
1 Like

Yeah, it would have been great if terraform had a way of setting a default value for a variable if it didn’t exist. In my case, I have a template for various virtual machines and only for some of them does it make sense to use certain variables. Because terraform treats undeclared variables as errors, I have to add an empty value for each virtual machine that doesn’t need it. Because of that the code becomes unwieldy.

1 Like

How can I upvote this? It would, indeed, be very helpful for Terraform templating engine to be able to assign default values to undeclared variables.

The top-level variables are statically checked because otherwise Terraform would give poor feedback about typos in the common case, and we saw this cause people confusion during early use of templatefile.

However, if you assign an object value into one of your template variables then you can use all of the usual tricks to concisely deal with attributes being null or being absent completely, such as the coalesce and try functions. This is similar to how in a normal Terraform configuration file you can’t refer to a variable that wasn’t declared, but you can dynamically test if a nested attribute of that variable is set.

The design of this part of Terraform prioritizes the common case of relatively simple templates with a fixed set of variables that are always used. For more complex situations you can use nested objects as an escape hatch.

1 Like

Thanks @apparentlymart ! I put in practice what you said and it works. Here’s a demo code.