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