github.com/hahmadia/mattermost-server@v5.11.1+incompatible/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 "io" 8 "sync" 9 10 "github.com/mattermost/mattermost-server/model" 11 "github.com/pkg/errors" 12 ) 13 14 // commonStore enables code sharing between different backing implementations 15 type commonStore struct { 16 emitter 17 18 configLock sync.RWMutex 19 config *model.Config 20 environmentOverrides map[string]interface{} 21 } 22 23 // Get fetches the current, cached configuration. 24 func (cs *commonStore) Get() *model.Config { 25 cs.configLock.RLock() 26 defer cs.configLock.RUnlock() 27 28 return cs.config 29 } 30 31 // GetEnvironmentOverrides fetches the configuration fields overridden by environment variables. 32 func (cs *commonStore) GetEnvironmentOverrides() map[string]interface{} { 33 cs.configLock.RLock() 34 defer cs.configLock.RUnlock() 35 36 return cs.environmentOverrides 37 } 38 39 // set replaces the current configuration in its entirety, and updates the backing store 40 // using the persist function argument. 41 // 42 // This function assumes no lock has been acquired, as it acquires a write lock itself. 43 func (cs *commonStore) set(newCfg *model.Config, validate func(*model.Config) error, persist func(*model.Config) error) (*model.Config, error) { 44 cs.configLock.Lock() 45 var unlockOnce sync.Once 46 defer unlockOnce.Do(cs.configLock.Unlock) 47 48 oldCfg := cs.config 49 50 // TODO: disallow attempting to save a directly modified config (comparing pointers). This 51 // wouldn't be an exhaustive check, given the use of pointers throughout the data 52 // structure, but might prevent common mistakes. Requires upstream changes first. 53 // if newCfg == oldCfg { 54 // return nil, errors.New("old configuration modified instead of cloning") 55 // } 56 57 newCfg = newCfg.Clone() 58 newCfg.SetDefaults() 59 60 // Sometimes the config is received with "fake" data in sensitive fields. Apply the real 61 // data from the existing config as necessary. 62 desanitize(oldCfg, newCfg) 63 64 if validate != nil { 65 if err := validate(newCfg); err != nil { 66 return nil, errors.Wrap(err, "new configuration is invalid") 67 } 68 } 69 70 if err := persist(newCfg); err != nil { 71 return nil, errors.Wrap(err, "failed to persist") 72 } 73 74 cs.config = newCfg 75 76 unlockOnce.Do(cs.configLock.Unlock) 77 78 // Notify listeners synchronously. Ideally, this would be asynchronous, but existing code 79 // assumes this and there would be increased complexity to avoid racing updates. 80 cs.invokeConfigListeners(oldCfg, newCfg) 81 82 return oldCfg, nil 83 } 84 85 // load updates the current configuration from the given io.ReadCloser. 86 // 87 // This function assumes no lock has been acquired, as it acquires a write lock itself. 88 func (cs *commonStore) load(f io.ReadCloser, needsSave bool, validate func(*model.Config) error, persist func(*model.Config) error) error { 89 allowEnvironmentOverrides := true 90 loadedCfg, environmentOverrides, err := unmarshalConfig(f, allowEnvironmentOverrides) 91 if err != nil { 92 return errors.Wrapf(err, "failed to unmarshal config") 93 } 94 95 // SetDefaults generates various keys and salts if not previously configured. Determine if 96 // such a change will be made before invoking. 97 needsSave = needsSave || loadedCfg.SqlSettings.AtRestEncryptKey == nil || len(*loadedCfg.SqlSettings.AtRestEncryptKey) == 0 98 needsSave = needsSave || loadedCfg.FileSettings.PublicLinkSalt == nil || len(*loadedCfg.FileSettings.PublicLinkSalt) == 0 99 100 loadedCfg.SetDefaults() 101 102 if validate != nil { 103 if err = validate(loadedCfg); err != nil { 104 return errors.Wrap(err, "invalid config") 105 } 106 } 107 108 if changed := fixConfig(loadedCfg); changed { 109 needsSave = true 110 } 111 112 cs.configLock.Lock() 113 var unlockOnce sync.Once 114 defer unlockOnce.Do(cs.configLock.Unlock) 115 116 if needsSave && persist != nil { 117 if err = persist(loadedCfg); err != nil { 118 return errors.Wrap(err, "failed to persist required changes after load") 119 } 120 } 121 122 oldCfg := cs.config 123 cs.config = loadedCfg 124 cs.environmentOverrides = environmentOverrides 125 126 unlockOnce.Do(cs.configLock.Unlock) 127 128 // Notify listeners synchronously. Ideally, this would be asynchronous, but existing code 129 // assumes this and there would be increased complexity to avoid racing updates. 130 cs.invokeConfigListeners(oldCfg, loadedCfg) 131 132 return nil 133 } 134 135 // validate checks if the given configuration is valid 136 func (cs *commonStore) validate(cfg *model.Config) error { 137 if err := cfg.IsValid(); err != nil { 138 return err 139 } 140 141 return nil 142 }