github.com/crspeller/mattermost-server@v0.0.0-20190328001957-a200beb3d111/config/common.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package config 5 6 import ( 7 "bytes" 8 "io" 9 "sync" 10 11 "github.com/crspeller/mattermost-server/model" 12 "github.com/pkg/errors" 13 ) 14 15 // commonStore enables code sharing between different backing implementations 16 type commonStore struct { 17 emitter 18 19 configLock sync.RWMutex 20 config *model.Config 21 configWithoutOverrides *model.Config 22 environmentOverrides map[string]interface{} 23 } 24 25 // Get fetches the current, cached configuration. 26 func (cs *commonStore) Get() *model.Config { 27 cs.configLock.RLock() 28 defer cs.configLock.RUnlock() 29 30 return cs.config 31 } 32 33 // GetEnvironmentOverrides fetches the configuration fields overridden by environment variables. 34 func (cs *commonStore) GetEnvironmentOverrides() map[string]interface{} { 35 cs.configLock.RLock() 36 defer cs.configLock.RUnlock() 37 38 return cs.environmentOverrides 39 } 40 41 // set replaces the current configuration in its entirety, and updates the backing store 42 // using the persist function argument. 43 // 44 // This function assumes no lock has been acquired, as it acquires a write lock itself. 45 func (cs *commonStore) set(newCfg *model.Config, validate func(*model.Config) error, persist func(*model.Config) error) (*model.Config, error) { 46 cs.configLock.Lock() 47 var unlockOnce sync.Once 48 defer unlockOnce.Do(cs.configLock.Unlock) 49 50 oldCfg := cs.config 51 52 // TODO: disallow attempting to save a directly modified config (comparing pointers). This 53 // wouldn't be an exhaustive check, given the use of pointers throughout the data 54 // structure, but might prevent common mistakes. Requires upstream changes first. 55 // if newCfg == oldCfg { 56 // return nil, errors.New("old configuration modified instead of cloning") 57 // } 58 59 newCfg = newCfg.Clone() 60 newCfg.SetDefaults() 61 62 // Sometimes the config is received with "fake" data in sensitive fields. Apply the real 63 // data from the existing config as necessary. 64 desanitize(oldCfg, newCfg) 65 66 if validate != nil { 67 if err := validate(newCfg); err != nil { 68 return nil, errors.Wrap(err, "new configuration is invalid") 69 } 70 } 71 72 if err := persist(cs.removeEnvOverrides(newCfg)); err != nil { 73 return nil, errors.Wrap(err, "failed to persist") 74 } 75 76 cs.config = newCfg 77 78 unlockOnce.Do(cs.configLock.Unlock) 79 80 // Notify listeners synchronously. Ideally, this would be asynchronous, but existing code 81 // assumes this and there would be increased complexity to avoid racing updates. 82 cs.invokeConfigListeners(oldCfg, newCfg) 83 84 return oldCfg, nil 85 } 86 87 // load updates the current configuration from the given io.ReadCloser. 88 // 89 // This function assumes no lock has been acquired, as it acquires a write lock itself. 90 func (cs *commonStore) load(f io.ReadCloser, needsSave bool, validate func(*model.Config) error, persist func(*model.Config) error) error { 91 // Duplicate f so that we can read a configuration without applying environment overrides 92 f2 := new(bytes.Buffer) 93 tee := io.TeeReader(f, f2) 94 95 allowEnvironmentOverrides := true 96 loadedCfg, environmentOverrides, err := unmarshalConfig(tee, allowEnvironmentOverrides) 97 if err != nil { 98 return errors.Wrapf(err, "failed to unmarshal config with env overrides") 99 } 100 101 // Keep track of the original values that the Environment settings overrode 102 loadedCfgWithoutEnvOverrides, _, err := unmarshalConfig(f2, false) 103 if err != nil { 104 return errors.Wrapf(err, "failed to unmarshal config without env overrides") 105 } 106 107 // SetDefaults generates various keys and salts if not previously configured. Determine if 108 // such a change will be made before invoking. 109 needsSave = needsSave || loadedCfg.SqlSettings.AtRestEncryptKey == nil || len(*loadedCfg.SqlSettings.AtRestEncryptKey) == 0 110 needsSave = needsSave || loadedCfg.FileSettings.PublicLinkSalt == nil || len(*loadedCfg.FileSettings.PublicLinkSalt) == 0 111 112 loadedCfg.SetDefaults() 113 114 if validate != nil { 115 if err = validate(loadedCfg); err != nil { 116 return errors.Wrap(err, "invalid config") 117 } 118 } 119 120 if changed := fixConfig(loadedCfg); changed { 121 needsSave = true 122 } 123 124 cs.configLock.Lock() 125 var unlockOnce sync.Once 126 defer unlockOnce.Do(cs.configLock.Unlock) 127 128 if needsSave && persist != nil { 129 cfgWithoutEnvOverrides := removeEnvOverrides(loadedCfg, loadedCfgWithoutEnvOverrides, environmentOverrides) 130 if err = persist(cfgWithoutEnvOverrides); err != nil { 131 return errors.Wrap(err, "failed to persist required changes after load") 132 } 133 } 134 135 oldCfg := cs.config 136 cs.config = loadedCfg 137 cs.configWithoutOverrides = loadedCfgWithoutEnvOverrides 138 cs.environmentOverrides = environmentOverrides 139 140 unlockOnce.Do(cs.configLock.Unlock) 141 142 // Notify listeners synchronously. Ideally, this would be asynchronous, but existing code 143 // assumes this and there would be increased complexity to avoid racing updates. 144 cs.invokeConfigListeners(oldCfg, loadedCfg) 145 146 return nil 147 } 148 149 // validate checks if the given configuration is valid 150 func (cs *commonStore) validate(cfg *model.Config) error { 151 if err := cfg.IsValid(); err != nil { 152 return err 153 } 154 155 return nil 156 } 157 158 // removeEnvOverrides returns a new config without the given environment overrides. 159 func (cs *commonStore) removeEnvOverrides(cfg *model.Config) *model.Config { 160 return removeEnvOverrides(cfg, cs.configWithoutOverrides, cs.environmentOverrides) 161 }