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  }