- Notifications
You must be signed in to change notification settings - Fork101
Closed
Milestone
Description
Module version
% go list -m github.com/hashicorp/terraform-plugin-frameworkgithub.com/hashicorp/terraform-plugin-framework v1.13.0
Relevant provider source code
Schema with SetNestedAttribute with a custom typeCaseInsensitiveStringType
that implementsStringSemanticEquals
.
resp.Schema= schema.Schema{Attributes:map[string]schema.Attribute{"id": schema.StringAttribute{Computed:true,PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown(),},},"set_attribute": schema.SetNestedAttribute{Optional:true,NestedObject: schema.NestedAttributeObject{Attributes:map[string]schema.Attribute{"resource_id": schema.StringAttribute{Required:true,},"value": schema.StringAttribute{CustomType: internaltypes.CaseInsensitiveStringType{},Description:"(case-insensitive) custom type",Required:true,},},},},},}
Terraform Configuration Files
resource"...""..." {set_attribute=[ { resource_id="id1" value="Value" }, { resource_id="id2" value="Value2" } ]}
Expected Behavior
Sets should not care about the order of the elements contained within. This is how theSetValue.Equals
function behaves here:
terraform-plugin-framework/types/basetypes/set_value.go
Lines 282 to 286 in4260861
for_,elem:=ranges.elements { | |
if!other.contains(elem) { | |
returnfalse | |
} | |
} |
This property should hold for both Equals and Semantic Equals
Actual Behavior
Semantic Equals compares elements based on their index in the elements array and will result in plan changes if the order of elements and case ofvalue
change duringRead
on the Resource:
terraform-plugin-framework/internal/fwschemadata/value_semantic_equality_set.go
Lines 131 to 166 in4260861
// Loop through proposed elements by delegating to the recursive semantic | |
// equality logic. This ensures that recursion will catch a further | |
// underlying element type has its semantic equality logic checked, even if | |
// the current element type does not implement the interface. | |
foridx,proposedNewValueElement:=rangeproposedNewValueElements { | |
// Ensure new value always contains all of proposed new value | |
newValueElements[idx]=proposedNewValueElement | |
ifidx>=len(priorValueElements) { | |
continue | |
} | |
elementReq:=ValueSemanticEqualityRequest{ | |
Path:req.Path.AtSetValue(proposedNewValueElement), | |
PriorValue:priorValueElements[idx], | |
ProposedNewValue:proposedNewValueElement, | |
} | |
elementResp:=&ValueSemanticEqualityResponse{ | |
NewValue:elementReq.ProposedNewValue, | |
} | |
ValueSemanticEquality(ctx,elementReq,elementResp) | |
resp.Diagnostics.Append(elementResp.Diagnostics...) | |
ifresp.Diagnostics.HasError() { | |
return | |
} | |
ifelementResp.NewValue.Equal(elementReq.ProposedNewValue) { | |
continue | |
} | |
updatedElements=true | |
newValueElements[idx]=elementResp.NewValue | |
} |
This results in a plan output like:
~ resource "..." "..." { id = "..." ~ set_attribute = [ - { - resource_id = "id1" -> null - value = "value" -> null }, - { - resource_id = "id2" -> null - value = "value2" -> null }, + { + resource_id = "id1" + value = "Value" }, + { + resource_id = "id2" + value = "Value2" }, ] }