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
Этот коммит содержится в:
Jesse Hallam
2018-05-29 10:55:49 -04:00
коммит произвёл Harrison Healey
родитель 4f5fdb6f89
Коммит d3cf110620
3 изменённых файлов: 252 добавлений и 44 удалений

Просмотреть файл

@@ -6,19 +6,10 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"path/filepath"
"syscall" "syscall"
)
func findMattermostBinary() string { "github.com/mattermost/mattermost-server/utils"
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"
}
func main() { func main() {
// Print angry message to use mattermost command directly // 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 := os.Args
args[0] = "mattermost" args[0] = "mattermost"
args = append(args, "--platform") 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.") fmt.Println("Could not start Mattermost, use the mattermost command directly.")
} }
} }

Просмотреть файл

@@ -32,36 +32,94 @@ const (
LOG_FILENAME = "mattermost.log" LOG_FILENAME = "mattermost.log"
) )
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
}
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)
}
}
}
if binaryDir != "" {
for _, baseSearchPath := range baseSearchPaths {
searchPaths = append(
searchPaths,
filepath.Join(binaryDir, baseSearchPath),
)
}
}
for _, parent := range searchPaths {
found, err := filepath.Abs(filepath.Join(parent, path))
if err != nil {
continue
} else if fileInfo, err := os.Stat(found); err == nil {
if filter != nil && filter(fileInfo) {
return found
} else {
return found
}
}
}
return ""
}
// FindConfigFile attempts to find an existing configuration file. fileName can be an absolute or // 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 // relative path or name such as "/opt/mattermost/config.json" or simply "config.json". An empty
// string is returned if no configuration is found. // string is returned if no configuration is found.
func FindConfigFile(fileName string) (path string) { func FindConfigFile(fileName string) (path string) {
if filepath.IsAbs(fileName) { found := FindFile(filepath.Join("config", fileName))
if _, err := os.Stat(fileName); err == nil { if found == "" {
return fileName found = FindPath(fileName, []string{"."}, nil)
}
} 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 ""
return found
} }
// FindDir looks for the given directory in nearby ancestors, falling back to `./` if not 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) { func FindDir(dir string) (string, bool) {
for _, parent := range []string{".", "..", "../..", "../../.."} { found := FindPath(dir, commonBaseSearchPaths, func(fileInfo os.FileInfo) bool {
foundDir, err := filepath.Abs(filepath.Join(parent, dir)) return fileInfo.IsDir()
if err != nil { })
continue if found == "" {
} else if _, err := os.Stat(foundDir); err == nil { return "./", false
return foundDir, true
}
} }
return "./", false
return found, true
} }
func MloggerConfigFromLoggerConfig(s *model.LogSettings) *mlog.LoggerConfiguration { func MloggerConfigFromLoggerConfig(s *model.LogSettings) *mlog.LoggerConfiguration {

Просмотреть файл

@@ -46,20 +46,172 @@ func TestTimezoneConfig(t *testing.T) {
} }
func TestFindConfigFile(t *testing.T) { func TestFindConfigFile(t *testing.T) {
dir, err := ioutil.TempDir("", "") t.Run("config.json in current working directory, not inside config/", func(t *testing.T) {
require.NoError(t, err) // Force a unique working directory
defer os.RemoveAll(dir) cwd, err := ioutil.TempDir("", "")
require.NoError(t, err)
defer os.RemoveAll(cwd)
path := filepath.Join(dir, "config.json") prevDir, err := os.Getwd()
require.NoError(t, ioutil.WriteFile(path, []byte("{}"), 0600)) require.NoError(t, err)
defer os.Chdir(prevDir)
os.Chdir(cwd)
assert.Equal(t, path, FindConfigFile(path)) configJson, err := filepath.Abs("config.json")
require.NoError(t, err)
require.NoError(t, ioutil.WriteFile(configJson, []byte("{}"), 0600))
prevDir, err := os.Getwd() // Relative paths end up getting symlinks fully resolved.
require.NoError(t, err) configJsonResolved, err := filepath.EvalSymlinks(configJson)
defer os.Chdir(prevDir) require.NoError(t, err)
os.Chdir(dir)
assert.Equal(t, path, FindConfigFile(path)) assert.Equal(t, configJsonResolved, FindConfigFile("config.json"))
})
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",
"",
},
}
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",
"../",
},
}
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) { func TestConfigFromEnviroVars(t *testing.T) {