Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/v1.15/BUG FIXES-20251105-131122.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: BUG FIXES
body: 'backend: Fixed a bug where backends began to ignore empty string values set in the backend configuration.'
time: 2025-11-05T13:11:22.098037Z
custom:
Issue: "37846"
51 changes: 51 additions & 0 deletions internal/backend/backendbase/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,54 @@ func TestBase_nullCrash(t *testing.T) {
}
})
}

func TestBase_emptyStringsUsed(t *testing.T) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've realised this overlaps somewhat with TestSDKLikeApplyEnvDefaults, but maybe it's useful to have this as a more explicit regression test?

// This test ensures that empty strings in config are used and aren't ignored
// in favor of SDKLikeDefaults

b := Base{
Schema: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"foo": {
Type: cty.String,
Required: true,
},
},
},
SDKLikeDefaults: SDKLikeDefaults{
"foo": {
Fallback: "fallback",
EnvVars: []string{"FOO"},
},
},
}

t.Run("empty string from config is used despite fallback default value and environment variable value set", func(t *testing.T) {
// There is a fallback value supplied by the environment
envValue := "value from ENV"
t.Setenv("FOO", envValue)

// We pass an explicit empty string as the value of foo
val, gotDiags := b.PrepareConfig(cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal(""),
}))
if gotDiags.HasErrors() {
t.Fatalf("unexpected error diagnostics\n%s", gotDiags.Err())
}
attrs := val.AsValueMap()
if v, ok := attrs["foo"]; ok {
if v.IsNull() {
t.Fatal("value shouldn't be null")
}
if v.AsString() != "" {
// If the empty string from config is ignored, the value here
// will be either thing defined in SDKLikeDefaults above:
// 1) the "fallback" string.
// 2) the value of FOO environment variable.
t.Fatalf("value should be an empty string, but got: %q", v.AsString())
}
} else {
t.Fatal("cannot find attribute foo")
}
})
}
5 changes: 5 additions & 0 deletions internal/backend/backendbase/sdklike.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ func (d SDKLikeDefaults) ApplyTo(base cty.Value) (cty.Value, error) {
retAttrs[attrName] = givenVal
continue
}
if !givenVal.IsNull() && (givenVal.Type() == cty.String) && (givenVal.AsString() == "") {
// Don't use defaults if a string attribute is explicitly set to an empty string in the configuration.
retAttrs[attrName] = givenVal
continue
}

// The legacy SDK shims convert all values into strings (for flatmap)
// and then do their work in terms of that, so we'll follow suit here.
Expand Down
4 changes: 2 additions & 2 deletions internal/backend/backendbase/sdklike_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,9 @@ func TestSDKLikeApplyEnvDefaults(t *testing.T) {
"string_set_fallback": cty.StringVal("set in config"),
"string_set_env": cty.StringVal("set in config"),
"string_fallback_null": cty.StringVal("boop from fallback"),
"string_fallback_empty": cty.StringVal("boop from fallback"),
"string_fallback_empty": cty.StringVal(""), // config value is used
"string_env_null": cty.StringVal("beep from environment"),
"string_env_empty": cty.StringVal("beep from environment"),
"string_env_empty": cty.StringVal(""), // config value is used
"string_env_unsetfirst": cty.StringVal("beep from environment"),
"string_env_unsetsecond": cty.StringVal("beep from environment"),
"string_nothing_null": cty.NullVal(cty.String),
Expand Down