github.com/adacta-ru/mattermost-server/v6@v6.0.0/config/store.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 "encoding/json" 9 "sync" 10 11 "github.com/adacta-ru/mattermost-server/v6/model" 12 "github.com/adacta-ru/mattermost-server/v6/utils/jsonutils" 13 "github.com/pkg/errors" 14 ) 15 16 // Listener is a callback function invoked when the configuration changes. 17 type Listener func(oldConfig *model.Config, newConfig *model.Config) 18 19 type BackingStore interface { 20 // Set replaces the current configuration in its entirety and updates the backing store. 21 Set(*model.Config) error 22 23 // Load retrieves the configuration stored. If there is no configuration stored 24 // the io.ReadCloser will be nil 25 Load() ([]byte, error) 26 27 // GetFile fetches the contents of a previously persisted configuration file. 28 // If no such file exists, an empty byte array will be returned without error. 29 GetFile(name string) ([]byte, error) 30 31 // SetFile sets or replaces the contents of a configuration file. 32 SetFile(name string, data []byte) error 33 34 // HasFile returns true if the given file was previously persisted. 35 HasFile(name string) (bool, error) 36 37 // RemoveFile removes a previously persisted configuration file. 38 RemoveFile(name string) error 39 40 // String describes the backing store for the config. 41 String() string 42 43 Watch(callback func()) error 44 45 // Close cleans up resources associated with the store. 46 Close() error 47 } 48 49 // NewStore creates a database or file store given a data source name by which to connect. 50 func NewStore(dsn string, watch bool, customDefaults *model.Config) (*Store, error) { 51 backingStore, err := getBackingStore(dsn, watch) 52 if err != nil { 53 return nil, err 54 } 55 56 store, err := NewStoreFromBacking(backingStore, customDefaults) 57 if err != nil { 58 backingStore.Close() 59 return nil, errors.Wrap(err, "failed to create store") 60 } 61 62 return store, nil 63 } 64 65 func NewStoreFromBacking(backingStore BackingStore, customDefaults *model.Config) (*Store, error) { 66 store := &Store{ 67 backingStore: backingStore, 68 configCustomDefaults: customDefaults, 69 } 70 71 if err := store.Load(); err != nil { 72 return nil, errors.Wrap(err, "unable to load on store creation") 73 } 74 75 if err := backingStore.Watch(func() { 76 store.Load() 77 }); err != nil { 78 return nil, errors.Wrap(err, "failed to watch backing store") 79 } 80 81 return store, nil 82 } 83 84 func getBackingStore(dsn string, watch bool) (BackingStore, error) { 85 if IsDatabaseDSN(dsn) { 86 return NewDatabaseStore(dsn) 87 } 88 89 return NewFileStore(dsn, watch) 90 } 91 92 func NewTestMemoryStore() *Store { 93 memoryStore, err := NewMemoryStore() 94 if err != nil { 95 panic("failed to initialize memory store: " + err.Error()) 96 } 97 98 configStore, err := NewStoreFromBacking(memoryStore, nil) 99 if err != nil { 100 panic("failed to initialize config store: " + err.Error()) 101 } 102 103 return configStore 104 } 105 106 type Store struct { 107 emitter 108 backingStore BackingStore 109 110 configLock sync.RWMutex 111 config *model.Config 112 configNoEnv *model.Config 113 configCustomDefaults *model.Config 114 115 persistFeatureFlags bool 116 } 117 118 // Get fetches the current, cached configuration. 119 func (s *Store) Get() *model.Config { 120 s.configLock.RLock() 121 defer s.configLock.RUnlock() 122 return s.config 123 } 124 125 // Get fetches the current, cached configuration without environment variable overrides. 126 func (s *Store) GetNoEnv() *model.Config { 127 s.configLock.RLock() 128 defer s.configLock.RUnlock() 129 return s.configNoEnv 130 } 131 132 // GetEnvironmentOverrides fetches the configuration fields overridden by environment variables. 133 func (s *Store) GetEnvironmentOverrides() map[string]interface{} { 134 return generateEnvironmentMap(GetEnvironment()) 135 } 136 137 // RemoveEnvironmentOverrides returns a new config without the environment 138 // overrides 139 func (s *Store) RemoveEnvironmentOverrides(cfg *model.Config) *model.Config { 140 s.configLock.RLock() 141 defer s.configLock.RUnlock() 142 return removeEnvOverrides(cfg, s.configNoEnv, s.GetEnvironmentOverrides()) 143 } 144 145 // PersistFeatures sets if the store should persist feature flags. 146 func (s *Store) PersistFeatures(persist bool) { 147 s.configLock.Lock() 148 defer s.configLock.Unlock() 149 s.persistFeatureFlags = persist 150 } 151 152 // Set replaces the current configuration in its entirety and updates the backing store. 153 func (s *Store) Set(newCfg *model.Config) (*model.Config, error) { 154 s.configLock.Lock() 155 var unlockOnce sync.Once 156 defer unlockOnce.Do(s.configLock.Unlock) 157 158 oldCfg := s.config.Clone() 159 160 // Really just for some tests we need to set defaults here 161 newCfg.SetDefaults() 162 163 // Sometimes the config is received with "fake" data in sensitive fields. Apply the real 164 // data from the existing config as necessary. 165 desanitize(oldCfg, newCfg) 166 167 if err := newCfg.IsValid(); err != nil { 168 return nil, errors.Wrap(err, "new configuration is invalid") 169 } 170 171 newCfg = removeEnvOverrides(newCfg, s.configNoEnv, s.GetEnvironmentOverrides()) 172 173 // Don't persist feature flags unless we are on MM cloud 174 // MM cloud uses config in the DB as a cache of the feature flag 175 // settings in case the management system is down when a pod starts. 176 if !s.persistFeatureFlags { 177 newCfg.FeatureFlags = nil 178 } 179 180 if err := s.backingStore.Set(newCfg); err != nil { 181 return nil, errors.Wrap(err, "failed to persist") 182 } 183 184 if err := s.loadLockedWithOld(oldCfg, &unlockOnce); err != nil { 185 return nil, errors.Wrap(err, "failed to load on save") 186 } 187 188 return oldCfg, nil 189 } 190 191 func (s *Store) loadLockedWithOld(oldCfg *model.Config, unlockOnce *sync.Once) error { 192 configBytes, err := s.backingStore.Load() 193 if err != nil { 194 return err 195 } 196 197 loadedConfig := &model.Config{} 198 if len(configBytes) != 0 { 199 if err = json.Unmarshal(configBytes, &loadedConfig); err != nil { 200 return jsonutils.HumanizeJsonError(err, configBytes) 201 } 202 } 203 204 // If we have custom defaults set, the initial config is merged on 205 // top of them and we delete them not to be used again in the 206 // configuration reloads 207 if s.configCustomDefaults != nil { 208 var mErr error 209 loadedConfig, mErr = Merge(s.configCustomDefaults, loadedConfig, nil) 210 if mErr != nil { 211 return errors.Wrap(mErr, "failed to merge custom config defaults") 212 } 213 s.configCustomDefaults = nil 214 } 215 216 loadedConfig.SetDefaults() 217 218 s.configNoEnv = loadedConfig.Clone() 219 fixConfig(s.configNoEnv) 220 221 loadedConfig = applyEnvironmentMap(loadedConfig, GetEnvironment()) 222 223 fixConfig(loadedConfig) 224 225 if err := loadedConfig.IsValid(); err != nil { 226 return errors.Wrap(err, "invalid config") 227 } 228 229 // Apply changes that may have happened on load to the backing store. 230 oldCfgBytes, err := json.Marshal(oldCfg) 231 if err != nil { 232 return errors.Wrap(err, "failed to marshal old config") 233 } 234 newCfgBytes, err := json.Marshal(loadedConfig) 235 if err != nil { 236 return errors.Wrap(err, "failed to marshal loaded config") 237 } 238 if len(configBytes) == 0 || !bytes.Equal(oldCfgBytes, newCfgBytes) { 239 if err := s.backingStore.Set(s.configNoEnv); err != nil { 240 if !errors.Is(err, ErrReadOnlyConfiguration) { 241 return errors.Wrap(err, "failed to persist") 242 } 243 } 244 } 245 246 s.config = loadedConfig 247 248 unlockOnce.Do(s.configLock.Unlock) 249 250 s.invokeConfigListeners(oldCfg, loadedConfig) 251 252 return nil 253 } 254 255 // Load updates the current configuration from the backing store, possibly initializing. 256 func (s *Store) Load() error { 257 s.configLock.Lock() 258 var unlockOnce sync.Once 259 defer unlockOnce.Do(s.configLock.Unlock) 260 261 oldCfg := s.config.Clone() 262 263 return s.loadLockedWithOld(oldCfg, &unlockOnce) 264 } 265 266 // GetFile fetches the contents of a previously persisted configuration file. 267 // If no such file exists, an empty byte array will be returned without error. 268 func (s *Store) GetFile(name string) ([]byte, error) { 269 s.configLock.RLock() 270 defer s.configLock.RUnlock() 271 return s.backingStore.GetFile(name) 272 } 273 274 // SetFile sets or replaces the contents of a configuration file. 275 func (s *Store) SetFile(name string, data []byte) error { 276 s.configLock.Lock() 277 defer s.configLock.Unlock() 278 return s.backingStore.SetFile(name, data) 279 } 280 281 // HasFile returns true if the given file was previously persisted. 282 func (s *Store) HasFile(name string) (bool, error) { 283 s.configLock.RLock() 284 defer s.configLock.RUnlock() 285 return s.backingStore.HasFile(name) 286 } 287 288 // RemoveFile removes a previously persisted configuration file. 289 func (s *Store) RemoveFile(name string) error { 290 s.configLock.Lock() 291 defer s.configLock.Unlock() 292 return s.backingStore.RemoveFile(name) 293 } 294 295 // String describes the backing store for the config. 296 func (s *Store) String() string { 297 return s.backingStore.String() 298 } 299 300 // Close cleans up resources associated with the store. 301 func (s *Store) Close() error { 302 s.configLock.Lock() 303 defer s.configLock.Unlock() 304 return s.backingStore.Close() 305 }