summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--utils/config.go93
-rw-r--r--utils/config_test.go98
2 files changed, 150 insertions, 41 deletions
diff --git a/utils/config.go b/utils/config.go
index 13295b362..b87f164ee 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -10,6 +10,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
+ "reflect"
"strconv"
"strings"
@@ -212,15 +213,8 @@ func (w *ConfigWatcher) Close() {
// ReadConfig reads and parses the given configuration.
func ReadConfig(r io.Reader, allowEnvironmentOverrides bool) (*model.Config, error) {
- v := viper.New()
+ v := newViper(allowEnvironmentOverrides)
- if allowEnvironmentOverrides {
- v.SetEnvPrefix("mm")
- v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
- v.AutomaticEnv()
- }
-
- v.SetConfigType("json")
if err := v.ReadConfig(r); err != nil {
return nil, err
}
@@ -236,6 +230,89 @@ func ReadConfig(r io.Reader, allowEnvironmentOverrides bool) (*model.Config, err
return &config, unmarshalErr
}
+func newViper(allowEnvironmentOverrides bool) *viper.Viper {
+ v := viper.New()
+
+ v.SetConfigType("json")
+
+ if allowEnvironmentOverrides {
+ v.SetEnvPrefix("mm")
+ v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
+ v.AutomaticEnv()
+ }
+
+ // Set zeroed defaults for all the config settings so that Viper knows what environment variables
+ // it needs to be looking for. The correct defaults will later be applied using Config.SetDefaults.
+ defaults := flattenStructToMap(structToMap(reflect.TypeOf(model.Config{})))
+
+ for key, value := range defaults {
+ v.SetDefault(key, value)
+ }
+
+ return v
+}
+
+// Converts a struct type into a nested map with keys matching the struct's fields and values
+// matching the zeroed value of the corresponding field.
+func structToMap(t reflect.Type) map[string]interface{} {
+ if t.Kind() != reflect.Struct {
+ // Should never hit this, but this will prevent a panic if that does happen somehow
+ return nil
+ }
+
+ out := make(map[string]interface{})
+
+ for i := 0; i < t.NumField(); i++ {
+ field := t.Field(i)
+
+ var value interface{}
+
+ switch field.Type.Kind() {
+ case reflect.Struct:
+ value = structToMap(field.Type)
+ case reflect.Ptr:
+ value = nil
+ default:
+ value = reflect.Zero(field.Type).Interface()
+ }
+
+ out[field.Name] = value
+ }
+
+ return out
+}
+
+// Flattens a nested map so that the result is a single map with keys corresponding to the
+// path through the original map. For example,
+// {
+// "a": {
+// "b": 1
+// },
+// "c": "sea"
+// }
+// would flatten to
+// {
+// "a.b": 1,
+// "c": "sea"
+// }
+func flattenStructToMap(in map[string]interface{}) map[string]interface{} {
+ out := make(map[string]interface{})
+
+ for key, value := range in {
+ if valueAsMap, ok := value.(map[string]interface{}); ok {
+ sub := flattenStructToMap(valueAsMap)
+
+ for subKey, subValue := range sub {
+ out[key+"."+subKey] = subValue
+ }
+ } else {
+ out[key] = value
+ }
+ }
+
+ return out
+}
+
// ReadConfigFile reads and parses the configuration at the given file path.
func ReadConfigFile(path string, allowEnvironmentOverrides bool) (*model.Config, error) {
f, err := os.Open(path)
diff --git a/utils/config_test.go b/utils/config_test.go
index 84e7291b0..a998bfbc6 100644
--- a/utils/config_test.go
+++ b/utils/config_test.go
@@ -50,48 +50,80 @@ func TestFindConfigFile(t *testing.T) {
}
func TestConfigFromEnviroVars(t *testing.T) {
- os.Setenv("MM_TEAMSETTINGS_SITENAME", "From Environment")
- os.Setenv("MM_TEAMSETTINGS_CUSTOMBRANDTEXT", "Custom Brand")
- os.Setenv("MM_SERVICESETTINGS_ENABLECOMMANDS", "false")
- os.Setenv("MM_SERVICESETTINGS_READTIMEOUT", "400")
-
TranslationsPreInit()
- cfg, cfgPath, err := LoadConfig("config.json")
- require.Nil(t, err)
- if cfg.TeamSettings.SiteName != "From Environment" {
- t.Fatal("Couldn't read config from environment var")
- }
+ config := `{
+ "ServiceSettings": {
+ "EnableCommands": true,
+ "ReadTimeout": 100
+ },
+ "TeamSettings": {
+ "SiteName": "Mattermost",
+ "CustomBrandText": ""
+ }
+ }`
- if *cfg.TeamSettings.CustomBrandText != "Custom Brand" {
- t.Fatal("Couldn't read config from environment var")
- }
+ t.Run("string settings", func(t *testing.T) {
+ os.Setenv("MM_TEAMSETTINGS_SITENAME", "From Environment")
+ os.Setenv("MM_TEAMSETTINGS_CUSTOMBRANDTEXT", "Custom Brand")
- if *cfg.ServiceSettings.EnableCommands {
- t.Fatal("Couldn't read config from environment var")
- }
+ cfg, err := ReadConfig(strings.NewReader(config), true)
+ require.Nil(t, err)
- if *cfg.ServiceSettings.ReadTimeout != 400 {
- t.Fatal("Couldn't read config from environment var")
- }
+ if cfg.TeamSettings.SiteName != "From Environment" {
+ t.Fatal("Couldn't read config from environment var")
+ }
- os.Unsetenv("MM_TEAMSETTINGS_SITENAME")
- os.Unsetenv("MM_TEAMSETTINGS_CUSTOMBRANDTEXT")
- os.Unsetenv("MM_SERVICESETTINGS_ENABLECOMMANDS")
- os.Unsetenv("MM_SERVICESETTINGS_READTIMEOUT")
+ if *cfg.TeamSettings.CustomBrandText != "Custom Brand" {
+ t.Fatal("Couldn't read config from environment var")
+ }
- cfg.TeamSettings.SiteName = "Mattermost"
- *cfg.ServiceSettings.SiteURL = ""
- *cfg.ServiceSettings.EnableCommands = true
- *cfg.ServiceSettings.ReadTimeout = 300
- SaveConfig(cfgPath, cfg)
+ os.Unsetenv("MM_TEAMSETTINGS_SITENAME")
+ os.Unsetenv("MM_TEAMSETTINGS_CUSTOMBRANDTEXT")
- cfg, _, err = LoadConfig("config.json")
- require.Nil(t, err)
+ cfg, err = ReadConfig(strings.NewReader(config), true)
+ require.Nil(t, err)
- if cfg.TeamSettings.SiteName != "Mattermost" {
- t.Fatal("should have been reset")
- }
+ if cfg.TeamSettings.SiteName != "Mattermost" {
+ t.Fatal("should have been reset")
+ }
+ })
+
+ t.Run("boolean setting", func(t *testing.T) {
+ os.Setenv("MM_SERVICESETTINGS_ENABLECOMMANDS", "false")
+ defer os.Unsetenv("MM_SERVICESETTINGS_ENABLECOMMANDS")
+
+ cfg, err := ReadConfig(strings.NewReader(config), true)
+ require.Nil(t, err)
+
+ if *cfg.ServiceSettings.EnableCommands {
+ t.Fatal("Couldn't read config from environment var")
+ }
+ })
+
+ t.Run("integer setting", func(t *testing.T) {
+ os.Setenv("MM_SERVICESETTINGS_READTIMEOUT", "400")
+ defer os.Unsetenv("MM_SERVICESETTINGS_READTIMEOUT")
+
+ cfg, err := ReadConfig(strings.NewReader(config), true)
+ require.Nil(t, err)
+
+ if *cfg.ServiceSettings.ReadTimeout != 400 {
+ t.Fatal("Couldn't read config from environment var")
+ }
+ })
+
+ t.Run("setting missing from config.json", func(t *testing.T) {
+ os.Setenv("MM_SERVICESETTINGS_SITEURL", "https://example.com")
+ defer os.Unsetenv("MM_SERVICESETTINGS_SITEURL")
+
+ cfg, err := ReadConfig(strings.NewReader(config), true)
+ require.Nil(t, err)
+
+ if *cfg.ServiceSettings.SiteURL != "https://example.com" {
+ t.Fatal("Couldn't read config from environment var")
+ }
+ })
}
func TestValidateLocales(t *testing.T) {