Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

terraform

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.

like image 379
FreeZey Avatar asked Sep 13 '21 15:09

FreeZey


People also ask

How do I pass a null value in Terraform?

Related to terraform issue: hashicorp/terraform#24142 We can't pass a null value to a module as it is passed explicitly instead of using the default set inside of the module. As a workaround we set empty string as default and set local variables conditionally.

What is default Terraform?

If present, the variable is considered to be optional and the default value will be used if no value is set when calling the module or running Terraform. The default argument requires a literal value and cannot reference other objects in the configuration.

What is default null value?

If no default value is declared explicitly, the default value is the null value. This usually makes sense because a null value can be considered to represent unknown data. In a table definition, default values are listed after the column data type.

What are two complex types in Terraform?

There are two categories of complex types: collection types (for grouping similar values), and structural types (for grouping potentially dissimilar values).


2 Answers

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]"
like image 96
Greg Hensley Avatar answered Oct 10 '22 05:10

Greg Hensley


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"
like image 24
SomeGuyOnAComputer Avatar answered Oct 10 '22 05:10

SomeGuyOnAComputer