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