MM-14442: Merge function, to enable merging of configs (#10423)

* MM-14442: Merge function, to enable merging of configs

Merge will return a new struct of the same type as base and
patch, with patch merged into base. Specifically, patch's values will be preferred
except when patch's value is `nil`.

Restrictions/guarantees:
- base or patch will not be modified
- base and patch can be pointers or values
- base and patch must be the same type
- base and patch must have no unexported types (at the moment)
- if maps or slices are different, the entire map or slice will be
  replaced (at the moment)

* License header

* major rewrite addressing PR comments from Jesse

* MM-14442: merge function for config files

- simplified merge for slices and maps
- fixed many problems with nested pointers/maps/slices
- all references are cloned
- 54 new tests, simplified tests for specific problems

* MM-14442: fixing formatting, comments
Этот коммит содержится в:
Christopher Poile
2019-03-19 09:45:20 -04:00
коммит произвёл Jesse Hallam
родитель 372ef87f76
Коммит 498b988a6b
5 изменённых файлов: 1892 добавлений и 0 удалений

Просмотреть файл

@@ -4,6 +4,7 @@
package config
import (
"github.com/mattermost/mattermost-server/utils"
"io"
"sync"
@@ -140,3 +141,14 @@ func (cs *commonStore) validate(cfg *model.Config) error {
return nil
}
// mergeConfig merges two configs together. The receiver's values are overwritten with the patch's
// values except when the patch's values are nil.
func (cs *commonStore) mergeConfig(patch *model.Config) (*model.Config, error) {
ret, err := utils.Merge(cs.config, patch)
if err != nil {
return nil, err
}
retC := ret.(model.Config)
return &retC, nil
}

Просмотреть файл

@@ -1,6 +1,9 @@
package config_test
import (
"github.com/mattermost/mattermost-server/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
"github.com/mattermost/mattermost-server/model"
@@ -74,3 +77,61 @@ func prepareExpectedConfig(t *testing.T, expectedCfg *model.Config) *model.Confi
return expectedCfg
}
func TestMergeConfigs(t *testing.T) {
t.Run("merge two default configs with different salts/keys", func(t *testing.T) {
base, err := config.NewMemoryStore()
require.NoError(t, err)
patch, err := config.NewMemoryStore()
require.NoError(t, err)
merged, err := base.MergeConfig(patch.Get())
require.NoError(t, err)
assert.Equal(t, patch.Get(), merged)
})
t.Run("merge identical configs", func(t *testing.T) {
base, err := config.NewMemoryStore()
require.NoError(t, err)
patch := base.Get().Clone()
merged, err := base.MergeConfig(patch)
require.NoError(t, err)
assert.Equal(t, base.Get(), merged)
assert.Equal(t, patch, merged)
})
t.Run("merge configs with a different setting", func(t *testing.T) {
base, err := config.NewMemoryStore()
require.NoError(t, err)
patch := base.Get().Clone()
patch.ServiceSettings.SiteURL = newString("http://newhost.ca")
merged, err := base.MergeConfig(patch)
require.NoError(t, err)
assert.NotEqual(t, base.Get(), merged)
assert.Equal(t, patch, merged)
})
t.Run("merge default config with changes from a mostly nil patch", func(t *testing.T) {
base, err := config.NewMemoryStore()
require.NoError(t, err)
patch := &model.Config{}
patch.ServiceSettings.SiteURL = newString("http://newhost.ca")
patch.GoogleSettings.Enable = newBool(true)
expected := base.Get().Clone()
expected.ServiceSettings.SiteURL = newString("http://newhost.ca")
expected.GoogleSettings.Enable = newBool(true)
merged, err := base.MergeConfig(patch)
require.NoError(t, err)
assert.NotEqual(t, base.Get(), merged)
assert.NotEqual(t, patch, merged)
assert.Equal(t, expected, merged)
})
}
func newBool(b bool) *bool { return &b }
func newString(s string) *string { return &s }

Просмотреть файл

@@ -26,3 +26,8 @@ func InitializeConfigurationsTable(db *sqlx.DB) error {
func ResolveConfigFilePath(path string) (string, error) {
return resolveConfigFilePath(path)
}
// GetCommonStore exposes the internal commonStore to test only.
func (ms *memoryStore) MergeConfig(patch *model.Config) (*model.Config, error) {
return ms.mergeConfig(patch)
}