terraform-plugin-framework
terraform-plugin-framework copied to clipboard
nil TypeMap permadiff
Module version
0.4.2
Relevant provider source code
"permissions": {
Type: types.MapType{ElemType: types.BoolType},
Optional: true,
},
Terraform Configuration Files
resource "sample_profile" "test" {
name = "test"
description = "test update"
# permissions = {}
}
Expected Behavior
After applying the initial config, I expect a subsequent plan to be noop.
Actual Behavior
Terraform seems to think the current state has an empty set (but not nil) which causes a diff with the plan (nil).
- permissions = {} -> null
Steps to Reproduce
References
I attempted to massage the plan to match
type fixEmptyMap struct {
emptyDescriptions
}
func (fixEmptyMap) Modify(_ context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) {
if req.AttributeState == nil {
return
}
state := req.AttributeState.(types.Map)
plan := req.AttributePlan.(types.Map)
if len(state.Elems) == 0 && !state.Null && plan.Null {
plan.Null = false
plan.Elems = make(map[string]attr.Value, 0)
resp.AttributePlan = plan
}
}
But got the following error on plan
╷
│ Error: Provider produced invalid plan
│
│ Provider "registry.terraform.io/hashicorp/salesforce" planned an invalid value for salesforce_profile.test.permissions: planned value cty.MapValEmpty(cty.Bool) for a non-computed attribute.
│
│ This is a bug in the provider, which should be reported in the provider's own issue tracker.
╵
Would you be able to show how the attribute is being set into state (if it is) during Create and/or Read?
The permissions go through a bit of an expansion from the API response but the logic is the following:
// expand permissions
permissions := make(map[string]attr.Value, len(includePermissions))
for _, k := range includePermissions {
v, ok := p[k]
trimmedKey := strings.TrimPrefix(k, "Permissions")
if ok {
permissions[trimmedKey] = types.Bool{Value: v.(bool)}
} else {
// set to unknown, maybe we should panic?
permissions[trimmedKey] = types.Bool{Unknown: true}
}
}
if len(permissions) > 0 {
data.Permissions = types.Map{ElemType: types.BoolType, Elems: permissions}
}
I am certain len(includePermissions) == 0 therefore, data.Permissions should just be the zero value of that type. I can confirm
State setting code is very simple within Read
d := pMap.ToStateData(data.PermissionKeys("Permissions")...)
// copy the ID back over
d.Id = data.Id
resp.Diagnostics = resp.State.Set(ctx, &d)
Confirmed, Permissions is whatever the zero value is
data := profileResourceData{
Name: p["Name"].(string),
UserLicenseId: p["UserLicenseId"].(string),
// Permissions is not touched
}
This may be similar to #201. Does explicitly setting the value Null: true help? e.g. something like:
if len(permissions) > 0 {
data.Permissions = types.Map{ElemType: types.BoolType, Elems: permissions}
} else {
data.Permissions = types.Map{ElemType: types.BoolType, Null: true}
}
Since types.Map uses Null bool under the hood, its zero-value would be false. This may however cause a different issue where if the configuration is permissions = {} that may now permadiff.
It might be possible to first manually passthrough the Config value (e.g. retrieved via GetAttribute) into Permissions then overwrite it after that length check.
It may also be possible to use Permissions map[string]bool or something similar in the data type to workaround this.
Hey again @appilon 👋 Did you find a resolution for this situation or does either of the above help?
@bflad I will prioritize revisiting this this week.
I encountered exactly the same problem today.
The backend always provides a empty Map when no entries are set, no nil pointer. Now i have two options, either return the empty map, or when no entries are set set to Null:true. Both will always conflict with one of the scenarios in the terraform config.
// assuming someone generates a empty map
Label={}
// or not set at all
Label=null
Technically i dont care, both will always be empty for me as it is not computed. Not sure how to handle this, maybe something like a custom plan may help like "NullAreEmpty"
We're discussing a potential path forward here over in #447 with potentially modifying the zero-values of the types value types to be null, so I'm going to close this issue out so focus efforts and discussion in one place.
@Project0 if you didn't try it already, I think you may be able to do something like you propose by setting the map attribute as Computed: true and introducing the mentioned attribute plan modifier that converts a null configuration value to an empty map plan value.
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.