diff --git a/stores/ini/store.go b/stores/ini/store.go index 9378fc62de..1a3a63f54d 100644 --- a/stores/ini/store.go +++ b/stores/ini/store.go @@ -26,7 +26,12 @@ func (store *Store) Name() string { } func (store Store) encodeTree(branches sops.TreeBranches) ([]byte, error) { - iniFile := ini.Empty(ini.LoadOptions{AllowNonUniqueSections: true}) + iniFile := ini.Empty(ini.LoadOptions{ + AllowNonUniqueSections: true, + AllowShadows: true, + // Keep identical repeated values too; they are separate INI entries. + AllowDuplicateShadowValues: true, + }) iniFile.DeleteSection(ini.DefaultSection) for _, branch := range branches { for _, item := range branch { @@ -85,7 +90,11 @@ func (store Store) iniFromTreeBranches(branches sops.TreeBranches) ([]byte, erro } func (store Store) treeBranchesFromIni(in []byte) (sops.TreeBranches, error) { - iniFile, err := ini.LoadSources(ini.LoadOptions{AllowNonUniqueSections: true}, in) + iniFile, err := ini.LoadSources(ini.LoadOptions{ + AllowNonUniqueSections: true, + AllowShadows: true, + AllowDuplicateShadowValues: true, + }, in) if err != nil { return nil, err } @@ -116,8 +125,10 @@ func (store Store) treeItemFromSection(section *ini.Section) (sops.TreeItem, err } for _, key := range section.Keys() { - item := sops.TreeItem{Key: key.Name(), Value: key.Value()} - items = append(items, item) + for _, value := range key.ValueWithShadows() { + item := sops.TreeItem{Key: key.Name(), Value: value} + items = append(items, item) + } if key.Comment != "" { items = append(items, sops.TreeItem{ Key: sops.Comment{ diff --git a/stores/ini/store_test.go b/stores/ini/store_test.go index b1aff6cf9a..bbc0c3e1e2 100644 --- a/stores/ini/store_test.go +++ b/stores/ini/store_test.go @@ -1,6 +1,7 @@ package ini import ( + "strings" "testing" "github.com/getsops/sops/v3" @@ -71,6 +72,48 @@ file="payroll.dat" assert.Equal(t, expected, branch) } +func TestRepeatedKeysRoundTrip(t *testing.T) { + input := []byte("[oslo_messaging_notifications]\ndriver = messagingv2\ndriver = log\ndriver = messagingv2\n") + store := Store{} + + branches, err := store.treeBranchesFromIni(input) + assert.NoError(t, err) + + section := branches[0][1].Value.(sops.TreeBranch) + assert.Equal(t, sops.TreeBranch{ + {Key: "driver", Value: "messagingv2"}, + {Key: "driver", Value: "log"}, + {Key: "driver", Value: "messagingv2"}, + }, section) + + encoded, err := store.iniFromTreeBranches(branches) + assert.NoError(t, err) + assert.Equal(t, input, encoded) + assert.Equal(t, 3, strings.Count(string(encoded), "driver =")) + + decoded, err := store.treeBranchesFromIni(encoded) + assert.NoError(t, err) + assert.Equal(t, branches, decoded) +} + +func TestRepeatedKeyCommentIsAddedOnce(t *testing.T) { + input := []byte("[sec]\ndriver = a\n; note mid\ndriver = b\n") + store := Store{} + + branches, err := store.treeBranchesFromIni(input) + assert.NoError(t, err) + + section := branches[0][1].Value.(sops.TreeBranch) + assert.Equal(t, sops.TreeBranch{ + {Key: "driver", Value: "a"}, + {Key: "driver", Value: "b"}, + {Key: sops.Comment{Value: "note mid"}, Value: nil}, + }, section) + encoded, err := store.iniFromTreeBranches(branches) + assert.NoError(t, err) + assert.Equal(t, 1, strings.Count(string(encoded), "note mid")) +} + func TestEncodeSimpleIni(t *testing.T) { branches := sops.TreeBranches{ sops.TreeBranch{