Skip to content

Commit 4e06c3c

Browse files
authored
Empty user config values should not override original config value / default (#2993)
* User should be able to clear the original config value but not the default
1 parent bb55ac1 commit 4e06c3c

File tree

4 files changed

+20
-168
lines changed

4 files changed

+20
-168
lines changed

api/pkg/template/config.go

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,9 @@ func (e *Engine) resolveConfigItem(name string) (*resolvedConfigItem, error) {
195195
}
196196

197197
// Priority: user value > config value > config default
198+
// User value is only used if it differs from the original config value (user made a change) - reason: sc-129708
198199
var userVal *string
199-
if v, exists := e.configValues[name]; exists {
200+
if v, exists := e.configValues[name]; exists && v.Value != templatedValue {
200201
userVal = &v.Value
201202
}
202203

@@ -208,8 +209,9 @@ func (e *Engine) resolveConfigItem(name string) (*resolvedConfigItem, error) {
208209
effectiveValue = templatedDefault
209210
}
210211

212+
// User filename is only used if it differs from the original config filename (user made a change) - reason: sc-129708
211213
var userFilename *string
212-
if v, exists := e.configValues[name]; exists {
214+
if v, exists := e.configValues[name]; exists && v.Filename != templatedFilename {
213215
userFilename = &v.Filename
214216
}
215217

@@ -277,19 +279,11 @@ func (e *Engine) configValueChanged(itemName string) bool {
277279
return true
278280
}
279281

280-
return prevVal.Value != currentVal.Value
281-
}
282-
283-
func (e *Engine) getItemFilename(configItem *kotsv1beta1.ConfigItem) string {
284-
// Priority: user value
285-
if userVal, exists := e.configValues[configItem.Name]; exists {
286-
return userVal.Filename
282+
if prevVal.Filename != currentVal.Filename {
283+
return true
287284
}
288285

289-
// Do not use the config item's filename for KOTS parity
290-
291-
// If still empty, return empty string
292-
return ""
286+
return prevVal.Value != currentVal.Value
293287
}
294288

295289
func (e *Engine) findConfigItem(name string) *kotsv1beta1.ConfigItem {

api/pkg/template/config_test.go

Lines changed: 2 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -248,9 +248,9 @@ func TestEngine_templateConfigItems(t *testing.T) {
248248
{
249249
Name: "user_cleared",
250250
Type: "file",
251-
Value: multitype.FromString(""),
251+
Value: multitype.FromString(""), // Empty differs from config value = user cleared it
252252
Default: multitype.FromString("user_cleared_default"),
253-
Filename: "",
253+
Filename: "", // Empty differs from config filename = user cleared it
254254
},
255255
{
256256
Name: "no_user_value",
@@ -544,143 +544,3 @@ func TestEngine_ShouldInvalidateItem(t *testing.T) {
544544
engine.depsTree = map[string][]string{}
545545
assert.False(t, engine.shouldInvalidateItem("item1"), "should not invalidate item1 as it doesn't exist in either config values")
546546
}
547-
548-
func TestEngine_GetItemFilename(t *testing.T) {
549-
config := &kotsv1beta1.Config{
550-
Spec: kotsv1beta1.ConfigSpec{
551-
Groups: []kotsv1beta1.ConfigGroup{
552-
{
553-
Name: "test",
554-
Items: []kotsv1beta1.ConfigItem{
555-
{Name: "file_with_user_filename"},
556-
{Name: "file_without_user_filename"},
557-
{Name: "file_with_empty_user_filename"},
558-
{Name: "non_file_item"},
559-
},
560-
},
561-
},
562-
},
563-
}
564-
565-
engine := NewEngine(config)
566-
567-
// Test 1: User provides filename - should use user filename
568-
engine.configValues = types.AppConfigValues{
569-
"file_with_user_filename": {Filename: "user-provided.txt", Value: "content"},
570-
}
571-
filename := engine.getItemFilename(&kotsv1beta1.ConfigItem{Name: "file_with_user_filename"})
572-
assert.Equal(t, "user-provided.txt", filename, "should use user-provided filename")
573-
574-
// Test 2: User doesn't provide filename - should return empty string
575-
engine.configValues = types.AppConfigValues{
576-
"file_without_user_filename": {Value: "content"},
577-
}
578-
filename = engine.getItemFilename(&kotsv1beta1.ConfigItem{Name: "file_without_user_filename"})
579-
assert.Equal(t, "", filename, "should return empty string when user doesn't provide filename")
580-
581-
// Test 3: User provides empty filename - should return empty string
582-
engine.configValues = types.AppConfigValues{
583-
"file_with_empty_user_filename": {Filename: "", Value: "content"},
584-
}
585-
filename = engine.getItemFilename(&kotsv1beta1.ConfigItem{Name: "file_with_empty_user_filename"})
586-
assert.Equal(t, "", filename, "should return empty string when user provides empty filename")
587-
588-
// Test 4: Item not in config values - should return empty string
589-
filename = engine.getItemFilename(&kotsv1beta1.ConfigItem{Name: "non_file_item"})
590-
assert.Equal(t, "", filename, "should return empty string for item not in config values")
591-
592-
// Test 5: Empty config values - should return empty string
593-
engine.configValues = types.AppConfigValues{}
594-
filename = engine.getItemFilename(&kotsv1beta1.ConfigItem{Name: "file_with_user_filename"})
595-
assert.Equal(t, "", filename, "should return empty string when config values is empty")
596-
}
597-
598-
func TestEngine_FilenamePreservationInTemplateConfigItems(t *testing.T) {
599-
config := &kotsv1beta1.Config{
600-
Spec: kotsv1beta1.ConfigSpec{
601-
Groups: []kotsv1beta1.ConfigGroup{
602-
{
603-
Name: "filename_test",
604-
Items: []kotsv1beta1.ConfigItem{
605-
{
606-
Name: "preserved_file",
607-
Type: "file",
608-
Value: multitype.FromString(""),
609-
Default: multitype.FromString("default.txt"),
610-
},
611-
{
612-
Name: "overwritten_file",
613-
Type: "file",
614-
Value: multitype.FromString(""),
615-
Default: multitype.FromString("default.txt"),
616-
},
617-
{
618-
Name: "text_item",
619-
Type: "text",
620-
Value: multitype.FromString(""),
621-
Default: multitype.FromString("default"),
622-
},
623-
},
624-
},
625-
},
626-
},
627-
}
628-
629-
engine := NewEngine(config, WithMode(ModeConfig))
630-
engine.configValues = types.AppConfigValues{
631-
"preserved_file": {Filename: "preserved.txt", Value: "content1"},
632-
"overwritten_file": {Filename: "overwritten.txt", Value: "content2"},
633-
"text_item": {Value: "text content"},
634-
}
635-
636-
result, err := engine.templateConfigItems()
637-
require.NoError(t, err)
638-
assert.NotNil(t, result)
639-
640-
// Verify filename is preserved for file items
641-
preservedItem := result.Spec.Groups[0].Items[0]
642-
assert.Equal(t, "preserved.txt", preservedItem.Filename, "filename should be preserved for preserved_file")
643-
644-
overwrittenItem := result.Spec.Groups[0].Items[1]
645-
assert.Equal(t, "overwritten.txt", overwrittenItem.Filename, "filename should be preserved for overwritten_file")
646-
647-
// Verify text items don't have filename field set
648-
textItem := result.Spec.Groups[0].Items[2]
649-
assert.Equal(t, "", textItem.Filename, "text items should not have filename field set")
650-
}
651-
652-
func TestEngine_GetItemFilenameDirect(t *testing.T) {
653-
// Test the getItemFilename function directly
654-
config := &kotsv1beta1.Config{
655-
Spec: kotsv1beta1.ConfigSpec{
656-
Groups: []kotsv1beta1.ConfigGroup{
657-
{
658-
Name: "test",
659-
Items: []kotsv1beta1.ConfigItem{
660-
{Name: "test_file"},
661-
},
662-
},
663-
},
664-
},
665-
}
666-
667-
engine := NewEngine(config)
668-
669-
// Test with user filename
670-
engine.configValues = types.AppConfigValues{
671-
"test_file": {Filename: "test.txt", Value: "content"},
672-
}
673-
674-
configItem := &kotsv1beta1.ConfigItem{Name: "test_file"}
675-
filename := engine.getItemFilename(configItem)
676-
677-
t.Logf("User filename test: expected 'test.txt', got '%s'", filename)
678-
assert.Equal(t, "test.txt", filename, "should return user filename when provided")
679-
680-
// Test without user filename
681-
engine.configValues = types.AppConfigValues{}
682-
filename = engine.getItemFilename(configItem)
683-
684-
t.Logf("No user filename test: expected '', got '%s'", filename)
685-
assert.Equal(t, "", filename, "should return empty string when no user filename")
686-
}

api/pkg/template/execute_test.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,16 +174,16 @@ func TestEngine_ValuePriority(t *testing.T) {
174174
require.NoError(t, err)
175175
assert.Equal(t, "fallback_default", result)
176176

177-
// Test with empty user value (should use empty string, not fall back to config value)
177+
// Test with empty user value - user cleared the value (differs from config value)
178178
emptyConfigValues := types.AppConfigValues{
179-
"database_host": {Value: ""}, // Empty user value should be used as-is
179+
"database_host": {Value: ""}, // Empty differs from config value "db-internal.company.com" = user cleared it
180180
"database_port": {Value: "5433"},
181181
}
182182
err = engine.Parse("{{repl ConfigOption \"database_url\" }}")
183183
require.NoError(t, err)
184184
result, err = engine.Execute(emptyConfigValues, WithProxySpec(&ecv1beta1.ProxySpec{}))
185185
require.NoError(t, err)
186-
assert.Equal(t, "postgres://:5433/app", result) // Empty host should result in empty string, not config fallback
186+
assert.Equal(t, "postgres://:5433/app", result) // Empty host = user cleared it
187187
}
188188

189189
func TestEngine_ConfigOptionEquals(t *testing.T) {
@@ -412,6 +412,13 @@ func TestEngine_ConfigOptionFilename(t *testing.T) {
412412
require.NoError(t, err)
413413
assert.Equal(t, "user_file.txt", result)
414414

415+
// Test with user providing empty filename - should return empty string
416+
result, err = engine.Execute(types.AppConfigValues{
417+
"a_file": {Value: userContentEncoded, Filename: ""},
418+
}, WithProxySpec(&ecv1beta1.ProxySpec{}))
419+
require.NoError(t, err)
420+
assert.Equal(t, "", result)
421+
415422
// Test with an unknown item - an error is returned and empty string is returned
416423
err = engine.Parse("{{repl ConfigOptionFilename \"notfound\" }}")
417424
require.NoError(t, err)

api/types/app.go

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package types
22

33
import (
4-
"strings"
5-
64
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
75
)
86

@@ -38,16 +36,9 @@ func ConvertToAppConfigValues(kotsConfigValues *kotsv1beta1.ConfigValues) AppCon
3836

3937
configValues := make(AppConfigValues)
4038
for key, value := range kotsConfigValues.Spec.Values {
41-
// Temporary fix for https://app.shortcut.com/replicated/story/129708/template-execution-fails-when-empty-user-config-values-override-generated-defaults
42-
// TODO: Remove this block of code and add a unit test for this function once a fix has been implemented
43-
temporaryFixValue := value.Value
44-
if strings.HasSuffix(key, "_json") && temporaryFixValue == "" {
45-
temporaryFixValue = "{}"
46-
}
47-
4839
configValues[key] = AppConfigValue{
4940
Default: value.Default,
50-
Value: temporaryFixValue,
41+
Value: value.Value,
5142
Data: value.Data,
5243
ValuePlaintext: value.ValuePlaintext,
5344
DataPlaintext: value.DataPlaintext,

0 commit comments

Comments
 (0)