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  }