Initial commit.
[src/xds/xds-agent.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 )
12
13 type SyncThingConf struct {
14         BinDir     string `json:"binDir"`
15         Home       string `json:"home"`
16         GuiAddress string `json:"gui-address"`
17         GuiAPIKey  string `json:"gui-apikey"`
18 }
19
20 type FileConfig struct {
21         HTTPPort string         `json:"httpPort"`
22         SThgConf *SyncThingConf `json:"syncthing"`
23 }
24
25 // getConfigFromFile reads configuration from a config file.
26 // Order to determine which config file is used:
27 //  1/ from command line option: "--config myConfig.json"
28 //  2/ $HOME/.xds/agent-config.json file
29 //  3/ <current_dir>/agent-config.json file
30 //  4/ <executable dir>/agent-config.json file
31
32 func updateConfigFromFile(c *Config, confFile string) (*FileConfig, error) {
33
34         searchIn := make([]string, 0, 3)
35         if confFile != "" {
36                 searchIn = append(searchIn, confFile)
37         }
38         if usr, err := user.Current(); err == nil {
39                 searchIn = append(searchIn, path.Join(usr.HomeDir, ".xds", "agent-config.json"))
40         }
41         cwd, err := os.Getwd()
42         if err == nil {
43                 searchIn = append(searchIn, path.Join(cwd, "agent-config.json"))
44         }
45         exePath, err := filepath.Abs(filepath.Dir(os.Args[0]))
46         if err == nil {
47                 searchIn = append(searchIn, path.Join(exePath, "agent-config.json"))
48         }
49
50         var cFile *string
51         for _, p := range searchIn {
52                 if _, err := os.Stat(p); err == nil {
53                         cFile = &p
54                         break
55                 }
56         }
57         fCfg := FileConfig{}
58         if cFile == nil {
59                 // No config file found
60                 return &fCfg, 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         if err := json.NewDecoder(fd).Decode(&fCfg); err != nil {
72                 return nil, err
73         }
74
75         // Support environment variables (IOW ${MY_ENV_VAR} syntax) in agent-config.json
76         // TODO: better to use reflect package to iterate on fields and be more generic
77         var rep string
78
79         if rep, err = resolveEnvVar(fCfg.SThgConf.BinDir); err != nil {
80                 return nil, err
81         }
82         fCfg.SThgConf.BinDir = path.Clean(rep)
83
84         if rep, err = resolveEnvVar(fCfg.SThgConf.Home); err != nil {
85                 return nil, err
86         }
87         fCfg.SThgConf.Home = path.Clean(rep)
88
89         return &fCfg, nil
90 }
91
92 // resolveEnvVar Resolved environment variable regarding the syntax ${MYVAR}
93 func resolveEnvVar(s string) (string, error) {
94         re := regexp.MustCompile("\\${(.*)}")
95         vars := re.FindAllStringSubmatch(s, -1)
96         res := s
97         for _, v := range vars {
98                 val := os.Getenv(v[1])
99                 if val == "" {
100                         return res, fmt.Errorf("ERROR: %s env variable not defined", v[1])
101                 }
102
103                 rer := regexp.MustCompile("\\${" + v[1] + "}")
104                 res = rer.ReplaceAllString(res, val)
105         }
106
107         return res, nil
108 }
109
110 // exists returns whether the given file or directory exists or not
111 func exists(path string) bool {
112         _, err := os.Stat(path)
113         if err == nil {
114                 return true
115         }
116         if os.IsNotExist(err) {
117                 return false
118         }
119         return true
120 }