MM-6839: searching for paths relative to executable (#8915)

* 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

* add unit tests for utils.FindFile to exclude directories

* fix filtering out directories in FindFile

* fix platform invoking ./bin/mattermost
Этот коммит содержится в:
Jesse Hallam
2018-06-06 10:18:24 -04:00
коммит произвёл Christopher Speller
родитель 7df3498947
Коммит 0ba0af889e
3 изменённых файлов: 366 добавлений и 45 удалений

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

@@ -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,15 @@ 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 {
fmt.Println("Could not start Mattermost, use the mattermost command directly.")
realMattermost := utils.FindFile("mattermost")
if realMattermost == "" {
realMattermost = utils.FindFile("bin/mattermost")
}
if realMattermost == "" {
fmt.Println("Could not start Mattermost, use the mattermost command directly: failed to find mattermost")
} else if err := syscall.Exec(realMattermost, args, nil); err != nil {
fmt.Printf("Could not start Mattermost, use the mattermost command directly: %s\n", err.Error())
}
}

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

@@ -32,36 +32,96 @@ const (
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 {
if filter(fileInfo) {
return found
}
} else {
return found
}
}
}
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) {
if filepath.IsAbs(fileName) {
if _, err := os.Stat(fileName); err == nil {
return fileName
}
} 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
}
}
found := FindFile(filepath.Join("config", fileName))
if found == "" {
found = FindPath(fileName, []string{"."}, nil)
}
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) {
for _, parent := range []string{".", "..", "../..", "../../.."} {
foundDir, err := filepath.Abs(filepath.Join(parent, dir))
if err != nil {
continue
} else if _, err := os.Stat(foundDir); err == nil {
return foundDir, true
}
found := FindPath(dir, commonBaseSearchPaths, func(fileInfo os.FileInfo) bool {
return fileInfo.IsDir()
})
if found == "" {
return "./", false
}
return "./", false
return found, true
}
func MloggerConfigFromLoggerConfig(s *model.LogSettings) *mlog.LoggerConfiguration {

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

@@ -5,6 +5,7 @@ package utils
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
@@ -46,20 +47,281 @@ 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)
path := filepath.Join(dir, "config.json")
require.NoError(t, ioutil.WriteFile(path, []byte("{}"), 0600))
prevDir, err := os.Getwd()
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()
require.NoError(t, err)
defer os.Chdir(prevDir)
os.Chdir(dir)
assert.Equal(t, path, FindConfigFile(path))
// Relative paths end up getting symlinks fully resolved.
configJsonResolved, err := filepath.EvalSymlinks(configJson)
require.NoError(t, err)
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 TestFindFile(t *testing.T) {
t.Run("files from various paths", func(t *testing.T) {
// Create the following directory structure:
// tmpDir1/
// file1.json
// file2.xml
// other.txt
// tmpDir2/
// other.txt/ [directory]
// tmpDir3/
// tmpDir4/
// tmpDir5/
tmpDir1, err := ioutil.TempDir("", "")
require.NoError(t, err)
defer os.RemoveAll(tmpDir1)
tmpDir2, err := ioutil.TempDir(tmpDir1, "")
require.NoError(t, err)
err = os.Mkdir(filepath.Join(tmpDir2, "other.txt"), 0700)
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)
type testCase struct {
Description string
Cwd *string
FileName string
Expected string
}
testCases := []testCase{}
for _, fileName := range []string{"file1.json", "file2.xml", "other.txt"} {
filePath := filepath.Join(tmpDir1, fileName)
require.NoError(t, ioutil.WriteFile(filePath, []byte("{}"), 0600))
// Relative paths end up getting symlinks fully resolved, so use this below as necessary.
filePathResolved, err := filepath.EvalSymlinks(filePath)
require.NoError(t, err)
testCases = append(testCases, []testCase{
{
fmt.Sprintf("absolute path to %s", fileName),
nil,
filePath,
filePath,
},
{
fmt.Sprintf("absolute path to %s from containing directory", fileName),
&tmpDir1,
filePath,
filePath,
},
{
fmt.Sprintf("relative path to %s from containing directory", fileName),
&tmpDir1,
fileName,
filePathResolved,
},
{
fmt.Sprintf("%s: subdirectory of containing directory", fileName),
&tmpDir2,
fileName,
filePathResolved,
},
{
fmt.Sprintf("%s: twice-nested subdirectory of containing directory", fileName),
&tmpDir3,
fileName,
filePathResolved,
},
{
fmt.Sprintf("%s: thrice-nested subdirectory of containing directory", fileName),
&tmpDir4,
fileName,
filePathResolved,
},
{
fmt.Sprintf("%s: can't find from four nesting levels deep", fileName),
&tmpDir5,
fileName,
"",
},
}...)
}
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, FindFile(testCase.FileName))
})
}
})
}
func TestConfigFromEnviroVars(t *testing.T) {