github.com/bensooraj/mattermost-server@v5.11.1+incompatible/config/file.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 "fmt" 9 "io" 10 "io/ioutil" 11 "os" 12 "path/filepath" 13 14 "github.com/pkg/errors" 15 16 "github.com/mattermost/mattermost-server/mlog" 17 "github.com/mattermost/mattermost-server/model" 18 "github.com/mattermost/mattermost-server/utils/fileutils" 19 ) 20 21 var ( 22 ErrReadOnlyConfiguration = errors.New("configuration is read-only") 23 ) 24 25 // FileStore is a config store backed by a file such as config/config.json. 26 // 27 // It also uses the folder containing the configuration file for storing other configuration files. 28 type FileStore struct { 29 commonStore 30 31 path string 32 watch bool 33 watcher *watcher 34 } 35 36 // NewFileStore creates a new instance of a config store backed by the given file path. 37 // 38 // If watch is true, any external changes to the file will force a reload. 39 func NewFileStore(path string, watch bool) (fs *FileStore, err error) { 40 resolvedPath, err := resolveConfigFilePath(path) 41 if err != nil { 42 return nil, err 43 } 44 45 fs = &FileStore{ 46 path: resolvedPath, 47 watch: watch, 48 } 49 if err = fs.Load(); err != nil { 50 return nil, errors.Wrap(err, "failed to load") 51 } 52 53 if fs.watch { 54 if err = fs.startWatcher(); err != nil { 55 mlog.Error("failed to start config watcher", mlog.String("path", path), mlog.Err(err)) 56 } 57 } 58 59 return fs, nil 60 } 61 62 // resolveConfigFilePath attempts to resolve the given configuration file path to an absolute path. 63 // 64 // Consideration is given to maintaining backwards compatibility when resolving the path to the 65 // configuration file. 66 func resolveConfigFilePath(path string) (string, error) { 67 // Absolute paths are explicit and require no resolution. 68 if filepath.IsAbs(path) { 69 return path, nil 70 } 71 72 // Search for the relative path to the file in the config folder, taking into account 73 // various common starting points. 74 if configFile := fileutils.FindFile(filepath.Join("config", path)); configFile != "" { 75 return configFile, nil 76 } 77 78 // Search for the relative path in the current working directory, also taking into account 79 // various common starting points. 80 if configFile := fileutils.FindPath(path, []string{"."}, nil); configFile != "" { 81 return configFile, nil 82 } 83 84 // Otherwise, search for the config/ folder using the same heuristics as above, and build 85 // an absolute path anchored there and joining the given input path (or plain filename). 86 if configFolder, found := fileutils.FindDir("config"); found { 87 return filepath.Join(configFolder, path), nil 88 } 89 90 // Fail altogether if we can't even find the config/ folder. This should only happen if 91 // the executable is relocated away from the supporting files. 92 return "", fmt.Errorf("failed to find config file %s", path) 93 } 94 95 // Set replaces the current configuration in its entirety and updates the backing store. 96 func (fs *FileStore) Set(newCfg *model.Config) (*model.Config, error) { 97 return fs.commonStore.set(newCfg, func(cfg *model.Config) error { 98 if *fs.config.ClusterSettings.Enable && *fs.config.ClusterSettings.ReadOnlyConfig { 99 return ErrReadOnlyConfiguration 100 } 101 102 return fs.commonStore.validate(cfg) 103 }, fs.persist) 104 } 105 106 // persist writes the configuration to the configured file. 107 func (fs *FileStore) persist(cfg *model.Config) error { 108 fs.stopWatcher() 109 110 b, err := marshalConfig(cfg) 111 if err != nil { 112 return errors.Wrap(err, "failed to serialize") 113 } 114 115 err = ioutil.WriteFile(fs.path, b, 0644) 116 if err != nil { 117 return errors.Wrap(err, "failed to write file") 118 } 119 120 if fs.watch { 121 if err = fs.startWatcher(); err != nil { 122 mlog.Error("failed to start config watcher", mlog.String("path", fs.path), mlog.Err(err)) 123 } 124 } 125 126 return nil 127 } 128 129 // Load updates the current configuration from the backing store. 130 func (fs *FileStore) Load() (err error) { 131 var needsSave bool 132 var f io.ReadCloser 133 134 f, err = os.Open(fs.path) 135 if os.IsNotExist(err) { 136 needsSave = true 137 defaultCfg := &model.Config{} 138 defaultCfg.SetDefaults() 139 140 var defaultCfgBytes []byte 141 defaultCfgBytes, err = marshalConfig(defaultCfg) 142 if err != nil { 143 return errors.Wrap(err, "failed to serialize default config") 144 } 145 146 f = ioutil.NopCloser(bytes.NewReader(defaultCfgBytes)) 147 148 } else if err != nil { 149 return errors.Wrapf(err, "failed to open %s for reading", fs.path) 150 } 151 defer func() { 152 closeErr := f.Close() 153 if err == nil && closeErr != nil { 154 err = errors.Wrap(closeErr, "failed to close") 155 } 156 }() 157 158 return fs.commonStore.load(f, needsSave, fs.commonStore.validate, fs.persist) 159 } 160 161 // GetFile fetches the contents of a previously persisted configuration file. 162 func (fs *FileStore) GetFile(name string) ([]byte, error) { 163 resolvedPath := filepath.Join(filepath.Dir(fs.path), name) 164 165 data, err := ioutil.ReadFile(resolvedPath) 166 if err != nil { 167 return nil, errors.Wrapf(err, "failed to read file from %s", resolvedPath) 168 } 169 170 return data, nil 171 } 172 173 // SetFile sets or replaces the contents of a configuration file. 174 func (fs *FileStore) SetFile(name string, data []byte) error { 175 resolvedPath := filepath.Join(filepath.Dir(fs.path), name) 176 177 err := ioutil.WriteFile(resolvedPath, data, 0777) 178 if err != nil { 179 return errors.Wrapf(err, "failed to write file to %s", resolvedPath) 180 } 181 182 return nil 183 } 184 185 // HasFile returns true if the given file was previously persisted. 186 func (fs *FileStore) HasFile(name string) (bool, error) { 187 resolvedPath := filepath.Join(filepath.Dir(fs.path), name) 188 189 _, err := os.Stat(resolvedPath) 190 if err != nil && os.IsNotExist(err) { 191 return false, nil 192 } else if err != nil { 193 return false, errors.Wrap(err, "failed to check if file exists") 194 } 195 196 return true, nil 197 } 198 199 // RemoveFile removes a previously persisted configuration file. 200 func (fs *FileStore) RemoveFile(name string) error { 201 resolvedPath := filepath.Join(filepath.Dir(fs.path), name) 202 203 err := os.Remove(resolvedPath) 204 if os.IsNotExist(err) { 205 return nil 206 } 207 if err != nil { 208 return errors.Wrap(err, "failed to remove file") 209 } 210 211 return err 212 } 213 214 // startWatcher starts a watcher to monitor for external config file changes. 215 func (fs *FileStore) startWatcher() error { 216 if fs.watcher != nil { 217 return nil 218 } 219 220 watcher, err := newWatcher(fs.path, func() { 221 if err := fs.Load(); err != nil { 222 mlog.Error("failed to reload file on change", mlog.String("path", fs.path), mlog.Err(err)) 223 } 224 }) 225 if err != nil { 226 return err 227 } 228 229 fs.watcher = watcher 230 231 return nil 232 } 233 234 // stopWatcher stops any previously started watcher. 235 func (fs *FileStore) stopWatcher() { 236 if fs.watcher == nil { 237 return 238 } 239 240 if err := fs.watcher.Close(); err != nil { 241 mlog.Error("failed to close watcher", mlog.Err(err)) 242 } 243 fs.watcher = nil 244 } 245 246 // String returns the path to the file backing the config. 247 func (fs *FileStore) String() string { 248 return "file://" + fs.path 249 } 250 251 // Close cleans up resources associated with the store. 252 func (fs *FileStore) Close() error { 253 fs.configLock.Lock() 254 defer fs.configLock.Unlock() 255 256 fs.stopWatcher() 257 258 return nil 259 }