Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

can function does not work properly with nonsensitive function #31646

Closed
KyleKotowick opened this issue Aug 16, 2022 · 2 comments · Fixed by #33758
Closed

can function does not work properly with nonsensitive function #31646

KyleKotowick opened this issue Aug 16, 2022 · 2 comments · Fixed by #33758
Assignees
Labels
bug config v1.2 Issues (primarily bugs) reported against v1.2 releases

Comments

@KyleKotowick
Copy link

Terraform Version

Terraform v1.2.7
on windows_amd64

Terraform Configuration Files

A:

output "can_nonsensitive" {
  value = can(nonsensitive(uuid()))
}

B:

output "nonsensitive" {
  value = nonsensitive(uuid())
}

Expected Behavior

Both outputs should have the same result, since they're both operating on a uuid() value.

Actual Behavior

A:

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

Outputs:

can_nonsensitive = true

B:

│ Error: Invalid function argument
│
│   on main.tf line 2, in output "nonsensitive":2:   value = nonsensitive(uuid())
│
│ Invalid value for "value" parameter: the given value is not sensitive, so this call is redundant.

Since the function throws an error when you actually try to use it, the can() in A should return false.

Steps to Reproduce

  1. terraform apply

Additional Comments

There's some other funny behaviour going on as well. Compare these two outputs:

output "can_nonsensitive_inline" {
  value = can(nonsensitive(uuid()))
}

locals {
  uuid = uuid()
}

output "can_nonsensitive_local" {
  value = can(nonsensitive(local.uuid))
}

Do a terraform plan:

Changes to Outputs:
  + can_nonsensitive_inline = true
  + can_nonsensitive_local  = (known after apply)

Why is one value known at plan-time, while the other is not?

Now do a terraform apply:

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

Outputs:

can_nonsensitive_inline = true
can_nonsensitive_local = false

The two outputs have different values, which they almost certainly should not.

Overall, some really funky behaviour going on. @apparentlymart another good one for you :)

@KyleKotowick KyleKotowick added bug new new issue not yet triaged labels Aug 16, 2022
@apparentlymart
Copy link
Contributor

apparentlymart commented Aug 16, 2022

Thanks for sharing this, @KyleKotowick.

I don't have a ready explanation for what's going on here, but for the benefit of someone investigating this (assuming it isn't me!) I'll note that can's implementation is explicitly "funny business", because in order to do its job it needs to delay evaluating its argument until the function is already running so that it can intercept any errors it produces.

So, for an expression like can(nonsensitive(uuid()):

  • When evaluating the can(...) call, the evaluator notices that can is a special function which requires access to the static expression in its first argument, rather than the result of evaluating that expression.
  • The evaluator therefore captures the raw expression AST node representing that nonsensitive(uuid()) portion, along with the evaluation context it that can was called from, to form an expression closure, which associates the expression AST with the set of variables and functions that would've been available when evaluating it.
  • The evaluator then calls can's implementation and passes in that expression closure as the argument.
  • The can implementation then finally actually evaluates the expression closure, allowing that function to see both the function result and any error it generated. It discards the main result entirely, and just checks whether it generated an error and returns false if so, and true if not.

Without actually digging into the code, I have a hunch that what's going on here is that the special evaluation behavior for can is somehow applying the unknown value short-circuit behavior "too early", so that the unknown value generated by uuid() is causing nonsensitive to not get evaluated at all, and it's instead just immediately successfully returning an unknown value. can therefore classifies that as a success, and returns true. But I think calling nonsensitive in the normal way bypasses the unknown-value short circuit to allow nonsensitive to recognize whether an unknown value is sensitive or not, which then allows it to fail as expected in the normal case.

The try function performs some similar funny business on all of its arguments, so it's likely that whatever is going wrong here for can is also equivalently wrong for try in an expression like try(nonsensitive(uuid()), "oops").

Copy link
Contributor

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Dec 11, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug config v1.2 Issues (primarily bugs) reported against v1.2 releases
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants