Using plan-modifiers (like UseStateForUnknown) on generated SingleNested attributes

Hey all. Trying to get help on this matter before opening an issue.

TLDR: Getting the following error when running terraform plan

Planning failed. Terraform encountered an error while generating this plan.

╷
│ Error: Attribute Missing
│ 
│   with usfu_car.audi,
│   on main.tf line 6, in resource "usfu_car" "audi":
│    6: resource "usfu_car" "audi" {
│ 
│ kind is missing from object
╵

Context/My Setup

I am working on a provider which has:

  1. A generated ‘provider code spec’ file (generated with tfplugingen-openapi generate)
  2. A generated resource schema (generated with tfplugingen-framework generate all)

My Setup

For simplicity, I created a reproduction of the very same issue, with a basic setup.

provider_code_spec.json:

{
  "version": "0.1",
  "provider": { "name": "usfu" },
  "resources": [
    {
      "name": "car",
      "schema": {
        "attributes": [
          {
            "name": "id",
            "string": { "computed_optional_required": "computed" }
          },
          {
            "name": "name",
            "string": { "computed_optional_required": "required" }
          },
          {
            "name": "color",
            "string": { "computed_optional_required": "computed_optional" }
          },
          {
            "name": "engine",
            "single_nested": {
              "computed_optional_required": "computed_optional",
              "attributes": [
                {
                  "name": "volume",
                  "int64": {
                    "computed_optional_required": "computed_optional"
                  }
                },
                {
                  "name": "kind",
                  "string": {
                    "computed_optional_required": "computed_optional"
                  }
                }
              ]
            }
          }
        ]
      }
    }
  ]
}

The generated schema (after running tfplugingen-framework generate all --input provider_code_spec.json --output internal/generated):

func CarResourceSchema(ctx context.Context) schema.Schema {
	return schema.Schema{
		Attributes: map[string]schema.Attribute{
			"color": schema.StringAttribute{
				Optional: true,
				Computed: true,
			},
			"engine": schema.SingleNestedAttribute{
				Attributes: map[string]schema.Attribute{
					"kind": schema.StringAttribute{
						Optional: true,
						Computed: true,
					},
					"volume": schema.Int64Attribute{
						Optional: true,
						Computed: true,
					},
				},
				CustomType: EngineType{
					ObjectType: types.ObjectType{
						AttrTypes: EngineValue{}.AttributeTypes(ctx),
					},
				},
				Optional: true,
				Computed: true,
			},
			"id": schema.StringAttribute{
				Computed: true,
			},
			"name": schema.StringAttribute{
				Required: true,
			},
		},
	}
}

What I was trying to achieve:

Modify the plan for the engine attribute (which is a SingleNested attribute), so that terraform will take the prior-state value, even if engine is null in the configuration.

Steps:

  1. Attaching a UseStateForUnknown plan-modifier to the engine attribute:
PlanModifiers: []planmodifier.Object{objectplanmodifier.UseStateForUnknown()},
  1. providing the following configuration:
resource "usfu_car" "audi" {
  name  = "A8"
  color = "white"
}
  1. Running terraform plan

Expected Result:

  • On the first apply (create):
    + engine = (known after apply)
  • On the second apply (resource already exists):
    “no changes. Your infrastructure matches the configuration”

Actual Result:

Error: Attribute Missing
// ...
kind is missing from object


Notes:

  1. This happens for all plan-modifiers (e.g RequiresReplace) and not specifically for UseStateForUnknown.
  2. using UseNonNullStateForUnknown doesn’t help (see previous note)
  3. The error is thrown at attribute_plan_modification.go, which calls the ValueFromObject method. For generated custom types, this method’s implementation is very strict.
  4. You can find the full reproduction code at terraform-provider-hashicups-defaultbug-repro/usfu-nested-repro/ISSUE.md at defaultbug-repro · nirkahana8/terraform-provider-hashicups-defaultbug-repro · GitHub