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  }