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 }