github.com/mattermost/mattermost-server/v5@v5.39.3/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 "encoding/json" 8 "reflect" 9 "sync" 10 11 "github.com/pkg/errors" 12 13 "github.com/mattermost/mattermost-server/v5/model" 14 "github.com/mattermost/mattermost-server/v5/utils/jsonutils" 15 ) 16 17 var ( 18 // ErrReadOnlyStore is returned when an attempt to modify a read-only 19 // configuration store is made. 20 ErrReadOnlyStore = errors.New("configuration store is read-only") 21 ) 22 23 // Store is the higher level object that handles storing and retrieval of config data. 24 // To do so it relies on a variety of backing stores (e.g. file, database, memory). 25 type Store struct { 26 emitter 27 backingStore BackingStore 28 29 configLock sync.RWMutex 30 config *model.Config 31 configNoEnv *model.Config 32 configCustomDefaults *model.Config 33 34 readOnly bool 35 readOnlyFF bool 36 } 37 38 // BackingStore defines the behaviour exposed by the underlying store 39 // implementation (e.g. file, database). 40 type BackingStore interface { 41 // Set replaces the current configuration in its entirety and updates the backing store. 42 Set(*model.Config) error 43 44 // Load retrieves the configuration stored. If there is no configuration stored 45 // the io.ReadCloser will be nil 46 Load() ([]byte, error) 47 48 // GetFile fetches the contents of a previously persisted configuration file. 49 // If no such file exists, an empty byte array will be returned without error. 50 GetFile(name string) ([]byte, error) 51 52 // SetFile sets or replaces the contents of a configuration file. 53 SetFile(name string, data []byte) error 54 55 // HasFile returns true if the given file was previously persisted. 56 HasFile(name string) (bool, error) 57 58 // RemoveFile removes a previously persisted configuration file. 59 RemoveFile(name string) error 60 61 // String describes the backing store for the config. 62 String() string 63 64 // Close cleans up resources associated with the store. 65 Close() error 66 } 67 68 // NewStoreFromBacking creates and returns a new config store given a backing store. 69 func NewStoreFromBacking(backingStore BackingStore, customDefaults *model.Config, readOnly bool) (*Store, error) { 70 store := &Store{ 71 backingStore: backingStore, 72 configCustomDefaults: customDefaults, 73 readOnly: readOnly, 74 readOnlyFF: true, 75 } 76 77 if err := store.Load(); err != nil { 78 return nil, errors.Wrap(err, "unable to load on store creation") 79 } 80 81 return store, nil 82 } 83 84 // NewStoreFromDSN creates and returns a new config store backed by either a database or file store 85 // depending on the value of the given data source name string. 86 func NewStoreFromDSN(dsn string, readOnly bool, customDefaults *model.Config) (*Store, error) { 87 var err error 88 var backingStore BackingStore 89 if IsDatabaseDSN(dsn) { 90 backingStore, err = NewDatabaseStore(dsn) 91 } else { 92 backingStore, err = NewFileStore(dsn) 93 } 94 if err != nil { 95 return nil, err 96 } 97 98 store, err := NewStoreFromBacking(backingStore, customDefaults, readOnly) 99 if err != nil { 100 backingStore.Close() 101 return nil, errors.Wrap(err, "failed to create store") 102 } 103 104 return store, nil 105 } 106 107 // NewTestMemoryStore returns a new config store backed by a memory store 108 // to be used for testing purposes. 109 func NewTestMemoryStore() *Store { 110 memoryStore, err := NewMemoryStore() 111 if err != nil { 112 panic("failed to initialize memory store: " + err.Error()) 113 } 114 115 configStore, err := NewStoreFromBacking(memoryStore, nil, false) 116 if err != nil { 117 panic("failed to initialize config store: " + err.Error()) 118 } 119 120 return configStore 121 } 122 123 // Get fetches the current, cached configuration. 124 func (s *Store) Get() *model.Config { 125 s.configLock.RLock() 126 defer s.configLock.RUnlock() 127 return s.config 128 } 129 130 // GetNoEnv fetches the current cached configuration without environment variable overrides. 131 func (s *Store) GetNoEnv() *model.Config { 132 s.configLock.RLock() 133 defer s.configLock.RUnlock() 134 return s.configNoEnv 135 } 136 137 // GetEnvironmentOverrides fetches the configuration fields overridden by environment variables. 138 func (s *Store) GetEnvironmentOverrides() map[string]interface{} { 139 return generateEnvironmentMap(GetEnvironment(), nil) 140 } 141 142 // GetEnvironmentOverridesWithFilter fetches the configuration fields overridden by environment variables. 143 // If filter is not nil and returns false for a struct field, that field will be omitted. 144 func (s *Store) GetEnvironmentOverridesWithFilter(filter func(reflect.StructField) bool) map[string]interface{} { 145 return generateEnvironmentMap(GetEnvironment(), filter) 146 } 147 148 // RemoveEnvironmentOverrides returns a new config without the environment 149 // overrides. 150 func (s *Store) RemoveEnvironmentOverrides(cfg *model.Config) *model.Config { 151 s.configLock.RLock() 152 defer s.configLock.RUnlock() 153 return removeEnvOverrides(cfg, s.configNoEnv, s.GetEnvironmentOverrides()) 154 } 155 156 // SetReadOnlyFF sets whether feature flags should be written out to 157 // config or treated as read-only. 158 func (s *Store) SetReadOnlyFF(readOnly bool) { 159 s.configLock.Lock() 160 defer s.configLock.Unlock() 161 s.readOnlyFF = readOnly 162 } 163 164 // Set replaces the current configuration in its entirety and updates the backing store. 165 // It returns both old and new versions of the config. 166 func (s *Store) Set(newCfg *model.Config) (*model.Config, *model.Config, error) { 167 s.configLock.Lock() 168 defer s.configLock.Unlock() 169 170 if s.readOnly { 171 return nil, nil, ErrReadOnlyStore 172 } 173 174 newCfg = newCfg.Clone() 175 oldCfg := s.config.Clone() 176 oldCfgNoEnv := s.configNoEnv 177 178 // Setting defaults allows us to accept partial config objects. 179 newCfg.SetDefaults() 180 181 // Sometimes the config is received with "fake" data in sensitive fields. Apply the real 182 // data from the existing config as necessary. 183 desanitize(oldCfg, newCfg) 184 185 if err := newCfg.IsValid(); err != nil { 186 return nil, nil, errors.Wrap(err, "new configuration is invalid") 187 } 188 189 // We attempt to remove any environment override that may be present in the input config. 190 newCfgNoEnv := removeEnvOverrides(newCfg, oldCfgNoEnv, s.GetEnvironmentOverrides()) 191 192 // Don't store feature flags unless we are on MM cloud 193 // MM cloud uses config in the DB as a cache of the feature flag 194 // settings in case the management system is down when a pod starts. 195 196 // Backing up feature flags section in case we need to restore them later on. 197 oldCfgFF := oldCfg.FeatureFlags 198 oldCfgNoEnvFF := oldCfgNoEnv.FeatureFlags 199 // Clearing FF sections to avoid both comparing and persisting them. 200 if s.readOnlyFF { 201 oldCfg.FeatureFlags = nil 202 newCfg.FeatureFlags = nil 203 newCfgNoEnv.FeatureFlags = nil 204 } 205 206 if err := s.backingStore.Set(newCfgNoEnv); err != nil { 207 return nil, nil, errors.Wrap(err, "failed to persist") 208 } 209 210 // We apply back environment overrides since the input config may or 211 // may not have them applied. 212 newCfg = applyEnvironmentMap(newCfgNoEnv, GetEnvironment()) 213 fixConfig(newCfg) 214 if err := newCfg.IsValid(); err != nil { 215 return nil, nil, errors.Wrap(err, "new configuration is invalid") 216 } 217 218 hasChanged, err := equal(oldCfg, newCfg) 219 if err != nil { 220 return nil, nil, errors.Wrap(err, "failed to compare configs") 221 } 222 223 // We restore the previously cleared feature flags sections back. 224 if s.readOnlyFF { 225 oldCfg.FeatureFlags = oldCfgFF 226 newCfg.FeatureFlags = oldCfgFF 227 newCfgNoEnv.FeatureFlags = oldCfgNoEnvFF 228 } 229 230 s.configNoEnv = newCfgNoEnv 231 s.config = newCfg 232 233 newCfgCopy := newCfg.Clone() 234 235 if hasChanged { 236 s.configLock.Unlock() 237 s.invokeConfigListeners(oldCfg, newCfgCopy.Clone()) 238 s.configLock.Lock() 239 } 240 241 return oldCfg, newCfgCopy, nil 242 } 243 244 // Load updates the current configuration from the backing store, possibly initializing. 245 func (s *Store) Load() error { 246 s.configLock.Lock() 247 defer s.configLock.Unlock() 248 249 oldCfg := &model.Config{} 250 if s.config != nil { 251 oldCfg = s.config.Clone() 252 } 253 254 configBytes, err := s.backingStore.Load() 255 if err != nil { 256 return err 257 } 258 259 loadedCfg := &model.Config{} 260 if len(configBytes) != 0 { 261 if err = json.Unmarshal(configBytes, &loadedCfg); err != nil { 262 return jsonutils.HumanizeJSONError(err, configBytes) 263 } 264 } 265 266 // If we have custom defaults set, the initial config is merged on 267 // top of them and we delete them not to be used again in the 268 // configuration reloads 269 if s.configCustomDefaults != nil { 270 var mErr error 271 loadedCfg, mErr = Merge(s.configCustomDefaults, loadedCfg, nil) 272 if mErr != nil { 273 return errors.Wrap(mErr, "failed to merge custom config defaults") 274 } 275 s.configCustomDefaults = nil 276 } 277 278 // We set the SiteURL to empty (if nil) so that the following call to 279 // SetDefaults() will generate missing data. This avoids an additional write 280 // to the backing store. 281 if loadedCfg.ServiceSettings.SiteURL == nil { 282 loadedCfg.ServiceSettings.SiteURL = model.NewString("") 283 } 284 285 // Setting defaults allows us to accept partial config objects. 286 loadedCfg.SetDefaults() 287 288 // No need to clone here since the below call to applyEnvironmentMap 289 // already does that internally. 290 loadedCfgNoEnv := loadedCfg 291 fixConfig(loadedCfgNoEnv) 292 293 loadedCfg = applyEnvironmentMap(loadedCfg, GetEnvironment()) 294 fixConfig(loadedCfg) 295 if err := loadedCfg.IsValid(); err != nil { 296 return errors.Wrap(err, "invalid config") 297 } 298 299 // Backing up feature flags section in case we need to restore them later on. 300 oldCfgFF := oldCfg.FeatureFlags 301 loadedCfgFF := loadedCfg.FeatureFlags 302 loadedCfgNoEnvFF := loadedCfgNoEnv.FeatureFlags 303 // Clearing FF sections to avoid both comparing and persisting them. 304 if s.readOnlyFF { 305 oldCfg.FeatureFlags = nil 306 loadedCfg.FeatureFlags = nil 307 loadedCfgNoEnv.FeatureFlags = nil 308 } 309 310 // Check for changes that may have happened on load to the backing store. 311 hasChanged, err := equal(oldCfg, loadedCfg) 312 if err != nil { 313 return errors.Wrap(err, "failed to compare configs") 314 } 315 316 // We write back to the backing store only if the store is not read-only 317 // and the config has either changed or is missing. 318 if !s.readOnly && (hasChanged || len(configBytes) == 0) { 319 err := s.backingStore.Set(loadedCfgNoEnv) 320 if err != nil && !errors.Is(err, ErrReadOnlyConfiguration) { 321 return errors.Wrap(err, "failed to persist") 322 } 323 } 324 325 // We restore the previously cleared feature flags sections back. 326 if s.readOnlyFF { 327 oldCfg.FeatureFlags = oldCfgFF 328 loadedCfg.FeatureFlags = loadedCfgFF 329 loadedCfgNoEnv.FeatureFlags = loadedCfgNoEnvFF 330 } 331 332 s.config = loadedCfg 333 s.configNoEnv = loadedCfgNoEnv 334 335 loadedCfgCopy := loadedCfg.Clone() 336 337 if hasChanged { 338 s.configLock.Unlock() 339 s.invokeConfigListeners(oldCfg, loadedCfgCopy) 340 s.configLock.Lock() 341 } 342 343 return nil 344 } 345 346 // GetFile fetches the contents of a previously persisted configuration file. 347 // If no such file exists, an empty byte array will be returned without error. 348 func (s *Store) GetFile(name string) ([]byte, error) { 349 s.configLock.RLock() 350 defer s.configLock.RUnlock() 351 return s.backingStore.GetFile(name) 352 } 353 354 // SetFile sets or replaces the contents of a configuration file. 355 func (s *Store) SetFile(name string, data []byte) error { 356 s.configLock.Lock() 357 defer s.configLock.Unlock() 358 if s.readOnly { 359 return ErrReadOnlyStore 360 } 361 return s.backingStore.SetFile(name, data) 362 } 363 364 // HasFile returns true if the given file was previously persisted. 365 func (s *Store) HasFile(name string) (bool, error) { 366 s.configLock.RLock() 367 defer s.configLock.RUnlock() 368 return s.backingStore.HasFile(name) 369 } 370 371 // RemoveFile removes a previously persisted configuration file. 372 func (s *Store) RemoveFile(name string) error { 373 s.configLock.Lock() 374 defer s.configLock.Unlock() 375 if s.readOnly { 376 return ErrReadOnlyStore 377 } 378 return s.backingStore.RemoveFile(name) 379 } 380 381 // String describes the backing store for the config. 382 func (s *Store) String() string { 383 return s.backingStore.String() 384 } 385 386 // Close cleans up resources associated with the store. 387 func (s *Store) Close() error { 388 s.configLock.Lock() 389 defer s.configLock.Unlock() 390 return s.backingStore.Close() 391 } 392 393 // IsReadOnly returns whether or not the store is read-only. 394 func (s *Store) IsReadOnly() bool { 395 s.configLock.RLock() 396 defer s.configLock.RUnlock() 397 return s.readOnly 398 }