The problem is that Plan
function (github.com/hashicorp/terraform-plugin-framework@v0.14.0/internal/fromproto6/plan.go) gets empty values for all fields in nested_data2
that are not specified in the configuration. The provider’s Read function does return these fields values.
So resp.PlannedState.Raw.Equal(req.PriorState.Raw)
in PlanResourceChange
RPC indeed returns false and all the diffs are the fields in the nested_data2
structure (that should be filled up with data from Read
function). It looks like proto6 MsgPack struct contains nil instead of the needed data.
That sounds like a bug somewhere, but I’m having a hard time reproducing the behavior you are seeing. I created a resource with the following GetSchema and Create methods, which just reflects Terraform’s plan data into the created resource state:
func (r *ExampleResource) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
return tfsdk.Schema{
// This description is used by the documentation generator and the language server.
MarkdownDescription: "Example resource",
Attributes: map[string]tfsdk.Attribute{
"list_nested_attribute": {
Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{
"nested_list_nested_attribute": {
Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{
"double_nested_bool_attribute": {
Optional: true,
Type: types.BoolType,
},
"double_nested_string_attribute": {
Optional: true,
Type: types.StringType,
},
}),
Optional: true,
},
}),
MarkdownDescription: "List nested attribute",
Optional: true,
},
"id": {
Computed: true,
MarkdownDescription: "Example identifier",
PlanModifiers: tfsdk.AttributePlanModifiers{
resource.UseStateForUnknown(),
},
Type: types.StringType,
},
},
}, nil
}
func (r *ExampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data *ExampleResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// If applicable, this is a great opportunity to initialize any necessary
// provider client data and make a call using it.
// httpResp, err := d.client.Do(httpReq)
// if err != nil {
// resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create example, got error: %s", err))
// return
// }
// For the purposes of this example code, hardcoding a response value to
// save into the Terraform state.
data.Id = types.StringValue("example-id")
// Write logs using the tflog package
// Documentation: https://terraform.io/plugin/log
tflog.Trace(ctx, "created a resource")
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
Then applied an example configuration:
resource "doublenested_example" "example" {
list_nested_attribute = [
{
nested_list_nested_attribute = [
{
double_nested_bool_attribute = true
}
]
},
]
}
Terraform showed a correct plan:
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# doublenested_example.example will be created
+ resource "doublenested_example" "example" {
+ id = (known after apply)
+ list_nested_attribute = [
+ {
+ nested_list_nested_attribute = [
+ {
+ double_nested_bool_attribute = true
},
]
},
]
}
Plan: 1 to add, 0 to change, 0 to destroy.
And the applied Terraform state looks correct (double_nested_string_attribute
is null
):
{
"version": 4,
"terraform_version": "1.3.3",
"serial": 2,
"lineage": "e16c9270-a21a-5e9b-b23e-688d9f69727d",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "doublenested_example",
"name": "example",
"provider": "provider[\"registry.terraform.io/hashicorp/doublenested\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "example-id",
"list_nested_attribute": [
{
"nested_list_nested_attribute": [
{
"double_nested_bool_attribute": true,
"double_nested_string_attribute": null
}
]
}
]
},
"sensitive_attributes": []
}
]
}
],
"check_results": []
}
Changing the double_nested_bool_attribute
value to false
to trigger a plan does what I’d expect in terms of the plan and applied state.
You’re mentioning your Read
method occasionally here, but it is difficult to discern if that might be influencing the errant behavior you are seeing without seeing the logic that saves that particular attribute.
In general, nested attributes should be preferred over blocks with protocol version 6, so it would be nice to figure out what is going on!