terraform-plugin-framework: Provider produced unexpected value after apply for a Computed attribute

This has been a discussion on plugin development for about two weeks with evidence leading to believe this more likely an issue than not, no hypothetical root cause or workaround, and the affected users in this situation are of interest to Hashicorp from a business perspective. I am therefore opening an issue here based on the above.

Module version

1.3.5

Relevant provider source code

See link to Hashicorp discuss.

Terraform Configuration Files

See link to Hashicorp discuss.

Debug Output

I cannot provide this easily as this is proprietary/NDA, but something could possibly be worked out if need be.

Expected Behavior

The plugin framework sets a Computed attribute to unknown during plan, and then allows setting the state to the correct actual values within the Create and/or Read.

Actual Behavior

If we are to believe the results of terraform-plugin-testing 1.5.1, and based on discussion thus far, then the Computed attribute value is set to unknown, then to null for unknown reasons post-plan creation (issue is here), and then to the correct accurate value. The plugin framework throws this behavior as an error during the State.set(). Error string is:

Error: Provider produced inconsistent result after apply … produced an unexpected new value: .bar was null, but now cty.ObjectValue(map[string]cty.Value{…})

Note the post-apply value is the expected and accurate value for the attribute according to the logs.

Steps to Reproduce

apply any resource with a nested type struct in the top level model that is also a schema nested attribute and tftype types.Object

References

n/a

About this issue

  • Original URL
  • State: open
  • Created 10 months ago
  • Comments: 18 (8 by maintainers)

Most upvoted comments

Would that not cause the following to fail though because it is passing:

ConfigPlanChecks: resource.ConfigPlanChecks{
   PreApply: []plancheck.PlanCheck{
       plancheck.ExpectUnknownValue("provider_foo.test", tfjsonpath.New("bar")),
   },
},

Based on how you described your error, I would expect this to pass successfully, but the test would fail due to your unexpected value after apply error.

I would expect that plancheck assertion to fail on PostApplyPreRefresh, as I believe you stated that your error was occurring after the initial apply, during the second apply? So maybe something like this:

ConfigPlanChecks: resource.ConfigPlanChecks{
    PostApplyPreRefresh: []plancheck.PlanCheck{
        plancheck.ExpectUnknownValue("provider_foo.test", tfjsonpath.New("bar")),
    },
},

I did observe that changing bar to id in the above test throws the error:

Step 1/1 error: Pre-apply plan check(s) failed:
path not found: specified key id not found in map

This looks like a bug in the error message of that plan check when the attribute is nil, I actually can recreate that with this new test where I’m forcing the Terraform core “inconsistent value” error for our MCVE: https://github.com/austinvalle/terraform-provider-sandbox/blob/dc12c4ec36a2437b65fea9caf6165dd9a3925fc8/internal/provider/thing_resource_test.go

--- FAIL: Test (0.22s)
    /Users/austin.valle/code/terraform-provider-sandbox/internal/provider/thing_resource_test.go:17: Step 1/1 error: Pre-apply plan check(s) failed:
        path not found: specified key bar not found in map
FAIL
FAIL	github.com/austinvalle/terraform-provider-sandbox/internal/provider	0.506s

Further inspection shows that the attribute does exist, but it’s nil image

I will write up an issue for terraform-plugin-testing to have that error message adjusted. It should be something like:

--- FAIL: Test (0.22s)
    /Users/austin.valle/code/terraform-provider-sandbox/internal/provider/thing_resource_test.go:17: Step 1/1 error: Pre-apply plan check(s) failed:
        specified key bar is nil, expected unknown
FAIL
FAIL	github.com/austinvalle/terraform-provider-sandbox/internal/provider	0.506s

Context

Since I’m going to reference the Terraform Resource lifecycle and the related RPCs in this response, here are helpful references that describe them:

Answering Questions

I thought a Create is followed by a subsequent Read to confirm creation, and I thought some previous comment implied that as well. In this discussion it seems I am mistaken though?

  • The Read function is only called during the ReadResource RPC, which occurs after validation and before the plan.
  • It’s entirely possible that you may have read comments referencing an old SDKv2 pattern, where after creating a resource, you would return the read function (i.e. your create function would call your read function before returning).
    • The plugin framework won’t call your Read function outside of the ReadResource RPC, so if you need to confirm creation, you can handle that in the Create.

Since this whole issue is a false positive error returned from resp.Diagnostics.Append(resp.State.Set(ctx, &data)…) then is there a workaround to ignore that completely (such as not appending to the diagnostics although that could have negative side-effects in other situations I would imagine)? According to the error logs the values in the state are being set to the correct and accurate values, and the issue is that for some unknown reason it was reset to null at some point without any intention from the code, and this triggers an error. If I could circumvent the error return due to the reset to null then that would be a workaround good enough for me.

  • I’m not convinced that the error is a false positive; I still believe there is something incorrectly setting your object to null during the plan, when it should be automatically marked as unknown (if there are no defaults, schema plan modifiers, or resource plan modifiers changing the value)
    • In the face of no plan modification or defaults, then I’d expect it to be a bug that can be recreated
  • To answer your question directly, this error is being raised on the Terraform core side as a part of it’s data consistency checks, and there is no way for a provider to influence that assertion.