In a provider I have the situation that a top-level resource attribute is both Optional and Computed with the corresponding API behavior that ff the attribute is not set during create the upstream API will set it to some value, let’s say it’s "default" string. From then on the value is never changed upstream (re-computed), but the user is free to change it to some desired value.
Now, the behavior that I’m looking for is that if the user removes the attribute from the resource entirely a diff in the plan is generated and I’d be able to send a "null" value upstream so that it can reset it to the default. The problem with the SDK is that no plan is generated, because it’s also marked as Computed. I think that’s a limitation with the SDK.
What would be the correct way to handle such a situation with the new framework?
Hi @timofurrer,
If I understand your question correctly, you essentially want the behavior “default” value for an attribute but that value is coming from an API call rather than a static, hardcoded value.
In the Framework, a default value is implemented using plan modification, you can implement a resource plan modifier that will call your API to set the value of your attribute if the value of the attribute in the configuration is Unknown.
For example, in the Local provider, the file_permission attribute is both Optional and Computed and uses an attribute-level plan modifier to set a default string. Please note that this example uses an attribute plan modifier instead of a resource plan modifier, in your case you would use a resource plan modifier as the Framework currently only supports external API calls for plan modifiers at the resource-level, but conceptually they are very similar.
If I understand your question correctly, you essentially want the behavior “default” value for an attribute but that value is coming from an API call rather than a static, hardcoded value.
Not exactly, the API is not known to the provider it all - not statically nor is it able to get it via an API call. What I’m looking for is a way to get a diff in the plan if the attribute is removed from the config entirely - so that the provider can send a null value to the API which resets the attribute to the default only the API backend knows.
I just commented out an Optional + Computed attribute with no PlanModifier from the config for a provider based on very recent main branch, and was surprised to find it did exactly what you’re saying: The previously configured value persisted in the tfstate and no new plan was generated.
I expected that removing the value for this attribute would be enough to generate a new plan, but it wasn’t.
The subject of what goes on behind the scenes of Terraform plan with regards to providers is pretty nuanced. Without going too much into the weeds, the behavior for Terraform for Optional + Computed attributes is to copy the prior state if there is no configuration for it.
To handle your situation in the framework, you can create an attribute or resource plan modifier as detailed in my previous reply except you would set the plan value to unknown if the request state value is not null and the request configuration value is null. This should then give you a diff in the terraform plan.
Alternatively, you can try splitting your attribute into two attributes, an Optional attribute that users can configure and sends the value to the API and a Computed attribute that Terraform would set to unknown if there is no configuration and that would cause a diff in the plan.
Please let me know if you have any other questions or if you need me to clarify.
Hello, I’m reviving an old subject but I’m in a similar situation and haven’t found a solution. I tried implementing a resource plan modifier that sets the plan value to unknown:
func (u unknownSetPlanModify) PlanModifySet(ctx context.Context, req planmodifier.SetRequest, resp *planmodifier.SetResponse) {
if !req.StateValue.IsNull() && req.ConfigValue.IsNull() {
resp.PlanValue = types.SetUnknown(req.StateValue.ElementType(ctx))
}
}
But I don’t understand why that would work. If I delete the value from my configuration, my state still stores the old value, the plan set it to unknown and sends null to the API. Then in the next plan, my state has the correct default value returned by the API, but the plan is unknown again, since the state is not null and the config is null.
I haven’t found a solution to handle this situation with a single computed + optional attribute.
Maybe putting another way; you cannot detect when an attribute value has been removed from the configuration when it is optional&computed, because you have no prior version of the configuration to compare with. The logic you want would require Terraform to store multiple config versions as well, which it wasn’t designed to do.
You can only detect removal of a value from configuration if it is not computed, so there is universal solution when you have an optional&computed attribute. Splitting the inputs and output of the resource into separate attributes is one method to have more control over the data. Another is if you can determine the defaults of the attribute (you can make API calls during the plan) you can then update the data immediately based on that information, or leave it intact if it already matches.
I tried implementing it splitting them into input and output. It works well, but I cannot find a way to not create a diff during planning just after an Import.
Since I cannot access the config during the Import/Read phase, how could i detect that the optional attribute present in the config does match the computed attribute that is set during Import ? Is there a way to do that with an attribute/resource Plan Modification ?
I think optimally for the read operation you would detect whether the existing value is a default or not, and set the optional input accordingly. If the attribute (or input+output pair) is meant to be treated as optional+computed, and if you can’t detect a default value, the best you could do is treat it as being set and also assign the input during the read operation.
As far as plan modifiers go, if it’s a single attribute you have the option to say that a change in value is semantically equivalent and keep the prior state instead. If they are separate attributes you are comparing, then you only need to be concerned with the computed one, and it’s entirely up to you whether you mark it as unknown or not. I’m not sure what your implementation looks like, but you can’t just use a simple check like IsNull(), you need to answer “would this value change the resource?”.