hashicorp / hashicorp/terraform-plugin-sdk

Inconsistency in setting state values

Open
#413 5 comments 0 reactions 1 assignee Claimed by @paultyng View on GitHub
bug subsystem/types terraform-plugin-framework
Dominant language
Go
Stars
485
Forks
244
Avg merge
19h 57m
Merged PRs (30d)
4

Description

### SDK version

```
{
"Path": "github.com/hashicorp/terraform-plugin-sdk",
"Version": "v1.10.0"
}
```

### Relevant provider source code

```go
package provider

import (
"testing"

"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/terraform"
)

func TestTerraform_still_have_bug_with_nested_structs_and_nil(t *testing.T) {
testResourceReadFn := func(d *schema.ResourceData, m interface{}) error {
d.SetId("0")
d.Set("primitive_str_set_to_nil", nil)
d.Set("primitive_str_c_set_to_nil", nil)
d.Set("obj_set", []map[string]interface{}{{
"primitive_str_set_to_nil": nil,
"primitive_str_c_set_to_nil": nil,
}})
return nil
}

testResource := func() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"primitive_str_unset": {
Type: schema.TypeString,
Optional: true,
},
"primitive_str_c_unset": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"primitive_str_set_to_nil": {
Type: schema.TypeString,
Optional: true,
},
"primitive_str_c_set_to_nil": {
Type: schema.TypeString,
Optional: true,
},
"obj_unset": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{},
},
"obj_c_unset": {
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: &schema.Resource{},
},
"obj_set_to_nil": {
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: &schema.Resource{},
},
"obj_c_set_to_nil": {
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: &schema.Resource{},
},
"obj_set": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"primitive_str_unset": {
Type: schema.TypeString,
Optional: true,
},
"primitive_str_c_unset": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"primitive_str_set_to_nil": {
Type: schema.TypeString,
Optional: true,
},
"primitive_str_c_set_to_nil": {
Type: schema.TypeString,
Optional: true,
},
},
},
},
},
Read: testResourceReadFn,
}
}

testProvider := func() *schema.Provider {
return &schema.Provider{
DataSourcesMap: map[string]*schema.Resource{
"test_resource": testResource(),
},
}
}

providers := map[string]terraform.ResourceProvider{
"test": testProvider(),
}

resource.Test(t, resource.TestCase{
Providers: providers,
Steps: []resource.TestStep{
{
Config: `
provider "test" {
}
data "test_resource" "t" {
}`,
Check: resource.ComposeAggregateTestCheckFunc(
// Expectations:
// Values which were not set should not be present in state
// Values which were set to nil should not be present in state
// Acual behaviour:

// The primitive values which were not set are missing in root namespace
resource.TestCheckNoResourceAttr("data.test_resource.t", "primitive_str_unset"),
resource.TestCheckNoResourceAttr("data.test_resource.t", "primitive_str_c_unset"),

// BUG:
// The primitive values which were set to nil are converted to empty value
resource.TestCheckResourceAttr("data.test_resource.t", "primitive_str_set_to_nil", ""),
resource.TestCheckResourceAttr("data.test_resource.t", "primitive_str_c_set_to_nil", ""),

// The objects which were not set are missing in root namespace
resource.TestCheckNoResourceAttr("data.test_resource.t", "obj_unset.#"),
resource.TestCheckNoResourceAttr("data.test_resource.t", "obj_c_unset.#"),

// The objects which were set to nil are missing in root namespace
resource.TestCheckNoResourceAttr("data.test_resource.t", "obj_set_to_nil.#"),
resource.TestCheckNoResourceAttr("data.test_resource.t", "obj_c_set_to_nil.#"),

// The object which is set is present
resource.TestCheckResourceAttr("data.test_resource.t", "obj_set.#", "1"),

// BUG:
// The primitive values which were not set are set to empty in nested structure
resource.TestCheckResourceAttr("data.test_resource.t", "obj_set.0.primitive_str_unset", ""),
resource.TestCheckResourceAttr("data.test_resource.t", "obj_set.0.primitive_str_c_unset", ""),

// BUG:
// The primitive values which were set to nil are converted to empty value
resource.TestCheckResourceAttr("data.test_resource.t", "obj_set.0.primitive_str_set_to_nil", ""),
resource.TestCheckResourceAttr("data.test_resource.t", "obj_set.0.primitive_str_c_set_to_nil", ""),
),
},
},
})
}

```

### Terraform Configuration Files

```hcl
...
```

### Debug Output

### Expected Behavior

TF should store values in state consistently, no matter where they appear.
"not-set" and "nil" values should not be present in terraform state
"not-set" and "nil" values should not be converted to empty values of corresponding type
| place | type | not set | set to nil |
|---|---|---|---|
| root | string | not present | not present |
| root | object | not present | not present |
| nested object | string | not present | not present |
### Actual Behavior

There is an inconsistency in how TF stores empty values in state see attached test case.
| place | type | not set | set to nil |
|---|---|---|---|
| root | string | not present | "" |
| root | object | not present | not present |
| nested object | string | "" | ""
### Steps to Reproduce

adopt and run attached test
### References

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.