Intermittent 'value depends on resource attributes that cannot be determined until apply' error

This is odd behaviour!

I set value in terraform.tfvars.

bastion_public_ip = ""

This worked:

count = var.bastion_enabled || var.bastion_public_ip != "" ? 1 : 0

Then yesterday, it failed as follows:

The "count" value depends on resource attributes that cannot be determined until apply

I managed to get it to work today by removing state files and reinitialising.

Can anyone suggest a reason as to why this happened?

There is more going on here than I first thought as it failed again. It may not be the best or correct way to workaround but enclosing var.bastion_public_ip in square brackets seems to work fine, i.e.

count = var.bastion_enabled || [var.bastion_public_ip] != "" ? 1 : 0

Hi @mick_a_thompson,

The rephrase the error slightly, when generating the plan, the count value is unknown. In your example, this would mean that either var.bastion_enabled or var.bastion_public_ip is unknown for some reason.

Changing the comparison to [var.bastion_public_ip] != "" works because you are now comparing a tuple value to a string, which is always going to be true, but this means your statement is now equivalent to

count = true ? 1 : 0

or just

count = 1

In order to try and help determine why the value may be unknown in some cases, we would need to see a complete example showing the inputs to these variables.

Yes, I since discovered that it only worked when I set a value.

This “unkown” is var.bastion_public_ip; it may also be set a value depending on my deployment. It is this that I was trying to account for.

The check works elsewhere just not in count, e.g.

bastion_private_key = var.bastion_enabled || var.bastion_public_ip != "" ? file(var.bastion_private_key_path) : ""

Hi @mick_a_thompson,

I think I would address this by slightly changing the interface to this module so that all of the settings related to “bastion” are grouped together in a single object and then the presence (non-null-ness) of that object is the decider for whether to work with it.

variable "bastion" {
  type = object({
    public_ip        = string
    private_key_path = string
  default = null

  validation {
    condition     = var.bastion == null || try(var.bastion.public_ip, "") != ""
    error_message = "Bastion public IP must be set if bastion is enabled."

  validation {
    condition     = var.bastion == null || try(var.bastion.private_key_path, "") != ""
    error_message = "Bastion public private key path must be set if bastion is enabled."
  count = var.bastion != null ? 1 : 0
  bastion_private_key = var.bastion != null ? file(var.bastion.private_key_path) : null

The advantage of this is that it separates the signal for whether it’s enabled (the null-ness of this variable) from the values of the attributes when it is enabled, and thus it allows the enabledness to be known even when the attributes aren’t known. That is, it allows for the following value to pass in for the first run where the public IP isn’t known yet:

  public_ip        = (known after apply)
  private_key_path = "example.pem"

…and thus it’ll get an unknown value only at var.bastion.public_ip, not at var.bastion itself.

Subjectively, I also tend to prefer grouping the settings for a single conceptual object into a single variable like this, but of course you might have different tastes on that. A different variant of this would be to use var.bastion_enabled exclusively to decide the “enabledness” and then just assume the others are all set appropriately when enabled, but grouping them all into one variable as I did here means you can write validation rules to catch the invalid cases up front and avoid having to deal with them repeatedly throughout the rest of your module.

1 Like

That’s great help. Thank you.

For info, bastion_enabled boolean deploys a bastion whereas bastion_public_ip != null uses an existing bastion.

Ahh, well I suppose then what I proposed will not exactly model the problem but hopefully the general idea of separating the “enabledness” from the unknown value is still helpful. An object-typed variable that can be null to disable something is still a good general pattern for situations like this, though really any sort of container data structure that can be known even when its element values aren’t will get the same effect.