Plan differences in a map

I am attempting to migrate an existing provider from SDKv2 to Framework and the strictness of the Framework is causing some errors.

The schema contains a Map that the user can define, additionally the API can modify the collection due to internal rules.

schema.MapAttribute{
    Computed:    true,
    Optional:    true,
    Description: "A list of properties",
    ElementType: types.StringType,

The config can have n values but if it is missing specific values they will be added by, for example:

properties = {
   "Property1" = "value1"
}

The return value from the API, will return:

properties = {
   "Property1" = "value1"
   "Property2" = "value2"
}

When this is mapped back to the state in the provider I get an error, Error: Provider produced inconsistent result after apply, new element "Property2" has appeared.

If the user adds Property2 in to the config directly there are no errors, but this relies on the user knowing this or modifying due to the error. Additionally, it doesn’t feel like the Property2 default should be hard coded or forced via validation, tomorrow the defaults might change and the provider ends up chasing the API.

I also tried adding the attribute plan modifier for this specific case, but it still errors the same:

if req.PlanValue.IsUnknown() {
    properties := make(map[string]attr.Value)
    properties["Property2"] = types.StringValue("true")
    req.PlanValue = types.MapValueMust(types.StringType, properties)
    return
}
current := req.PlanValue.Elements()
if _, ok := current["Property2"]; !ok {
    current["Property2"] = types.StringValue("true")
    req.PlanValue = types.MapValueMust(types.StringType, current)
    return
}

Also, if the user adds Property2 to the config the plan should be a no-op, but in this case the state won’t actually represent the state of the API, but is forced to represent the state of the config only.

Is there a solution to allow the config to be different that the state where the API determines it to be .

Hi @ben.pearce :wave:t6:,

Sorry that you’re running into trouble here. You are correct that the framework requires stricter data consistency than the SDKv2. To give a bit of background, Terraform v0.12 and higher introduced stricter data consistency rules. Since the original terraform-plugin-sdk predated Terraform v0.12, Terraform continued to allow providers written in the sdk to bypass some of these rules by raising a warning instead of an error. In designing the terraform-plugin-framework, we’ve decided to no longer allow these rules to be bypassed. We understand that this could lead to a frustrating experience when migrating SDKv2 providers to framework.

For your specific case, unfortunately, this means that there’s not a simple way to allow the external API to modify the map attribute if it is set in configuration. Terraform does not allow the provider to change what is set in configuration. If what the API returns is inconcequential such as a different ordering in the map, then you could define a custom type with semantic equality to ignore the API value.

But since you want to return the API values to the user, you will need to split your original Map attribute into two separate Map attributes. You can define one optional Map attribute for the practitioner-configured properties to send to the API and one computed Map attribute for the properties returned by the API:

"properties": schema.MapAttribute{
	Description: "A list of properties",
	ElementType: types.StringType,
	Optional:    true,
},
   
"api-properties": schema.MapAttribute{
	Description: "A list of properties returned by the API."
	ElementType: types.StringType,
	Computed:    true,
},

I hope this helps.