-
Notifications
You must be signed in to change notification settings - Fork 92
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
Consider MatchElementStateForUnknown() Plan Modifiers #717
Labels
enhancement
New feature or request
Comments
bflad
added a commit
that referenced
this issue
Apr 6, 2023
…ing implementation error when under a list or set Reference: #709 Reference: #717 This change will adjust the `UseStateForUnknown` plan modifiers to return an implementation error if defined on an attribute below a list or set. In any cases where the list or set elements were rearranged or removed, the plan modifier result could cause invalid data being proposed in the plan or Terraform data consistency errors. These situations require additional value identifying information from the provider developer to ensure that the prior state for an element is correctly realigned, however the `UseStateForUnknown` plan modifier takes no parameters for such information and adding those parameters would represent a breaking change to the function signature. Future framework proposals will investigate whether a second plan modifier can be introduced to cover this situation. The new plan modification unit tests reproduced the issue prior to the new implementation error: ``` --- FAIL: TestAttributeModifyPlan (0.00s) --- FAIL: TestAttributeModifyPlan/attribute-set-nested-nested-usestateforunknown-elements-rearranged (0.00s) attribute_plan_modification_test.go:1627: Unexpected response (-wanted, +got): fwserver.ModifyAttributePlanResponse{ - AttributePlan: s`[{"nested_computed":"statevalue2","nested_required":"testvalue2"},{"nested_computed":"statevalue1","nested_required":"testvalue1"}]`, + AttributePlan: s`[{"nested_computed":"statevalue1","nested_required":"testvalue2"},{"nested_computed":"statevalue2","nested_required":"testvalue1"}]`, Diagnostics: nil, RequiresReplace: s"[]", Private: nil, } --- FAIL: TestAttributeModifyPlan/attribute-set-nested-nested-usestateforunknown-elements-removed (0.00s) attribute_plan_modification_test.go:1627: Unexpected response (-wanted, +got): fwserver.ModifyAttributePlanResponse{ - AttributePlan: s`[{"nested_computed":"statevalue2","nested_required":"testvalue2"}]`, + AttributePlan: s`[{"nested_computed":"statevalue1","nested_required":"testvalue2"}]`, Diagnostics: nil, RequiresReplace: s"[]", Private: nil, } --- FAIL: TestAttributeModifyPlan/attribute-list-nested-nested-usestateforunknown-elements-removed (0.00s) attribute_plan_modification_test.go:1627: Unexpected response (-wanted, +got): fwserver.ModifyAttributePlanResponse{ - AttributePlan: s`[{"nested_computed":"statevalue2","nested_required":"testvalue2"}]`, + AttributePlan: s`[{"nested_computed":"statevalue1","nested_required":"testvalue2"}]`, Diagnostics: nil, RequiresReplace: s"[]", Private: nil, } --- FAIL: TestAttributeModifyPlan/attribute-list-nested-nested-usestateforunknown-elements-rearranged (0.00s) attribute_plan_modification_test.go:1627: Unexpected response (-wanted, +got): fwserver.ModifyAttributePlanResponse{ - AttributePlan: s`[{"nested_computed":"statevalue2","nested_required":"testvalue2"},{"nested_computed":"statevalue1","nested_required":"testvalue1"}]`, + AttributePlan: s`[{"nested_computed":"statevalue1","nested_required":"testvalue2"},{"nested_computed":"statevalue2","nested_required":"testvalue1"}]`, Diagnostics: nil, RequiresReplace: s"[]", Private: nil, } --- FAIL: TestBlockModifyPlan (0.00s) --- FAIL: TestBlockModifyPlan/block-list-nested-usestateforunknown-elements-removed (0.00s) block_plan_modification_test.go:2858: Unexpected response (+wanted, -got): fwserver.ModifyAttributePlanResponse{ - AttributePlan: s`[{"nested_computed":"statevalue2","nested_required":"testvalue2"}]`, + AttributePlan: s`[{"nested_computed":"statevalue1","nested_required":"testvalue2"}]`, Diagnostics: nil, RequiresReplace: s"[]", Private: nil, } --- FAIL: TestBlockModifyPlan/block-set-nested-usestateforunknown-elements-removed (0.00s) block_plan_modification_test.go:2858: Unexpected response (+wanted, -got): fwserver.ModifyAttributePlanResponse{ - AttributePlan: s`[{"nested_computed":"statevalue2","nested_required":"testvalue2"}]`, + AttributePlan: s`[{"nested_computed":"statevalue1","nested_required":"testvalue2"}]`, Diagnostics: nil, RequiresReplace: s"[]", Private: nil, } --- FAIL: TestBlockModifyPlan/block-list-nested-usestateforunknown-elements-rearranged (0.00s) block_plan_modification_test.go:2858: Unexpected response (+wanted, -got): fwserver.ModifyAttributePlanResponse{ - AttributePlan: s`[{"nested_computed":"statevalue2","nested_required":"testvalue2"},{"nested_computed":"statevalue1","nested_required":"testvalue1"}]`, + AttributePlan: s`[{"nested_computed":"statevalue1","nested_required":"testvalue2"},{"nested_computed":"statevalue2","nested_required":"testvalue1"}]`, Diagnostics: nil, RequiresReplace: s"[]", Private: nil, } --- FAIL: TestBlockModifyPlan/block-set-nested-usestateforunknown-elements-rearranged (0.00s) block_plan_modification_test.go:2858: Unexpected response (+wanted, -got): fwserver.ModifyAttributePlanResponse{ - AttributePlan: s`[{"nested_computed":"statevalue2","nested_required":"testvalue2"},{"nested_computed":"statevalue1","nested_required":"testvalue1"}]`, + AttributePlan: s`[{"nested_computed":"statevalue1","nested_required":"testvalue2"},{"nested_computed":"statevalue2","nested_required":"testvalue1"}]`, Diagnostics: nil, RequiresReplace: s"[]", Private: nil, } ```
bflad
added a commit
that referenced
this issue
Apr 6, 2023
…ing implementation error when under a list or set (#711) Reference: #709 Reference: #717 This change will adjust the `UseStateForUnknown` plan modifiers to return an implementation error if defined on an attribute below a list or set. In any cases where the list or set elements were rearranged or removed, the plan modifier result could cause invalid data being proposed in the plan or Terraform data consistency errors. These situations require additional value identifying information from the provider developer to ensure that the prior state for an element is correctly realigned, however the `UseStateForUnknown` plan modifier takes no parameters for such information and adding those parameters would represent a breaking change to the function signature. Future framework proposals will investigate whether a second plan modifier can be introduced to cover this situation. The new plan modification unit tests reproduced the issue prior to the new implementation error: ``` --- FAIL: TestAttributeModifyPlan (0.00s) --- FAIL: TestAttributeModifyPlan/attribute-set-nested-nested-usestateforunknown-elements-rearranged (0.00s) attribute_plan_modification_test.go:1627: Unexpected response (-wanted, +got): fwserver.ModifyAttributePlanResponse{ - AttributePlan: s`[{"nested_computed":"statevalue2","nested_required":"testvalue2"},{"nested_computed":"statevalue1","nested_required":"testvalue1"}]`, + AttributePlan: s`[{"nested_computed":"statevalue1","nested_required":"testvalue2"},{"nested_computed":"statevalue2","nested_required":"testvalue1"}]`, Diagnostics: nil, RequiresReplace: s"[]", Private: nil, } --- FAIL: TestAttributeModifyPlan/attribute-set-nested-nested-usestateforunknown-elements-removed (0.00s) attribute_plan_modification_test.go:1627: Unexpected response (-wanted, +got): fwserver.ModifyAttributePlanResponse{ - AttributePlan: s`[{"nested_computed":"statevalue2","nested_required":"testvalue2"}]`, + AttributePlan: s`[{"nested_computed":"statevalue1","nested_required":"testvalue2"}]`, Diagnostics: nil, RequiresReplace: s"[]", Private: nil, } --- FAIL: TestAttributeModifyPlan/attribute-list-nested-nested-usestateforunknown-elements-removed (0.00s) attribute_plan_modification_test.go:1627: Unexpected response (-wanted, +got): fwserver.ModifyAttributePlanResponse{ - AttributePlan: s`[{"nested_computed":"statevalue2","nested_required":"testvalue2"}]`, + AttributePlan: s`[{"nested_computed":"statevalue1","nested_required":"testvalue2"}]`, Diagnostics: nil, RequiresReplace: s"[]", Private: nil, } --- FAIL: TestAttributeModifyPlan/attribute-list-nested-nested-usestateforunknown-elements-rearranged (0.00s) attribute_plan_modification_test.go:1627: Unexpected response (-wanted, +got): fwserver.ModifyAttributePlanResponse{ - AttributePlan: s`[{"nested_computed":"statevalue2","nested_required":"testvalue2"},{"nested_computed":"statevalue1","nested_required":"testvalue1"}]`, + AttributePlan: s`[{"nested_computed":"statevalue1","nested_required":"testvalue2"},{"nested_computed":"statevalue2","nested_required":"testvalue1"}]`, Diagnostics: nil, RequiresReplace: s"[]", Private: nil, } --- FAIL: TestBlockModifyPlan (0.00s) --- FAIL: TestBlockModifyPlan/block-list-nested-usestateforunknown-elements-removed (0.00s) block_plan_modification_test.go:2858: Unexpected response (+wanted, -got): fwserver.ModifyAttributePlanResponse{ - AttributePlan: s`[{"nested_computed":"statevalue2","nested_required":"testvalue2"}]`, + AttributePlan: s`[{"nested_computed":"statevalue1","nested_required":"testvalue2"}]`, Diagnostics: nil, RequiresReplace: s"[]", Private: nil, } --- FAIL: TestBlockModifyPlan/block-set-nested-usestateforunknown-elements-removed (0.00s) block_plan_modification_test.go:2858: Unexpected response (+wanted, -got): fwserver.ModifyAttributePlanResponse{ - AttributePlan: s`[{"nested_computed":"statevalue2","nested_required":"testvalue2"}]`, + AttributePlan: s`[{"nested_computed":"statevalue1","nested_required":"testvalue2"}]`, Diagnostics: nil, RequiresReplace: s"[]", Private: nil, } --- FAIL: TestBlockModifyPlan/block-list-nested-usestateforunknown-elements-rearranged (0.00s) block_plan_modification_test.go:2858: Unexpected response (+wanted, -got): fwserver.ModifyAttributePlanResponse{ - AttributePlan: s`[{"nested_computed":"statevalue2","nested_required":"testvalue2"},{"nested_computed":"statevalue1","nested_required":"testvalue1"}]`, + AttributePlan: s`[{"nested_computed":"statevalue1","nested_required":"testvalue2"},{"nested_computed":"statevalue2","nested_required":"testvalue1"}]`, Diagnostics: nil, RequiresReplace: s"[]", Private: nil, } --- FAIL: TestBlockModifyPlan/block-set-nested-usestateforunknown-elements-rearranged (0.00s) block_plan_modification_test.go:2858: Unexpected response (+wanted, -got): fwserver.ModifyAttributePlanResponse{ - AttributePlan: s`[{"nested_computed":"statevalue2","nested_required":"testvalue2"},{"nested_computed":"statevalue1","nested_required":"testvalue1"}]`, + AttributePlan: s`[{"nested_computed":"statevalue1","nested_required":"testvalue2"},{"nested_computed":"statevalue2","nested_required":"testvalue1"}]`, Diagnostics: nil, RequiresReplace: s"[]", Private: nil, } ``` Co-authored-by: Austin Valle <austinvalle@gmail.com>
bflad
added a commit
that referenced
this issue
Apr 7, 2023
Reference: #711 Reference: #717 This change implements a new plan modifier to cover cases where `UseStateForUnknown` will now raise an implementation error when under a list or set nested attribute or block. Provider developers can supply all configurable and identifying nested object attributes which can be used to align and preserve prior state data for this attribute. An example schema implementation: ```go schema.SetNestedAttribute{ NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "computed_attr": schema.StringAttribute{ Computed: true, PlanModifiers: []planmodifier.String{ // Preseve this computed value during updates. stringplanmodifier.MatchElementStateForUnknown( // Identify matching prior state value based on configurable_attr path.MatchRelative().AtParent().AtName("configurable_attr"), // ... potentially others ... ), }, }, "configurable_attr": schema.StringAttribute{ Required: true, }, }, }, Optional: true, } ```
This was referenced Apr 7, 2023
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Module version
Use-cases
Over in #709, it was discovered that the
UseStateForUnknown()
plan modifier (amongst other custom ones) can return unexpected values when a parent list or set has elements rearranged or removed. In particular:UseStateForUnknown()
)schema.ListNestedAttribute
,schema.ListNestedBlock
,schema.SetNestedAttribute
, orschema.SetNestedBlock
This is because the framework's list and set data structures are array index based and the planning implementation will align configuration, plan, and prior state values based on that index. The implementation itself was an design choice where other choices introduced their own potential tradeoffs, such as requiring provider developers to implement much more explicit logic into their schemas. It was particularly desirable to avoid the prior terraform-plugin-sdk model of set and plan difference handling, which consistently had bug reports which were difficult or not possible to fix without breaking changes. In any event, the framework's data handling for attribute plan modifiers cannot potentially be changed until a theoretical next major version, so that is not an option in this case.
This leaves the framework maintainers in an awkward position where
UseStateForUnknown()
can introduce unexpected plans or Terraform data consistency errors, which framework-native functionality should never do. It is possible to detect when the plan modifier is being called while under a list or set, which means that it can raise its own implementation error to tell developers to remove the plan modifier. This does mean however, that previously working cases such as unchanged lists/sets or ones where only new list/set elements were added will lose their ability to display a more known plan.Attempted Solutions
Provider developers implementing custom attribute plan modifiers with fairly complex data handling logic.
Proposal
Parent Plan Modifier
One possible proposal would be a new list/set attribute plan modifier that enables provider developers to input tracking attributes which the plan modifier logic would realign the prior state values of any unknown computed values. As a design sketch:
While requiring less provider developer implementation details especially in the case of many computed attributes and potentially being a slightly easier framework implementation, this approach does have a few immediate downsides:
Nested Plan Modifiers
Another possible proposal would be new typed attribute plan modifiers, such as in the
resource/schema/stringplanmodifer
package, where the provider developer can optionally realign the prior state based on identifying attributes. As a design sketch:This means that each underlying attribute can opt into the behavior as appropriate (action where its implemented and explicit choice of effect). There is slightly less discoverability concerns since the implementation would be next to
UseStateForUnknown()
.The plan modifier logic will need to implement some rigorous validation where it raises errors if:
References
The text was updated successfully, but these errors were encountered: