summaryrefslogtreecommitdiffstats
path: root/lib/xdsconfig
diff options
context:
space:
mode:
authorSebastien Douheret <sebastien.douheret@iot.bzh>2017-05-11 19:42:00 +0200
committerSebastien Douheret <sebastien.douheret@iot.bzh>2017-05-11 19:42:22 +0200
commitec7051e1da665206f594c7616ad381bfeaea333a (patch)
treeecc01ee358794c9d8c5fbb87d2f5b6ce3f60f431 /lib/xdsconfig
parentca3e1762832b27dc25cf90125b376c56e24e2db2 (diff)
Initial main commit.
Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
Diffstat (limited to 'lib/xdsconfig')
-rw-r--r--lib/xdsconfig/builderconfig.go50
-rw-r--r--lib/xdsconfig/config.go231
-rw-r--r--lib/xdsconfig/fileconfig.go133
-rw-r--r--lib/xdsconfig/folderconfig.go79
-rw-r--r--lib/xdsconfig/foldersconfig.go47
5 files changed, 540 insertions, 0 deletions
diff --git a/lib/xdsconfig/builderconfig.go b/lib/xdsconfig/builderconfig.go
new file mode 100644
index 0000000..c64fe9c
--- /dev/null
+++ b/lib/xdsconfig/builderconfig.go
@@ -0,0 +1,50 @@
+package xdsconfig
+
+import (
+ "errors"
+ "net"
+)
+
+// BuilderConfig represents the builder container configuration
+type BuilderConfig struct {
+ IP string `json:"ip"`
+ Port string `json:"port"`
+ SyncThingID string `json:"syncThingID"`
+}
+
+// NewBuilderConfig creates a new BuilderConfig instance
+func NewBuilderConfig(stID string) (BuilderConfig, error) {
+ // Do we really need it ? may be not accessible from client side
+ ip, err := getLocalIP()
+ if err != nil {
+ return BuilderConfig{}, err
+ }
+
+ b := BuilderConfig{
+ IP: ip, // TODO currently not used
+ Port: "", // TODO currently not used
+ SyncThingID: stID,
+ }
+ return b, nil
+}
+
+// Copy makes a real copy of BuilderConfig
+func (c *BuilderConfig) Copy(n BuilderConfig) {
+ // TODO
+}
+
+func getLocalIP() (string, error) {
+ addrs, err := net.InterfaceAddrs()
+ if err != nil {
+ return "", err
+ }
+ for _, address := range addrs {
+ // check the address type and if it is not a loopback the display it
+ if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
+ if ipnet.IP.To4() != nil {
+ return ipnet.IP.String(), nil
+ }
+ }
+ }
+ return "", errors.New("Cannot determined local IP")
+}
diff --git a/lib/xdsconfig/config.go b/lib/xdsconfig/config.go
new file mode 100644
index 0000000..df98439
--- /dev/null
+++ b/lib/xdsconfig/config.go
@@ -0,0 +1,231 @@
+package xdsconfig
+
+import (
+ "fmt"
+ "strings"
+
+ "os"
+
+ "time"
+
+ "github.com/Sirupsen/logrus"
+ "github.com/codegangsta/cli"
+ "github.com/iotbzh/xds-server/lib/syncthing"
+)
+
+// Config parameters (json format) of /config command
+type Config struct {
+ Version string `json:"version"`
+ APIVersion string `json:"apiVersion"`
+ VersionGitTag string `json:"gitTag"`
+ Builder BuilderConfig `json:"builder"`
+ Folders FoldersConfig `json:"folders"`
+
+ // Private / un-exported fields
+ progName string
+ fileConf FileConfig
+ WebAppDir string `json:"-"`
+ HTTPPort string `json:"-"`
+ ShareRootDir string `json:"-"`
+ Log *logrus.Logger `json:"-"`
+ SThg *st.SyncThing `json:"-"`
+}
+
+// Config default values
+const (
+ DefaultAPIVersion = "1"
+ DefaultPort = "8000"
+ DefaultShareDir = "/mnt/share"
+ DefaultLogLevel = "error"
+)
+
+// Init loads the configuration on start-up
+func Init(ctx *cli.Context) (Config, error) {
+ var err error
+
+ // Set logger level and formatter
+ log := ctx.App.Metadata["logger"].(*logrus.Logger)
+
+ logLevel := ctx.GlobalString("log")
+ if logLevel == "" {
+ logLevel = DefaultLogLevel
+ }
+ if log.Level, err = logrus.ParseLevel(logLevel); err != nil {
+ fmt.Printf("Invalid log level : \"%v\"\n", logLevel)
+ os.Exit(1)
+ }
+ log.Formatter = &logrus.TextFormatter{}
+
+ // Define default configuration
+ c := Config{
+ Version: ctx.App.Metadata["version"].(string),
+ APIVersion: DefaultAPIVersion,
+ VersionGitTag: ctx.App.Metadata["git-tag"].(string),
+ Builder: BuilderConfig{},
+ Folders: FoldersConfig{},
+
+ progName: ctx.App.Name,
+ WebAppDir: "webapp/dist",
+ HTTPPort: DefaultPort,
+ ShareRootDir: DefaultShareDir,
+ Log: log,
+ SThg: nil,
+ }
+
+ // config file settings overwrite default config
+ err = updateConfigFromFile(&c, ctx.GlobalString("config"))
+ if err != nil {
+ return Config{}, err
+ }
+
+ // Update location of shared dir if needed
+ if !dirExists(c.ShareRootDir) {
+ if err := os.MkdirAll(c.ShareRootDir, 0770); err != nil {
+ c.Log.Fatalf("No valid shared directory found (err=%v)", err)
+ }
+ }
+ c.Log.Infoln("Share root directory: ", c.ShareRootDir)
+
+ // FIXME - add a builder interface and support other builder type (eg. native)
+ builderType := "syncthing"
+
+ switch builderType {
+ case "syncthing":
+ // Syncthing settings only configurable from config.json file
+ stGuiAddr := c.fileConf.SThgConf.GuiAddress
+ stGuiApikey := c.fileConf.SThgConf.GuiAPIKey
+ if stGuiAddr == "" {
+ stGuiAddr = "http://localhost:8384"
+ }
+ if stGuiAddr[0:7] != "http://" {
+ stGuiAddr = "http://" + stGuiAddr
+ }
+
+ // Retry if connection fail
+ retry := 5
+ for retry > 0 {
+ c.SThg = st.NewSyncThing(stGuiAddr, stGuiApikey, c.Log)
+ if c.SThg != nil {
+ break
+ }
+ c.Log.Warningf("Establishing connection to Syncthing (retry %d/5)", retry)
+ time.Sleep(time.Second)
+ retry--
+ }
+ if c.SThg == nil {
+ c.Log.Fatalf("ERROR: cannot connect to Syncthing (url: %s)", stGuiAddr)
+ }
+
+ // Retrieve Syncthing config
+ id, err := c.SThg.IDGet()
+ if err != nil {
+ return Config{}, err
+ }
+
+ if c.Builder, err = NewBuilderConfig(id); err != nil {
+ c.Log.Fatalln(err)
+ }
+
+ // Retrieve initial Syncthing config
+ stCfg, err := c.SThg.ConfigGet()
+ if err != nil {
+ return Config{}, err
+ }
+ for _, stFld := range stCfg.Folders {
+ relativePath := strings.TrimPrefix(stFld.RawPath, c.ShareRootDir)
+ if relativePath == "" {
+ relativePath = stFld.RawPath
+ }
+ newFld := NewFolderConfig(stFld.ID, stFld.Label, c.ShareRootDir, strings.Trim(relativePath, "/"))
+ c.Folders = c.Folders.Update(FoldersConfig{newFld})
+ }
+
+ default:
+ log.Fatalln("Unsupported builder type")
+ }
+
+ return c, nil
+}
+
+// GetFolderFromID retrieves the Folder config from id
+func (c *Config) GetFolderFromID(id string) *FolderConfig {
+ if idx := c.Folders.GetIdx(id); idx != -1 {
+ return &c.Folders[idx]
+ }
+ return nil
+}
+
+// UpdateAll updates all the current configuration
+func (c *Config) UpdateAll(newCfg Config) error {
+ return fmt.Errorf("Not Supported")
+ /*
+ if err := VerifyConfig(newCfg); err != nil {
+ return err
+ }
+
+ // TODO: c.Builder = c.Builder.Update(newCfg.Builder)
+ c.Folders = c.Folders.Update(newCfg.Folders)
+
+ // SEB A SUP model.NotifyListeners(c, NotifyFoldersChange, FolderConfig{})
+ // FIXME To be tested & improved error handling
+ for _, f := range c.Folders {
+ if err := c.SThg.FolderChange(st.FolderChangeArg{
+ ID: f.ID,
+ Label: f.Label,
+ RelativePath: f.RelativePath,
+ SyncThingID: f.SyncThingID,
+ ShareRootDir: c.ShareRootDir,
+ }); err != nil {
+ return err
+ }
+ }
+
+ return nil
+ */
+}
+
+// UpdateFolder updates a specific folder into the current configuration
+func (c *Config) UpdateFolder(newFolder FolderConfig) (FolderConfig, error) {
+ if err := FolderVerify(newFolder); err != nil {
+ return FolderConfig{}, err
+ }
+
+ c.Folders = c.Folders.Update(FoldersConfig{newFolder})
+
+ // SEB A SUP model.NotifyListeners(c, NotifyFolderAdd, newFolder)
+ err := c.SThg.FolderChange(st.FolderChangeArg{
+ ID: newFolder.ID,
+ Label: newFolder.Label,
+ RelativePath: newFolder.RelativePath,
+ SyncThingID: newFolder.SyncThingID,
+ ShareRootDir: c.ShareRootDir,
+ })
+
+ newFolder.BuilderSThgID = c.Builder.SyncThingID // FIXME - should be removed after local ST config rework
+ newFolder.Status = FolderStatusEnable
+
+ return newFolder, err
+}
+
+// DeleteFolder deletes a specific folder
+func (c *Config) DeleteFolder(id string) (FolderConfig, error) {
+ var fld FolderConfig
+ var err error
+
+ //SEB A SUP model.NotifyListeners(c, NotifyFolderDelete, fld)
+ if err = c.SThg.FolderDelete(id); err != nil {
+ return fld, err
+ }
+
+ c.Folders, fld, err = c.Folders.Delete(id)
+
+ return fld, err
+}
+
+func dirExists(path string) bool {
+ _, err := os.Stat(path)
+ if os.IsNotExist(err) {
+ return false
+ }
+ return true
+}
diff --git a/lib/xdsconfig/fileconfig.go b/lib/xdsconfig/fileconfig.go
new file mode 100644
index 0000000..262d023
--- /dev/null
+++ b/lib/xdsconfig/fileconfig.go
@@ -0,0 +1,133 @@
+package xdsconfig
+
+import (
+ "encoding/json"
+ "os"
+ "os/user"
+ "path"
+ "path/filepath"
+ "regexp"
+ "strings"
+)
+
+type SyncThingConf struct {
+ 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"`
+}
+
+// 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/ <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
+ }
+
+ // 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
+ fCfg.WebAppDir = path.Clean(resolveEnvVar(fCfg.WebAppDir))
+ fCfg.ShareRootDir = path.Clean(resolveEnvVar(fCfg.ShareRootDir))
+ fCfg.SThgConf.Home = path.Clean(resolveEnvVar(fCfg.SThgConf.Home))
+
+ // 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 {
+ re := regexp.MustCompile("\\${(.*)}")
+ vars := re.FindAllStringSubmatch(s, -1)
+ res := s
+ for _, v := range vars {
+ val := os.Getenv(v[1])
+ if val != "" {
+ rer := regexp.MustCompile("\\${" + v[1] + "}")
+ res = rer.ReplaceAllString(res, val)
+ }
+ }
+ return res
+}
+
+// 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
+}
diff --git a/lib/xdsconfig/folderconfig.go b/lib/xdsconfig/folderconfig.go
new file mode 100644
index 0000000..e8bff4f
--- /dev/null
+++ b/lib/xdsconfig/folderconfig.go
@@ -0,0 +1,79 @@
+package xdsconfig
+
+import (
+ "fmt"
+ "log"
+ "path/filepath"
+)
+
+// FolderType constances
+const (
+ FolderTypeDocker = 0
+ FolderTypeWindowsSubsystem = 1
+ FolderTypeCloudSync = 2
+
+ FolderStatusErrorConfig = "ErrorConfig"
+ FolderStatusDisable = "Disable"
+ FolderStatusEnable = "Enable"
+)
+
+// FolderType is the type of sharing folder
+type FolderType int
+
+// FolderConfig is the config for one folder
+type FolderConfig struct {
+ ID string `json:"id" binding:"required"`
+ Label string `json:"label"`
+ RelativePath string `json:"path"`
+ Type FolderType `json:"type"`
+ SyncThingID string `json:"syncThingID"`
+ BuilderSThgID string `json:"builderSThgID"`
+ Status string `json:"status"`
+
+ // Private fields
+ rootPath string
+}
+
+// NewFolderConfig creates a new folder object
+func NewFolderConfig(id, label, rootDir, path string) FolderConfig {
+ return FolderConfig{
+ ID: id,
+ Label: label,
+ RelativePath: path,
+ Type: FolderTypeCloudSync,
+ SyncThingID: "",
+ Status: FolderStatusDisable,
+ rootPath: rootDir,
+ }
+}
+
+// GetFullPath returns the full path
+func (c *FolderConfig) GetFullPath(dir string) string {
+ if &dir == nil {
+ dir = ""
+ }
+ if filepath.IsAbs(dir) {
+ return filepath.Join(c.rootPath, dir)
+ }
+ return filepath.Join(c.rootPath, c.RelativePath, dir)
+}
+
+// FolderVerify is called to verify that a configuration is valid
+func FolderVerify(fCfg FolderConfig) error {
+ var err error
+
+ if fCfg.Type != FolderTypeCloudSync {
+ err = fmt.Errorf("Unsupported folder type")
+ }
+
+ if fCfg.SyncThingID == "" {
+ err = fmt.Errorf("device id not set (SyncThingID field)")
+ }
+
+ if err != nil {
+ fCfg.Status = FolderStatusErrorConfig
+ log.Printf("ERROR FolderVerify: %v\n", err)
+ }
+
+ return err
+}
diff --git a/lib/xdsconfig/foldersconfig.go b/lib/xdsconfig/foldersconfig.go
new file mode 100644
index 0000000..4ad16df
--- /dev/null
+++ b/lib/xdsconfig/foldersconfig.go
@@ -0,0 +1,47 @@
+package xdsconfig
+
+import (
+ "fmt"
+)
+
+// FoldersConfig contains all the folder configurations
+type FoldersConfig []FolderConfig
+
+// GetIdx returns the index of the folder matching id in FoldersConfig array
+func (c FoldersConfig) GetIdx(id string) int {
+ for i := range c {
+ if id == c[i].ID {
+ return i
+ }
+ }
+ return -1
+}
+
+// Update is used to fully update or add a new FolderConfig
+func (c FoldersConfig) Update(newCfg FoldersConfig) FoldersConfig {
+ for i := range newCfg {
+ found := false
+ for j := range c {
+ if newCfg[i].ID == c[j].ID {
+ c[j] = newCfg[i]
+ found = true
+ break
+ }
+ }
+ if !found {
+ c = append(c, newCfg[i])
+ }
+ }
+ return c
+}
+
+// Delete is used to delete a folder matching id in FoldersConfig array
+func (c FoldersConfig) Delete(id string) (FoldersConfig, FolderConfig, error) {
+ if idx := c.GetIdx(id); idx != -1 {
+ f := c[idx]
+ c = append(c[:idx], c[idx+1:]...)
+ return c, f, nil
+ }
+
+ return c, FolderConfig{}, fmt.Errorf("invalid id")
+}