diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/apiv1/agent.go | 70 | ||||
-rw-r--r-- | lib/apiv1/apiv1.go | 11 | ||||
-rw-r--r-- | lib/apiv1/events.go | 9 | ||||
-rw-r--r-- | lib/apiv1/exec.go | 25 | ||||
-rw-r--r-- | lib/apiv1/folders.go | 22 | ||||
-rw-r--r-- | lib/apiv1/make.go | 30 | ||||
-rw-r--r-- | lib/apiv1/sdks.go | 6 | ||||
-rw-r--r-- | lib/apiv1/version.go | 2 | ||||
-rw-r--r-- | lib/crosssdk/sdk.go | 7 | ||||
-rw-r--r-- | lib/crosssdk/sdks.go | 75 | ||||
-rw-r--r-- | lib/folder/folder-interface.go | 15 | ||||
-rw-r--r-- | lib/folder/folder-pathmap.go | 52 | ||||
-rw-r--r-- | lib/folder/folder-st-disable.go | 6 | ||||
-rw-r--r-- | lib/model/folders.go | 47 | ||||
-rw-r--r-- | lib/session/session.go | 32 | ||||
-rw-r--r-- | lib/syncthing/folder-st.go | 10 | ||||
-rw-r--r-- | lib/syncthing/st.go | 18 | ||||
-rw-r--r-- | lib/syncthing/stfolder.go | 24 | ||||
-rw-r--r-- | lib/webserver/server.go | 6 | ||||
-rw-r--r-- | lib/xdsconfig/builderconfig.go | 5 | ||||
-rw-r--r-- | lib/xdsconfig/config.go | 32 | ||||
-rw-r--r-- | lib/xdsconfig/data.go | 87 | ||||
-rw-r--r-- | lib/xdsconfig/fileconfig.go | 19 |
23 files changed, 386 insertions, 224 deletions
diff --git a/lib/apiv1/agent.go b/lib/apiv1/agent.go deleted file mode 100644 index 925f12b..0000000 --- a/lib/apiv1/agent.go +++ /dev/null @@ -1,70 +0,0 @@ -package apiv1 - -import ( - "net/http" - "path" - "strings" - - "path/filepath" - - "github.com/gin-gonic/gin" - common "github.com/iotbzh/xds-common/golib" -) - -// XDSAgentTarball . -type XDSAgentTarball struct { - OS string `json:"os"` - Arch string `json:"arch"` - Version string `json:"version"` - RawVersion string `json:"raw-version"` - FileURL string `json:"fileUrl"` -} - -// XDSAgentInfo . -type XDSAgentInfo struct { - Tarballs []XDSAgentTarball `json:"tarballs"` -} - -// getXdsAgentInfo : return various information about Xds Agent -func (s *APIService) getXdsAgentInfo(c *gin.Context) { - - res := XDSAgentInfo{} - tarballURL := "assets/xds-agent-tarballs" - tarballDir := filepath.Join(s.cfg.FileConf.WebAppDir, "assets", "xds-agent-tarballs") - if common.Exists(tarballDir) { - files, err := filepath.Glob(path.Join(tarballDir, "xds-agent_*.zip")) - if err != nil { - s.log.Debugf("Error while retrieving xds-agent tarballs: dir=%s, error=%v", tarballDir, err) - } - for _, ff := range files { - file := filepath.Base(ff) - // Assume that tarball name format is: xds-agent_OS-ARCH-RAWVERSION.zip - fs := strings.TrimSuffix(strings.TrimPrefix(file, "xds-agent_"), ".zip") - f := strings.Split(fs, "-") - - if len(f) >= 3 { - vers := strings.Split(f[2], "_") - ver := f[2] - if len(vers) > 1 { - ver = vers[0] - } - - newT := XDSAgentTarball{ - OS: f[0], - Arch: f[1], - Version: ver, - RawVersion: f[2], - FileURL: filepath.Join(tarballURL, file), - } - - s.log.Infof("Added XDS-Agent tarball: %s", file) - res.Tarballs = append(res.Tarballs, newT) - - } else { - s.log.Debugf("Error while retrieving xds-agent, decoding failure: file:%v", ff) - } - } - } - - c.JSON(http.StatusOK, res) -} diff --git a/lib/apiv1/apiv1.go b/lib/apiv1/apiv1.go index 262f513..d10a08e 100644 --- a/lib/apiv1/apiv1.go +++ b/lib/apiv1/apiv1.go @@ -34,19 +34,18 @@ func New(r *gin.Engine, sess *session.Sessions, cfg *xdsconfig.Config, mfolders } s.apiRouter.GET("/version", s.getVersion) - s.apiRouter.GET("/xdsagent/info", s.getXdsAgentInfo) s.apiRouter.GET("/config", s.getConfig) s.apiRouter.POST("/config", s.setConfig) s.apiRouter.GET("/folders", s.getFolders) - s.apiRouter.GET("/folder/:id", s.getFolder) - s.apiRouter.POST("/folder", s.addFolder) - s.apiRouter.POST("/folder/sync/:id", s.syncFolder) - s.apiRouter.DELETE("/folder/:id", s.delFolder) + s.apiRouter.GET("/folders/:id", s.getFolder) + s.apiRouter.POST("/folders", s.addFolder) + s.apiRouter.POST("/folders/sync/:id", s.syncFolder) + s.apiRouter.DELETE("/folders/:id", s.delFolder) s.apiRouter.GET("/sdks", s.getSdks) - s.apiRouter.GET("/sdk/:id", s.getSdk) + s.apiRouter.GET("/sdks/:id", s.getSdk) s.apiRouter.POST("/make", s.buildMake) s.apiRouter.POST("/make/:id", s.buildMake) diff --git a/lib/apiv1/events.go b/lib/apiv1/events.go index da8298c..9444262 100644 --- a/lib/apiv1/events.go +++ b/lib/apiv1/events.go @@ -112,6 +112,9 @@ func (s *APIService) eventsRegister(c *gin.Context) { Folder: *cfg, } + s.log.Debugf("WS Emit %s - Status=%10s, IsInSync=%6v, ID=%s", + EventEventType+evType, cfg.Status, cfg.IsInSync, cfg.ID) + if err := (*so).Emit(EventEventType+evType, msg); err != nil { s.log.Errorf("WS Emit Folder StateChanged event : %v", err) } @@ -119,11 +122,15 @@ func (s *APIService) eventsRegister(c *gin.Context) { data := make(folder.EventCBData) data["sid"] = sess.ID - err := s.mfolders.RegisterEventChange(args.ProjectID, &cbFunc, &data) + prjID, err := s.mfolders.ResolveID(args.ProjectID) if err != nil { common.APIError(c, err.Error()) return } + if err = s.mfolders.RegisterEventChange(prjID, &cbFunc, &data); err != nil { + common.APIError(c, err.Error()) + return + } c.JSON(http.StatusOK, gin.H{"status": "OK"}) } diff --git a/lib/apiv1/exec.go b/lib/apiv1/exec.go index de40c70..30444c1 100644 --- a/lib/apiv1/exec.go +++ b/lib/apiv1/exec.go @@ -19,7 +19,8 @@ type ( // ExecArgs JSON parameters of /exec command ExecArgs struct { ID string `json:"id" binding:"required"` - SdkID string `json:"sdkid"` // sdk ID to use for setting env + SdkID string `json:"sdkID"` // sdk ID to use for setting env + CmdID string `json:"cmdID"` // command unique ID Cmd string `json:"cmd" binding:"required"` Args []string `json:"args"` Env []string `json:"env"` @@ -104,15 +105,19 @@ func (s *APIService) execCmd(c *gin.Context) { } // Allow to pass id in url (/exec/:id) or as JSON argument - id := c.Param("id") - if id == "" { - id = args.ID + idArg := c.Param("id") + if idArg == "" { + idArg = args.ID } - if id == "" { + if idArg == "" { common.APIError(c, "Invalid id") return } - + id, err := s.mfolders.ResolveID(idArg) + if err != nil { + common.APIError(c, err.Error()) + return + } f := s.mfolders.Get(id) if f == nil { common.APIError(c, "Unknown id") @@ -168,11 +173,13 @@ func (s *APIService) execCmd(c *gin.Context) { } // Unique ID for each commands - cmdID := strconv.Itoa(execCommandID) - execCommandID++ + if args.CmdID == "" { + args.CmdID = s.cfg.ServerUID[:18] + "_" + strconv.Itoa(execCommandID) + execCommandID++ + } // Create new execution over WS context - execWS := eows.New(strings.Join(cmd, " "), cmdArgs, sop, sess.ID, cmdID) + execWS := eows.New(strings.Join(cmd, " "), cmdArgs, sop, sess.ID, args.CmdID) execWS.Log = s.log // Append client project dir to environment diff --git a/lib/apiv1/folders.go b/lib/apiv1/folders.go index a231b86..398e21c 100644 --- a/lib/apiv1/folders.go +++ b/lib/apiv1/folders.go @@ -16,7 +16,12 @@ func (s *APIService) getFolders(c *gin.Context) { // getFolder returns a specific folder configuration func (s *APIService) getFolder(c *gin.Context) { - f := s.mfolders.Get(c.Param("id")) + id, err := s.mfolders.ResolveID(c.Param("id")) + if err != nil { + common.APIError(c, err.Error()) + return + } + f := s.mfolders.Get(id) if f == nil { common.APIError(c, "Invalid id") return @@ -67,11 +72,14 @@ func (s *APIService) addFolder(c *gin.Context) { // syncFolder force synchronization of folder files func (s *APIService) syncFolder(c *gin.Context) { - id := c.Param("id") - + id, err := s.mfolders.ResolveID(c.Param("id")) + if err != nil { + common.APIError(c, err.Error()) + return + } s.log.Debugln("Sync folder id: ", id) - err := s.mfolders.ForceSync(id) + err = s.mfolders.ForceSync(id) if err != nil { common.APIError(c, err.Error()) return @@ -82,7 +90,11 @@ func (s *APIService) syncFolder(c *gin.Context) { // delFolder deletes folder from server config func (s *APIService) delFolder(c *gin.Context) { - id := c.Param("id") + id, err := s.mfolders.ResolveID(c.Param("id")) + if err != nil { + common.APIError(c, err.Error()) + return + } s.log.Debugln("Delete folder id ", id) diff --git a/lib/apiv1/make.go b/lib/apiv1/make.go index cf76476..6e0c7d6 100644 --- a/lib/apiv1/make.go +++ b/lib/apiv1/make.go @@ -15,7 +15,8 @@ import ( // MakeArgs is the parameters (json format) of /make command type MakeArgs struct { ID string `json:"id"` - SdkID string `json:"sdkid"` // sdk ID to use for setting env + SdkID string `json:"sdkID"` // sdk ID to use for setting env + CmdID string `json:"cmdID"` // command unique ID Args []string `json:"args"` // args to pass to make command Env []string `json:"env"` RPath string `json:"rpath"` // relative path into project @@ -67,15 +68,19 @@ func (s *APIService) buildMake(c *gin.Context) { } // Allow to pass id in url (/make/:id) or as JSON argument - id := c.Param("id") - if id == "" { - id = args.ID + idArg := c.Param("id") + if idArg == "" { + idArg = args.ID } - if id == "" { + if idArg == "" { common.APIError(c, "Invalid id") return } - + id, err := s.mfolders.ResolveID(idArg) + if err != nil { + common.APIError(c, err.Error()) + return + } pf := s.mfolders.Get(id) if pf == nil { common.APIError(c, "Unknown id") @@ -171,8 +176,11 @@ func (s *APIService) buildMake(c *gin.Context) { } } - cmdID := strconv.Itoa(makeCommandID) - makeCommandID++ + // Unique ID for each commands + if args.CmdID == "" { + args.CmdID = s.cfg.ServerUID[:18] + "_" + strconv.Itoa(makeCommandID) + makeCommandID++ + } cmd := []string{} // Retrieve env command regarding Sdk ID @@ -186,14 +194,14 @@ func (s *APIService) buildMake(c *gin.Context) { cmd = append(cmd, args.Args...) } - s.log.Debugf("Execute [Cmd ID %d]: %v", cmdID, cmd) + s.log.Debugf("Execute [Cmd ID %d]: %v", args.CmdID, cmd) data := make(map[string]interface{}) data["ID"] = prj.ID data["RootPath"] = prj.RootPath data["ExitImmediate"] = args.ExitImmediate - err := common.ExecPipeWs(cmd, args.Env, sop, sess.ID, cmdID, execTmo, s.log, oCB, eCB, &data) + err = common.ExecPipeWs(cmd, args.Env, sop, sess.ID, args.CmdID, execTmo, s.log, oCB, eCB, &data) if err != nil { common.APIError(c, err.Error()) return @@ -202,6 +210,6 @@ func (s *APIService) buildMake(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "OK", - "cmdID": cmdID, + "cmdID": args.CmdID, }) } diff --git a/lib/apiv1/sdks.go b/lib/apiv1/sdks.go index 52af506..f67a0ef 100644 --- a/lib/apiv1/sdks.go +++ b/lib/apiv1/sdks.go @@ -2,7 +2,6 @@ package apiv1 import ( "net/http" - "strconv" "github.com/gin-gonic/gin" common "github.com/iotbzh/xds-common/golib" @@ -15,12 +14,11 @@ func (s *APIService) getSdks(c *gin.Context) { // getSdk returns a specific Sdk configuration func (s *APIService) getSdk(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) + id, err := s.sdks.ResolveID(c.Param("id")) if err != nil { - common.APIError(c, "Invalid id") + common.APIError(c, err.Error()) return } - sdk := s.sdks.Get(id) if sdk.Profile == "" { common.APIError(c, "Invalid id") diff --git a/lib/apiv1/version.go b/lib/apiv1/version.go index e022441..8f928ec 100644 --- a/lib/apiv1/version.go +++ b/lib/apiv1/version.go @@ -7,6 +7,7 @@ import ( ) type version struct { + ID string `json:"id"` Version string `json:"version"` APIVersion string `json:"apiVersion"` VersionGitTag string `json:"gitTag"` @@ -15,6 +16,7 @@ type version struct { // getInfo : return various information about server func (s *APIService) getVersion(c *gin.Context) { response := version{ + ID: s.cfg.ServerUID, Version: s.cfg.Version, APIVersion: s.cfg.APIVersion, VersionGitTag: s.cfg.VersionGitTag, diff --git a/lib/crosssdk/sdk.go b/lib/crosssdk/sdk.go index 5a5770d..5be8954 100644 --- a/lib/crosssdk/sdk.go +++ b/lib/crosssdk/sdk.go @@ -3,6 +3,8 @@ package crosssdk import ( "fmt" "path/filepath" + + uuid "github.com/satori/go.uuid" ) // SDK Define a cross tool chain used to build application @@ -31,8 +33,9 @@ func NewCrossSDK(path string) (*SDK, error) { d = filepath.Dir(d) s.Profile = filepath.Base(d) - s.ID = s.Profile + "_" + s.Arch + "_" + s.Version - s.Name = s.Arch + " (" + s.Version + ")" + // Use V3 to ensure that we get same uuid on restart + s.ID = uuid.NewV3(uuid.FromStringOrNil("sdks"), s.Profile+"_"+s.Arch+"_"+s.Version).String() + s.Name = s.Arch + " (" + s.Version + ")" envFile := filepath.Join(path, "environment-setup*") ef, err := filepath.Glob(envFile) diff --git a/lib/crosssdk/sdks.go b/lib/crosssdk/sdks.go index 0da0d1b..e3d6607 100644 --- a/lib/crosssdk/sdks.go +++ b/lib/crosssdk/sdks.go @@ -1,8 +1,10 @@ package crosssdk import ( + "fmt" "path" "path/filepath" + "strings" "sync" "github.com/Sirupsen/logrus" @@ -12,14 +14,16 @@ import ( // SDKs List of installed SDK type SDKs struct { - Sdks []SDK + Sdks map[string]*SDK mutex sync.Mutex } // Init creates a new instance of Syncthing func Init(cfg *xdsconfig.Config, log *logrus.Logger) (*SDKs, error) { - s := SDKs{} + s := SDKs{ + Sdks: make(map[string]*SDK), + } // Retrieve installed sdks sdkRD := cfg.FileConf.SdkRootDir @@ -44,7 +48,7 @@ func Init(cfg *xdsconfig.Config, log *logrus.Logger) (*SDKs, error) { log.Debugf("Error while processing SDK dir=%s, err=%s", d, err.Error()) continue } - s.Sdks = append(s.Sdks, *sdk) + s.Sdks[sdk.ID] = sdk } } @@ -53,23 +57,50 @@ func Init(cfg *xdsconfig.Config, log *logrus.Logger) (*SDKs, error) { return &s, nil } -// GetAll returns all existing SDKs -func (s *SDKs) GetAll() []SDK { - s.mutex.Lock() - defer s.mutex.Unlock() - res := s.Sdks - return res +// ResolveID Complete an SDK ID (helper for user that can use partial ID value) +func (s *SDKs) ResolveID(id string) (string, error) { + if id == "" { + return "", nil + } + + match := []string{} + for iid := range s.Sdks { + fmt.Printf("SEB prefix iid=%v id=%v\n", iid, id) + if strings.HasPrefix(iid, id) { + match = append(match, iid) + fmt.Printf(" SEB match (%d): %v\n", len(match), match) + } + } + fmt.Printf("SEB match (%d): %v\n", len(match), match) + + if len(match) == 1 { + return match[0], nil + } else if len(match) == 0 { + return id, fmt.Errorf("Unknown id") + } + return id, fmt.Errorf("Multiple IDs found with provided prefix: " + id) } // Get returns an SDK from id -func (s *SDKs) Get(id int) SDK { +func (s *SDKs) Get(id string) *SDK { s.mutex.Lock() defer s.mutex.Unlock() - if id < 0 || id > len(s.Sdks) { - return SDK{} + sc, exist := s.Sdks[id] + if !exist { + return nil + } + return sc +} + +// GetAll returns all existing SDKs +func (s *SDKs) GetAll() []SDK { + s.mutex.Lock() + defer s.mutex.Unlock() + res := []SDK{} + for _, v := range s.Sdks { + res = append(res, *v) } - res := s.Sdks[id] return res } @@ -82,15 +113,15 @@ func (s *SDKs) GetEnvCmd(id string, defaultID string) []string { s.mutex.Lock() defer s.mutex.Unlock() - defaultEnv := []string{} - for _, sdk := range s.Sdks { - if sdk.ID == id { - return sdk.GetEnvCmd() - } - if sdk.ID == defaultID { - defaultEnv = sdk.GetEnvCmd() - } + + if sdk, exist := s.Sdks[id]; exist { + return sdk.GetEnvCmd() } + + if sdk, exist := s.Sdks[defaultID]; defaultID != "" && exist { + return sdk.GetEnvCmd() + } + // Return default env that may be empty - return defaultEnv + return []string{} } diff --git a/lib/folder/folder-interface.go b/lib/folder/folder-interface.go index 4beccb8..9eb6829 100644 --- a/lib/folder/folder-interface.go +++ b/lib/folder/folder-interface.go @@ -1,12 +1,12 @@ package folder // FolderType definition -type FolderType int +type FolderType string const ( - TypePathMap = 1 - TypeCloudSync = 2 - TypeCifsSmb = 3 + TypePathMap = "PathMap" + TypeCloudSync = "CloudSync" + TypeCifsSmb = "CIFS" ) // Folder Status definition @@ -61,10 +61,13 @@ type FolderConfig struct { // PathMapConfig Path mapping specific data type PathMapConfig struct { ServerPath string `json:"serverPath"` + + // Don't keep temporary file name (IOW we don't want to save it and reuse it) + CheckFile string `json:"checkFile" xml:"-"` + CheckContent string `json:"checkContent" xml:"-"` } // CloudSyncConfig CloudSync (AKA Syncthing) specific data type CloudSyncConfig struct { - SyncThingID string `json:"syncThingID"` - BuilderSThgID string `json:"builderSThgID"` + SyncThingID string `json:"syncThingID"` } diff --git a/lib/folder/folder-pathmap.go b/lib/folder/folder-pathmap.go index 1020026..e200164 100644 --- a/lib/folder/folder-pathmap.go +++ b/lib/folder/folder-pathmap.go @@ -24,13 +24,20 @@ type PathMap struct { func NewFolderPathMap(gc *xdsconfig.Config) *PathMap { f := PathMap{ globalConfig: gc, + config: FolderConfig{ + Status: StatusDisable, + }, } return &f } // NewUID Get a UUID func (f *PathMap) NewUID(suffix string) string { - return uuid.NewV1().String() + "_" + suffix + uuid := uuid.NewV1().String() + if len(suffix) > 0 { + uuid += "_" + suffix + } + return uuid } // Add a new folder @@ -55,22 +62,43 @@ func (f *PathMap) Add(cfg FolderConfig) (*FolderConfig, error) { if !common.Exists(dir) { return nil, fmt.Errorf("ServerPath directory is not accessible: %s", dir) } - file, err := ioutil.TempFile(dir, "xds_pathmap_check") - if err != nil { - return nil, fmt.Errorf("ServerPath sanity check error: %s", err.Error()) - } - defer os.Remove(file.Name()) - - msg := "sanity check PathMap Add folder" - n, err := file.Write([]byte(msg)) - if err != nil || n != len(msg) { - return nil, fmt.Errorf("ServerPath sanity check error: %s", err.Error()) - } f.config = cfg f.config.RootPath = dir f.config.DataPathMap.ServerPath = dir f.config.IsInSync = true + + // Verify file created by XDS agent when needed + if cfg.DataPathMap.CheckFile != "" { + errMsg := "ServerPath sanity check error (%d): %v" + ckFile := f.ConvPathCli2Svr(cfg.DataPathMap.CheckFile) + if !common.Exists(ckFile) { + return nil, fmt.Errorf(errMsg, 1, "file not present") + } + if cfg.DataPathMap.CheckContent != "" { + fd, err := os.OpenFile(ckFile, os.O_APPEND|os.O_RDWR, 0600) + if err != nil { + return nil, fmt.Errorf(errMsg, 2, err) + } + defer fd.Close() + + // Check specific message written by agent + content, err := ioutil.ReadAll(fd) + if err != nil { + return nil, fmt.Errorf(errMsg, 3, err) + } + if string(content) != cfg.DataPathMap.CheckContent { + return nil, fmt.Errorf(errMsg, 4, "file content differ") + } + + // Write a specific message that will be check back on agent side + msg := "Pathmap checked message written by xds-server ID: " + f.globalConfig.ServerUID + "\n" + if n, err := fd.WriteString(msg); n != len(msg) || err != nil { + return nil, fmt.Errorf(errMsg, 5, err) + } + } + } + f.config.Status = StatusEnable return &f.config, nil diff --git a/lib/folder/folder-st-disable.go b/lib/folder/folder-st-disable.go index f90b776..7b53ca8 100644 --- a/lib/folder/folder-st-disable.go +++ b/lib/folder/folder-st-disable.go @@ -25,7 +25,11 @@ func NewFolderSTDisable(gc *xdsconfig.Config) *STFolderDisable { // NewUID Get a UUID func (f *STFolderDisable) NewUID(suffix string) string { - return uuid.NewV1().String() + "_" + suffix + uuid := uuid.NewV1().String() + if len(suffix) > 0 { + uuid += "_" + suffix + } + return uuid } // Add a new folder diff --git a/lib/model/folders.go b/lib/model/folders.go index 576c4a2..b8e6cf5 100644 --- a/lib/model/folders.go +++ b/lib/model/folders.go @@ -146,6 +146,27 @@ func (f *Folders) SaveConfig() error { return foldersConfigWrite(f.fileOnDisk, f.getConfigArrUnsafe()) } +// ResolveID Complete a Folder ID (helper for user that can use partial ID value) +func (f *Folders) ResolveID(id string) (string, error) { + if id == "" { + return "", nil + } + + match := []string{} + for iid := range f.folders { + if strings.HasPrefix(iid, id) { + match = append(match, iid) + } + } + + if len(match) == 1 { + return match[0], nil + } else if len(match) == 0 { + return id, fmt.Errorf("Unknown id") + } + return id, fmt.Errorf("Multiple IDs found with provided prefix: " + id) +} + // Get returns the folder config or nil if not existing func (f *Folders) Get(id string) *folder.IFOLDER { if id == "" { @@ -168,8 +189,7 @@ func (f *Folders) GetConfigArr() []folder.FolderConfig { // getConfigArrUnsafe Same as GetConfigArr without mutex protection func (f *Folders) getConfigArrUnsafe() []folder.FolderConfig { - var conf []folder.FolderConfig - + conf := []folder.FolderConfig{} for _, v := range f.folders { conf = append(conf, (*v).GetConfig()) } @@ -214,24 +234,23 @@ func (f *Folders) createUpdate(newF folder.FolderConfig, create bool, initial bo return nil, fmt.Errorf("Unsupported folder type") } + // Allocate a new UUID + if create { + newF.ID = fld.NewUID("") + } + if !create && newF.ID == "" { + return nil, fmt.Errorf("Cannot update folder with null ID") + } + // Set default value if needed if newF.Status == "" { newF.Status = folder.StatusDisable } if newF.Label == "" { - newF.Label = filepath.Base(newF.ClientPath) + "_" + newF.ID[0:8] - } - - // Allocate a new UUID - if create { - i := len(newF.Label) - if i > 20 { - i = 20 + newF.Label = filepath.Base(newF.ClientPath) + if len(newF.ID) > 8 { + newF.Label += "_" + newF.ID[0:8] } - newF.ID = fld.NewUID(newF.Label[:i]) - } - if !create && newF.ID == "" { - return nil, fmt.Errorf("Cannot update folder with null ID") } // Normalize path (needed for Windows path including bashlashes) diff --git a/lib/session/session.go b/lib/session/session.go index d4e1ad3..60b7b8a 100644 --- a/lib/session/session.go +++ b/lib/session/session.go @@ -36,27 +36,29 @@ type ClientSession struct { // Sessions holds client sessions type Sessions struct { - router *gin.Engine - cookieMaxAge int64 - sessMap map[string]ClientSession - mutex sync.Mutex - log *logrus.Logger - stop chan struct{} // signals intentional stop + router *gin.Engine + cookieMaxAge int64 + sessMap map[string]ClientSession + mutex sync.Mutex + log *logrus.Logger + LogLevelSilly bool + stop chan struct{} // signals intentional stop } // NewClientSessions . -func NewClientSessions(router *gin.Engine, log *logrus.Logger, cookieMaxAge string) *Sessions { +func NewClientSessions(router *gin.Engine, log *logrus.Logger, cookieMaxAge string, sillyLog bool) *Sessions { ckMaxAge, err := strconv.ParseInt(cookieMaxAge, 10, 0) if err != nil { ckMaxAge = 0 } s := Sessions{ - router: router, - cookieMaxAge: ckMaxAge, - sessMap: make(map[string]ClientSession), - mutex: sync.NewMutex(), - log: log, - stop: make(chan struct{}), + router: router, + cookieMaxAge: ckMaxAge, + sessMap: make(map[string]ClientSession), + mutex: sync.NewMutex(), + log: log, + LogLevelSilly: sillyLog, + stop: make(chan struct{}), } s.router.Use(s.Middleware()) @@ -197,15 +199,13 @@ func (s *Sessions) refresh(sid string) { } func (s *Sessions) monitorSessMap() { - const dbgFullTrace = false // for debugging - for { select { case <-s.stop: s.log.Debugln("Stop monitorSessMap") return case <-time.After(sessionMonitorTime * time.Second): - if dbgFullTrace { + if s.LogLevelSilly { s.log.Debugf("Sessions Map size: %d", len(s.sessMap)) s.log.Debugf("Sessions Map : %v", s.sessMap) } diff --git a/lib/syncthing/folder-st.go b/lib/syncthing/folder-st.go index 7e1fe55..f25a505 100644 --- a/lib/syncthing/folder-st.go +++ b/lib/syncthing/folder-st.go @@ -39,7 +39,11 @@ func (f *STFolder) NewUID(suffix string) string { if i > 15 { i = 15 } - return uuid.NewV1().String()[:14] + f.st.MyID[:i] + "_" + suffix + uuid := uuid.NewV1().String()[:14] + f.st.MyID[:i] + if len(suffix) > 0 { + uuid += "_" + suffix + } + return uuid } // Add a new folder @@ -57,10 +61,8 @@ func (f *STFolder) Add(cfg folder.FolderConfig) (*folder.FolderConfig, error) { f.fConfig = cfg - f.fConfig.DataCloudSync.BuilderSThgID = f.st.MyID // FIXME - should be removed after local ST config rework - // Update Syncthing folder - // (expect if status is ErrorConfig) + // (except if status is ErrorConfig) // TODO: add cache to avoid multiple requests on startup if f.fConfig.Status != folder.StatusErrorConfig { id, err := f.st.FolderChange(f.fConfig) diff --git a/lib/syncthing/st.go b/lib/syncthing/st.go index 99a17a1..d1ebbe6 100644 --- a/lib/syncthing/st.go +++ b/lib/syncthing/st.go @@ -34,16 +34,16 @@ type SyncThing struct { STICmd *exec.Cmd MyID string Connected bool + Events *Events // Private fields binDir string logsDir string exitSTChan chan ExitChan exitSTIChan chan ExitChan - conf *xdsconfig.Config client *common.HTTPClient log *logrus.Logger - Events *Events + conf *xdsconfig.Config } // ExitChan Channel used for process exit @@ -134,7 +134,8 @@ func (s *SyncThing) startProc(exeName string, args []string, env []string, eChan // Kill existing process (useful for debug ;-) ) if os.Getenv("DEBUG_MODE") != "" { - exec.Command("bash", "-c", "pkill -9 "+exeName).Output() + fmt.Printf("\n!!! DEBUG_MODE set: KILL existing %s process(es) !!!\n", exeName) + exec.Command("bash", "-c", "ps -ax |grep "+exeName+" |grep "+s.BaseURL+" |cut -d' ' -f 1|xargs -I{} kill -9 {}").Output() } // When not set (or set to '.') set bin to path of xds-agent executable @@ -227,7 +228,6 @@ func (s *SyncThing) Start() (*exec.Cmd, error) { env := []string{ "STNODEFAULTFOLDER=1", "STNOUPGRADE=1", - "STNORESTART=1", // FIXME SEB remove ? } s.STCmd, err = s.startProc("syncthing", args, env, &s.exitSTChan) @@ -317,7 +317,12 @@ func (s *SyncThing) Connect() error { common.HTTPClientConfig{ URLPrefix: "/rest", HeaderClientKeyName: "X-Syncthing-ID", + LogOut: s.conf.LogVerboseOut, + LogPrefix: "SYNCTHING: ", + LogLevel: common.HTTPLogLevelWarning, }) + s.client.SetLogLevel(s.log.Level.String()) + if err != nil { msg := ": " + err.Error() if strings.Contains(err.Error(), "connection refused") { @@ -329,11 +334,6 @@ func (s *SyncThing) Connect() error { return fmt.Errorf("ERROR: cannot connect to Syncthing (null client)") } - // Redirect HTTP log into a file - s.client.SetLogLevel(s.conf.Log.Level.String()) - s.client.LoggerPrefix = "SYNCTHING: " - s.client.LoggerOut = s.conf.LogVerboseOut - s.MyID, err = s.IDGet() if err != nil { return fmt.Errorf("ERROR: cannot retrieve ID") diff --git a/lib/syncthing/stfolder.go b/lib/syncthing/stfolder.go index 70ac70a..503ba4b 100644 --- a/lib/syncthing/stfolder.go +++ b/lib/syncthing/stfolder.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/iotbzh/xds-server/lib/folder" - "github.com/syncthing/syncthing/lib/config" + stconfig "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/protocol" ) @@ -32,9 +32,9 @@ func (s *SyncThing) FolderLoadFromStConfig(f *[]folder.FolderConfig) error { } for _, stFld := range stCfg.Folders { - cliPath := strings.TrimPrefix(stFld.RawPath, s.conf.FileConf.ShareRootDir) + cliPath := strings.TrimPrefix(stFld.Path, s.conf.FileConf.ShareRootDir) if cliPath == "" { - cliPath = stFld.RawPath + cliPath = stFld.Path } *f = append(*f, folder.FolderConfig{ ID: stFld.ID, @@ -69,7 +69,7 @@ func (s *SyncThing) FolderChange(f folder.FolderConfig) (string, error) { return "", err } - newDevice := config.DeviceConfiguration{ + newDevice := stconfig.DeviceConfiguration{ DeviceID: devID, Name: stClientID, Addresses: []string{"dynamic"}, @@ -95,22 +95,22 @@ func (s *SyncThing) FolderChange(f folder.FolderConfig) (string, error) { id = stClientID[0:15] + "_" + label } - folder := config.FolderConfiguration{ - ID: id, - Label: label, - RawPath: filepath.Join(s.conf.FileConf.ShareRootDir, f.ClientPath), + folder := stconfig.FolderConfiguration{ + ID: id, + Label: label, + Path: filepath.Join(s.conf.FileConf.ShareRootDir, f.ClientPath), } if s.conf.FileConf.SThgConf.RescanIntervalS > 0 { folder.RescanIntervalS = s.conf.FileConf.SThgConf.RescanIntervalS } - folder.Devices = append(folder.Devices, config.FolderDeviceConfiguration{ + folder.Devices = append(folder.Devices, stconfig.FolderDeviceConfiguration{ DeviceID: newDevice.DeviceID, }) found = false - var fld config.FolderConfiguration + var fld stconfig.FolderConfiguration for _, fld = range stCfg.Folders { if folder.ID == fld.ID { fld = folder @@ -155,8 +155,8 @@ func (s *SyncThing) FolderDelete(id string) error { } // FolderConfigGet Returns the configuration of a specific folder -func (s *SyncThing) FolderConfigGet(folderID string) (config.FolderConfiguration, error) { - fc := config.FolderConfiguration{} +func (s *SyncThing) FolderConfigGet(folderID string) (stconfig.FolderConfiguration, error) { + fc := stconfig.FolderConfiguration{} if folderID == "" { return fc, fmt.Errorf("folderID not set") } diff --git a/lib/webserver/server.go b/lib/webserver/server.go index a2fdf6f..85a2c40 100644 --- a/lib/webserver/server.go +++ b/lib/webserver/server.go @@ -30,6 +30,7 @@ type Server struct { mfolders *model.Folders sdks *crosssdk.SDKs log *logrus.Logger + sillyLog bool stop chan struct{} // signals intentional stop } @@ -37,7 +38,7 @@ const indexFilename = "index.html" const cookieMaxAge = "3600" // New creates an instance of Server -func New(cfg *xdsconfig.Config, mfolders *model.Folders, sdks *crosssdk.SDKs, logr *logrus.Logger) *Server { +func New(cfg *xdsconfig.Config, mfolders *model.Folders, sdks *crosssdk.SDKs, logr *logrus.Logger, sillyLog bool) *Server { // Setup logging for gin router if logr.Level == logrus.DebugLevel { @@ -66,6 +67,7 @@ func New(cfg *xdsconfig.Config, mfolders *model.Folders, sdks *crosssdk.SDKs, lo mfolders: mfolders, sdks: sdks, log: logr, + sillyLog: sillyLog, stop: make(chan struct{}), } @@ -83,7 +85,7 @@ func (s *Server) Serve() error { s.router.Use(s.middlewareCORS()) // Sessions manager - s.sessions = session.NewClientSessions(s.router, s.log, cookieMaxAge) + s.sessions = session.NewClientSessions(s.router, s.log, cookieMaxAge, s.sillyLog) // Create REST API s.api = apiv1.New(s.router, s.sessions, s.cfg, s.mfolders, s.sdks) diff --git a/lib/xdsconfig/builderconfig.go b/lib/xdsconfig/builderconfig.go index c64fe9c..6fc1814 100644 --- a/lib/xdsconfig/builderconfig.go +++ b/lib/xdsconfig/builderconfig.go @@ -28,10 +28,7 @@ func NewBuilderConfig(stID string) (BuilderConfig, error) { return b, nil } -// Copy makes a real copy of BuilderConfig -func (c *BuilderConfig) Copy(n BuilderConfig) { - // TODO -} +/*** Private ***/ func getLocalIP() (string, error) { addrs, err := net.InterfaceAddrs() diff --git a/lib/xdsconfig/config.go b/lib/xdsconfig/config.go index 84e0778..0fc1346 100644 --- a/lib/xdsconfig/config.go +++ b/lib/xdsconfig/config.go @@ -13,10 +13,12 @@ import ( // 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"` + ServerUID string `json:"id"` + Version string `json:"version"` + APIVersion string `json:"apiVersion"` + VersionGitTag string `json:"gitTag"` + SupportedSharing map[string]bool `json:"supportedSharing"` + Builder BuilderConfig `json:"builder"` // Private (un-exported fields in REST GET /config route) Options Options `json:"-"` @@ -55,12 +57,19 @@ func Init(cliCtx *cli.Context, log *logrus.Logger) (*Config, error) { dfltSTHomeDir = resDir } + uuid, err := ServerIDGet() + if err != nil { + return nil, err + } + // Define default configuration c := Config{ - Version: cliCtx.App.Metadata["version"].(string), - APIVersion: DefaultAPIVersion, - VersionGitTag: cliCtx.App.Metadata["git-tag"].(string), - Builder: BuilderConfig{}, + ServerUID: uuid, + Version: cliCtx.App.Metadata["version"].(string), + APIVersion: DefaultAPIVersion, + VersionGitTag: cliCtx.App.Metadata["git-tag"].(string), + Builder: BuilderConfig{}, + SupportedSharing: map[string]bool{ /*FIXME USE folder.TypePathMap*/ "PathMap": true}, Options: Options{ ConfigFile: cliCtx.GlobalString("config"), @@ -79,6 +88,8 @@ func Init(cliCtx *cli.Context, log *logrus.Logger) (*Config, error) { Log: log, } + c.Log.Infoln("Server UUID: ", uuid) + // config file settings overwrite default config err = readGlobalConfig(&c, c.Options.ConfigFile) if err != nil { @@ -121,8 +132,9 @@ func Init(cliCtx *cli.Context, log *logrus.Logger) (*Config, error) { return nil, fmt.Errorf("Cannot create logs dir: %v", err) } } - c.Log.Infoln("Logs file: ", c.Options.LogFile) - c.Log.Infoln("Logs directory: ", c.FileConf.LogsDir) + + c.Log.Infoln("Logs file: ", c.Options.LogFile) + c.Log.Infoln("Logs directory: ", c.FileConf.LogsDir) return &c, nil } diff --git a/lib/xdsconfig/data.go b/lib/xdsconfig/data.go new file mode 100644 index 0000000..65e0fc6 --- /dev/null +++ b/lib/xdsconfig/data.go @@ -0,0 +1,87 @@ +package xdsconfig + +import ( + "encoding/xml" + "fmt" + "os" + + common "github.com/iotbzh/xds-common/golib" + uuid "github.com/satori/go.uuid" + "github.com/syncthing/syncthing/lib/sync" +) + +// xmlServerData contains persistent data stored/loaded by server +type xmlServerData struct { + XMLName xml.Name `xml:"XDS-Server"` + Version string `xml:"version,attr"` + Data ServerData `xml:"server-data"` +} + +type ServerData struct { + ID string `xml:"id"` +} + +var sdMutex = sync.NewMutex() + +// ServerIDGet +func ServerIDGet() (string, error) { + var f string + var err error + + d := ServerData{} + if f, err = ServerDataFilenameGet(); err != nil { + return "", err + } + if err = serverDataRead(f, &d); err != nil || d.ID == "" { + // Create a new uuid when not found + d.ID = uuid.NewV1().String() + if err := serverDataWrite(f, d); err != nil { + return "", err + } + } + return d.ID, nil +} + +// serverDataRead reads data saved on disk +func serverDataRead(file string, data *ServerData) error { + if !common.Exists(file) { + return fmt.Errorf("No folder config file found (%s)", file) + } + + sdMutex.Lock() + defer sdMutex.Unlock() + + fd, err := os.Open(file) + defer fd.Close() + if err != nil { + return err + } + + xsd := xmlServerData{} + err = xml.NewDecoder(fd).Decode(&xsd) + if err == nil { + *data = xsd.Data + } + return err +} + +// serverDataWrite writes persistant data to disk +func serverDataWrite(file string, data ServerData) error { + sdMutex.Lock() + defer sdMutex.Unlock() + + fd, err := os.OpenFile(file, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666) + defer fd.Close() + if err != nil { + return err + } + + xsd := &xmlServerData{ + Version: "1", + Data: data, + } + + enc := xml.NewEncoder(fd) + enc.Indent("", " ") + return enc.Encode(xsd) +} diff --git a/lib/xdsconfig/fileconfig.go b/lib/xdsconfig/fileconfig.go index 2651caf..dafb034 100644 --- a/lib/xdsconfig/fileconfig.go +++ b/lib/xdsconfig/fileconfig.go @@ -16,6 +16,8 @@ const ( ConfigDir = ".xds-server" // GlobalConfigFilename Global config filename GlobalConfigFilename = "config.json" + // ServerDataFilename Server data filename + ServerDataFilename = "server-data.xml" // FoldersConfigFilename Folders config filename FoldersConfigFilename = "server-config_folders.xml" ) @@ -82,7 +84,7 @@ func readGlobalConfig(c *Config, confFile string) error { // No config file found return nil } - c.Log.Infof("Use config file: %s", *cFile) + 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) @@ -146,11 +148,20 @@ func readGlobalConfig(c *Config, confFile string) error { return nil } -// FoldersConfigFilenameGet -func FoldersConfigFilenameGet() (string, error) { +func configFilenameGet(cfgFile string) (string, error) { usr, err := user.Current() if err != nil { return "", err } - return path.Join(usr.HomeDir, ConfigDir, FoldersConfigFilename), nil + return path.Join(usr.HomeDir, ConfigDir, cfgFile), nil +} + +// FoldersConfigFilenameGet +func FoldersConfigFilenameGet() (string, error) { + return configFilenameGet(FoldersConfigFilename) +} + +// ServerDataFilenameGet +func ServerDataFilenameGet() (string, error) { + return configFilenameGet(ServerDataFilename) } |