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

Plan Modifiers Not Executed Before Plan Computed-Only Unknown Marking #183

Closed
bflad opened this issue Sep 28, 2021 · 7 comments
Closed

Plan Modifiers Not Executed Before Plan Computed-Only Unknown Marking #183

bflad opened this issue Sep 28, 2021 · 7 comments
Labels
breaking-change This PR introduces a breaking change or the resolution of this issue may require a breaking change. bug Something isn't working
Milestone

Comments

@bflad
Copy link
Member

bflad commented Sep 28, 2021

Module version

Followup to #181 fix.

Relevant framework source code

// In tfsdk.TestServerPlanResourceChange:
		"attr_plan_modifiers_trigger_computed_unknown": {
			resource:     "test_attribute_plan_modifiers",
			resourceType: testServeResourceTypeAttributePlanModifiersType,
			priorState: tftypes.NewValue(testServeResourceTypeAttributePlanModifiersType, map[string]tftypes.Value{
				"computed_string_no_modifiers": tftypes.NewValue(tftypes.String, "statevalue"),
				"name":                         tftypes.NewValue(tftypes.String, "TESTATTRONE"),
				"size":                         tftypes.NewValue(tftypes.Number, 3),
				"scratch_disk": tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{
					"id":        tftypes.String,
					"interface": tftypes.String,
					"filesystem": tftypes.Object{AttributeTypes: map[string]tftypes.Type{
						"size":   tftypes.Number,
						"format": tftypes.String,
					}},
				}}, map[string]tftypes.Value{
					"id":        tftypes.NewValue(tftypes.String, "my-scr-disk"),
					"interface": tftypes.NewValue(tftypes.String, "scsi"),
					"filesystem": tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{
						"size":   tftypes.Number,
						"format": tftypes.String,
					}}, nil),
				}),
				"region": tftypes.NewValue(tftypes.String, "DEFAULTVALUE"),
			}),
			config: tftypes.NewValue(testServeResourceTypeAttributePlanModifiersType, map[string]tftypes.Value{
				"computed_string_no_modifiers": tftypes.NewValue(tftypes.String, nil),
				"name":                         tftypes.NewValue(tftypes.String, "TESTATTRONE"),
				"size":                         tftypes.NewValue(tftypes.Number, 3),
				"scratch_disk": tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{
					"id":        tftypes.String,
					"interface": tftypes.String,
					"filesystem": tftypes.Object{AttributeTypes: map[string]tftypes.Type{
						"size":   tftypes.Number,
						"format": tftypes.String,
					}},
				}}, map[string]tftypes.Value{
					"id":        tftypes.NewValue(tftypes.String, "my-scr-disk"),
					"interface": tftypes.NewValue(tftypes.String, "scsi"),
					"filesystem": tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{
						"size":   tftypes.Number,
						"format": tftypes.String,
					}}, nil),
				}),
				"region": tftypes.NewValue(tftypes.String, "DEFAULTVALUE"),
			}),
			proposedNewState: tftypes.NewValue(testServeResourceTypeAttributePlanModifiersType, map[string]tftypes.Value{
				"computed_string_no_modifiers": tftypes.NewValue(tftypes.String, "statevalue"),
				"name":                         tftypes.NewValue(tftypes.String, "TESTATTRONE"),
				"size":                         tftypes.NewValue(tftypes.Number, 3),
				"scratch_disk": tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{
					"id":        tftypes.String,
					"interface": tftypes.String,
					"filesystem": tftypes.Object{AttributeTypes: map[string]tftypes.Type{
						"size":   tftypes.Number,
						"format": tftypes.String,
					}},
				}}, map[string]tftypes.Value{
					"id":        tftypes.NewValue(tftypes.String, "my-scr-disk"),
					"interface": tftypes.NewValue(tftypes.String, "scsi"),
					"filesystem": tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{
						"size":   tftypes.Number,
						"format": tftypes.String,
					}}, nil),
				}),
				"region": tftypes.NewValue(tftypes.String, "DEFAULTVALUE"),
			}),
			expectedPlannedState: tftypes.NewValue(testServeResourceTypeAttributePlanModifiersType, map[string]tftypes.Value{
				"computed_string_no_modifiers": tftypes.NewValue(tftypes.String, tftypes.UnknownValue),
				"name":                         tftypes.NewValue(tftypes.String, "MODIFIED_TWO"),
				"size":                         tftypes.NewValue(tftypes.Number, 3),
				"scratch_disk": tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{
					"id":        tftypes.String,
					"interface": tftypes.String,
					"filesystem": tftypes.Object{AttributeTypes: map[string]tftypes.Type{
						"size":   tftypes.Number,
						"format": tftypes.String,
					}},
				}}, map[string]tftypes.Value{
					"id":        tftypes.NewValue(tftypes.String, "my-scr-disk"),
					"interface": tftypes.NewValue(tftypes.String, "scsi"),
					"filesystem": tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{
						"size":   tftypes.Number,
						"format": tftypes.String,
					}}, nil),
				}),
				"region": tftypes.NewValue(tftypes.String, "DEFAULTVALUE"),
			}),
			expectedRequiresReplace: []*tftypes.AttributePath{tftypes.NewAttributePath().WithAttributeName("scratch_disk").WithAttributeName("interface")},
		},

Expected Behavior

Having attribute or resource plan modifiers that cause a plan difference would cause the framework behavior of marking all Computed-only attributes as unknown in the plan. Attribute and resource plan modifiers would then re-run to allow providers to overwrite the unknowns or further perform plan actions.

e.g. Test passes as computed_string_no_modifiers should be unknown.

Actual Behavior

Attribute and resource plan modifiers are only run after the marking of all Computed-only attributes, meaning that any changes caused by the plan modifiers may not correctly surface the marking behavior.

e.g. Test fails

    --- FAIL: TestServerPlanResourceChange/attr_plan_modifiers_trigger_computed_unknown (0.01s)
        serve_test.go:3310: Unexpected diff in planned state (+wanted, -got):   tftypes.Value(
            -   {
            -           typ: tftypes.Object{
            -                   AttributeTypes: map[string]tftypes.Type{
            -                           "computed_string_no_modifiers": tftypes.primitive{name: "String"},
            -                           "name":                         tftypes.primitive{name: "String"},
            -                           "region":                       tftypes.primitive{name: "String"},
            -                           "scratch_disk": tftypes.Object{
            -                                   AttributeTypes: map[string]tftypes.Type{
            -                                           "filesystem": tftypes.Object{...},
            -                                           "id":         tftypes.primitive{...},
            -                                           "interface":  tftypes.primitive{...},
            -                                   },
            -                           },
            -                           "size": tftypes.primitive{name: "Number"},
            -                   },
            -           },
            -           value: map[string]tftypes.Value{
            -                   "computed_string_no_modifiers": {typ: tftypes.primitive{name: "String"}, value: string("statevalue")},
            -                   "name":                         {typ: tftypes.primitive{name: "String"}, value: string("MODIFIED_TWO")},
            -                   "region":                       {typ: tftypes.primitive{name: "String"}, value: string("DEFAULTVALUE")},
            -                   "scratch_disk": {
            -                           typ: tftypes.Object{
            -                                   AttributeTypes: map[string]tftypes.Type{
            -                                           "filesystem": tftypes.Object{...},
            -                                           "id":         tftypes.primitive{...},
            -                                           "interface":  tftypes.primitive{...},
            -                                   },
            -                           },
            -                           value: map[string]tftypes.Value{
            -                                   "filesystem": {typ: tftypes.Object{...}},
            -                                   "id":         {typ: tftypes.primitive{...}, value: string("my-scr-disk")},
            -                                   "interface":  {typ: tftypes.primitive{...}, value: string("scsi")},
            -                           },
            -                   },
            -                   "size": {
            -                           typ:   tftypes.primitive{name: "Number"},
            -                           value: &big.Float{prec: 53, form: 1, mant: big.nat{13835058055282163712}, exp: 2},
            -                   },
            -           },
            -   },
            +   {
            +           typ: tftypes.Object{
            +                   AttributeTypes: map[string]tftypes.Type{
            +                           "computed_string_no_modifiers": tftypes.primitive{name: "String"},
            +                           "name":                         tftypes.primitive{name: "String"},
            +                           "region":                       tftypes.primitive{name: "String"},
            +                           "scratch_disk": tftypes.Object{
            +                                   AttributeTypes: map[string]tftypes.Type{
            +                                           "filesystem": tftypes.Object{...},
            +                                           "id":         tftypes.primitive{...},
            +                                           "interface":  tftypes.primitive{...},
            +                                   },
            +                           },
            +                           "size": tftypes.primitive{name: "Number"},
            +                   },
            +           },
            +           value: map[string]tftypes.Value{
            +                   "computed_string_no_modifiers": {typ: tftypes.primitive{name: "String"}, value: tftypes.unknown(0)},
            +                   "name":                         {typ: tftypes.primitive{name: "String"}, value: string("MODIFIED_TWO")},
            +                   "region":                       {typ: tftypes.primitive{name: "String"}, value: string("DEFAULTVALUE")},
            +                   "scratch_disk": {
            +                           typ: tftypes.Object{
            +                                   AttributeTypes: map[string]tftypes.Type{
            +                                           "filesystem": tftypes.Object{...},
            +                                           "id":         tftypes.primitive{...},
            +                                           "interface":  tftypes.primitive{...},
            +                                   },
            +                           },
            +                           value: map[string]tftypes.Value{
            +                                   "filesystem": {typ: tftypes.Object{...}},
            +                                   "id":         {typ: tftypes.primitive{...}, value: string("my-scr-disk")},
            +                                   "interface":  {typ: tftypes.primitive{...}, value: string("scsi")},
            +                           },
            +                   },
            +                   "size": {
            +                           typ:   tftypes.primitive{name: "Number"},
            +                           value: &big.Float{prec: 64, form: 1, mant: big.nat{13835058055282163712}, exp: 2},
            +                   },
            +           },
            +   },
              )

Steps to Reproduce

  1. go test -count=1 ./... with the above test added

References

@bflad bflad added the bug Something isn't working label Sep 28, 2021
bflad added a commit that referenced this issue Sep 28, 2021
…knowns for Computed-only attributes in plans

Reference: #181

Previously, any `Computed` attributes would always trigger plan differences due to the unknown marking.

```
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:

  # hashicups_order.edu will be updated in-place
  ~ resource "hashicups_order" "edu" {
      ~ id           = "1" -> (known after apply)
      ~ items        = [
          ~ {
            ~ coffee   = {
              + description = (known after apply)
                id          = 3
              ~ image       = "/nomad.png" -> (known after apply)
              ~ name        = "Nomadicano" -> (known after apply)
              ~ price       = 150 -> (known after apply)
              ~ teaser      = "Drink one today and you will want to schedule another" -> (known after apply)
            }
              # (1 unchanged attribute hidden)

            ~ coffee   = {
              + description = (known after apply)
                id          = 1
              ~ image       = "/packer.png" -> (known after apply)
              ~ name        = "Packer Spiced Latte" -> (known after apply)
              ~ price       = 350 -> (known after apply)
              ~ teaser      = "Packed with goodness to spice up your images" -> (known after apply)
            }
              # (2 unchanged attributes hidden)
          },
        ]
      ~ last_updated = "Tuesday, 28-Sep-21 11:53:27 EDT" -> (known after apply)
    }

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

This change limits the unknown marking to only be performed when there is actually a change between the `ProposedNewState` and `State`.

Without a configuration change:

```
No changes. Your infrastructure matches the configuration.
```

With a configuration change:

```
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:

  # hashicups_order.edu will be updated in-place
  ~ resource "hashicups_order" "edu" {
      ~ id           = "1" -> (known after apply)
      ~ items        = [
          ~ {
            ~ coffee   = {
              + description = (known after apply)
                id          = 3
              ~ image       = "/nomad.png" -> (known after apply)
              ~ name        = "Nomadicano" -> (known after apply)
              ~ price       = 150 -> (known after apply)
              ~ teaser      = "Drink one today and you will want to schedule another" -> (known after apply)
            }
              # (1 unchanged attribute hidden)

            ~ coffee   = {
              + description = (known after apply)
              ~ id          = 1 -> 2
              ~ image       = "/packer.png" -> (known after apply)
              ~ name        = "Packer Spiced Latte" -> (known after apply)
              ~ price       = 350 -> (known after apply)
              ~ teaser      = "Packed with goodness to spice up your images" -> (known after apply)
            }
              # (2 unchanged attributes hidden)
          },
        ]
      ~ last_updated = "Tuesday, 28-Sep-21 11:53:27 EDT" -> (known after apply)
    }

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

As a followup, it may be necessary to invoke two passes of attribute and resource plan modifiers to ensure any changes captured by those modifiers triggers the unknown marking. As it stands now, a plan with no configuration changes, but changes due to plan modifiers will not correctly trigger the unknown marking. See also: #183
kmoe added a commit that referenced this issue Sep 29, 2021
…knowns for Computed-only attributes in plans (#184)

* tfsdk: Check ProposedNewState for State differences before marking unknowns for Computed-only attributes in plans

Reference: #181

Previously, any `Computed` attributes would always trigger plan differences due to the unknown marking.

```
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:

  # hashicups_order.edu will be updated in-place
  ~ resource "hashicups_order" "edu" {
      ~ id           = "1" -> (known after apply)
      ~ items        = [
          ~ {
            ~ coffee   = {
              + description = (known after apply)
                id          = 3
              ~ image       = "/nomad.png" -> (known after apply)
              ~ name        = "Nomadicano" -> (known after apply)
              ~ price       = 150 -> (known after apply)
              ~ teaser      = "Drink one today and you will want to schedule another" -> (known after apply)
            }
              # (1 unchanged attribute hidden)

            ~ coffee   = {
              + description = (known after apply)
                id          = 1
              ~ image       = "/packer.png" -> (known after apply)
              ~ name        = "Packer Spiced Latte" -> (known after apply)
              ~ price       = 350 -> (known after apply)
              ~ teaser      = "Packed with goodness to spice up your images" -> (known after apply)
            }
              # (2 unchanged attributes hidden)
          },
        ]
      ~ last_updated = "Tuesday, 28-Sep-21 11:53:27 EDT" -> (known after apply)
    }

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

This change limits the unknown marking to only be performed when there is actually a change between the `ProposedNewState` and `State`.

Without a configuration change:

```
No changes. Your infrastructure matches the configuration.
```

With a configuration change:

```
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:

  # hashicups_order.edu will be updated in-place
  ~ resource "hashicups_order" "edu" {
      ~ id           = "1" -> (known after apply)
      ~ items        = [
          ~ {
            ~ coffee   = {
              + description = (known after apply)
                id          = 3
              ~ image       = "/nomad.png" -> (known after apply)
              ~ name        = "Nomadicano" -> (known after apply)
              ~ price       = 150 -> (known after apply)
              ~ teaser      = "Drink one today and you will want to schedule another" -> (known after apply)
            }
              # (1 unchanged attribute hidden)

            ~ coffee   = {
              + description = (known after apply)
              ~ id          = 1 -> 2
              ~ image       = "/packer.png" -> (known after apply)
              ~ name        = "Packer Spiced Latte" -> (known after apply)
              ~ price       = 350 -> (known after apply)
              ~ teaser      = "Packed with goodness to spice up your images" -> (known after apply)
            }
              # (2 unchanged attributes hidden)
          },
        ]
      ~ last_updated = "Tuesday, 28-Sep-21 11:53:27 EDT" -> (known after apply)
    }

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

As a followup, it may be necessary to invoke two passes of attribute and resource plan modifiers to ensure any changes captured by those modifiers triggers the unknown marking. As it stands now, a plan with no configuration changes, but changes due to plan modifiers will not correctly trigger the unknown marking. See also: #183


Co-authored-by: Katy Moe <katy@katy.moe>
@bflad
Copy link
Member Author

bflad commented Nov 11, 2021

It'd probably be best to tackle this after #224 is reviewed and potentially merged.

@bflad bflad added this to the v1.0.0 milestone Mar 16, 2022
@PJB3005
Copy link

PJB3005 commented Aug 11, 2022

This actually extends to more than just computed-only attributes. And the consequences of the computed-only unknown marking system seem larger in scope than just this ordering bug. I don't know how feasible of a change this is, but if my understanding is correct I would like to maybe propose a better solution? I'm not too familiar with Terraform's internals, so apologies if I'm completely off on this analysis:

Unknown values are treated as "get the new value from the provider create/update". But we don't want computed attributes to always be in the unknown state, since that would force terraform apply to always trigger an update even if you don't change anything. So we manually mark all computed attributes as plan-unknown if we're definitely sure an update is required (becasue of another config change). If the config hasn't changed, there is effectively a UseStateForUnknown() modifier in effect on all computed attributes.

The marking-if-we-definitely-know-there's-a-change has three major problems, as far as I can tell:

  1. It relies on having a reliable way of knowing that the config changed. This check is problematic because it effectively relies on the terraform state to be a config mirror. This property is upheld for required/optional attributes, but for optional-computed attributes there is no such guarantee: If I remove a computed-optional from the config, Terraform has no way to tell whether the state value came from past config or from the provider, meaning it's impossible for this check to work reliably.
  2. Marking runs before plan modifiers, so if a plan modifier changes the plan, the marking will not happen. (This issue)
  3. Plan modifiers receive different request inputs depending on whether the marking has happened. This seems error prone to take into account when writing plan modifiers.

These issues combine to make a default value modifier like #285 somewhat problematic and error prone: while (2) would fix the issue of "if the default value changes, things need to be marked as unknown correctly", it doesn't fix (1) or (3).

The proposed fix in this issue would maybe fix (2), but it sounds very confusing to run plan modifiers multiple times with different inputs.

It sounds to me like what we actually need is for attributes to be set as "may change if an update is going to happen, but I won't cause an update myself". This would be the default plan for all computed attributes, always. If when the plan is built all attributes have either "unknown if update" or match state, no update has to happen on the resource. If a value is "true unknown" or different from the state, then an update will happen and all "unknown if update" properties get flipped to unknown as well.

Plan modifiers can mess with this to their heart's content: changing an attribute plan to a true unknown or a value will trigger an update, without need to re-run all modifiers after. All modifiers get consistent and predictable sets of inputs.

I don't know how feasible this would be to implement though, for example if there's any problems with protocol versioning or whatever.

@bflad
Copy link
Member Author

bflad commented Aug 26, 2022

Thank you for the comment there, @PJB3005 👍 I want to provide some additional context about Terraform and framework design, as a drive-by comment while my focus is currently elsewhere since there's a lot of details about this particular portion of Terraform, the protocol, and the framework.

Some of the Terraform details about planning behaviors and each of the data objects during that phase can be found in the following upstream documentation:

While the original design choices in this framework have prioritized the following two details that are pertinent here:

  • Creating clear abstractions that enable provider developers to interact fully with the protocol, including all request and response fields
  • Limiting framework-defined automatic behaviors, instead relying on Terraform's intentional design constraints where necessary

The automatic unknown marking was a pragmatic compromise because it was extremely likely that provider developers and practitioners would begin receiving "provider produced inconsistent result after apply" errors from Terraform as it has many constraints on how configuration, plan, and state data must operate across the resource lifecycle. (Aside: The previous terraform-plugin-sdk sets a special protocol flag to demote these errors to warning logs for various backwards compatibility reasons.) It would have otherwise introduced an unreasonable amount of provider developer burden to always generate a perfect plan according to those constraints. So far it appears this choice has been working out okay, due to lack of dissenting feedback in this issue tracker and HashiCorp Discuss. This edge with differing inputs to attribute plan modifiers seems to be the roughest, albeit on a much smaller scale than prior to the automatic behavior introduction.

There's some additional nuance to planning and the current framework implementation that also affects the choices here:

  • Resource planning can be called when there are no actual configuration changes. I offhandedly mention this in Computed attrs are not set to unknown after a AttributePlanModifier causes a non-empty plan #456 and I just double checked that Terraform will actually do this at least in the current source code and I'm fairly sure now its been done like this for quite some time.
  • Terraform seems to allow non-configurable attribute value changes during this type of planning, which may be a little unintuitive given the other planning constraints. I may followup with the Terraform team on this particular point.
  • While the framework-defined unknown marking behavior is protected from these "no update" plans currently, the schema-based and resource-level plan modifiers are not. I spent some time earlier today trying to look through our past plan design documentation and initial code implementations. I was not immediately able to find if this was a deliberate choice or just a consequence of the iterative addition of logic in this area.
  • Attributes in this framework are implemented as a Go map currently, which will have random iteration across keys and therefore it is unreliable to base attribute plan modifiers based on the changes of other attributes' plan modifiers. They also all only see the original plan, not any updated attribute values in the plan caused by other attributes (nested attributes/blocks do see the updated values, but refer also to Consider Flipping Attribute Plan Modifier Algorithm from Top Down to Bottom Up #225). I don't think we'd want to change the attribute plan modifier behaviors to make their execution within a schema somehow deterministic (e.g. lexicographical ordering), since any choice there would never satisfy all use cases.

If I'm understanding your whole comment above (you may find some of the Terraform repository documentation links help clarify some of your points there), I believe that this in particular:

It sounds to me like what we actually need is for attributes to be set as "may change if an update is going to happen, but I won't cause an update myself".

Along with getting some Terraform design clarification on whether "no update" plans should actually be able to update non-configurable values will help guide what the framework should do or allow in this situation.

The framework could possibly just ignore running schema-based or resource-level plan modification on those types of plans, leaving the refresh phase of the ReadResource RPC as the regular source of those types of state changes (Aside: Technically state upgrades can do what they wish to the state without practitioner knowledge, but that's outside this discussion.) This would alleviate any need to run plan modifiers twice, fixes situations like #456, and prevents plan modifiers from needing to worry about that planning case (e.g. just assume its an actual create/update/delete), but it may introduce its own set of unacceptable provider design limitations. It may be intentional that there is not a constraint in Terraform for this.

Another option instead of running the plan modifiers twice may be running the automatic unknown marking again after always running the other plan modifiers, keeping the same plan versus prior state check to still try to prevent spurious plans. I think this may be what you are alluding to, but maybe phrased differently for the actual implementation. This would help with preventing errors in situations like #456, but still means that plan modifiers need to account for "no update" plans in their logic.

I hope this helps expand the discussion here. Please reach out with any questions.

@bflad
Copy link
Member Author

bflad commented Aug 26, 2022

I was able to get some Terraform design clarification around "no configuration update" plans where Computed-only (read-only) attributes can plan updates in this manner. It was brought up that provider developers could use this mechanism to signal potential resource behavior changes to practitioners via an apply cycle, rather than via more practitioner-transparent mechanisms such as resource state upgrades or updates during refresh logic which would only show up in drift output, if enabled. While this type of provider logic may be less common, it might be ideal if the framework did not particularly make it more difficult or convoluted to implement, similar to other design choices we have made in the framework. In effect, I think this means we should shy away from automatically ignoring schema-based or resource-level plan modification on those types of plans.

What this does mean though is that we should likely consider prioritizing the documentation of various possible plan modifier implementation details that provider developers may want to check, such as detecting whether a resource is being created, a resource is being destroyed, or whether configuration updates are actually occurring.

The differing attribute plan value problem is still an interesting situation though. Plan modification logic can always reliably check the attribute configuration value for null, but an attribute plan value may either be null (if the marking didn't happen because the plan didn't have any configuration changes) or already marked as unknown (if the plan did have configuration changes). If this is a problem in practice, it would be helpful to see a particular use case that currently cannot be achieved, since the configuration value, current plan value (after prior plan modifiers on the attribute), and prior state values are available.

@bflad
Copy link
Member Author

bflad commented Aug 29, 2022

After some further investigation this morning, the framework cannot just automatically run the Computed attribute configuration null to unknown logic after plan modifiers, since it would undo some of the prior plan modification as its only looking at the configuration. The framework cannot also just automatically run something similar, but only for the plan, since there would be no way for provider developers to set/leave a Computed attribute as null in the plan which should be valid. I believe this leaves us back to the originally proposed option of running plan modifiers twice, if we are to do anything, ensuring that the automatic null to unknown handling is called between those and never at the end.

For provider developers wishing to not run plan modifier logic when there are no changes, they can return early with the following for now:

// Prevent further action when the plan has no changes
if req.Plan.Raw.Equal(req.State.Raw) {
  return
}

bflad added a commit that referenced this issue Aug 29, 2022
…ch has modified and unmodified Computed attributes

Reference: #183
Reference: #456
bflad added a commit that referenced this issue Aug 30, 2022
…ch has modified and unmodified Computed attributes (#463)

Reference: #183
Reference: #456
@bendbennett bendbennett added the breaking-change This PR introduces a breaking change or the resolution of this issue may require a breaking change. label Nov 15, 2022
@bflad bflad modified the milestones: v1.0.0, v2.0.0 Dec 13, 2022
@bflad bflad modified the milestones: v2.0.0, v1.2.0 Jan 13, 2023
@bflad
Copy link
Member Author

bflad commented Mar 20, 2023

This should be covered by #668, which introduces new resource default value handling before the unknown marking, which will release with terraform-plugin-framework version 1.2.0 🔜 . If there are further bug reports, feature requests, or documentation suggestions with resource default value handling, please raise a new issue. 👍

@bflad bflad closed this as completed Mar 20, 2023
@github-actions
Copy link

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 Apr 20, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
breaking-change This PR introduces a breaking change or the resolution of this issue may require a breaking change. bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants