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