hashicorp / hashicorp/terraform-plugin-framework

Value of optional attributes with defaults is mishandled when multiple exist in the same block

Open
#1,256 1 comment 0 reactions 0 assignees View on GitHub
bug
Dominant language
Go
Stars
384
Forks
107
Avg merge
3m
Merged PRs (30d)
1

Description

### Module version

```
github.com/hashicorp/terraform-plugin-framework v1.17.0
```

### Relevant provider source code

```go
func (r IncusNetworkLBResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"network": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},

"listen_address": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"description": schema.StringAttribute{
Optional: true,
Computed: true,
Default: stringdefault.StaticString(""),
},

"project": schema.StringAttribute{
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
},

"remote": schema.StringAttribute{
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},

"config": schema.MapAttribute{
Optional: true,
Computed: true,
ElementType: types.StringType,
},
},

Blocks: map[string]schema.Block{
"backend": schema.SetNestedBlock{
Description: "Network load balancer backend",
NestedObject: schema.NestedBlockObject{
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Required: true,
Description: "LB backend name",
},

"description": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "LB backend description",
Default: stringdefault.StaticString(""),
},

"target_address": schema.StringAttribute{
Required: true,
Description: "LB backend target address",
},

"target_port": schema.StringAttribute{
Optional: true,
Description: "LB backend target port",
},
},
},
},
"port": schema.SetNestedBlock{
Description: "Network load balancer port",
NestedObject: schema.NestedBlockObject{
Attributes: map[string]schema.Attribute{
"description": schema.StringAttribute{
Optional: true,
Computed: true,
Default: stringdefault.StaticString(""),
Description: "Port description",
},

"protocol": schema.StringAttribute{
Optional: true,
Computed: true,
Default: stringdefault.StaticString("tcp"),
Description: "Port protocol",
Validators: []validator.String{
stringvalidator.OneOf("tcp", "udp"),
},
},

"listen_port": schema.StringAttribute{
Required: true,
Description: "Port to listen to",
},

"target_backend": schema.SetAttribute{
Required: true,
Description: "List of target LB backends",
ElementType: types.StringType,
Validators: []validator.Set{
setvalidator.SizeAtLeast(1),
},
},
},
},
},
},
}
}
```

### Terraform Configuration Files

```hcl
resource "incus_network_lb" "test_v4_debug" {
network = incus_network.ovn-test.name
listen_address = "10.10.10.50"
port {
protocol = "udp"
listen_port = "100"
target_backend = [
"toto_udp",
]
}
backend {
description = "myback"
name = "toto_udp"
target_address = "10.10.10.150"
}
}
```

### Debug Output

```
{"@caller":"/home/admin/go/pkg/mod/github.com/hashicorp/terraform-plugin-framework@v1.17.0/internal/fwschemadata/data_default.go:370","@level":"trace","@message":"setting attribute port[Value({\"description\":\"\",\"listen_port\":\"100\",\"protocol\":\"udp\",\"target_backend\":[\"toto_udp\"]})].protocol to default value: \"tcp\"","@mo
dule":"sdk.framework","@timestamp":"2026-01-17T17:03:25.869120+01:00","tf_provider_addr":"registry.opentofu.org/lxc/incus","tf_req_id":"e81a5e95-bcca-1cf8-d36f-457cfb8ed825","tf_resource_type":"incus_network_lb","tf_rpc":"PlanResourceChange"}
```

### Expected Behavior

The port protocol should remain UDP and not be set to the default value

### Actual Behavior

The port protocol is changed to TCP

### Steps to Reproduce

1. `tofu apply` a first time; the port will be correctly created in UDP.
2. `tofu plan` or `tofu apply` ; the port protocol will be changed to TCP

### Workaround

+ Setting a description (the only other optional attribute with a default in the same block) prevents the bug.

### References

### Issue location

Using Delve, I was able to locate the issue at `github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata.Data.ValueAtPath`
When we are not setting the `description` attribute in the port block, the framework will set it to its default value at [internal/fwschemadata/data_default.go:350](https://github.com/hashicorp/terraform-plugin-framework/blob/0412a32ada866ee741a7ad0d2a099b5c8d0a57a1/internal/fwschemadata/data_default.go#L350). This behavior is expected.
Then Terraform will look for the `protocol` attribute of the port in [internal/fwschemadata.Data.ValueAtPath()](https://github.com/hashicorp/terraform-plugin-framework/blob/0412a32ada866ee741a7ad0d2a099b5c8d0a57a1/internal/fwschemadata/data_value.go#L23)
[Line 64 `tfValue, err := d.TerraformValueAtTerraformPath(ctx, tftypesPath)`](https://github.com/hashicorp/terraform-plugin-framework/blob/0412a32ada866ee741a7ad0d2a099b5c8d0a57a1/internal/fwschemadata/data_value.go#L64) returns `nil` and `ErrInvalidStep`.
Since `tfValue` is `nil` it will cause the `protocol` attribute to be set to his default.

### Cause of the issue

This error is raised by https://github.com/hashicorp/terraform-plugin-go/blob/dfb1155c79e13595a8c70a4c487e1a0ba9a478f5/tftypes/value.go#L202
This is caused by `stepValue` (the state of the `port` block as initially defined, who got its value from the `d` arg in the function [`internal/fwschemadata.Data.ValueAtPath()`](https://github.com/hashicorp/terraform-plugin-framework/blob/0412a32ada866ee741a7ad0d2a099b5c8d0a57a1/internal/fwschemadata/data_value.go#L23)) differing from `sl` (who represents the state of the `port` as it is now, meaning after Terraform sets `description` to its default value. The `sl` value comes from `tftypesPath` https://github.com/hashicorp/terraform-plugin-framework/blob/0412a32ada866ee741a7ad0d2a099b5c8d0a57a1/internal/fwschemadata/data_value.go#L26)
This means that in `stepValue`, the `description` attribute of the port block is nil, while in `sl` it is an empty string `""`.
Therefore these 2 variables are not equal, and this interrupts the function `TerraformValueAtTerraformPath()` before it can retrieve the value of the protocol attribute.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.