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.
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.
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.
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.
There are two categories of complex types: collection types (for grouping similar values), and structural types (for grouping potentially dissimilar values).
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]"
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
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
}
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"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With