summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Hallam <jesse.hallam@gmail.com>2018-05-29 10:55:49 -0400
committerHarrison Healey <harrisonmhealey@gmail.com>2018-05-29 10:55:49 -0400
commitd3cf110620033f8831a55a3fd911d7864b6aab4a (patch)
tree3b25cede4bf947227f1ec3206c2b1ab55db3f15b
parent4f5fdb6f8959cdf7d7b2cb93ea77960429bcac8a (diff)
downloadchat-d3cf110620033f8831a55a3fd911d7864b6aab4a.tar.gz
chat-d3cf110620033f8831a55a3fd911d7864b6aab4a.tar.bz2
chat-d3cf110620033f8831a55a3fd911d7864b6aab4a.zip
MM-6839: search relative to executable (#8853)
* MM-6839: searching for paths relative to executable In addition to searching relative to the current working directory, also search relative to the location of the binary. This helps locate config and i18n files when invoking an absolute path to the mattermost binary. * MM-6839: find mattermost/ binary using utils.FindFile
-rw-r--r--cmd/platform/main.go22
-rw-r--r--utils/config.go100
-rw-r--r--utils/config_test.go174
3 files changed, 252 insertions, 44 deletions
diff --git a/cmd/platform/main.go b/cmd/platform/main.go
index b5ea51920..6d9952c70 100644
--- a/cmd/platform/main.go
+++ b/cmd/platform/main.go
@@ -6,19 +6,10 @@ package main
import (
"fmt"
"os"
- "path/filepath"
"syscall"
-)
-func findMattermostBinary() string {
- for _, file := range []string{"./mattermost", "../mattermost", "./bin/mattermost"} {
- path, _ := filepath.Abs(file)
- if stat, err := os.Stat(path); err == nil && !stat.IsDir() {
- return path
- }
- }
- return "./mattermost"
-}
+ "github.com/mattermost/mattermost-server/utils"
+)
func main() {
// Print angry message to use mattermost command directly
@@ -33,7 +24,14 @@ The platform binary will be removed in a future version.
args := os.Args
args[0] = "mattermost"
args = append(args, "--platform")
- if err := syscall.Exec(findMattermostBinary(), args, nil); err != nil {
+
+ realMattermost := utils.FindFile("mattermost")
+ if realMattermost == "" {
+ // This will still fail, of course.
+ realMattermost = "./mattermost"
+ }
+
+ if err := syscall.Exec(utils.FindFile("mattermost"), args, nil); err != nil {
fmt.Println("Could not start Mattermost, use the mattermost command directly.")
}
}
diff --git a/utils/config.go b/utils/config.go
index 00fd2642a..da1918490 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -32,36 +32,94 @@ const (
LOG_FILENAME = "mattermost.log"
)
-// FindConfigFile attempts to find an existing configuration file. fileName can be an absolute or
-// relative path or name such as "/opt/mattermost/config.json" or simply "config.json". An empty
-// string is returned if no configuration is found.
-func FindConfigFile(fileName string) (path string) {
- if filepath.IsAbs(fileName) {
- if _, err := os.Stat(fileName); err == nil {
- return fileName
+var (
+ commonBaseSearchPaths = []string{
+ ".",
+ "..",
+ "../..",
+ "../../..",
+ }
+)
+
+func FindPath(path string, baseSearchPaths []string, filter func(os.FileInfo) bool) string {
+ if filepath.IsAbs(path) {
+ if _, err := os.Stat(path); err == nil {
+ return path
}
- } else {
- for _, dir := range []string{"./config", "../config", "../../config", "../../../config", "."} {
- path, _ := filepath.Abs(filepath.Join(dir, fileName))
- if _, err := os.Stat(path); err == nil {
- return path
+
+ return ""
+ }
+
+ searchPaths := []string{}
+ for _, baseSearchPath := range baseSearchPaths {
+ searchPaths = append(searchPaths, baseSearchPath)
+ }
+
+ // Additionally attempt to search relative to the location of the running binary.
+ var binaryDir string
+ if exe, err := os.Executable(); err == nil {
+ if exe, err = filepath.EvalSymlinks(exe); err == nil {
+ if exe, err = filepath.Abs(exe); err == nil {
+ binaryDir = filepath.Dir(exe)
}
}
}
- return ""
-}
+ if binaryDir != "" {
+ for _, baseSearchPath := range baseSearchPaths {
+ searchPaths = append(
+ searchPaths,
+ filepath.Join(binaryDir, baseSearchPath),
+ )
+ }
+ }
-// FindDir looks for the given directory in nearby ancestors, falling back to `./` if not found.
-func FindDir(dir string) (string, bool) {
- for _, parent := range []string{".", "..", "../..", "../../.."} {
- foundDir, err := filepath.Abs(filepath.Join(parent, dir))
+ for _, parent := range searchPaths {
+ found, err := filepath.Abs(filepath.Join(parent, path))
if err != nil {
continue
- } else if _, err := os.Stat(foundDir); err == nil {
- return foundDir, true
+ } else if fileInfo, err := os.Stat(found); err == nil {
+ if filter != nil && filter(fileInfo) {
+ return found
+ } else {
+ return found
+ }
}
}
- return "./", false
+
+ return ""
+}
+
+// FindConfigFile attempts to find an existing configuration file. fileName can be an absolute or
+// relative path or name such as "/opt/mattermost/config.json" or simply "config.json". An empty
+// string is returned if no configuration is found.
+func FindConfigFile(fileName string) (path string) {
+ found := FindFile(filepath.Join("config", fileName))
+ if found == "" {
+ found = FindPath(fileName, []string{"."}, nil)
+ }
+
+ return found
+}
+
+// FindFile looks for the given file in nearby ancestors relative to the current working
+// directory as well as the directory of the executable.
+func FindFile(path string) string {
+ return FindPath(path, commonBaseSearchPaths, func(fileInfo os.FileInfo) bool {
+ return !fileInfo.IsDir()
+ })
+}
+
+// FindDir looks for the given directory in nearby ancestors relative to the current working
+// directory as well as the directory of the executable, falling back to `./` if not found.
+func FindDir(dir string) (string, bool) {
+ found := FindPath(dir, commonBaseSearchPaths, func(fileInfo os.FileInfo) bool {
+ return fileInfo.IsDir()
+ })
+ if found == "" {
+ return "./", false
+ }
+
+ return found, true
}
func MloggerConfigFromLoggerConfig(s *model.LogSettings) *mlog.LoggerConfiguration {
diff --git a/utils/config_test.go b/utils/config_test.go
index 75bbc420f..f57add303 100644
--- a/utils/config_test.go
+++ b/utils/config_test.go
@@ -46,20 +46,172 @@ func TestTimezoneConfig(t *testing.T) {
}
func TestFindConfigFile(t *testing.T) {
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
+ t.Run("config.json in current working directory, not inside config/", func(t *testing.T) {
+ // Force a unique working directory
+ cwd, err := ioutil.TempDir("", "")
+ require.NoError(t, err)
+ defer os.RemoveAll(cwd)
+
+ prevDir, err := os.Getwd()
+ require.NoError(t, err)
+ defer os.Chdir(prevDir)
+ os.Chdir(cwd)
+
+ configJson, err := filepath.Abs("config.json")
+ require.NoError(t, err)
+ require.NoError(t, ioutil.WriteFile(configJson, []byte("{}"), 0600))
+
+ // Relative paths end up getting symlinks fully resolved.
+ configJsonResolved, err := filepath.EvalSymlinks(configJson)
+ require.NoError(t, err)
+
+ assert.Equal(t, configJsonResolved, FindConfigFile("config.json"))
+ })
- path := filepath.Join(dir, "config.json")
- require.NoError(t, ioutil.WriteFile(path, []byte("{}"), 0600))
+ t.Run("config/config.json from various paths", func(t *testing.T) {
+ // Create the following directory structure:
+ // tmpDir1/
+ // config/
+ // config.json
+ // tmpDir2/
+ // tmpDir3/
+ // tmpDir4/
+ // tmpDir5/
+ tmpDir1, err := ioutil.TempDir("", "")
+ require.NoError(t, err)
+ defer os.RemoveAll(tmpDir1)
+
+ err = os.Mkdir(filepath.Join(tmpDir1, "config"), 0700)
+ require.NoError(t, err)
+
+ tmpDir2, err := ioutil.TempDir(tmpDir1, "")
+ require.NoError(t, err)
+
+ tmpDir3, err := ioutil.TempDir(tmpDir2, "")
+ require.NoError(t, err)
+
+ tmpDir4, err := ioutil.TempDir(tmpDir3, "")
+ require.NoError(t, err)
+
+ tmpDir5, err := ioutil.TempDir(tmpDir4, "")
+ require.NoError(t, err)
+
+ configJson := filepath.Join(tmpDir1, "config", "config.json")
+ require.NoError(t, ioutil.WriteFile(configJson, []byte("{}"), 0600))
+
+ // Relative paths end up getting symlinks fully resolved, so use this below as necessary.
+ configJsonResolved, err := filepath.EvalSymlinks(configJson)
+ require.NoError(t, err)
+
+ testCases := []struct {
+ Description string
+ Cwd *string
+ FileName string
+ Expected string
+ }{
+ {
+ "absolute path to config.json",
+ nil,
+ configJson,
+ configJson,
+ },
+ {
+ "absolute path to config.json from directory containing config.json",
+ &tmpDir1,
+ configJson,
+ configJson,
+ },
+ {
+ "relative path to config.json from directory containing config.json",
+ &tmpDir1,
+ "config.json",
+ configJsonResolved,
+ },
+ {
+ "subdirectory of directory containing config.json",
+ &tmpDir2,
+ "config.json",
+ configJsonResolved,
+ },
+ {
+ "twice-nested subdirectory of directory containing config.json",
+ &tmpDir3,
+ "config.json",
+ configJsonResolved,
+ },
+ {
+ "thrice-nested subdirectory of directory containing config.json",
+ &tmpDir4,
+ "config.json",
+ configJsonResolved,
+ },
+ {
+ "can't find from four nesting levels deep",
+ &tmpDir5,
+ "config.json",
+ "",
+ },
+ }
- assert.Equal(t, path, FindConfigFile(path))
+ for _, testCase := range testCases {
+ t.Run(testCase.Description, func(t *testing.T) {
+ if testCase.Cwd != nil {
+ prevDir, err := os.Getwd()
+ require.NoError(t, err)
+ defer os.Chdir(prevDir)
+ os.Chdir(*testCase.Cwd)
+ }
+
+ assert.Equal(t, testCase.Expected, FindConfigFile(testCase.FileName))
+ })
+ }
+ })
+
+ t.Run("config/config.json relative to executable", func(t *testing.T) {
+ osExecutable, err := os.Executable()
+ require.NoError(t, err)
+ osExecutableDir := filepath.Dir(osExecutable)
+
+ // Force a working directory different than the executable.
+ cwd, err := ioutil.TempDir("", "")
+ require.NoError(t, err)
+ defer os.RemoveAll(cwd)
+
+ prevDir, err := os.Getwd()
+ require.NoError(t, err)
+ defer os.Chdir(prevDir)
+ os.Chdir(cwd)
+
+ testCases := []struct {
+ Description string
+ RelativePath string
+ }{
+ {
+ "config/config.json",
+ ".",
+ },
+ {
+ "../config/config.json",
+ "../",
+ },
+ }
- prevDir, err := os.Getwd()
- require.NoError(t, err)
- defer os.Chdir(prevDir)
- os.Chdir(dir)
- assert.Equal(t, path, FindConfigFile(path))
+ for _, testCase := range testCases {
+ t.Run(testCase.Description, func(t *testing.T) {
+ // Install the config in config/config.json relative to the executable
+ configJson := filepath.Join(osExecutableDir, testCase.RelativePath, "config", "config.json")
+ require.NoError(t, os.Mkdir(filepath.Dir(configJson), 0700))
+ require.NoError(t, ioutil.WriteFile(configJson, []byte("{}"), 0600))
+ defer os.RemoveAll(filepath.Dir(configJson))
+
+ // Relative paths end up getting symlinks fully resolved.
+ configJsonResolved, err := filepath.EvalSymlinks(configJson)
+ require.NoError(t, err)
+
+ assert.Equal(t, configJsonResolved, FindConfigFile("config.json"))
+ })
+ }
+ })
}
func TestConfigFromEnviroVars(t *testing.T) {