How to detect Optional/Computed single nested attributes being removed from config

Summary

Removal of O/C single nested attributes don’t trigger a plan change. However when unrelated attributes are changed, a plan change is triggered and the nested attribute is passed as unknown in the plan.

Full example

For example, we start with this definition where read_only_specs is an Optional / Computed SingleNestedAttribute and all its attributes are also Optional / Computed:

resource "mongodbatlas_advanced_cluster" "test" {
  project_id   = "<PROJECT_ID>"
  name         = "test"
  cluster_type = "SHARDED"
  replication_specs = [
    {
      region_configs = [
        {
          provider_name = "AWS"
          region_name   = "US_EAST_1"
          priority      = 7
          electable_specs = {
            node_count    = 3
            instance_size = "M10"
          }
          read_only_specs = {
            node_count    = 1
            instance_size = "M10"
          }
        }
      ]
    }
  ]
}
  1. We do terraform apply to create the resource.
  2. We remove the read_only_specs attribute from the config.
  3. We do terraform plan (or apply) and no plan changes are shown. Debugging the provider with breakpoints, I can see in Read operation that read_only_specs state is known, in ModifyPlan operation both state and plan for read_only_specs are known, config is null as it has been deleted from the definition. This behavior is expected, as read_only_specs is O/C and it’s not anymore in the definition, it behaves as Optional, Read returns the same state, so the plan matches the state and no plan changes are shown.
  4. We change an unrelated attribute, e.g. region_name from US_EAST_1 to US_WEST_2.
  5. We do terraform plan (or apply) and it shows this plan change:
...
 ~ read_only_specs        = {
     ~ disk_iops       = 3000 -> (known after apply)
     ~ disk_size_gb    = 10 -> (known after apply)
     ~ ebs_volume_type = "STANDARD" -> (known after apply)
     ~ instance_size   = "M10" -> (known after apply)
     ~ node_count      = 1 -> (known after apply)
} -> (known after apply)
    ~ region_name            = "US_EAST_1" -> "US_WEST_2"
...

Read operation sets the response with a known value for read_only_specs, however ModifyPlan and Update receive a plan that is unknown, presumably because it behaves as Optional as it’s not in the config anymore.

IMPORTANT: Our Update API assumes that you want to delete / set to default when a block is not passed in the update request payload. We don’t pass unknown values in the payload. So in this case, we’ll end up deleting the read-only nodes when the read_only_specs block is removed from the config.

Questions

  1. Is there a way to detect a plan change when an O/C single nested attribute is deleted from the config?
  2. How can we differentiate the case where the attribute was never present in the config vs it was deleted, as the plan is unknown in both cases?
  3. What is the recommended behavior when an O/C single nested attribute is deleted from the config?
    A. Behave like if it was never there and reset to default values (e.g. remove read-only nodes).
    B. Keep the plan with the state info (e.g. keep read-only nodes), even if they are not in the config anymore, similar to SDKv2 behavior.
  4. Right now we’re using B as it’s safer for the customer. For example if they’re importing an existing resource with read-only nodes and forget read_only_specs in the config, it’s confusing to show “read_only_specs will be known after apply” and then delete the nodes. In order to do this we’re implementing ModifyPlan to copy attributes from read_only_specs state to plan when plan is unknown. Is this a good approach or there is a better way?
  5. In case A is recommended, we came with the idea to implement ModifyPlan to set read_only_specs to null (so plan is shown, and it’s correctly showing that it will be deleted / set to null) when it is in the state but not in the config but this sounds like complex logic. Also not sure if this goes against a TF best practice to show no plan changes if an O/C attribute is deleted. Is this a good approach or there is a better way?
  6. Right now we’re more concerned with O/C single nested attributes, but this also happens with regular attributes so same questions apply. E.g., if node_count is deleted from read_only_specs, keeping read_only_specs in the config, is the recommended behavior to keep the nodes (keep 1 node) or deleting them (0 nodes)?

Thanks.

1 Like

Hi @lantoli,

Having a completely optional+computed attribute like this will always pose certain problems, which is why it’s often more convenient to build a resource with separate inputs and outputs to differentiate what is provided by the user, and what is generated by the provider.

  1. Yes, but you need to do that manually within the provider based on your particular resource behavior. If the attribute, and all it’s nested attributes are optional+computed, and they are completely removed from the configuration, neither Terraform nor the framework can tell if what exists in the state came from the user or came from the provider, so there’s no way to correctly predict a change.

  2. As pointed out in 1, you cannot in a general sense, because the information is simply not there (that would require Terraform to store multiple configuration versions as well). What you can do is use some knowledge about how your resource behaves to determine if something was computed or user-provided and make a decision on what the planned value should be.

The rest is entirely up to what you need from your resource. Remember that once the config has been removed, these attributes are completely computed, so the provider must compute any changes to that attribute. Having the plan return unknown, null, the prior value, or another known default are all valid options depending on the resource.

For detecting a plan change when an O/C single nested attribute is deleted, Terraform doesn’t natively differentiate between “never present” and “removed” since both result in an unknown plan state. Your approach (B) of copying attributes from state to plan in ModifyPlan is a reasonable safeguard, ensuring unexpected deletions don’t occur.

If behavior A (resetting to defaults) is preferred, implementing ModifyPlan to explicitly set read_only_specs to null when absent in the config could work, but it adds complexity and might not align with Terraform’s best practices of minimizing implicit plan changes.

For regular attributes like node_count, consistency is key—either keep existing values or reset to defaults, but ensure users clearly see the impact in the plan. Would you prefer explicit deletions (A) or a safer state-preserving approach (B) long-term?