Terraform Schema for Nested Optional Attributes

I am currently writing a custom provider using the latest Terraform-Plugin-Framework

I am experiencing an issue where setting a top level nested attribute as optional is ignoring all computed fields that are nested.

Example:

"relationships": schema.SingleNestedAttribute{
						Optional: true,
						Attributes: map[string]schema.Attribute{
							"organization": schema.ObjectAttribute{
								Computed: true,
								PlanModifiers: []planmodifier.Object{
									objectplanmodifier.UseStateForUnknown(),
								},
								AttributeTypes: map[string]attr.Type{
									"data": types.ObjectType{
										AttrTypes: map[string]attr.Type{
											"id":   types.StringType,
											"type": types.StringType,
										},
									},
									"links": types.ObjectType{
										AttrTypes: map[string]attr.Type{
											"self": types.StringType,
										},
									},
								},
							},
							"workflowrule": schema.SingleNestedAttribute{
								Optional: true,
								PlanModifiers: []planmodifier.Object{
									objectplanmodifier.RequiresReplace(),
								},
								Attributes: map[string]schema.Attribute{
									"data": schema.ObjectAttribute{
										Optional: true,
										PlanModifiers: []planmodifier.Object{
											objectplanmodifier.RequiresReplace(),
										},
										AttributeTypes: map[string]attr.Type{
											"id":   types.StringType,
											"type": types.StringType,
										},
									},
									"links": schema.ObjectAttribute{
										Computed: true,
										PlanModifiers: []planmodifier.Object{
											objectplanmodifier.UseStateForUnknown(),
										},
										AttributeTypes: map[string]attr.Type{
											"self": types.StringType,
										},
									},
								},
							},
						},
					},

Failing Terraform:

resource "help_me" "help" {
  data = {
    attributes = {
      address = "test"
      name    = "test"
      type    = "test"
    }
  }
}

Working Terraform:

resource "help_me2" "help2" {
  data = {
    attributes = {
      address = "test"
      name    = "test"
      type    = "test"
    }
    relationships = {
      workflowrule = {
        data = {
          id   = "422"
          type = "workflowRules"
        }
      }
    }
  }
}

Error I get when running that first Terraform:

Error: Provider produced inconsistent result after apply
│
│ When applying changes to help_me.help, provider
│ "provider[\"super/secret"]" produced an unexpected new
│ value: .data.relationships: was null, but now
│ cty.ObjectVal(map[string]cty.Value{"organization":cty.ObjectVal(map[string]cty.Value{"data":cty.ObjectVal(map[string]cty.Value{"id":cty.StringVal("1"),
│ "type":cty.StringVal("organizations")}),
│ "links":cty.ObjectVal(map[string]cty.Value{"self":cty.StringVal("alsosupersecret")})}),
│ "workflowrule":cty.ObjectVal(map[string]cty.Value{"data":cty.ObjectVal(map[string]cty.Value{"id":cty.StringVal(""),
│ "type":cty.StringVal("")}),
│ "links":cty.ObjectVal(map[string]cty.Value{"self":cty.StringVal("")})})}).
│
│ This is a bug in the provider, which should be reported in the provider's
│ own issue tracker.

If I set Computed to true at the Relationships level both of these code blocks work. HOWEVER, I am in a situation where if someone changed Workflowrule on the provider side I need to be able to track that drift. So if workflowrule changes on the provider side and I run “terraform plan” or “terraform apply” it will show “no changes needed” for the first terraform block due to Computed not checking for changing values at run time.

Let me know if I’m approaching this wrong or what I could do to make this work.

Hi @stefenAMZ,

If the attribute value is going to be assigned by the provider, then it must be computed and must not exist in the configuration. If someone makes a change outside of Terraform, and that change does not contradict any of the configuration, then there is ostensibly no change to display (which is what you are seeing).

Because this attribute is checked during ReadResource, that is the only time when a change in computed values could possibly be detected. Terraform exposes these as “changes outside of Terraform”, but these are only shown in normal plans if they could have contributed to changes. A -refresh-only plan will always show all external changes, as will the full -json format of any normal plan. That is all informational however, and Terraform has no way to act on these changes if they don’t contradict the configuration.

The change as described is completely outside of Terraform’s control, so showing an error in the CLI is generally not helpful since the user can’t remediate the error via Terraform. You could probably detect and trigger this error during the Read function for the resource if it’s critical that all progress in Terraform be blocked until the external change is reverted, but that would be quite surprising to most Terraform users.