Update doc: add current dir in config.json discovery logic.
[src/xds/xds-server.git] / lib / xdsconfig / fileconfig.go
1 package xdsconfig
2
3 import (
4         "encoding/json"
5         "fmt"
6         "os"
7         "os/user"
8         "path"
9         "path/filepath"
10         "regexp"
11         "strings"
12 )
13
14 type SyncThingConf struct {
15         Home       string `json:"home"`
16         GuiAddress string `json:"gui-address"`
17         GuiAPIKey  string `json:"gui-apikey"`
18 }
19
20 type FileConfig struct {
21         WebAppDir    string        `json:"webAppDir"`
22         ShareRootDir string        `json:"shareRootDir"`
23         HTTPPort     string        `json:"httpPort"`
24         SThgConf     SyncThingConf `json:"syncthing"`
25 }
26
27 // getConfigFromFile reads configuration from a config file.
28 // Order to determine which config file is used:
29 //  1/ from command line option: "--config myConfig.json"
30 //  2/ $HOME/.xds/config.json file
31 //  3/ <current_dir>/agent-config.json file
32 //  4/ <xds-server executable dir>/config.json file
33
34 func updateConfigFromFile(c *Config, confFile string) error {
35
36         searchIn := make([]string, 0, 3)
37         if confFile != "" {
38                 searchIn = append(searchIn, confFile)
39         }
40         if usr, err := user.Current(); err == nil {
41                 searchIn = append(searchIn, path.Join(usr.HomeDir, ".xds", "config.json"))
42         }
43         cwd, err := os.Getwd()
44         if err == nil {
45                 searchIn = append(searchIn, path.Join(cwd, "config.json"))
46         }
47         exePath, err := filepath.Abs(filepath.Dir(os.Args[0]))
48         if err == nil {
49                 searchIn = append(searchIn, path.Join(exePath, "config.json"))
50         }
51
52         var cFile *string
53         for _, p := range searchIn {
54                 if _, err := os.Stat(p); err == nil {
55                         cFile = &p
56                         break
57                 }
58         }
59         if cFile == nil {
60                 // No config file found
61                 return nil
62         }
63
64         c.Log.Infof("Use config file: %s", *cFile)
65
66         // TODO move on viper package to support comments in JSON and also
67         // bind with flags (command line options)
68         // see https://github.com/spf13/viper#working-with-flags
69
70         fd, _ := os.Open(*cFile)
71         defer fd.Close()
72         fCfg := FileConfig{}
73         if err := json.NewDecoder(fd).Decode(&fCfg); err != nil {
74                 return err
75         }
76         c.fileConf = fCfg
77
78         // Support environment variables (IOW ${MY_ENV_VAR} syntax) in config.json
79         // TODO: better to use reflect package to iterate on fields and be more generic
80         var rep string
81         if rep, err = resolveEnvVar(fCfg.WebAppDir); err != nil {
82                 return err
83         }
84         fCfg.WebAppDir = path.Clean(rep)
85
86         if rep, err = resolveEnvVar(fCfg.ShareRootDir); err != nil {
87                 return err
88         }
89         fCfg.ShareRootDir = path.Clean(rep)
90
91         if rep, err = resolveEnvVar(fCfg.SThgConf.Home); err != nil {
92                 return err
93         }
94         fCfg.SThgConf.Home = path.Clean(rep)
95
96         // Config file settings overwrite default config
97
98         if fCfg.WebAppDir != "" {
99                 c.WebAppDir = strings.Trim(fCfg.WebAppDir, " ")
100         }
101         // Is it a full path ?
102         if !strings.HasPrefix(c.WebAppDir, "/") && exePath != "" {
103                 // Check first from current directory
104                 for _, rootD := range []string{cwd, exePath} {
105                         ff := path.Join(rootD, c.WebAppDir, "index.html")
106                         if exists(ff) {
107                                 c.WebAppDir = path.Join(rootD, c.WebAppDir)
108                                 break
109                         }
110                 }
111         }
112
113         if fCfg.ShareRootDir != "" {
114                 c.ShareRootDir = fCfg.ShareRootDir
115         }
116
117         if fCfg.HTTPPort != "" {
118                 c.HTTPPort = fCfg.HTTPPort
119         }
120
121         return nil
122 }
123
124 // resolveEnvVar Resolved environment variable regarding the syntax ${MYVAR}
125 func resolveEnvVar(s string) (string, error) {
126         re := regexp.MustCompile("\\${(.*)}")
127         vars := re.FindAllStringSubmatch(s, -1)
128         res := s
129         for _, v := range vars {
130                 val := os.Getenv(v[1])
131                 if val == "" {
132                         return res, fmt.Errorf("ERROR: %s env variable not defined", v[1])
133                 }
134
135                 rer := regexp.MustCompile("\\${" + v[1] + "}")
136                 res = rer.ReplaceAllString(res, val)
137         }
138
139         return res, nil
140 }
141
142 // exists returns whether the given file or directory exists or not
143 func exists(path string) bool {
144         _, err := os.Stat(path)
145         if err == nil {
146                 return true
147         }
148         if os.IsNotExist(err) {
149                 return false
150         }
151         return true
152 }