Variables: NULL, empty string and default values

Hello,
I would like to use a default value when a variable is set to an empty string.

Somehow I’m missing something obvious. Appreciate your input.

Code:

module:

data "aws_key_pair" "EC2_Key" {
...
  key_name           = var.key_name
...
}

Variable file:

variable "key_name" {
  description = "The new keyname"
  default = "ID_EC2_DS"
}

The calling module:

module "vm" {
  source        = "..//modules/AWS/comp/ec2"
  key_name           = var.key_name == "" ? null : var.key_name
...
}

Calling module var:

variable "key_name" {}

Calling module’s autovars:

key_name = ""

The use case:
if key_name is not specified (is an empty string), the default value (“ID_EC2_DS”) should be used.

Execution log:

module.vm.aws_iam_role.VM_role: Refreshing state... [id=DS_VM_role]
module.vm.data.aws_key_pair.EC2_Key[0]: Reading...
module.vm.aws_ebs_encryption_by_default.encryption: Refreshing state... [id=terraform-20230111225914694900000001]
module.vm.data.aws_ami.AM2: Reading...
module.vm.aws_key_pair.vm_key[0]: Refreshing state... [id=ID_EC2_DS]
module.vm.data.aws_ami.AM2: Read complete after 1s [id=ami-0316c16981504920a]
module.vm.aws_iam_instance_profile.DS_VM_profile: Refreshing state... [id=DS_VM_profile]
╷
│ Error: multiple EC2 Key Pairs matched; use additional constraints to reduce matches to a single EC2 Key Pair
│ 
│   with module.vm.data.aws_key_pair.EC2_Key[0],
│   on ../../../../../modules/AWS/comp/ec2/ec2.tf line 18, in data "aws_key_pair" "EC2_Key":
│   18: data "aws_key_pair" "EC2_Key" {

Version:

terraform version
Terraform v1.3.4
on linux_amd64
+ provider registry.terraform.io/hashicorp/aws v4.49.0

Appears that since terraform 1.1.0 this should be “just happening”: Input Variables - Configuration Language | Terraform | HashiCorp Developer

Passing a null value as a module input argument will override any default value.
Setting nullable to false ensures that the variable value will never be null within the module. If nullable is false and the variable has a default value, then Terraform uses the default when a module input argument is null.

Clearly, this is NOT happening for me …

Hi @AZZ,

That is the relevant documentation, and it sounds exactly like what is happening here – null is being taken in place of the variable default. If you want the default value when an explicit null is assigned, then you must set nullable = false in the variable declaration.

1 Like

The “beauty” of the late-night work - it works as expected indeed :slight_smile:

no, wait. IT is NOT working:

create_key = false
key_name = ""

in the module:

variable "key_name" {
  description = "The new keyname"
  default = "ID_EC2_DS"
  nullable = false
}

data "aws_key_pair" "EC2_Key" {
  count = (var.create_key ? 0 : 1 )
  key_name           = var.key_name
#  key_name           = var.key_name == "" ? null : var.key_name

#  key_name           = "EC2_Key"
#  include_public_key = true
}

and the log:

module.vm.data.aws_key_pair.EC2_Key[0]: Reading...
module.vm.aws_iam_role.VM_role: Refreshing state... [id=DS_VM_role]
module.vm.data.aws_ami.AM2: Reading...
module.vm.aws_ebs_encryption_by_default.encryption: Refreshing state... [id=terraform-20230111225914694900000001]
module.vm.aws_key_pair.vm_key[0]: Refreshing state... [id=ID_EC2_DS]
module.vm.data.aws_ami.AM2: Read complete after 1s [id=ami-0316c16981504920a]
module.vm.aws_iam_instance_profile.DS_VM_profile: Refreshing state... [id=DS_VM_profile]
╷
│ Error: multiple EC2 Key Pairs matched; use additional constraints to reduce matches to a single EC2 Key Pair
│ 
│   with module.vm.data.aws_key_pair.EC2_Key[0],
│   on ../../../../../modules/AWS/comp/ec2/ec2.tf line 18, in data "aws_key_pair" "EC2_Key":
│   18: data "aws_key_pair" "EC2_Key" {
│ 

How can i see what exactly the variable is evaluated to ?

If the provider isn’t giving a good error message, I would just find any other way to get the value to show up in the plan. Probably the easiest is to assign it into a null_resource.

@jbardin ,
thank you for the null_resource - something new I learned today.

code:

resource "null_resource" "nr" {

  triggers = {
    kn = var.key_name
  }
  provisioner "local-exec" {
    command = "echo"
  }
}

with the definition:

variable "key_name" {
  description = "The new keyname"
  default = "ID_EC2_DS"
  nullable = false
}

and var:

key_name = ""

Output:

# null_resource.nr will be created
  + resource "null_resource" "nr" {
      + id       = (known after apply)
      + triggers = {
          + "kn" = ""
        }
    }

Looks like an empty string (still)?

P.S. If I add a value “TADA” to the var, it outputs it correctly:

 # null_resource.nr will be created
  + resource "null_resource" "nr" {
      + id       = (known after apply)
      + triggers = {
          + "kn" = "TADA"
        }
    }

P.P.S. If I do not specify the variable in autovars - I get the default value as expected.

#key_name = ""

if

key_name = null

the default value gets applied as expected too.

An update to 1.3.7 didn’t help either.

Looks like this bug Default value are applied for empty string when there's no existing state. · Issue #21760 · hashicorp/terraform · GitHub ?

P.S. The behavior is consistent with observed previously. So not the 21760 bug.

Hi @AZZ,

Default values only apply when the caller provides no value at all, either by omitting the argument in the module block or (for non-nullable variables) explicitly setting it to null.

If the caller sends "" then they have now defined the variable to be an empty string and so the default cannot apply.

If you want to also treat an empty string as “unset” then you won’t be able to do that using the built-in capabilities of input variables but there are some other options:

  • Write a validation block inside the variable block which considers empty string as an invalid value and tells the caller to use null instead.
  • Set the default value for the variable to be "" and then use other logic inside your module to select another value instead whenever the given value is an empty string. This is effectively implementing your own version of default values using expressions in your module, so you can choose your own rules for what “unset” means.
1 Like

@apparentlymart ,
the “” != NULL, makes a lot of sense to me. I just wish i saw it somewhere mentioned explicitly in the documentation. But maybe I just didn’t read carefully enough.

The “validation” is probably a way forward for me.

Thank you!
AZZ

Hi @AZZ,

I think what I recommended above is the result of a combination of a few different documented behaviors. In case it’s useful for further learning, here are the two main ones that cover what we discussed here:

  • Disallowing Null Input Values discusses the nullable = false argument that you were discussing with @jbardin earlier in this conversation. It mentions how the treatment of null differs depending on how you set nullable.

  • Types and Values includes some discussion about what null represents throughout the language:

    a value that represents absence or omission. If you set an argument of a resource 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.

    This paragraph admittedly mentions resources specifically even though this idea of null representing omission is true of most parts of the Terraform language. Input variables have a special historical exception controlled by this special nullable option, but if you set nullable = false then they will behave exactly how resource arguments behave, as described here.

I understand that it can often be hard to collect information from various parts of the documentation together to develop a mental model about a new feature you aren’t already familiar with. I wish it were easier to draw all of the connections between all of the language features in all of the different ways they are used, but unfortunately many language features (including null) can be used in many different contexts and so it isn’t really viable to define them in full everywhere we discuss them, or to predict every possible combination of features someone might have questions about. :confounded:

Hopefully the above links are still helpful in retrospect to give some additional background information beyond what we discussed directly here!

1 Like