PlanModifiers does not work in nested attribute
shiyuhang0 opened this issue · comments
Module version
github.com/hashicorp/terraform-plugin-framework v1.6.1
go 1.21
github.com/hashicorp/terraform-plugin-sdk/v2 v2.30.0
Relevant provider source code
schema
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Required: true,
},
"name": schema.StringAttribute{
Required: true,
},
"status": schema.SingleNestedAttribute{
Computed: true,
Attributes: map[string]schema.Attribute{
"version": schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"cluster_status": schema.StringAttribute{
Computed: true,
},
},
},
},
}
Terraform Configuration Files
Debug Output
Expected Behavior
version
attribute uses the state value when generating the plan as it applys the UseStateForUnknown
.
Actual Behavior
Change name and execute terraform plan
always occur version = "xxx" -> (known after apply)
.
version does not use the state value. Seems PlanModifiers does not work in the nested attribute.
Steps to Reproduce
References
Hi @shiyuhang0 👋
Sorry you ran into trouble here. The issue you describe has some overlap with Defaults don't work in nested attributes. Briefly, the UseStateForUnknown()
plan modifier declared on the version
attribute is only invoked when the value of the object that contains this attribute (i.e., status
) is known (i.e., not null or unknown). As no configuration is being supplied for status
, as it's computed, the value of this attribute will be unknown.
For instances in which there is a single attribute within the SingleNestedAttribute
, one option would be to declare an object-level UseStateForUnknown()
plan modifier. If the status
object only contained the version
attribute, I believe that this would have the desired effect of persisting the version value with the plan showing the previous value from state for version
.
"status": schema.SingleNestedAttribute{
Computed: true,
Attributes: map[string]schema.Attribute{
"version": schema.StringAttribute{
Computed: true,
},
},
PlanModifiers: []planmodifier.Object{
objectplanmodifier.UseStateForUnknown(),
},
},
However, the schema you have illustrated contains an additional attribute cluster_status
, which I'm assuming needs to be modifiable in the Update
method. If this is the case then you can't set an object-level UseStateForUnknown()
plan modifier on status, as Terraform will not allow you to mutate the value in the resource Update
method if it has already been determined in the plan through usage of the UseStateForUnknown()
plan modifier.
One option in cases where you have nested attributes that either require the previous value from state to be persisted during Update
operations (e.g., version
) or for their value to be mutated (e.g., cluster_status
) would be to remove the plan modifier at the attribute level and handle this logic within the Update
method. However, this will always result in the plan showing known after apply
for both version
and cluster_status
.
Another option might be to write a plan modifier for the SingleNestedAttribute
, status
, that uses the value from state for version
, but leaves the value for cluster_status
as unknown, and requiring setting in the Update()
method.
@bendbennett Thanks a lot!
I don't want to make both version and cluster_status show known after apply
. So I wrote a custom plan modifier. I think it is just a workaround, hope the framework will provide a more convenient way.
// clusterResourceStatusModifier implements the plan modifier.
type clusterResourceStatusModifier struct{}
func (m clusterResourceStatusModifier) Description(_ context.Context) string {
return "The plan modifier for status attribute. It will apply useStateForUnknownModifier to all the nested attributes except the cluster_status attribute."
}
// MarkdownDescription returns a markdown description of the plan modifier.
func (m clusterResourceStatusModifier) MarkdownDescription(_ context.Context) string {
return "The plan modifier for status attribute. It will apply useStateForUnknownModifier to all the nested attributes except the cluster_status attribute."
}
// PlanModifyObject implements the plan modification logic.
func (m clusterResourceStatusModifier) PlanModifyObject(ctx context.Context, req planmodifier.ObjectRequest, resp *planmodifier.ObjectResponse) {
// Do nothing if there is no state value.
if req.StateValue.IsNull() {
return
}
// Do nothing if there is a known planned value.
if !req.PlanValue.IsUnknown() {
return
}
// Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up.
if req.ConfigValue.IsUnknown() {
return
}
// Does not apply to cluster_status attribute
attributes := req.StateValue.Attributes()
attributes["cluster_status"] = types.StringUnknown()
newStateValue, diag := basetypes.NewObjectValue(req.StateValue.AttributeTypes(ctx), attributes)
resp.Diagnostics.Append(diag...)
resp.PlanValue = newStateValue
}
func clusterResourceStatus() planmodifier.Object {
return clusterResourceStatusModifier{}
}
I think this issue can be closed if there is no plan to optimize the plugin-framework.
Hi @shiyuhang0,
I believe that a bespoke plan modifier at the object level (i.e., on status
, the SingleNestedAttribute
is the best approach at this time.
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.