-
Notifications
You must be signed in to change notification settings - Fork 232
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Inconsistency in setting state values #413
Comments
I tried this test against |
@paultyng yes, it passes according to actual behaviour here is example that should fail: 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"
)
// Test for https://github.com/hashicorp/terraform-plugin-sdk/issues/413
func TestTerraform_still_have_bug_413(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"),
// The primitive values which were set to nil are converted to empty value
resource.TestCheckNoResourceAttr("data.test_resource.t", "primitive_str_set_to_nil"),
resource.TestCheckNoResourceAttr("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"),
// The primitive values which were not set are set to empty in nested structure
resource.TestCheckNoResourceAttr("data.test_resource.t", "obj_set.0.primitive_str_unset"),
resource.TestCheckNoResourceAttr("data.test_resource.t", "obj_set.0.primitive_str_c_unset"),
// The primitive values which were set to nil are converted to empty value
resource.TestCheckNoResourceAttr("data.test_resource.t", "obj_set.0.primitive_str_set_to_nil"),
resource.TestCheckNoResourceAttr("data.test_resource.t", "obj_set.0.primitive_str_c_set_to_nil"),
),
},
},
})
} fragment w/ errors
|
Got it, yes this still happens in v2, just ran this test against it. I think there are a number of things at play here, but just some context, the Terraform type system and the wire protocol for providers does differentiate between null, an empty value (like A very long response to essentially say unfortunately this will probably remain the case using the existing functions, but that being said, we have plans for adding opt-in support for direct access to things like the new type system (see #261 ) and other things in v2, which should make this simpler going forward. |
Thank you for clarification. I understand that It will be hard to fix without breaking all the libraries and providers depending on SDK. I'm currently working with a quite inconvenient API where Do you have any advise or general recommendation on how to handle such cases? For now I see 3 options
All these options have some inconvenience and risk of errors (accordingly to prev. list):
May I also ask you to keep this bug open or document this behaviour for people who will have the same problem as I have? |
Yeah, I think historically I typically have reached for your second option there, using a sentinel value for "auto" or -1 or something but I've seen all those in various providers so I think it just depends on what you feel comfortable maintaining for your users and what is an acceptable divergence from the upstream API. Just to give you an idea of roadmap, a release candidate of v2 should be coming in the next few weeks (we have some providers actively testing on master currently). We are pushing to allow people to opt in to more of the 0.12 typing / new functionality in the new SDK this year, if we can expose it in a clean, opt-in manner, so hopefully this issue will go away sooner than later and we can have people not need to worry about this nuance for much longer. Re; keeping it open, its still visible in search when closed and we want to only keep actionable issues open to make our backlog management a bit more straightforward. Since the real fix for this is #261, which is exposing the newer typing, I'd rather people just focus on that one. I do appreciate you doing this investigation though and will point people to this issue if they are asking about similar behavior. |
SDK version
Relevant provider source code
Terraform Configuration Files
...
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
Actual Behavior
There is an inconsistency in how TF stores empty values in state see attached test case.
Steps to Reproduce
adopt and run attached test
References
The text was updated successfully, but these errors were encountered: