hashicorp / hashicorp/terraform-plugin-sdk
Inconsistency in setting state values
- 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
Assessment
This issue has not been assessed yet.