aboutsummaryrefslogtreecommitdiffstats
path: root/lib/xdsconfig/fileconfig.go
blob: 3daf77c07537d652158dd9c1363b30f66339edda (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package xdsconfig

import (
	"encoding/json"
	"fmt"
	"os"
	"os/user"
	"path"
	"path/filepath"
	"regexp"
	"strings"
)

type SyncThingConf struct {
	BinDir     string `json:"binDir"`
	Home       string `json:"home"`
	GuiAddress string `json:"gui-address"`
	GuiAPIKey  string `json:"gui-apikey"`
}

type FileConfig struct {
	WebAppDir    string         `json:"webAppDir"`
	ShareRootDir string         `json:"shareRootDir"`
	HTTPPort     string         `json:"httpPort"`
	SThgConf     *SyncThingConf `json:"syncthing"`
	LogsDir      string         `json:"logsDir"`
}

// getConfigFromFile reads configuration from a config file.
// Order to determine which config file is used:
//  1/ from command line option: "--config myConfig.json"
//  2/ $HOME/.xds/config.json file
//  3/ <current_dir>/config.json file
//  4/ <xds-server executable dir>/config.json file

func updateConfigFromFile(c *Config, confFile string) error {

	searchIn := make([]string, 0, 3)
	if confFile != "" {
		searchIn = append(searchIn, confFile)
	}
	if usr, err := user.Current(); err == nil {
		searchIn = append(searchIn, path.Join(usr.HomeDir, ".xds", "config.json"))
	}
	cwd, err := os.Getwd()
	if err == nil {
		searchIn = append(searchIn, path.Join(cwd, "config.json"))
	}
	exePath, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err == nil {
		searchIn = append(searchIn, path.Join(exePath, "config.json"))
	}

	var cFile *string
	for _, p := range searchIn {
		if _, err := os.Stat(p); err == nil {
			cFile = &p
			break
		}
	}
	if cFile == nil {
		// No config file found
		return nil
	}

	c.Log.Infof("Use config file: %s", *cFile)

	// TODO move on viper package to support comments in JSON and also
	// bind with flags (command line options)
	// see https://github.com/spf13/viper#working-with-flags

	fd, _ := os.Open(*cFile)
	defer fd.Close()
	fCfg := FileConfig{}
	if err := json.NewDecoder(fd).Decode(&fCfg); err != nil {
		return err
	}
	c.FileConf = fCfg

	// Support environment variables (IOW ${MY_ENV_VAR} syntax) in config.json
	// TODO: better to use reflect package to iterate on fields and be more generic
	var rep string
	if rep, err = resolveEnvVar(fCfg.WebAppDir); err != nil {
		return err
	}
	fCfg.WebAppDir = path.Clean(rep)

	if rep, err = resolveEnvVar(fCfg.ShareRootDir); err != nil {
		return err
	}
	fCfg.ShareRootDir = path.Clean(rep)

	if rep, err = resolveEnvVar(fCfg.SThgConf.Home); err != nil {
		return err
	}
	fCfg.SThgConf.Home = path.Clean(rep)

	// Config file settings overwrite default config

	if fCfg.WebAppDir != "" {
		c.WebAppDir = strings.Trim(fCfg.WebAppDir, " ")
	}
	// Is it a full path ?
	if !strings.HasPrefix(c.WebAppDir, "/") && exePath != "" {
		// Check first from current directory
		for _, rootD := range []string{cwd, exePath} {
			ff := path.Join(rootD, c.WebAppDir, "index.html")
			if exists(ff) {
				c.WebAppDir = path.Join(rootD, c.WebAppDir)
				break
			}
		}
	}

	if fCfg.ShareRootDir != "" {
		c.ShareRootDir = fCfg.ShareRootDir
	}

	if fCfg.HTTPPort != "" {
		c.HTTPPort = fCfg.HTTPPort
	}

	return nil
}

// resolveEnvVar Resolved environment variable regarding the syntax ${MYVAR}
func resolveEnvVar(s string) (string, error) {
	re := regexp.MustCompile("\\${(.*)}")
	vars := re.FindAllStringSubmatch(s, -1)
	res := s
	for _, v := range vars {
		val := os.Getenv(v[1])
		if val == "" {
			return res, fmt.Errorf("ERROR: %s env variable not defined", v[1])
		}

		rer := regexp.MustCompile("\\${" + v[1] + "}")
		res = rer.ReplaceAllString(res, val)
	}

	return res, nil
}

// exists returns whether the given file or directory exists or not
func exists(path string) bool {
	_, err := os.Stat(path)
	if err == nil {
		return true
	}
	if os.IsNotExist(err) {
		return false
	}
	return true
}