FreeZey
FreeZey

Reputation: 2812

How can I make Terraform replace a null value with a default value?

The Terraform documentation indicates this should already be happening:

https://www.terraform.io/docs/language/expressions/types.html

null: a value that represents absence or omission. If you set an argument of a resource or module to null, Terraform behaves as though you had completely omitted it — it will use the argument's default value if it has one, or raise an error if the argument is mandatory.

I'm calling a module "foo" that has the following variable file:

variable "bar" {
  type    = string
  default = "HelloWorld"
}

Example 1

When I call it using this code:

module "foo" {
  source = "../modules/foo"
  bar = null
}

The result is an error. Invalid value for "str" parameter: argument must not be null. Trigger when bar is being used.

Example 2

When I call it using this code (omitting it, rather than nulling it):

module "foo" {
  source = "../modules/foo"
  # bar = null
}

The result is that it works. The "bar" variable is defaulted to "HelloWorld".

This appears to be a bug In Terraform that someone else also raised but wasn't resolved. https://github.com/hashicorp/terraform/issues/27730

Does anyone know a solution or a work around?

Version information:

Terraform v1.0.5
on linux_amd64
+ provider registry.terraform.io/hashicorp/google v3.51.0
+ provider registry.terraform.io/hashicorp/null v3.1.0
+ provider registry.terraform.io/hashicorp/random v3.1.0
+ provider registry.terraform.io/hashicorp/time v0.7.2

Workaround

Based on @Matt Schuchard's comment and some research there's a ugly solution using the conditional check:

variable "foo" {
  type    = string
  default = "HelloWorld"
}
locals {
  foo = var.foo == null ? "HelloWorld" : var.foo
}

Why

My use case is an attempt to avoid duplicated code. I have 2 very similar modules, one being a subset of the other. The solution I'm using is to put the modules in sequence calling each, i.e. a grandparent, parent and child.

I want to have the variables available to the "grandparent" but if they're omitted then the module below "child" should set them using a default value, e.g. "HelloWorld". But to exposed those variables all the way through the family line I have to include them in all modules and in the high modules (grandparent and parent) I want to default them to null, allowing them to be optional but also still causing them to be set to a default in the "child" further down the line.

...I think I need a diagram.

Upvotes: 26

Views: 100834

Answers (3)

Manu Vamadevan
Manu Vamadevan

Reputation: 1

I hope a combination of contains() with a condition can solve the problem (terraform versions > 1.xx)

I could solve a for_each solution based on this combination. In the following example, the .value part contains a set of attributes. A bit lazy to experiment with the exact problem ;) , but this may help.

your_target_attr = contains(keys(each.value), "your_possible_attribute_value") ? each.value.your_possible_attribute_value : var.your_default_value

Upvotes: 0

SomeGuyOnAComputer
SomeGuyOnAComputer

Reputation: 6208

This is for older versions of Terraform

Use the nullable approach if using Terraform 1.1.x or above and if you do not need to support older versions of terraform


You can set the default to null and set the real default value in the local via a coalesce function

Use try

  • This works for a null and empty string
variable "foo" {
  type    = string
  default = null
}

locals {
  # return var.foo if it's not null
  # return "HelloWorld" if var.foo is null
  foo = try(length(var.foo), 0) > 0 ? var.foo : "HelloWorld"
}

output "foo" {
  value = local.foo
}

Use coalesce

  • This only works for a null string and not an empty string
variable "foo" {
  type    = string
  default = null
}

locals {
  # return var.foo if it's not null
  # return "HelloWorld" if var.foo is null
  foo = coalesce(var.foo_coalesce, "HelloWorld")
}

output "foo" {
  value = local.foo
}

Using the default value of null returns HelloWorld

$ terraform apply -auto-approve

...

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

foo = "HelloWorld"

Using a new value negates the default set in the local

$ terraform apply -auto-approve -var="foo=HelloUniverse"

...

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

foo = "HelloUniverse"

Upvotes: 13

Greg Hensley
Greg Hensley

Reputation: 624

As of Terraform 1.1.0, variable declarations now support a nullable argument. It defaults to true to preserve the existing behavior. However, any variable with nullable=false that is unspecified or set to null will instead be assigned the default value.

main.tf:

variable "nullable" {
  type    = string
  default = "Used default value"
}

output "nullable" {
  value = coalesce(var.nullable, "[null]")
}

variable "non_nullable" {
  type     = string
  default  = "Used default value"
  nullable = false
}

output "non_nullable" {
  value = coalesce(var.non_nullable, "[null]")
}

terraform.tfvars

nullable     = null
non_nullable = null

Note the use of coalesce in the output blocks. Terraform elides any outputs that are set to null, so this ensures that any null value still shows something in the output.

After applying this configuration, we can see from running terraform output that when nullable=true (the default) a variable keeps an explicitly set null value but with nullable=false any null value is ignored in favor of the default.

# terraform output
non_nullable = "Used default value"
nullable = "[null]"

Upvotes: 32

Related Questions