hashicorp / hashicorp/terraform-plugin-framework
Default values within sets cause crashes and/or incorrect behaviour
- Dominant language
- Go
- Stars
- 384
- Forks
- 107
- Avg merge
- 3m
- Merged PRs (30d)
- 1
Description
### Module version
```
v1.3.1
```
### Relevant provider source code
This is the entire source code of a single .go file provider that demonstrates the issues:
```go
package main
import (
"context"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
)
func main() {
err := providerserver.Serve(
context.Background(),
func() provider.Provider {
return new(bugProvider)
},
providerserver.ServeOpts{
Address: "bug/bug/bug",
},
)
if err != nil {
panic(err)
}
}
type bugProvider struct{}
func (p *bugProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
resp.TypeName = "bug"
}
func (p *bugProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
}
func (p *bugProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
}
func (p *bugProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
return nil
}
func (p *bugProvider) Resources(ctx context.Context) []func() resource.Resource {
return []func() resource.Resource{
func() resource.Resource {
return new(bugResource)
},
}
}
type bugResource struct{}
func (r bugResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_bug"
}
func (r bugResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"set": schema.SetNestedAttribute{
Required: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"fruit": schema.StringAttribute{
Optional: true,
Computed: true,
Default: stringdefault.StaticString("orange"),
},
"other": schema.StringAttribute{
Optional: true,
Computed: true,
Default: stringdefault.StaticString("other"),
},
},
},
},
},
}
}
func (r bugResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
}
func (r bugResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
resp.State.Raw = req.Plan.Raw
}
func (r bugResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
resp.State.Raw = req.Plan.Raw
}
func (r bugResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
}
```
### Terraform Configuration Files
```hcl
terraform {
required_providers {
bug = {
source = "bug/bug/bug"
}
}
}
resource "bug_bug" "this" {
set = [
{ fruit = "apple" },
{ fruit = "banana" },
{ fruit = "kumquat" },
]
}
```
### Debug Output
https://gist.github.com/maxb/f0b606530531a1f7e89c1ac122f141da
### Steps to Reproduce
1. Copy the included provider source code to a file, add a `go.mod` including the latest version of terraform-plugin-framework (no other dependencies needed), build the provider.
2. Set up `dev_overrides` so you can test the provider.
3. Copy the included Terraform configuration to a file
4. Run `terraform apply -auto-approve` (no initial state is needed)
5. Run `terraform apply -auto-approve` again ... **the provider panics/crashes**
Secondary related bug:
1. Remove any `terraform.tfstate` from the above reproduction
2. Reduce the number of items in the set in the Terraform configuration from 3 to 1
3. Repeatedly run `terraform apply -auto-approve` ... observe that provider repeatedly changes the value back and forth on each run, oscillating between the value actually written in the configuration, and the value set as a default in the code.
### Partial diagnosis
The terraform-plugin-framework appears to use a bafflingly complex algorithm to apply defaults, involving correlating the state and the configuration.
This is a victim of its own complexity when dealing with sets of objects, as the identity of an object within a set incorporates its own value ... a value that may itself have defaults. This means the identity of a set member in the config may omit unspecified attributes, whilst the identity of the same set member in the state will include unspecified attributes, now set to their default values.
Contributor guide
Assessment
This issue has not been assessed yet.