terraform-plugin-framework icon indicating copy to clipboard operation
terraform-plugin-framework copied to clipboard

Consider introducing helper methods for marking unconfigured `Computed` attributes as `<unknown>`

Open austinvalle opened this issue 11 months ago • 2 comments

Module version

v1.14.1

Background

Briefly, terraform-plugin-framework (Framework) differs from the previous terraform-plugin-sdk/v2 (SDKv2) in how it handles plan modification during PlanResourceChange.

  • SDKv2 has a CustomizeDiff function that providers can define to influence the plan, and relies soley on Terraform's proposed_new_state for it's plan, which often leads to data consistency problems as Terraform always suggests keeping prior state values for computed attributes, rather than marking them as <unknown> so they can be returned during apply. Since SDKv2 does not need to follow data consistency rules, Terraform allows this behavior in isolation, but often causes downstream problems with other providers when an inconsistent data value is returned during apply.
  • Framework is required by Terraform to follow these data consistency rules exactly, so the planning behavior is different from SDKv2. Framework always marks computed attributes that are unconfigured as <unknown> if there is a "planned update" proposed by Terraform (determined with prior_state and the same proposed_new_state mentioned earlier).
    • Typically, providers can use the UseStateForUnknown plan modifier to "revert" this marking of <unknown> and get a similar behavior to SDKv2's plan.

Problem

One of the issues with this plan approach in Framework is that we detect a planned update before a provider has a chance to run plan modification. The rough order of operations is:

  1. PlanResourceChange is called by Terraform
  2. Framework applies Default values for computed attributes to proposed_new_state from Terraform
  3. Framework checks if there is a planned updated (i.e. a diff between prior state and proposed_new_state w/ defaults
  4. Framework runs provider-defined attribute plan modifiers
  5. Framework runs provider-defined ModifyPlan
  6. Framework returns the final plan result to Terraform

If any of the provider-defined logic in step #4 revert the original plan to being a "no-op", all of the Computed attributes will still be marked as unknown. So there will still be a plan, but just of computed values that were marked as unknown because Framework assumed there would be an update. (For most use-cases, this unknown marking is essential, but here it is causing us problems)

If you have a use-case like this, where the plan modification during step #4 might end in a no-op, you can use the UseStateForUnknown plan modifier to achieve that no-op behavior, but there are still cases where you might want a computed attribute to being marked as unknown. In that case you could write a ModifyPlan function like this example: https://github.com/austinvalle/terraform-provider-sandbox/blob/9f43a630d4c3d43b72ea67414ff711548bdc19b9/internal/provider/thing_resource.go#L64-L79

This example has a subtle bug itself and is thus error-prone, since you should check if the value is configured before marking as unknown. You can write a generic function to implement this, but since all underlying Terraform data is written with terraform-plugin-go, it's not easy or familiar for provider developers to use, see: https://github.com/austinvalle/terraform-provider-sandbox/blob/9f43a630d4c3d43b72ea67414ff711548bdc19b9/internal/provider/thing_resource.go#L83-L107

Proposal

Write a helper function that can safely mark a computed attribute as unknown given the correct config/plan data. This function could be placed on the tfsdk.Plan object, or perhaps just a function in a package. The function should ensure that it doesn't mark attributes that cannot be unknown, either due to the config having a value, or the schema not marking the attribute as Computed.

It might also be helpful to accept multiple attribute paths? Or having a "mark all computed" as unknown. The implementation should be similar to internal/fwserver/MarkComputedNilsAsUnknown, with the added flexibility of targeting a single attribute. It's unclear if it'd be useful to recognize default values here, since the usage should likely always be in the ModifyPlan function.

austinvalle avatar Mar 21 '25 15:03 austinvalle

It's been a little bit since I opened this GH issue, but there isn't any active development on this as we're not sure exactly what the design of the helper should be and we don't have any active use-cases pushing this work.

One way to help here is If you're running into a problem that you believe is related to this issue, please share the use-case here and we can start to figure out what a useful helper might look like to help address this 👍🏻

austinvalle avatar Jun 16 '25 19:06 austinvalle

I'm running into a somewhat related issue where this could be useful, but from the opposite side.

The behavior I'm running into is that if there are no planned changes until step 4 produced by an attribute plan modifier, Framework doesn't go back and mark all of the computed attributes as unknown (which it passed on doing in step 3), and thus carries on using their previous state values as known values. This results in Provider produced inconsistent result errors if any of those computed attributes come back with different values from the API post-apply. Having to manually set these computed attributes to <unknown> in a resource plan modifier is painful, since we need to keep the plan modifier in sync with our resource schema as new computed attributes are added.

To more concretely describe our use case, we have a resource that has a pair of attributes:

  • An optional string attribute directory that contains a path to a directory of files on disk to be uploaded
  • A computed string attribute asset_manifest_sha256 which tracks any content changes to those files on disk. The value of this attribute is recomputed during planning using an attribute plan modifier, and is intended to trigger a reupload of the files if any changes have occurred.

If the only change to the resource is from content changes to the files on disk, Framework returns the following plan:

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # cloudflare_workers_script.workers_script will be updated in-place
  ~ resource "cloudflare_workers_script" "workers_script" {
      ~ assets              = {
          ~ asset_manifest_sha256 = "f28b2736f6de69bee4ba03d1a388fe54d06bcc54f485a81f580e5f8a38cc60f3" -> "dc7a7fe6de47611a93a5219ca484666b307e5214f68c44eda78df8ec210b5d57"
            # (3 unchanged attributes hidden)
        }
        id                  = "tf-assets-script"
        # (17 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

Notably, none of the computed attributes are unknown in the plan and will result in errors after apply, e.g. modified_on will always have changed.

Some way of getting Framework to mark all computed attributes as <unknown> after the attribute plan modifiers determined there should be a planned change would be very helpful.

1000hz avatar Sep 12 '25 17:09 1000hz