mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
While sending the config broadcast message across a cluster, we would include the environment overrides in the config. We fix this by exposing a config config store method to remove the overrides, and then calling that before saving the config across the cluster. Co-authored-by: mattermod <mattermod@users.noreply.github.com>
171 lines
5.6 KiB
Go
171 lines
5.6 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package config
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/mattermost/mattermost-server/v5/model"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// commonStore enables code sharing between different backing implementations
|
|
type commonStore struct {
|
|
emitter
|
|
|
|
configLock sync.RWMutex
|
|
config *model.Config
|
|
configWithoutOverrides *model.Config
|
|
environmentOverrides map[string]interface{}
|
|
}
|
|
|
|
// Get fetches the current, cached configuration.
|
|
func (cs *commonStore) Get() *model.Config {
|
|
cs.configLock.RLock()
|
|
defer cs.configLock.RUnlock()
|
|
|
|
return cs.config
|
|
}
|
|
|
|
// GetEnvironmentOverrides fetches the configuration fields overridden by environment variables.
|
|
func (cs *commonStore) GetEnvironmentOverrides() map[string]interface{} {
|
|
cs.configLock.RLock()
|
|
defer cs.configLock.RUnlock()
|
|
|
|
return cs.environmentOverrides
|
|
}
|
|
|
|
// set replaces the current configuration in its entirety, and updates the backing store
|
|
// using the persist function argument.
|
|
//
|
|
// This function assumes no lock has been acquired, as it acquires a write lock itself.
|
|
func (cs *commonStore) set(newCfg *model.Config, allowEnvironmentOverrides bool, validate func(*model.Config) error, persist func(*model.Config) error) (*model.Config, error) {
|
|
cs.configLock.Lock()
|
|
var unlockOnce sync.Once
|
|
defer unlockOnce.Do(cs.configLock.Unlock)
|
|
|
|
oldCfg := cs.config
|
|
|
|
// TODO: disallow attempting to save a directly modified config (comparing pointers). This
|
|
// wouldn't be an exhaustive check, given the use of pointers throughout the data
|
|
// structure, but might prevent common mistakes. Requires upstream changes first.
|
|
// if newCfg == oldCfg {
|
|
// return nil, errors.New("old configuration modified instead of cloning")
|
|
// }
|
|
|
|
// To both clone and re-apply the environment variable overrides we marshal and then
|
|
// unmarshal the config again.
|
|
var err error
|
|
newCfg, _, err = unmarshalConfig(strings.NewReader(newCfg.ToJson()), allowEnvironmentOverrides)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to unmarshal config with env overrides")
|
|
}
|
|
|
|
newCfg.SetDefaults()
|
|
|
|
// Sometimes the config is received with "fake" data in sensitive fields. Apply the real
|
|
// data from the existing config as necessary.
|
|
desanitize(oldCfg, newCfg)
|
|
|
|
if validate != nil {
|
|
if err := validate(newCfg); err != nil {
|
|
return nil, errors.Wrap(err, "new configuration is invalid")
|
|
}
|
|
}
|
|
|
|
if err := persist(cs.RemoveEnvironmentOverrides(newCfg)); err != nil {
|
|
return nil, errors.Wrap(err, "failed to persist")
|
|
}
|
|
|
|
cs.config = newCfg
|
|
|
|
unlockOnce.Do(cs.configLock.Unlock)
|
|
|
|
// Notify listeners synchronously. Ideally, this would be asynchronous, but existing code
|
|
// assumes this and there would be increased complexity to avoid racing updates.
|
|
cs.invokeConfigListeners(oldCfg, newCfg)
|
|
|
|
return oldCfg, nil
|
|
}
|
|
|
|
// load updates the current configuration from the given io.ReadCloser.
|
|
//
|
|
// This function assumes no lock has been acquired, as it acquires a write lock itself.
|
|
func (cs *commonStore) load(f io.ReadCloser, needsSave bool, validate func(*model.Config) error, persist func(*model.Config) error) error {
|
|
// Duplicate f so that we can read a configuration without applying environment overrides
|
|
f2 := new(bytes.Buffer)
|
|
tee := io.TeeReader(f, f2)
|
|
|
|
allowEnvironmentOverrides := true
|
|
loadedCfg, environmentOverrides, err := unmarshalConfig(tee, allowEnvironmentOverrides)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to unmarshal config with env overrides")
|
|
}
|
|
|
|
// Keep track of the original values that the Environment settings overrode
|
|
loadedCfgWithoutEnvOverrides, _, err := unmarshalConfig(f2, false)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to unmarshal config without env overrides")
|
|
}
|
|
|
|
// SetDefaults generates various keys and salts if not previously configured. Determine if
|
|
// such a change will be made before invoking.
|
|
needsSave = needsSave || loadedCfg.SqlSettings.AtRestEncryptKey == nil || len(*loadedCfg.SqlSettings.AtRestEncryptKey) == 0
|
|
needsSave = needsSave || loadedCfg.FileSettings.PublicLinkSalt == nil || len(*loadedCfg.FileSettings.PublicLinkSalt) == 0
|
|
|
|
loadedCfg.SetDefaults()
|
|
loadedCfgWithoutEnvOverrides.SetDefaults()
|
|
|
|
if validate != nil {
|
|
if err = validate(loadedCfg); err != nil {
|
|
return errors.Wrap(err, "invalid config")
|
|
}
|
|
}
|
|
|
|
if changed := fixConfig(loadedCfg); changed {
|
|
needsSave = true
|
|
}
|
|
|
|
cs.configLock.Lock()
|
|
var unlockOnce sync.Once
|
|
defer unlockOnce.Do(cs.configLock.Unlock)
|
|
|
|
if needsSave && persist != nil {
|
|
cfgWithoutEnvOverrides := removeEnvOverrides(loadedCfg, loadedCfgWithoutEnvOverrides, environmentOverrides)
|
|
if err = persist(cfgWithoutEnvOverrides); err != nil {
|
|
return errors.Wrap(err, "failed to persist required changes after load")
|
|
}
|
|
}
|
|
|
|
oldCfg := cs.config
|
|
cs.config = loadedCfg
|
|
cs.configWithoutOverrides = loadedCfgWithoutEnvOverrides
|
|
cs.environmentOverrides = environmentOverrides
|
|
|
|
unlockOnce.Do(cs.configLock.Unlock)
|
|
|
|
// Notify listeners synchronously. Ideally, this would be asynchronous, but existing code
|
|
// assumes this and there would be increased complexity to avoid racing updates.
|
|
cs.invokeConfigListeners(oldCfg, loadedCfg)
|
|
|
|
return nil
|
|
}
|
|
|
|
// validate checks if the given configuration is valid
|
|
func (cs *commonStore) validate(cfg *model.Config) error {
|
|
if err := cfg.IsValid(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoveEnvironmentOverrides returns a new config without the given environment overrides.
|
|
func (cs *commonStore) RemoveEnvironmentOverrides(cfg *model.Config) *model.Config {
|
|
return removeEnvOverrides(cfg, cs.configWithoutOverrides, cs.environmentOverrides)
|
|
}
|