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