diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/apiv1/make.go | 215 | ||||
-rw-r--r-- | lib/apiv1/version.go | 26 | ||||
-rw-r--r-- | lib/crosssdk/sdk.go | 56 | ||||
-rw-r--r-- | lib/syncthing/stfolder.go | 14 | ||||
-rw-r--r-- | lib/xdsconfig/builderconfig.go | 15 | ||||
-rw-r--r-- | lib/xdsconfig/config.go | 23 | ||||
-rw-r--r-- | lib/xdsserver/apiv1-config.go (renamed from lib/apiv1/config.go) | 10 | ||||
-rw-r--r-- | lib/xdsserver/apiv1-events.go (renamed from lib/apiv1/events.go) | 56 | ||||
-rw-r--r-- | lib/xdsserver/apiv1-exec.go (renamed from lib/apiv1/exec.go) | 140 | ||||
-rw-r--r-- | lib/xdsserver/apiv1-folders.go (renamed from lib/apiv1/folders.go) | 16 | ||||
-rw-r--r-- | lib/xdsserver/apiv1-make.go | 214 | ||||
-rw-r--r-- | lib/xdsserver/apiv1-sdks.go (renamed from lib/apiv1/sdks.go) | 2 | ||||
-rw-r--r-- | lib/xdsserver/apiv1-version.go | 20 | ||||
-rw-r--r-- | lib/xdsserver/apiv1.go (renamed from lib/apiv1/apiv1.go) | 28 | ||||
-rw-r--r-- | lib/xdsserver/folder-interface.go | 22 | ||||
-rw-r--r-- | lib/xdsserver/folder-pathmap.go (renamed from lib/folder/folder-pathmap.go) | 30 | ||||
-rw-r--r-- | lib/xdsserver/folder-st-disable.go (renamed from lib/folder/folder-st-disable.go) | 22 | ||||
-rw-r--r-- | lib/xdsserver/folder-st.go (renamed from lib/syncthing/folder-st.go) | 54 | ||||
-rw-r--r-- | lib/xdsserver/folders.go (renamed from lib/model/folders.go) | 91 | ||||
-rw-r--r-- | lib/xdsserver/sdk.go | 56 | ||||
-rw-r--r-- | lib/xdsserver/sdks.go (renamed from lib/crosssdk/sdks.go) | 37 | ||||
-rw-r--r-- | lib/xdsserver/sessions.go (renamed from lib/session/session.go) | 20 | ||||
-rw-r--r-- | lib/xdsserver/webserver.go (renamed from lib/webserver/server.go) | 88 | ||||
-rw-r--r-- | lib/xdsserver/xdsserver.go | 202 | ||||
-rw-r--r-- | lib/xsapiv1/config.go | 18 | ||||
-rw-r--r-- | lib/xsapiv1/events.go | 31 | ||||
-rw-r--r-- | lib/xsapiv1/exec.go | 76 | ||||
-rw-r--r-- | lib/xsapiv1/folders.go (renamed from lib/folder/folder-interface.go) | 21 | ||||
-rw-r--r-- | lib/xsapiv1/sdks.go | 14 | ||||
-rw-r--r-- | lib/xsapiv1/version.go | 9 |
30 files changed, 910 insertions, 716 deletions
diff --git a/lib/apiv1/make.go b/lib/apiv1/make.go deleted file mode 100644 index 6e0c7d6..0000000 --- a/lib/apiv1/make.go +++ /dev/null @@ -1,215 +0,0 @@ -package apiv1 - -import ( - "net/http" - "strings" - - "time" - - "strconv" - - "github.com/gin-gonic/gin" - common "github.com/iotbzh/xds-common/golib" -) - -// 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 - 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 - ExitImmediate bool `json:"exitImmediate"` // when true, exit event sent immediately when command exited (IOW, don't wait file synchronization) - CmdTimeout int `json:"timeout"` // command completion timeout in Second -} - -// MakeOutMsg Message send on each output (stdout+stderr) of make command -type MakeOutMsg struct { - CmdID string `json:"cmdID"` - Timestamp string `json:"timestamp"` - Stdout string `json:"stdout"` - Stderr string `json:"stderr"` -} - -// MakeExitMsg Message send on make command exit -type MakeExitMsg struct { - CmdID string `json:"cmdID"` - Timestamp string `json:"timestamp"` - Code int `json:"code"` - Error error `json:"error"` -} - -// MakeOutEvent Event send in WS when characters are received on stdout/stderr -const MakeOutEvent = "make:output" - -// MakeExitEvent Event send in WS when command exited -const MakeExitEvent = "make:exit" - -var makeCommandID = 1 - -func (s *APIService) buildMake(c *gin.Context) { - var args MakeArgs - - if c.BindJSON(&args) != nil { - common.APIError(c, "Invalid arguments") - return - } - - sess := s.sessions.Get(c) - if sess == nil { - common.APIError(c, "Unknown sessions") - return - } - sop := sess.IOSocket - if sop == nil { - common.APIError(c, "Websocket not established") - return - } - - // Allow to pass id in url (/make/:id) or as JSON argument - idArg := c.Param("id") - if idArg == "" { - idArg = args.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") - return - } - folder := *pf - prj := folder.GetConfig() - - execTmo := args.CmdTimeout - if execTmo == 0 { - // TODO get default timeout from config.json file - execTmo = 24 * 60 * 60 // 1 day - } - - // TODO merge all code below with exec.go - - // Define callback for output - var oCB common.EmitOutputCB - oCB = func(sid string, cmdID string, stdout, stderr string, data *map[string]interface{}) { - // IO socket can be nil when disconnected - so := s.sessions.IOSocketGet(sid) - if so == nil { - s.log.Infof("%s not emitted: WS closed - sid: %s - msg id:%s", MakeOutEvent, sid, cmdID) - return - } - - // Retrieve project ID and RootPath - prjID := (*data)["ID"].(string) - prjRootPath := (*data)["RootPath"].(string) - - // Cleanup any references to internal rootpath in stdout & stderr - stdout = strings.Replace(stdout, prjRootPath, "", -1) - stderr = strings.Replace(stderr, prjRootPath, "", -1) - - s.log.Debugf("%s emitted - WS sid %s - id:%d - prjID:%s", MakeOutEvent, sid, id, prjID) - - // FIXME replace by .BroadcastTo a room - err := (*so).Emit(MakeOutEvent, MakeOutMsg{ - CmdID: cmdID, - Timestamp: time.Now().String(), - Stdout: stdout, - Stderr: stderr, - }) - if err != nil { - s.log.Errorf("WS Emit : %v", err) - } - } - - // Define callback for output - eCB := func(sid string, cmdID string, code int, err error, data *map[string]interface{}) { - s.log.Debugf("Command [Cmd ID %s] exited: code %d, error: %v", cmdID, code, err) - - // IO socket can be nil when disconnected - so := s.sessions.IOSocketGet(sid) - if so == nil { - s.log.Infof("%s not emitted - WS closed (id:%s", MakeExitEvent, cmdID) - return - } - - // Retrieve project ID and RootPath - prjID := (*data)["ID"].(string) - exitImm := (*data)["ExitImmediate"].(bool) - - // XXX - workaround to be sure that Syncthing detected all changes - if err := s.mfolders.ForceSync(prjID); err != nil { - s.log.Errorf("Error while syncing folder %s: %v", prjID, err) - } - if !exitImm { - // Wait end of file sync - // FIXME pass as argument - tmo := 60 - for t := tmo; t > 0; t-- { - s.log.Debugf("Wait file insync for %s (%d/%d)", prjID, t, tmo) - if sync, err := s.mfolders.IsFolderInSync(prjID); sync || err != nil { - if err != nil { - s.log.Errorf("ERROR IsFolderInSync (%s): %v", prjID, err) - } - break - } - time.Sleep(time.Second) - } - } - - // FIXME replace by .BroadcastTo a room - e := (*so).Emit(MakeExitEvent, MakeExitMsg{ - CmdID: id, - Timestamp: time.Now().String(), - Code: code, - Error: err, - }) - if e != nil { - s.log.Errorf("WS Emit : %v", e) - } - } - - // 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 - if envCmd := s.sdks.GetEnvCmd(args.SdkID, prj.DefaultSdk); len(envCmd) > 0 { - cmd = append(cmd, envCmd...) - cmd = append(cmd, "&&") - } - - cmd = append(cmd, "cd", folder.GetFullPath(args.RPath), "&&", "make") - if len(args.Args) > 0 { - cmd = append(cmd, args.Args...) - } - - 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, args.CmdID, execTmo, s.log, oCB, eCB, &data) - if err != nil { - common.APIError(c, err.Error()) - return - } - - c.JSON(http.StatusOK, - gin.H{ - "status": "OK", - "cmdID": args.CmdID, - }) -} diff --git a/lib/apiv1/version.go b/lib/apiv1/version.go deleted file mode 100644 index 8f928ec..0000000 --- a/lib/apiv1/version.go +++ /dev/null @@ -1,26 +0,0 @@ -package apiv1 - -import ( - "net/http" - - "github.com/gin-gonic/gin" -) - -type version struct { - ID string `json:"id"` - Version string `json:"version"` - APIVersion string `json:"apiVersion"` - VersionGitTag string `json:"gitTag"` -} - -// 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, - } - - c.JSON(http.StatusOK, response) -} diff --git a/lib/crosssdk/sdk.go b/lib/crosssdk/sdk.go deleted file mode 100644 index 5be8954..0000000 --- a/lib/crosssdk/sdk.go +++ /dev/null @@ -1,56 +0,0 @@ -package crosssdk - -import ( - "fmt" - "path/filepath" - - uuid "github.com/satori/go.uuid" -) - -// SDK Define a cross tool chain used to build application -type SDK struct { - ID string `json:"id" binding:"required"` - Name string `json:"name"` - Profile string `json:"profile"` - Version string `json:"version"` - Arch string `json:"arch"` - Path string `json:"path"` - - // Not exported fields - EnvFile string `json:"-"` -} - -// NewCrossSDK creates a new instance of Syncthing -func NewCrossSDK(path string) (*SDK, error) { - // Assume that we have .../<profile>/<version>/<arch> - s := SDK{Path: path} - - s.Arch = filepath.Base(path) - - d := filepath.Dir(path) - s.Version = filepath.Base(d) - - d = filepath.Dir(d) - s.Profile = filepath.Base(d) - - // 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) - if err != nil { - return nil, fmt.Errorf("Cannot retrieve environment setup file: %v", err) - } - if len(ef) != 1 { - return nil, fmt.Errorf("No environment setup file found match %s", envFile) - } - s.EnvFile = ef[0] - - return &s, nil -} - -// GetEnvCmd returns the command used to initialized the environment -func (s *SDK) GetEnvCmd() []string { - return []string{"source", s.EnvFile} -} diff --git a/lib/syncthing/stfolder.go b/lib/syncthing/stfolder.go index 503ba4b..1a5a7ec 100644 --- a/lib/syncthing/stfolder.go +++ b/lib/syncthing/stfolder.go @@ -6,13 +6,13 @@ import ( "path/filepath" "strings" - "github.com/iotbzh/xds-server/lib/folder" + "github.com/iotbzh/xds-server/lib/xsapiv1" stconfig "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/protocol" ) // FolderLoadFromStConfig Load/Retrieve folder config from syncthing database -func (s *SyncThing) FolderLoadFromStConfig(f *[]folder.FolderConfig) error { +func (s *SyncThing) FolderLoadFromStConfig(f *[]xsapiv1.FolderConfig) error { defaultSdk := "" // cannot know which was the default sdk @@ -36,15 +36,15 @@ func (s *SyncThing) FolderLoadFromStConfig(f *[]folder.FolderConfig) error { if cliPath == "" { cliPath = stFld.Path } - *f = append(*f, folder.FolderConfig{ + *f = append(*f, xsapiv1.FolderConfig{ ID: stFld.ID, Label: stFld.Label, ClientPath: strings.TrimRight(cliPath, "/"), - Type: folder.TypeCloudSync, - Status: folder.StatusDisable, + Type: xsapiv1.TypeCloudSync, + Status: xsapiv1.StatusDisable, DefaultSdk: defaultSdk, RootPath: s.conf.FileConf.ShareRootDir, - DataCloudSync: folder.CloudSyncConfig{SyncThingID: devID}, + DataCloudSync: xsapiv1.CloudSyncConfig{SyncThingID: devID}, }) } @@ -52,7 +52,7 @@ func (s *SyncThing) FolderLoadFromStConfig(f *[]folder.FolderConfig) error { } // FolderChange is called when configuration has changed -func (s *SyncThing) FolderChange(f folder.FolderConfig) (string, error) { +func (s *SyncThing) FolderChange(f xsapiv1.FolderConfig) (string, error) { // Get current config stCfg, err := s.ConfigGet() diff --git a/lib/xdsconfig/builderconfig.go b/lib/xdsconfig/builderconfig.go index 6fc1814..3b1ca55 100644 --- a/lib/xdsconfig/builderconfig.go +++ b/lib/xdsconfig/builderconfig.go @@ -3,24 +3,19 @@ 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"` -} + "github.com/iotbzh/xds-server/lib/xsapiv1" +) // NewBuilderConfig creates a new BuilderConfig instance -func NewBuilderConfig(stID string) (BuilderConfig, error) { +func NewBuilderConfig(stID string) (xsapiv1.BuilderConfig, error) { // Do we really need it ? may be not accessible from client side ip, err := getLocalIP() if err != nil { - return BuilderConfig{}, err + return xsapiv1.BuilderConfig{}, err } - b := BuilderConfig{ + b := xsapiv1.BuilderConfig{ IP: ip, // TODO currently not used Port: "", // TODO currently not used SyncThingID: stID, diff --git a/lib/xdsconfig/config.go b/lib/xdsconfig/config.go index 0fc1346..0201d40 100644 --- a/lib/xdsconfig/config.go +++ b/lib/xdsconfig/config.go @@ -9,16 +9,13 @@ import ( "github.com/Sirupsen/logrus" "github.com/codegangsta/cli" common "github.com/iotbzh/xds-common/golib" + "github.com/iotbzh/xds-server/lib/xsapiv1" ) // Config parameters (json format) of /config command type Config struct { - 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"` + // Public APIConfig fields + xsapiv1.APIConfig // Private (un-exported fields in REST GET /config route) Options Options `json:"-"` @@ -64,12 +61,14 @@ func Init(cliCtx *cli.Context, log *logrus.Logger) (*Config, error) { // Define default configuration c := Config{ - 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}, + APIConfig: xsapiv1.APIConfig{ + ServerUID: uuid, + Version: cliCtx.App.Metadata["version"].(string), + APIVersion: DefaultAPIVersion, + VersionGitTag: cliCtx.App.Metadata["git-tag"].(string), + Builder: xsapiv1.BuilderConfig{}, + SupportedSharing: map[string]bool{ /*FIXME USE folder.TypePathMap*/ "PathMap": true}, + }, Options: Options{ ConfigFile: cliCtx.GlobalString("config"), diff --git a/lib/apiv1/config.go b/lib/xdsserver/apiv1-config.go index 4b53217..5a5bb6e 100644 --- a/lib/apiv1/config.go +++ b/lib/xdsserver/apiv1-config.go @@ -1,4 +1,4 @@ -package apiv1 +package xdsserver import ( "net/http" @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" common "github.com/iotbzh/xds-common/golib" - "github.com/iotbzh/xds-server/lib/xdsconfig" + "github.com/iotbzh/xds-server/lib/xsapiv1" ) var confMut sync.Mutex @@ -16,7 +16,7 @@ func (s *APIService) getConfig(c *gin.Context) { confMut.Lock() defer confMut.Unlock() - c.JSON(http.StatusOK, s.cfg) + c.JSON(http.StatusOK, s.Config) } // SetConfig sets server configuration @@ -24,7 +24,7 @@ func (s *APIService) setConfig(c *gin.Context) { // FIXME - must be tested c.JSON(http.StatusNotImplemented, "Not implemented") - var cfgArg xdsconfig.Config + var cfgArg xsapiv1.APIConfig if c.BindJSON(&cfgArg) != nil { common.APIError(c, "Invalid arguments") @@ -34,7 +34,7 @@ func (s *APIService) setConfig(c *gin.Context) { confMut.Lock() defer confMut.Unlock() - s.log.Debugln("SET config: ", cfgArg) + s.Log.Debugln("SET config: ", cfgArg) common.APIError(c, "Not Supported") } diff --git a/lib/apiv1/events.go b/lib/xdsserver/apiv1-events.go index d837571..3823f9e 100644 --- a/lib/apiv1/events.go +++ b/lib/xdsserver/apiv1-events.go @@ -1,43 +1,13 @@ -package apiv1 +package xdsserver import ( "net/http" "strings" "time" - "github.com/iotbzh/xds-server/lib/folder" - "github.com/gin-gonic/gin" common "github.com/iotbzh/xds-common/golib" -) - -// EventArgs is the parameters (json format) of /events/register command -type EventRegisterArgs struct { - Name string `json:"name"` - ProjectID string `json:"filterProjectID"` -} - -type EventUnRegisterArgs struct { - Name string `json:"name"` - ID int `json:"id"` -} - -// EventMsg Message send -type EventMsg struct { - Time string `json:"time"` - Type string `json:"type"` - Folder folder.FolderConfig `json:"folder"` -} - -// EventEvent Event send in WS when an internal event (eg. Syncthing event is received) -const ( - // EventTypePrefix Used as event prefix - EventTypePrefix = "event:" // following by event type - - // Supported Events type - EVTAll = EventTypePrefix + "all" - EVTFolderChange = EventTypePrefix + "folder-change" // type EventMsg with Data type apiv1.??? - EVTFolderStateChange = EventTypePrefix + "folder-state-change" // type EventMsg with Data type apiv1.??? + "github.com/iotbzh/xds-server/lib/xsapiv1" ) // eventsList Registering for events that will be send over a WS @@ -47,7 +17,7 @@ func (s *APIService) eventsList(c *gin.Context) { // eventsRegister Registering for events that will be send over a WS func (s *APIService) eventsRegister(c *gin.Context) { - var args EventRegisterArgs + var args xsapiv1.EventRegisterArgs if c.BindJSON(&args) != nil { common.APIError(c, "Invalid arguments") @@ -60,7 +30,7 @@ func (s *APIService) eventsRegister(c *gin.Context) { return } - evType := strings.TrimPrefix(EVTFolderStateChange, EventTypePrefix) + evType := strings.TrimPrefix(xsapiv1.EVTFolderStateChange, xsapiv1.EventTypePrefix) if args.Name != evType { common.APIError(c, "Unsupported event name") return @@ -102,8 +72,8 @@ func (s *APIService) eventsRegister(c *gin.Context) { id, err := s.mfolders.SThg.Events.Register(args.Name, cbFunc, args.ProjectID, &data) */ - var cbFunc folder.EventCB - cbFunc = func(cfg *folder.FolderConfig, data *folder.EventCBData) { + var cbFunc FolderEventCB + cbFunc = func(cfg *xsapiv1.FolderConfig, data *FolderEventCBData) { ssid := (*data)["sid"].(string) so := s.sessions.IOSocketGet(ssid) if so == nil { @@ -114,20 +84,20 @@ func (s *APIService) eventsRegister(c *gin.Context) { return } - msg := EventMsg{ + msg := xsapiv1.EventMsg{ Time: time.Now().String(), Type: evType, Folder: *cfg, } - s.log.Debugf("WS Emit %s - Status=%10s, IsInSync=%6v, ID=%s", - EventTypePrefix+evType, cfg.Status, cfg.IsInSync, cfg.ID) + s.Log.Debugf("WS Emit %s - Status=%10s, IsInSync=%6v, ID=%s", + xsapiv1.EventTypePrefix+evType, cfg.Status, cfg.IsInSync, cfg.ID) - if err := (*so).Emit(EventTypePrefix+evType, msg); err != nil { - s.log.Errorf("WS Emit Folder StateChanged event : %v", err) + if err := (*so).Emit(xsapiv1.EventTypePrefix+evType, msg); err != nil { + s.Log.Errorf("WS Emit Folder StateChanged event : %v", err) } } - data := make(folder.EventCBData) + data := make(FolderEventCBData) data["sid"] = sess.ID prjID, err := s.mfolders.ResolveID(args.ProjectID) @@ -145,7 +115,7 @@ func (s *APIService) eventsRegister(c *gin.Context) { // eventsRegister Registering for events that will be send over a WS func (s *APIService) eventsUnRegister(c *gin.Context) { - var args EventUnRegisterArgs + var args xsapiv1.EventUnRegisterArgs if c.BindJSON(&args) != nil || args.Name == "" || args.ID < 0 { common.APIError(c, "Invalid arguments") diff --git a/lib/apiv1/exec.go b/lib/xdsserver/apiv1-exec.go index baf431f..ce5e7b7 100644 --- a/lib/apiv1/exec.go +++ b/lib/xdsserver/apiv1-exec.go @@ -1,4 +1,4 @@ -package apiv1 +package xdsserver import ( "fmt" @@ -12,91 +12,17 @@ import ( "github.com/gin-gonic/gin" common "github.com/iotbzh/xds-common/golib" "github.com/iotbzh/xds-common/golib/eows" + "github.com/iotbzh/xds-server/lib/xsapiv1" "github.com/kr/pty" ) -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 - CmdID string `json:"cmdID"` // command unique ID - Cmd string `json:"cmd" binding:"required"` - Args []string `json:"args"` - Env []string `json:"env"` - RPath string `json:"rpath"` // relative path into project - TTY bool `json:"tty"` // Use a tty, specific to gdb --tty option - TTYGdbserverFix bool `json:"ttyGdbserverFix"` // Set to true to activate gdbserver workaround about inferior output - ExitImmediate bool `json:"exitImmediate"` // when true, exit event sent immediately when command exited (IOW, don't wait file synchronization) - CmdTimeout int `json:"timeout"` // command completion timeout in Second - } - - // ExecRes JSON result of /exec command - ExecRes struct { - Status string `json:"status"` // status OK - CmdID string `json:"cmdID"` // command unique ID - } - - // ExecSigRes JSON result of /signal command - ExecSigRes struct { - Status string `json:"status"` // status OK - CmdID string `json:"cmdID"` // command unique ID - } - - // ExecInMsg Message used to received input characters (stdin) - ExecInMsg struct { - CmdID string `json:"cmdID"` - Timestamp string `json:"timestamp"` - Stdin string `json:"stdin"` - } - - // ExecOutMsg Message used to send output characters (stdout+stderr) - ExecOutMsg struct { - CmdID string `json:"cmdID"` - Timestamp string `json:"timestamp"` - Stdout string `json:"stdout"` - Stderr string `json:"stderr"` - } - - // ExecExitMsg Message sent when executed command exited - ExecExitMsg struct { - CmdID string `json:"cmdID"` - Timestamp string `json:"timestamp"` - Code int `json:"code"` - Error error `json:"error"` - } - - // ExecSignalArgs JSON parameters of /exec/signal command - ExecSignalArgs struct { - CmdID string `json:"cmdID" binding:"required"` // command id - Signal string `json:"signal" binding:"required"` // signal number - } -) - -const ( - // ExecInEvent Event send in WS when characters are sent (stdin) - ExecInEvent = "exec:input" - - // ExecOutEvent Event send in WS when characters are received (stdout or stderr) - ExecOutEvent = "exec:output" - - // ExecExitEvent Event send in WS when program exited - ExecExitEvent = "exec:exit" - - // ExecInferiorInEvent Event send in WS when characters are sent to an inferior (used by gdb inferior/tty) - ExecInferiorInEvent = "exec:inferior-input" - - // ExecInferiorOutEvent Event send in WS when characters are received by an inferior - ExecInferiorOutEvent = "exec:inferior-output" -) - var execCommandID = 1 // ExecCmd executes remotely a command func (s *APIService) execCmd(c *gin.Context) { var gdbPty, gdbTty *os.File var err error - var args ExecArgs + var args xsapiv1.ExecArgs if c.BindJSON(&args) != nil { common.APIError(c, "Invalid arguments") return @@ -180,19 +106,19 @@ func (s *APIService) execCmd(c *gin.Context) { return } - s.log.Debugf("Client command tty: %v %v\n", gdbTty.Name(), gdbTty.Name()) + s.Log.Debugf("Client command tty: %v %v\n", gdbTty.Name(), gdbTty.Name()) cmdArgs = append(cmdArgs, "--tty="+gdbTty.Name()) } // Unique ID for each commands if args.CmdID == "" { - args.CmdID = s.cfg.ServerUID[:18] + "_" + strconv.Itoa(execCommandID) + args.CmdID = s.Config.ServerUID[:18] + "_" + strconv.Itoa(execCommandID) execCommandID++ } // Create new execution over WS context execWS := eows.New(strings.Join(cmd, " "), cmdArgs, sop, sess.ID, args.CmdID) - execWS.Log = s.log + execWS.Log = s.Log // Append client project dir to environment execWS.Env = append(args.Env, "CLIENT_PROJECT_DIR="+prj.ClientPath) @@ -207,9 +133,9 @@ func (s *APIService) execCmd(c *gin.Context) { } // Define callback for input (stdin) - execWS.InputEvent = ExecInEvent + execWS.InputEvent = xsapiv1.ExecInEvent execWS.InputCB = func(e *eows.ExecOverWS, stdin string) (string, error) { - s.log.Debugf("STDIN <<%v>>", strings.Replace(stdin, "\n", "\\n", -1)) + s.Log.Debugf("STDIN <<%v>>", strings.Replace(stdin, "\n", "\\n", -1)) // Handle Ctrl-D if len(stdin) == 1 && stdin == "\x04" { @@ -223,7 +149,7 @@ func (s *APIService) execCmd(c *gin.Context) { prjID := (*data)["ID"].(string) f := s.mfolders.Get(prjID) if f == nil { - s.log.Errorf("InputCB: Cannot get folder ID %s", prjID) + s.Log.Errorf("InputCB: Cannot get folder ID %s", prjID) } else { // Translate paths from client to server stdin = (*f).ConvPathCli2Svr(stdin) @@ -237,7 +163,7 @@ func (s *APIService) execCmd(c *gin.Context) { // IO socket can be nil when disconnected so := s.sessions.IOSocketGet(e.Sid) if so == nil { - s.log.Infof("%s not emitted: WS closed (sid:%s, msgid:%s)", ExecOutEvent, e.Sid, e.CmdID) + s.Log.Infof("%s not emitted: WS closed (sid:%s, msgid:%s)", xsapiv1.ExecOutEvent, e.Sid, e.CmdID) return } @@ -248,30 +174,30 @@ func (s *APIService) execCmd(c *gin.Context) { f := s.mfolders.Get(prjID) if f == nil { - s.log.Errorf("OutputCB: Cannot get folder ID %s", prjID) + s.Log.Errorf("OutputCB: Cannot get folder ID %s", prjID) } else { // Translate paths from server to client stdout = (*f).ConvPathSvr2Cli(stdout) stderr = (*f).ConvPathSvr2Cli(stderr) } - s.log.Debugf("%s emitted - WS sid[4:] %s - id:%s - prjID:%s", ExecOutEvent, e.Sid[4:], e.CmdID, prjID) + s.Log.Debugf("%s emitted - WS sid[4:] %s - id:%s - prjID:%s", xsapiv1.ExecOutEvent, e.Sid[4:], e.CmdID, prjID) if stdout != "" { - s.log.Debugf("STDOUT <<%v>>", strings.Replace(stdout, "\n", "\\n", -1)) + s.Log.Debugf("STDOUT <<%v>>", strings.Replace(stdout, "\n", "\\n", -1)) } if stderr != "" { - s.log.Debugf("STDERR <<%v>>", strings.Replace(stderr, "\n", "\\n", -1)) + s.Log.Debugf("STDERR <<%v>>", strings.Replace(stderr, "\n", "\\n", -1)) } // FIXME replace by .BroadcastTo a room - err := (*so).Emit(ExecOutEvent, ExecOutMsg{ + err := (*so).Emit(xsapiv1.ExecOutEvent, xsapiv1.ExecOutMsg{ CmdID: e.CmdID, Timestamp: time.Now().String(), Stdout: stdout, Stderr: stderr, }) if err != nil { - s.log.Errorf("WS Emit : %v", err) + s.Log.Errorf("WS Emit : %v", err) } // XXX - Workaround due to gdbserver bug that doesn't redirect @@ -289,27 +215,27 @@ func (s *APIService) execCmd(c *gin.Context) { out = strings.Replace(out, "\\r", "\r", -1) out = strings.Replace(out, "\\t", "\t", -1) - s.log.Debugf("STDOUT INFERIOR: <<%v>>", out) - err := (*so).Emit(ExecInferiorOutEvent, ExecOutMsg{ + s.Log.Debugf("STDOUT INFERIOR: <<%v>>", out) + err := (*so).Emit(xsapiv1.ExecInferiorOutEvent, xsapiv1.ExecOutMsg{ CmdID: e.CmdID, Timestamp: time.Now().String(), Stdout: out, Stderr: "", }) if err != nil { - s.log.Errorf("WS Emit : %v", err) + s.Log.Errorf("WS Emit : %v", err) } } } } else { - s.log.Errorf("INFERIOR out parsing error: stdout=<%v>", stdout) + s.Log.Errorf("INFERIOR out parsing error: stdout=<%v>", stdout) } } } // Define callback for output execWS.ExitCB = func(e *eows.ExecOverWS, code int, err error) { - s.log.Debugf("Command [Cmd ID %s] exited: code %d, error: %v", e.CmdID, code, err) + s.Log.Debugf("Command [Cmd ID %s] exited: code %d, error: %v", e.CmdID, code, err) // Close client tty defer func() { @@ -324,7 +250,7 @@ func (s *APIService) execCmd(c *gin.Context) { // IO socket can be nil when disconnected so := s.sessions.IOSocketGet(e.Sid) if so == nil { - s.log.Infof("%s not emitted - WS closed (id:%s)", ExecExitEvent, e.CmdID) + s.Log.Infof("%s not emitted - WS closed (id:%s)", xsapiv1.ExecExitEvent, e.CmdID) return } @@ -335,34 +261,34 @@ func (s *APIService) execCmd(c *gin.Context) { // XXX - workaround to be sure that Syncthing detected all changes if err := s.mfolders.ForceSync(prjID); err != nil { - s.log.Errorf("Error while syncing folder %s: %v", prjID, err) + s.Log.Errorf("Error while syncing folder %s: %v", prjID, err) } if !exitImm { // Wait end of file sync // FIXME pass as argument tmo := 60 for t := tmo; t > 0; t-- { - s.log.Debugf("Wait file in-sync for %s (%d/%d)", prjID, t, tmo) + s.Log.Debugf("Wait file in-sync for %s (%d/%d)", prjID, t, tmo) if sync, err := s.mfolders.IsFolderInSync(prjID); sync || err != nil { if err != nil { - s.log.Errorf("ERROR IsFolderInSync (%s): %v", prjID, err) + s.Log.Errorf("ERROR IsFolderInSync (%s): %v", prjID, err) } break } time.Sleep(time.Second) } - s.log.Debugf("OK file are synchronized.") + s.Log.Debugf("OK file are synchronized.") } // FIXME replace by .BroadcastTo a room - errSoEmit := (*so).Emit(ExecExitEvent, ExecExitMsg{ + errSoEmit := (*so).Emit(xsapiv1.ExecExitEvent, xsapiv1.ExecExitMsg{ CmdID: e.CmdID, Timestamp: time.Now().String(), Code: code, Error: err, }) if errSoEmit != nil { - s.log.Errorf("WS Emit : %v", errSoEmit) + s.Log.Errorf("WS Emit : %v", errSoEmit) } } @@ -378,7 +304,7 @@ func (s *APIService) execCmd(c *gin.Context) { execWS.UserData = &data // Start command execution - s.log.Infof("Execute [Cmd ID %s]: %v %v", execWS.CmdID, execWS.Cmd, execWS.Args) + s.Log.Infof("Execute [Cmd ID %s]: %v %v", execWS.CmdID, execWS.Cmd, execWS.Args) err = execWS.Start() if err != nil { @@ -386,19 +312,19 @@ func (s *APIService) execCmd(c *gin.Context) { return } - c.JSON(http.StatusOK, ExecRes{Status: "OK", CmdID: execWS.CmdID}) + c.JSON(http.StatusOK, xsapiv1.ExecResult{Status: "OK", CmdID: execWS.CmdID}) } // ExecCmd executes remotely a command func (s *APIService) execSignalCmd(c *gin.Context) { - var args ExecSignalArgs + var args xsapiv1.ExecSignalArgs if c.BindJSON(&args) != nil { common.APIError(c, "Invalid arguments") return } - s.log.Debugf("Signal %s for command ID %s", args.Signal, args.CmdID) + s.Log.Debugf("Signal %s for command ID %s", args.Signal, args.CmdID) e := eows.GetEows(args.CmdID) if e == nil { @@ -412,5 +338,5 @@ func (s *APIService) execSignalCmd(c *gin.Context) { return } - c.JSON(http.StatusOK, ExecSigRes{Status: "OK", CmdID: args.CmdID}) + c.JSON(http.StatusOK, xsapiv1.ExecSigResult{Status: "OK", CmdID: args.CmdID}) } diff --git a/lib/apiv1/folders.go b/lib/xdsserver/apiv1-folders.go index 073445c..fe11e52 100644 --- a/lib/apiv1/folders.go +++ b/lib/xdsserver/apiv1-folders.go @@ -1,4 +1,4 @@ -package apiv1 +package xdsserver import ( "net/http" @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" common "github.com/iotbzh/xds-common/golib" - "github.com/iotbzh/xds-server/lib/folder" + "github.com/iotbzh/xds-server/lib/xsapiv1" ) // getFolders returns all folders configuration @@ -32,13 +32,13 @@ func (s *APIService) getFolder(c *gin.Context) { // addFolder adds a new folder to server config func (s *APIService) addFolder(c *gin.Context) { - var cfgArg folder.FolderConfig + var cfgArg xsapiv1.FolderConfig if c.BindJSON(&cfgArg) != nil { common.APIError(c, "Invalid arguments") return } - s.log.Debugln("Add folder config: ", cfgArg) + s.Log.Debugln("Add folder config: ", cfgArg) newFld, err := s.mfolders.Add(cfgArg) if err != nil { @@ -77,7 +77,7 @@ func (s *APIService) syncFolder(c *gin.Context) { common.APIError(c, err.Error()) return } - s.log.Debugln("Sync folder id: ", id) + s.Log.Debugln("Sync folder id: ", id) err = s.mfolders.ForceSync(id) if err != nil { @@ -96,7 +96,7 @@ func (s *APIService) delFolder(c *gin.Context) { return } - s.log.Debugln("Delete folder id ", id) + s.Log.Debugln("Delete folder id ", id) delEntry, err := s.mfolders.Delete(id) if err != nil { @@ -114,9 +114,9 @@ func (s *APIService) updateFolder(c *gin.Context) { return } - s.log.Debugln("Update folder id ", id) + s.Log.Debugln("Update folder id ", id) - var cfgArg folder.FolderConfig + var cfgArg xsapiv1.FolderConfig if c.BindJSON(&cfgArg) != nil { common.APIError(c, "Invalid arguments") return diff --git a/lib/xdsserver/apiv1-make.go b/lib/xdsserver/apiv1-make.go new file mode 100644 index 0000000..bcb4d95 --- /dev/null +++ b/lib/xdsserver/apiv1-make.go @@ -0,0 +1,214 @@ +package xdsserver + +import ( + "github.com/gin-gonic/gin" + common "github.com/iotbzh/xds-common/golib" +) + +/* TODO: Deprecated - should be removed +// 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 + 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 + ExitImmediate bool `json:"exitImmediate"` // when true, exit event sent immediately when command exited (IOW, don't wait file synchronization) + CmdTimeout int `json:"timeout"` // command completion timeout in Second +} + +// MakeOutMsg Message send on each output (stdout+stderr) of make command +type MakeOutMsg struct { + CmdID string `json:"cmdID"` + Timestamp string `json:"timestamp"` + Stdout string `json:"stdout"` + Stderr string `json:"stderr"` +} + +// MakeExitMsg Message send on make command exit +type MakeExitMsg struct { + CmdID string `json:"cmdID"` + Timestamp string `json:"timestamp"` + Code int `json:"code"` + Error error `json:"error"` +} + +// MakeOutEvent Event send in WS when characters are received on stdout/stderr +const MakeOutEvent = "make:output" + +// MakeExitEvent Event send in WS when command exited +const MakeExitEvent = "make:exit" + +var makeCommandID = 1 +*/ + +func (s *APIService) buildMake(c *gin.Context) { + common.APIError(c, "/make route is not longer supported, use /exec instead") + + /* + var args MakeArgs + + if c.BindJSON(&args) != nil { + common.APIError(c, "Invalid arguments") + return + } + + sess := s.sessions.Get(c) + if sess == nil { + common.APIError(c, "Unknown sessions") + return + } + sop := sess.IOSocket + if sop == nil { + common.APIError(c, "Websocket not established") + return + } + + // Allow to pass id in url (/make/:id) or as JSON argument + idArg := c.Param("id") + if idArg == "" { + idArg = args.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") + return + } + folder := *pf + prj := folder.GetConfig() + + execTmo := args.CmdTimeout + if execTmo == 0 { + // TODO get default timeout from config.json file + execTmo = 24 * 60 * 60 // 1 day + } + + // TODO merge all code below with exec.go + + // Define callback for output + var oCB common.EmitOutputCB + oCB = func(sid string, cmdID string, stdout, stderr string, data *map[string]interface{}) { + // IO socket can be nil when disconnected + so := s.sessions.IOSocketGet(sid) + if so == nil { + s.log.Infof("%s not emitted: WS closed - sid: %s - msg id:%s", MakeOutEvent, sid, cmdID) + return + } + + // Retrieve project ID and RootPath + prjID := (*data)["ID"].(string) + prjRootPath := (*data)["RootPath"].(string) + + // Cleanup any references to internal rootpath in stdout & stderr + stdout = strings.Replace(stdout, prjRootPath, "", -1) + stderr = strings.Replace(stderr, prjRootPath, "", -1) + + s.log.Debugf("%s emitted - WS sid %s - id:%d - prjID:%s", MakeOutEvent, sid, id, prjID) + + // FIXME replace by .BroadcastTo a room + err := (*so).Emit(MakeOutEvent, MakeOutMsg{ + CmdID: cmdID, + Timestamp: time.Now().String(), + Stdout: stdout, + Stderr: stderr, + }) + if err != nil { + s.log.Errorf("WS Emit : %v", err) + } + } + + // Define callback for output + eCB := func(sid string, cmdID string, code int, err error, data *map[string]interface{}) { + s.log.Debugf("Command [Cmd ID %s] exited: code %d, error: %v", cmdID, code, err) + + // IO socket can be nil when disconnected + so := s.sessions.IOSocketGet(sid) + if so == nil { + s.log.Infof("%s not emitted - WS closed (id:%s", MakeExitEvent, cmdID) + return + } + + // Retrieve project ID and RootPath + prjID := (*data)["ID"].(string) + exitImm := (*data)["ExitImmediate"].(bool) + + // XXX - workaround to be sure that Syncthing detected all changes + if err := s.mfolders.ForceSync(prjID); err != nil { + s.log.Errorf("Error while syncing folder %s: %v", prjID, err) + } + if !exitImm { + // Wait end of file sync + // FIXME pass as argument + tmo := 60 + for t := tmo; t > 0; t-- { + s.log.Debugf("Wait file insync for %s (%d/%d)", prjID, t, tmo) + if sync, err := s.mfolders.IsFolderInSync(prjID); sync || err != nil { + if err != nil { + s.log.Errorf("ERROR IsFolderInSync (%s): %v", prjID, err) + } + break + } + time.Sleep(time.Second) + } + } + + // FIXME replace by .BroadcastTo a room + e := (*so).Emit(MakeExitEvent, MakeExitMsg{ + CmdID: id, + Timestamp: time.Now().String(), + Code: code, + Error: err, + }) + if e != nil { + s.log.Errorf("WS Emit : %v", e) + } + } + + // 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 + if envCmd := s.sdks.GetEnvCmd(args.SdkID, prj.DefaultSdk); len(envCmd) > 0 { + cmd = append(cmd, envCmd...) + cmd = append(cmd, "&&") + } + + cmd = append(cmd, "cd", folder.GetFullPath(args.RPath), "&&", "make") + if len(args.Args) > 0 { + cmd = append(cmd, args.Args...) + } + + 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, args.CmdID, execTmo, s.log, oCB, eCB, &data) + if err != nil { + common.APIError(c, err.Error()) + return + } + + c.JSON(http.StatusOK, + gin.H{ + "status": "OK", + "cmdID": args.CmdID, + }) + */ +} diff --git a/lib/apiv1/sdks.go b/lib/xdsserver/apiv1-sdks.go index f67a0ef..be9fcf7 100644 --- a/lib/apiv1/sdks.go +++ b/lib/xdsserver/apiv1-sdks.go @@ -1,4 +1,4 @@ -package apiv1 +package xdsserver import ( "net/http" diff --git a/lib/xdsserver/apiv1-version.go b/lib/xdsserver/apiv1-version.go new file mode 100644 index 0000000..2c2547c --- /dev/null +++ b/lib/xdsserver/apiv1-version.go @@ -0,0 +1,20 @@ +package xdsserver + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/iotbzh/xds-server/lib/xsapiv1" +) + +// getInfo : return various information about server +func (s *APIService) getVersion(c *gin.Context) { + response := xsapiv1.Version{ + ID: s.Config.ServerUID, + Version: s.Config.Version, + APIVersion: s.Config.APIVersion, + VersionGitTag: s.Config.VersionGitTag, + } + + c.JSON(http.StatusOK, response) +} diff --git a/lib/apiv1/apiv1.go b/lib/xdsserver/apiv1.go index fffed2d..1f6df9e 100644 --- a/lib/apiv1/apiv1.go +++ b/lib/xdsserver/apiv1.go @@ -1,36 +1,20 @@ -package apiv1 +package xdsserver import ( - "github.com/Sirupsen/logrus" "github.com/gin-gonic/gin" - - "github.com/iotbzh/xds-server/lib/crosssdk" - "github.com/iotbzh/xds-server/lib/model" - "github.com/iotbzh/xds-server/lib/session" - "github.com/iotbzh/xds-server/lib/xdsconfig" ) // APIService . type APIService struct { - router *gin.Engine + *Context apiRouter *gin.RouterGroup - sessions *session.Sessions - cfg *xdsconfig.Config - mfolders *model.Folders - sdks *crosssdk.SDKs - log *logrus.Logger } -// New creates a new instance of API service -func New(r *gin.Engine, sess *session.Sessions, cfg *xdsconfig.Config, mfolders *model.Folders, sdks *crosssdk.SDKs) *APIService { +// NewAPIV1 creates a new instance of API service +func NewAPIV1(ctx *Context) *APIService { s := &APIService{ - router: r, - sessions: sess, - apiRouter: r.Group("/api/v1"), - cfg: cfg, - mfolders: mfolders, - sdks: sdks, - log: cfg.Log, + Context: ctx, + apiRouter: ctx.WWWServer.router.Group("/api/v1"), } s.apiRouter.GET("/version", s.getVersion) diff --git a/lib/xdsserver/folder-interface.go b/lib/xdsserver/folder-interface.go new file mode 100644 index 0000000..c2b2ada --- /dev/null +++ b/lib/xdsserver/folder-interface.go @@ -0,0 +1,22 @@ +package xdsserver + +import "github.com/iotbzh/xds-server/lib/xsapiv1" + +type FolderEventCBData map[string]interface{} +type FolderEventCB func(cfg *xsapiv1.FolderConfig, data *FolderEventCBData) + +// IFOLDER Folder interface +type IFOLDER interface { + NewUID(suffix string) string // Get a new folder UUID + Add(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) // Add a new folder + GetConfig() xsapiv1.FolderConfig // Get folder public configuration + GetFullPath(dir string) string // Get folder full path + ConvPathCli2Svr(s string) string // Convert path from Client to Server + ConvPathSvr2Cli(s string) string // Convert path from Server to Client + Remove() error // Remove a folder + Update(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) // Update a new folder + RegisterEventChange(cb *FolderEventCB, data *FolderEventCBData) error // Request events registration (sent through WS) + UnRegisterEventChange() error // Un-register events + Sync() error // Force folder files synchronization + IsInSync() (bool, error) // Check if folder files are in-sync +} diff --git a/lib/folder/folder-pathmap.go b/lib/xdsserver/folder-pathmap.go index c5691a3..c5318de 100644 --- a/lib/folder/folder-pathmap.go +++ b/lib/xdsserver/folder-pathmap.go @@ -1,4 +1,4 @@ -package folder +package xdsserver import ( "fmt" @@ -8,7 +8,7 @@ import ( "strings" common "github.com/iotbzh/xds-common/golib" - "github.com/iotbzh/xds-server/lib/xdsconfig" + "github.com/iotbzh/xds-server/lib/xsapiv1" uuid "github.com/satori/go.uuid" ) @@ -16,16 +16,16 @@ import ( // PathMap . type PathMap struct { - globalConfig *xdsconfig.Config - config FolderConfig + *Context + config xsapiv1.FolderConfig } // NewFolderPathMap Create a new instance of PathMap -func NewFolderPathMap(gc *xdsconfig.Config) *PathMap { +func NewFolderPathMap(ctx *Context) *PathMap { f := PathMap{ - globalConfig: gc, - config: FolderConfig{ - Status: StatusDisable, + Context: ctx, + config: xsapiv1.FolderConfig{ + Status: xsapiv1.StatusDisable, }, } return &f @@ -41,7 +41,7 @@ func (f *PathMap) NewUID(suffix string) string { } // Add a new folder -func (f *PathMap) Add(cfg FolderConfig) (*FolderConfig, error) { +func (f *PathMap) Add(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) { if cfg.DataPathMap.ServerPath == "" { return nil, fmt.Errorf("ServerPath must be set") } @@ -49,7 +49,7 @@ func (f *PathMap) Add(cfg FolderConfig) (*FolderConfig, error) { // Use shareRootDir if ServerPath is a relative path dir := cfg.DataPathMap.ServerPath if !filepath.IsAbs(dir) { - dir = filepath.Join(f.globalConfig.FileConf.ShareRootDir, dir) + dir = filepath.Join(f.Config.FileConf.ShareRootDir, dir) } // Sanity check @@ -92,20 +92,20 @@ func (f *PathMap) Add(cfg FolderConfig) (*FolderConfig, error) { } // 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" + msg := "Pathmap checked message written by xds-server ID: " + f.Config.ServerUID + "\n" if n, err := fd.WriteString(msg); n != len(msg) || err != nil { return nil, fmt.Errorf(errMsg, 5, err) } } } - f.config.Status = StatusEnable + f.config.Status = xsapiv1.StatusEnable return &f.config, nil } // GetConfig Get public part of folder config -func (f *PathMap) GetConfig() FolderConfig { +func (f *PathMap) GetConfig() xsapiv1.FolderConfig { return f.config } @@ -146,7 +146,7 @@ func (f *PathMap) Remove() error { } // Update update some fields of a folder -func (f *PathMap) Update(cfg FolderConfig) (*FolderConfig, error) { +func (f *PathMap) Update(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) { if f.config.ID != cfg.ID { return nil, fmt.Errorf("Invalid id") } @@ -155,7 +155,7 @@ func (f *PathMap) Update(cfg FolderConfig) (*FolderConfig, error) { } // RegisterEventChange requests registration for folder change event -func (f *PathMap) RegisterEventChange(cb *EventCB, data *EventCBData) error { +func (f *PathMap) RegisterEventChange(cb *FolderEventCB, data *FolderEventCBData) error { return nil } diff --git a/lib/folder/folder-st-disable.go b/lib/xdsserver/folder-st-disable.go index e936494..64b1efc 100644 --- a/lib/folder/folder-st-disable.go +++ b/lib/xdsserver/folder-st-disable.go @@ -1,7 +1,7 @@ -package folder +package xdsserver import ( - "github.com/iotbzh/xds-server/lib/xdsconfig" + "github.com/iotbzh/xds-server/lib/xsapiv1" uuid "github.com/satori/go.uuid" ) @@ -11,14 +11,14 @@ import ( // STFolderDisable . type STFolderDisable struct { - globalConfig *xdsconfig.Config - config FolderConfig + *Context + config xsapiv1.FolderConfig } // NewFolderSTDisable Create a new instance of STFolderDisable -func NewFolderSTDisable(gc *xdsconfig.Config) *STFolderDisable { +func NewFolderSTDisable(ctx *Context) *STFolderDisable { f := STFolderDisable{ - globalConfig: gc, + Context: ctx, } return &f } @@ -33,15 +33,15 @@ func (f *STFolderDisable) NewUID(suffix string) string { } // Add a new folder -func (f *STFolderDisable) Add(cfg FolderConfig) (*FolderConfig, error) { +func (f *STFolderDisable) Add(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) { f.config = cfg - f.config.Status = StatusDisable + f.config.Status = xsapiv1.StatusDisable f.config.IsInSync = false return &f.config, nil } // GetConfig Get public part of folder config -func (f *STFolderDisable) GetConfig() FolderConfig { +func (f *STFolderDisable) GetConfig() xsapiv1.FolderConfig { return f.config } @@ -66,12 +66,12 @@ func (f *STFolderDisable) Remove() error { } // Update update some fields of a folder -func (f *STFolderDisable) Update(cfg FolderConfig) (*FolderConfig, error) { +func (f *STFolderDisable) Update(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) { return nil, nil } // RegisterEventChange requests registration for folder change event -func (f *STFolderDisable) RegisterEventChange(cb *EventCB, data *EventCBData) error { +func (f *STFolderDisable) RegisterEventChange(cb *FolderEventCB, data *FolderEventCBData) error { return nil } diff --git a/lib/syncthing/folder-st.go b/lib/xdsserver/folder-st.go index 74aa4bb..04bbf76 100644 --- a/lib/syncthing/folder-st.go +++ b/lib/xdsserver/folder-st.go @@ -1,4 +1,4 @@ -package st +package xdsserver import ( "fmt" @@ -6,8 +6,8 @@ import ( "path/filepath" "strings" - "github.com/iotbzh/xds-server/lib/folder" - "github.com/iotbzh/xds-server/lib/xdsconfig" + "github.com/iotbzh/xds-server/lib/xsapiv1" + st "github.com/iotbzh/xds-server/lib/syncthing" uuid "github.com/satori/go.uuid" "github.com/syncthing/syncthing/lib/config" ) @@ -16,20 +16,20 @@ import ( // STFolder . type STFolder struct { - globalConfig *xdsconfig.Config - st *SyncThing - fConfig folder.FolderConfig + *Context + st *st.SyncThing + fConfig xsapiv1.FolderConfig stfConfig config.FolderConfiguration eventIDs []int - eventChangeCB *folder.EventCB - eventChangeCBData *folder.EventCBData + eventChangeCB *FolderEventCB + eventChangeCBData *FolderEventCBData } // NewFolderST Create a new instance of STFolder -func (s *SyncThing) NewFolderST(gc *xdsconfig.Config) *STFolder { +func NewFolderST(ctx *Context, sthg *st.SyncThing) *STFolder { return &STFolder{ - globalConfig: gc, - st: s, + Context: ctx, + st: sthg, } } @@ -47,7 +47,7 @@ func (f *STFolder) NewUID(suffix string) string { } // Add a new folder -func (f *STFolder) Add(cfg folder.FolderConfig) (*folder.FolderConfig, error) { +func (f *STFolder) Add(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) { // Sanity check if cfg.DataCloudSync.SyncThingID == "" { @@ -56,7 +56,7 @@ func (f *STFolder) Add(cfg folder.FolderConfig) (*folder.FolderConfig, error) { // rootPath should not be empty if cfg.RootPath == "" { - cfg.RootPath = f.globalConfig.FileConf.ShareRootDir + cfg.RootPath = f.Config.FileConf.ShareRootDir } f.fConfig = cfg @@ -64,7 +64,7 @@ func (f *STFolder) Add(cfg folder.FolderConfig) (*folder.FolderConfig, error) { // Update Syncthing folder // (except if status is ErrorConfig) // TODO: add cache to avoid multiple requests on startup - if f.fConfig.Status != folder.StatusErrorConfig { + if f.fConfig.Status != xsapiv1.StatusErrorConfig { id, err := f.st.FolderChange(f.fConfig) if err != nil { return nil, err @@ -72,12 +72,12 @@ func (f *STFolder) Add(cfg folder.FolderConfig) (*folder.FolderConfig, error) { f.stfConfig, err = f.st.FolderConfigGet(id) if err != nil { - f.fConfig.Status = folder.StatusErrorConfig + f.fConfig.Status = xsapiv1.StatusErrorConfig return nil, err } // Register to events to update folder status - for _, evName := range []string{EventStateChanged, EventFolderPaused} { + for _, evName := range []string{st.EventStateChanged, st.EventFolderPaused} { evID, err := f.st.Events.Register(evName, f.cbEventState, id, nil) if err != nil { return nil, err @@ -86,14 +86,14 @@ func (f *STFolder) Add(cfg folder.FolderConfig) (*folder.FolderConfig, error) { } f.fConfig.IsInSync = false // will be updated later by events - f.fConfig.Status = folder.StatusEnable + f.fConfig.Status = xsapiv1.StatusEnable } return &f.fConfig, nil } // GetConfig Get public part of folder config -func (f *STFolder) GetConfig() folder.FolderConfig { +func (f *STFolder) GetConfig() xsapiv1.FolderConfig { return f.fConfig } @@ -144,7 +144,7 @@ func (f *STFolder) Remove() error { } // Update update some fields of a folder -func (f *STFolder) Update(cfg folder.FolderConfig) (*folder.FolderConfig, error) { +func (f *STFolder) Update(cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) { if f.fConfig.ID != cfg.ID { return nil, fmt.Errorf("Invalid id") } @@ -153,7 +153,7 @@ func (f *STFolder) Update(cfg folder.FolderConfig) (*folder.FolderConfig, error) } // RegisterEventChange requests registration for folder event change -func (f *STFolder) RegisterEventChange(cb *folder.EventCB, data *folder.EventCBData) error { +func (f *STFolder) RegisterEventChange(cb *FolderEventCB, data *FolderEventCBData) error { f.eventChangeCB = cb f.eventChangeCBData = data return nil @@ -182,25 +182,25 @@ func (f *STFolder) IsInSync() (bool, error) { } // callback use to update IsInSync status -func (f *STFolder) cbEventState(ev Event, data *EventsCBData) { +func (f *STFolder) cbEventState(ev st.Event, data *st.EventsCBData) { prevSync := f.fConfig.IsInSync prevStatus := f.fConfig.Status switch ev.Type { - case EventStateChanged: + case st.EventStateChanged: to := ev.Data["to"] switch to { case "scanning", "syncing": - f.fConfig.Status = folder.StatusSyncing + f.fConfig.Status = xsapiv1.StatusSyncing case "idle": - f.fConfig.Status = folder.StatusEnable + f.fConfig.Status = xsapiv1.StatusEnable } f.fConfig.IsInSync = (to == "idle") - case EventFolderPaused: - if f.fConfig.Status == folder.StatusEnable { - f.fConfig.Status = folder.StatusPause + case st.EventFolderPaused: + if f.fConfig.Status == xsapiv1.StatusEnable { + f.fConfig.Status = xsapiv1.StatusPause } f.fConfig.IsInSync = false } diff --git a/lib/model/folders.go b/lib/xdsserver/folders.go index 0e28538..e36f84c 100644 --- a/lib/model/folders.go +++ b/lib/xdsserver/folders.go @@ -1,4 +1,4 @@ -package model +package xdsserver import ( "encoding/xml" @@ -9,28 +9,25 @@ import ( "strings" "time" - "github.com/Sirupsen/logrus" "github.com/franciscocpg/reflectme" common "github.com/iotbzh/xds-common/golib" - "github.com/iotbzh/xds-server/lib/folder" - "github.com/iotbzh/xds-server/lib/syncthing" + "github.com/iotbzh/xds-server/lib/xsapiv1" "github.com/iotbzh/xds-server/lib/xdsconfig" "github.com/syncthing/syncthing/lib/sync" ) // Folders Represent a an XDS folders type Folders struct { + *Context fileOnDisk string - Conf *xdsconfig.Config - Log *logrus.Logger - SThg *st.SyncThing - folders map[string]*folder.IFOLDER + folders map[string]*IFOLDER registerCB []RegisteredCB } +// RegisteredCB Hold registered callbacks type RegisteredCB struct { - cb *folder.EventCB - data *folder.EventCBData + cb *FolderEventCB + data *FolderEventCBData } // Mutex to make add/delete atomic @@ -38,25 +35,23 @@ var fcMutex = sync.NewMutex() var ffMutex = sync.NewMutex() // FoldersNew Create a new instance of Model Folders -func FoldersNew(cfg *xdsconfig.Config, st *st.SyncThing) *Folders { +func FoldersNew(ctx *Context) *Folders { file, _ := xdsconfig.FoldersConfigFilenameGet() return &Folders{ + Context: ctx, fileOnDisk: file, - Conf: cfg, - Log: cfg.Log, - SThg: st, - folders: make(map[string]*folder.IFOLDER), + folders: make(map[string]*IFOLDER), registerCB: []RegisteredCB{}, } } // LoadConfig Load folders configuration from disk func (f *Folders) LoadConfig() error { - var flds []folder.FolderConfig - var stFlds []folder.FolderConfig + var flds []xsapiv1.FolderConfig + var stFlds []xsapiv1.FolderConfig // load from disk - if f.Conf.Options.NoFolderConfig { + if f.Config.Options.NoFolderConfig { f.Log.Infof("Don't read folder config file (-no-folderconfig option is set)") } else if f.fileOnDisk != "" { f.Log.Infof("Use folder config file: %s", f.fileOnDisk) @@ -90,8 +85,8 @@ func (f *Folders) LoadConfig() error { if xf.ID == stf.ID { found = true // sanity check - if xf.Type != folder.TypeCloudSync { - flds[i].Status = folder.StatusErrorConfig + if xf.Type != xsapiv1.TypeCloudSync { + flds[i].Status = xsapiv1.StatusErrorConfig } break } @@ -107,7 +102,7 @@ func (f *Folders) LoadConfig() error { if f.SThg != nil { for i, xf := range flds { // only for syncthing project - if xf.Type != folder.TypeCloudSync { + if xf.Type != xsapiv1.TypeCloudSync { continue } found := false @@ -118,7 +113,7 @@ func (f *Folders) LoadConfig() error { } } if !found { - flds[i].Status = folder.StatusErrorConfig + flds[i].Status = xsapiv1.StatusErrorConfig } } } @@ -169,7 +164,7 @@ func (f *Folders) ResolveID(id string) (string, error) { } // Get returns the folder config or nil if not existing -func (f *Folders) Get(id string) *folder.IFOLDER { +func (f *Folders) Get(id string) *IFOLDER { if id == "" { return nil } @@ -181,7 +176,7 @@ func (f *Folders) Get(id string) *folder.IFOLDER { } // GetConfigArr returns the config of all folders as an array -func (f *Folders) GetConfigArr() []folder.FolderConfig { +func (f *Folders) GetConfigArr() []xsapiv1.FolderConfig { fcMutex.Lock() defer fcMutex.Unlock() @@ -189,8 +184,8 @@ func (f *Folders) GetConfigArr() []folder.FolderConfig { } // getConfigArrUnsafe Same as GetConfigArr without mutex protection -func (f *Folders) getConfigArrUnsafe() []folder.FolderConfig { - conf := []folder.FolderConfig{} +func (f *Folders) getConfigArrUnsafe() []xsapiv1.FolderConfig { + conf := []xsapiv1.FolderConfig{} for _, v := range f.folders { conf = append(conf, (*v).GetConfig()) } @@ -198,12 +193,12 @@ func (f *Folders) getConfigArrUnsafe() []folder.FolderConfig { } // Add adds a new folder -func (f *Folders) Add(newF folder.FolderConfig) (*folder.FolderConfig, error) { +func (f *Folders) Add(newF xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) { return f.createUpdate(newF, true, false) } // CreateUpdate creates or update a folder -func (f *Folders) createUpdate(newF folder.FolderConfig, create bool, initial bool) (*folder.FolderConfig, error) { +func (f *Folders) createUpdate(newF xsapiv1.FolderConfig, create bool, initial bool) (*xsapiv1.FolderConfig, error) { fcMutex.Lock() defer fcMutex.Unlock() @@ -217,20 +212,20 @@ func (f *Folders) createUpdate(newF folder.FolderConfig, create bool, initial bo } // Create a new folder object - var fld folder.IFOLDER + var fld IFOLDER switch newF.Type { // SYNCTHING - case folder.TypeCloudSync: + case xsapiv1.TypeCloudSync: if f.SThg != nil { - fld = f.SThg.NewFolderST(f.Conf) + fld = NewFolderST(f.Context, f.SThg) } else { f.Log.Debugf("Disable project %v (syncthing not initialized)", newF.ID) - fld = folder.NewFolderSTDisable(f.Conf) + fld = NewFolderSTDisable(f.Context) } // PATH MAP - case folder.TypePathMap: - fld = folder.NewFolderPathMap(f.Conf) + case xsapiv1.TypePathMap: + fld = NewFolderPathMap(f.Context) default: return nil, fmt.Errorf("Unsupported folder type") } @@ -245,7 +240,7 @@ func (f *Folders) createUpdate(newF folder.FolderConfig, create bool, initial bo // Set default value if needed if newF.Status == "" { - newF.Status = folder.StatusDisable + newF.Status = xsapiv1.StatusDisable } if newF.Label == "" { newF.Label = filepath.Base(newF.ClientPath) @@ -260,7 +255,7 @@ func (f *Folders) createUpdate(newF folder.FolderConfig, create bool, initial bo // Add new folder newFolder, err := fld.Add(newF) if err != nil { - newF.Status = folder.StatusErrorConfig + newF.Status = xsapiv1.StatusErrorConfig log.Printf("ERROR Adding folder: %v\n", err) return newFolder, err } @@ -293,13 +288,13 @@ func (f *Folders) createUpdate(newF folder.FolderConfig, create bool, initial bo } // Delete deletes a specific folder -func (f *Folders) Delete(id string) (folder.FolderConfig, error) { +func (f *Folders) Delete(id string) (xsapiv1.FolderConfig, error) { var err error fcMutex.Lock() defer fcMutex.Unlock() - fld := folder.FolderConfig{} + fld := xsapiv1.FolderConfig{} fc, exist := f.folders[id] if !exist { return fld, fmt.Errorf("unknown id") @@ -320,7 +315,7 @@ func (f *Folders) Delete(id string) (folder.FolderConfig, error) { } // Update Update a specific folder -func (f *Folders) Update(id string, cfg folder.FolderConfig) (*folder.FolderConfig, error) { +func (f *Folders) Update(id string, cfg xsapiv1.FolderConfig) (*xsapiv1.FolderConfig, error) { fcMutex.Lock() defer fcMutex.Unlock() @@ -330,12 +325,12 @@ func (f *Folders) Update(id string, cfg folder.FolderConfig) (*folder.FolderConf } // Copy current in a new object to change nothing in case of an error rises - newCfg := folder.FolderConfig{} + newCfg := xsapiv1.FolderConfig{} reflectme.Copy((*fc).GetConfig(), &newCfg) // Only update some fields dirty := false - for _, fieldName := range folder.FolderConfigUpdatableFields { + for _, fieldName := range xsapiv1.FolderConfigUpdatableFields { valNew, err := reflectme.GetField(cfg, fieldName) if err == nil { valCur, err := reflectme.GetField(newCfg, fieldName) @@ -368,9 +363,9 @@ func (f *Folders) Update(id string, cfg folder.FolderConfig) (*folder.FolderConf } // RegisterEventChange requests registration for folder event change -func (f *Folders) RegisterEventChange(id string, cb *folder.EventCB, data *folder.EventCBData) error { +func (f *Folders) RegisterEventChange(id string, cb *FolderEventCB, data *FolderEventCBData) error { - flds := make(map[string]*folder.IFOLDER) + flds := make(map[string]*IFOLDER) if id != "" { // Register to a specific folder flds[id] = f.Get(id) @@ -413,13 +408,13 @@ func (f *Folders) IsFolderInSync(id string) (bool, error) { // Use XML format and not json to be able to save/load all fields including // ones that are masked in json (IOW defined with `json:"-"`) type xmlFolders struct { - XMLName xml.Name `xml:"folders"` - Version string `xml:"version,attr"` - Folders []folder.FolderConfig `xml:"folders"` + XMLName xml.Name `xml:"folders"` + Version string `xml:"version,attr"` + Folders []xsapiv1.FolderConfig `xml:"folders"` } // foldersConfigRead reads folders config from disk -func foldersConfigRead(file string, folders *[]folder.FolderConfig) error { +func foldersConfigRead(file string, folders *[]xsapiv1.FolderConfig) error { if !common.Exists(file) { return fmt.Errorf("No folder config file found (%s)", file) } @@ -442,7 +437,7 @@ func foldersConfigRead(file string, folders *[]folder.FolderConfig) error { } // foldersConfigWrite writes folders config on disk -func foldersConfigWrite(file string, folders []folder.FolderConfig) error { +func foldersConfigWrite(file string, folders []xsapiv1.FolderConfig) error { ffMutex.Lock() defer ffMutex.Unlock() diff --git a/lib/xdsserver/sdk.go b/lib/xdsserver/sdk.go new file mode 100644 index 0000000..c0acb24 --- /dev/null +++ b/lib/xdsserver/sdk.go @@ -0,0 +1,56 @@ +package xdsserver + +import ( + "fmt" + "path/filepath" + + "github.com/iotbzh/xds-server/lib/xsapiv1" + uuid "github.com/satori/go.uuid" +) + +// CrossSDK Hold SDK config +type CrossSDK struct { + sdk xsapiv1.SDK +} + +// NewCrossSDK creates a new instance of Syncthing +func NewCrossSDK(path string) (*CrossSDK, error) { + // Assume that we have .../<profile>/<version>/<arch> + s := CrossSDK{ + sdk: xsapiv1.SDK{Path: path}, + } + + s.sdk.Arch = filepath.Base(path) + + d := filepath.Dir(path) + s.sdk.Version = filepath.Base(d) + + d = filepath.Dir(d) + s.sdk.Profile = filepath.Base(d) + + // Use V3 to ensure that we get same uuid on restart + s.sdk.ID = uuid.NewV3(uuid.FromStringOrNil("sdks"), s.sdk.Profile+"_"+s.sdk.Arch+"_"+s.sdk.Version).String() + s.sdk.Name = s.sdk.Arch + " (" + s.sdk.Version + ")" + + envFile := filepath.Join(path, "environment-setup*") + ef, err := filepath.Glob(envFile) + if err != nil { + return nil, fmt.Errorf("Cannot retrieve environment setup file: %v", err) + } + if len(ef) != 1 { + return nil, fmt.Errorf("No environment setup file found match %s", envFile) + } + s.sdk.EnvFile = ef[0] + + return &s, nil +} + +// Get Return SDK definition +func (s *CrossSDK) Get() *xsapiv1.SDK { + return &s.sdk +} + +// GetEnvCmd returns the command used to initialized the environment +func (s *CrossSDK) GetEnvCmd() []string { + return []string{"source", s.sdk.EnvFile} +} diff --git a/lib/crosssdk/sdks.go b/lib/xdsserver/sdks.go index a3da184..1a40ab5 100644 --- a/lib/crosssdk/sdks.go +++ b/lib/xdsserver/sdks.go @@ -1,4 +1,4 @@ -package crosssdk +package xdsserver import ( "fmt" @@ -7,33 +7,34 @@ import ( "strings" "sync" - "github.com/Sirupsen/logrus" common "github.com/iotbzh/xds-common/golib" - "github.com/iotbzh/xds-server/lib/xdsconfig" + "github.com/iotbzh/xds-server/lib/xsapiv1" ) // SDKs List of installed SDK type SDKs struct { - Sdks map[string]*SDK + *Context + Sdks map[string]*CrossSDK mutex sync.Mutex } -// Init creates a new instance of Syncthing -func Init(cfg *xdsconfig.Config, log *logrus.Logger) (*SDKs, error) { +// NewSDKs creates a new instance of SDKs +func NewSDKs(ctx *Context) (*SDKs, error) { s := SDKs{ - Sdks: make(map[string]*SDK), + Context: ctx, + Sdks: make(map[string]*CrossSDK), } // Retrieve installed sdks - sdkRD := cfg.FileConf.SdkRootDir + sdkRD := ctx.Config.FileConf.SdkRootDir if common.Exists(sdkRD) { // Assume that SDK install tree is <rootdir>/<profile>/<version>/<arch> dirs, err := filepath.Glob(path.Join(sdkRD, "*", "*", "*")) if err != nil { - log.Debugf("Error while retrieving SDKs: dir=%s, error=%s", sdkRD, err.Error()) + ctx.Log.Debugf("Error while retrieving SDKs: dir=%s, error=%s", sdkRD, err.Error()) return &s, err } s.mutex.Lock() @@ -43,16 +44,16 @@ func Init(cfg *xdsconfig.Config, log *logrus.Logger) (*SDKs, error) { if !common.IsDir(d) { continue } - sdk, err := NewCrossSDK(d) + cSdk, err := NewCrossSDK(d) if err != nil { - log.Debugf("Error while processing SDK dir=%s, err=%s", d, err.Error()) + ctx.Log.Debugf("Error while processing SDK dir=%s, err=%s", d, err.Error()) continue } - s.Sdks[sdk.ID] = sdk + s.Sdks[cSdk.sdk.ID] = cSdk } } - log.Debugf("SDKs: %d cross sdks found", len(s.Sdks)) + ctx.Log.Debugf("SDKs: %d cross sdks found", len(s.Sdks)) return &s, nil } @@ -79,7 +80,7 @@ func (s *SDKs) ResolveID(id string) (string, error) { } // Get returns an SDK from id -func (s *SDKs) Get(id string) *SDK { +func (s *SDKs) Get(id string) *xsapiv1.SDK { s.mutex.Lock() defer s.mutex.Unlock() @@ -87,16 +88,16 @@ func (s *SDKs) Get(id string) *SDK { if !exist { return nil } - return sc + return (*sc).Get() } // GetAll returns all existing SDKs -func (s *SDKs) GetAll() []SDK { +func (s *SDKs) GetAll() []xsapiv1.SDK { s.mutex.Lock() defer s.mutex.Unlock() - res := []SDK{} + res := []xsapiv1.SDK{} for _, v := range s.Sdks { - res = append(res, *v) + res = append(res, *(*v).Get()) } return res } diff --git a/lib/session/session.go b/lib/xdsserver/sessions.go index 60b7b8a..6da9fd8 100644 --- a/lib/session/session.go +++ b/lib/xdsserver/sessions.go @@ -1,4 +1,4 @@ -package session +package xdsserver import ( "encoding/base64" @@ -36,7 +36,7 @@ type ClientSession struct { // Sessions holds client sessions type Sessions struct { - router *gin.Engine + *Context cookieMaxAge int64 sessMap map[string]ClientSession mutex sync.Mutex @@ -46,21 +46,19 @@ type Sessions struct { } // NewClientSessions . -func NewClientSessions(router *gin.Engine, log *logrus.Logger, cookieMaxAge string, sillyLog bool) *Sessions { +func NewClientSessions(ctx *Context, cookieMaxAge string) *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, - LogLevelSilly: sillyLog, - stop: make(chan struct{}), + Context: ctx, + cookieMaxAge: ckMaxAge, + sessMap: make(map[string]ClientSession), + mutex: sync.NewMutex(), + stop: make(chan struct{}), } - s.router.Use(s.Middleware()) + s.WWWServer.router.Use(s.Middleware()) // Start monitoring of sessions Map (use to manage expiration and cleanup) go s.monitorSessMap() diff --git a/lib/webserver/server.go b/lib/xdsserver/webserver.go index 85a2c40..0e1676a 100644 --- a/lib/webserver/server.go +++ b/lib/xdsserver/webserver.go @@ -1,4 +1,4 @@ -package webserver +package xdsserver import ( "fmt" @@ -12,62 +12,45 @@ import ( "github.com/gin-contrib/static" "github.com/gin-gonic/gin" "github.com/googollee/go-socket.io" - "github.com/iotbzh/xds-server/lib/apiv1" - "github.com/iotbzh/xds-server/lib/crosssdk" - "github.com/iotbzh/xds-server/lib/model" - "github.com/iotbzh/xds-server/lib/session" - "github.com/iotbzh/xds-server/lib/xdsconfig" ) -// Server . -type Server struct { +// WebServer . +type WebServer struct { + *Context router *gin.Engine - api *apiv1.APIService + api *APIService sIOServer *socketio.Server webApp *gin.RouterGroup - cfg *xdsconfig.Config - sessions *session.Sessions - mfolders *model.Folders - sdks *crosssdk.SDKs - log *logrus.Logger - sillyLog bool stop chan struct{} // signals intentional stop } 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, sillyLog bool) *Server { +// NewWebServer creates an instance of WebServer +func NewWebServer(ctx *Context) *WebServer { // Setup logging for gin router - if logr.Level == logrus.DebugLevel { + if ctx.Log.Level == logrus.DebugLevel { gin.SetMode(gin.DebugMode) } else { gin.SetMode(gin.ReleaseMode) } // Redirect gin logs into another logger (LogVerboseOut may be stderr or a file) - gin.DefaultWriter = cfg.LogVerboseOut - gin.DefaultErrorWriter = cfg.LogVerboseOut - log.SetOutput(cfg.LogVerboseOut) + gin.DefaultWriter = ctx.Config.LogVerboseOut + gin.DefaultErrorWriter = ctx.Config.LogVerboseOut + log.SetOutput(ctx.Config.LogVerboseOut) // FIXME - fix pb about isTerminal=false when out is in VSC Debug Console // Creates gin router r := gin.New() - svr := &Server{ + svr := &WebServer{ router: r, api: nil, sIOServer: nil, webApp: nil, - cfg: cfg, - sessions: nil, - mfolders: mfolders, - sdks: sdks, - log: logr, - sillyLog: sillyLog, stop: make(chan struct{}), } @@ -75,7 +58,7 @@ func New(cfg *xdsconfig.Config, mfolders *model.Folders, sdks *crosssdk.SDKs, lo } // Serve starts a new instance of the Web Server -func (s *Server) Serve() error { +func (s *WebServer) Serve() error { var err error // Setup middlewares @@ -84,16 +67,13 @@ func (s *Server) Serve() error { s.router.Use(s.middlewareXDSDetails()) s.router.Use(s.middlewareCORS()) - // Sessions manager - 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) + s.api = NewAPIV1(s.Context) // Websocket routes s.sIOServer, err = socketio.NewServer(nil) if err != nil { - s.log.Fatalln(err) + s.Log.Fatalln(err) } s.router.GET("/socket.io/", s.socketHandler) @@ -104,12 +84,12 @@ func (s *Server) Serve() error { */ // Web Application (serve on / ) - idxFile := path.Join(s.cfg.FileConf.WebAppDir, indexFilename) + idxFile := path.Join(s.Config.FileConf.WebAppDir, indexFilename) if _, err := os.Stat(idxFile); err != nil { - s.log.Fatalln("Web app directory not found, check/use webAppDir setting in config file: ", idxFile) + s.Log.Fatalln("Web app directory not found, check/use webAppDir setting in config file: ", idxFile) } - s.log.Infof("Serve WEB app dir: %s", s.cfg.FileConf.WebAppDir) - s.router.Use(static.Serve("/", static.LocalFile(s.cfg.FileConf.WebAppDir, true))) + s.Log.Infof("Serve WEB app dir: %s", s.Config.FileConf.WebAppDir) + s.router.Use(static.Serve("/", static.LocalFile(s.Config.FileConf.WebAppDir, true))) s.webApp = s.router.Group("/", s.serveIndexFile) { s.webApp.GET("/") @@ -118,10 +98,10 @@ func (s *Server) Serve() error { // Serve in the background serveError := make(chan error, 1) go func() { - msg := fmt.Sprintf("Web Server running on localhost:%s ...\n", s.cfg.FileConf.HTTPPort) - s.log.Infof(msg) + msg := fmt.Sprintf("Web Server running on localhost:%s ...\n", s.Config.FileConf.HTTPPort) + s.Log.Infof(msg) fmt.Printf(msg) - serveError <- http.ListenAndServe(":"+s.cfg.FileConf.HTTPPort, s.router) + serveError <- http.ListenAndServe(":"+s.Config.FileConf.HTTPPort, s.router) }() // Wait for stop, restart or error signals @@ -129,36 +109,36 @@ func (s *Server) Serve() error { case <-s.stop: // Shutting down permanently s.sessions.Stop() - s.log.Infoln("shutting down (stop)") + s.Log.Infoln("shutting down (stop)") case err = <-serveError: // Error due to listen/serve failure - s.log.Errorln(err) + s.Log.Errorln(err) } return nil } // Stop web server -func (s *Server) Stop() { +func (s *WebServer) Stop() { close(s.stop) } // serveIndexFile provides initial file (eg. index.html) of webapp -func (s *Server) serveIndexFile(c *gin.Context) { +func (s *WebServer) serveIndexFile(c *gin.Context) { c.HTML(200, indexFilename, gin.H{}) } // Add details in Header -func (s *Server) middlewareXDSDetails() gin.HandlerFunc { +func (s *WebServer) middlewareXDSDetails() gin.HandlerFunc { return func(c *gin.Context) { - c.Header("XDS-Version", s.cfg.Version) - c.Header("XDS-API-Version", s.cfg.APIVersion) + c.Header("XDS-Version", s.Config.Version) + c.Header("XDS-API-Version", s.Config.APIVersion) c.Next() } } // CORS middleware -func (s *Server) middlewareCORS() gin.HandlerFunc { +func (s *WebServer) middlewareCORS() gin.HandlerFunc { return func(c *gin.Context) { if c.Request.Method == "OPTIONS" { c.Header("Access-Control-Allow-Origin", "*") @@ -174,7 +154,7 @@ func (s *Server) middlewareCORS() gin.HandlerFunc { } // socketHandler is the handler for the "main" websocket connection -func (s *Server) socketHandler(c *gin.Context) { +func (s *WebServer) socketHandler(c *gin.Context) { // Retrieve user session sess := s.sessions.Get(c) @@ -184,17 +164,17 @@ func (s *Server) socketHandler(c *gin.Context) { } s.sIOServer.On("connection", func(so socketio.Socket) { - s.log.Debugf("WS Connected (SID=%v)", so.Id()) + s.Log.Debugf("WS Connected (SID=%v)", so.Id()) s.sessions.UpdateIOSocket(sess.ID, &so) so.On("disconnection", func() { - s.log.Debugf("WS disconnected (SID=%v)", so.Id()) + s.Log.Debugf("WS disconnected (SID=%v)", so.Id()) s.sessions.UpdateIOSocket(sess.ID, nil) }) }) s.sIOServer.On("error", func(so socketio.Socket, err error) { - s.log.Errorf("WS SID=%v Error : %v", so.Id(), err.Error()) + s.Log.Errorf("WS SID=%v Error : %v", so.Id(), err.Error()) }) s.sIOServer.ServeHTTP(c.Writer, c.Request) diff --git a/lib/xdsserver/xdsserver.go b/lib/xdsserver/xdsserver.go new file mode 100644 index 0000000..dcdedc4 --- /dev/null +++ b/lib/xdsserver/xdsserver.go @@ -0,0 +1,202 @@ +package xdsserver + +import ( + "fmt" + "os" + "os/exec" + "os/signal" + "path/filepath" + "syscall" + "time" + + "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" + "github.com/iotbzh/xds-server/lib/xsapiv1" + + "github.com/iotbzh/xds-server/lib/syncthing" + "github.com/iotbzh/xds-server/lib/xdsconfig" +) + +const cookieMaxAge = "3600" + +// Context holds the XDS server context +type Context struct { + ProgName string + Cli *cli.Context + Config *xdsconfig.Config + Log *logrus.Logger + LogLevelSilly bool + SThg *st.SyncThing + SThgCmd *exec.Cmd + SThgInotCmd *exec.Cmd + mfolders *Folders + sdks *SDKs + WWWServer *WebServer + sessions *Sessions + Exit chan os.Signal +} + +// NewXdsServer Create a new instance of XDS server +func NewXdsServer(cliCtx *cli.Context) *Context { + var err error + + // Set logger level and formatter + log := cliCtx.App.Metadata["logger"].(*logrus.Logger) + + logLevel := cliCtx.GlobalString("log") + if logLevel == "" { + logLevel = "error" // FIXME get from Config 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{} + + sillyVal, sillyLog := os.LookupEnv("XDS_LOG_SILLY") + + // Define default configuration + ctx := Context{ + ProgName: cliCtx.App.Name, + Cli: cliCtx, + Log: log, + LogLevelSilly: (sillyLog && sillyVal == "1"), + Exit: make(chan os.Signal, 1), + } + + // register handler on SIGTERM / exit + signal.Notify(ctx.Exit, os.Interrupt, syscall.SIGTERM) + go handlerSigTerm(&ctx) + + return &ctx +} + +// Run Main function called to run XDS Server +func (ctx *Context) Run() (int, error) { + var err error + + // Logs redirected into a file when logfile option or logsDir config is set + ctx.Config.LogVerboseOut = os.Stderr + if ctx.Config.FileConf.LogsDir != "" { + if ctx.Config.Options.LogFile != "stdout" { + logFile := ctx.Config.Options.LogFile + + fdL, err := os.OpenFile(logFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666) + if err != nil { + msgErr := fmt.Sprintf("Cannot create log file %s", logFile) + return int(syscall.EPERM), fmt.Errorf(msgErr) + } + ctx.Log.Out = fdL + + ctx._logPrint("Logging file: %s\n", logFile) + } + + logFileHTTPReq := filepath.Join(ctx.Config.FileConf.LogsDir, "xds-server-verbose.log") + fdLH, err := os.OpenFile(logFileHTTPReq, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666) + if err != nil { + msgErr := fmt.Sprintf("Cannot create log file %s", logFileHTTPReq) + return int(syscall.EPERM), fmt.Errorf(msgErr) + } + ctx.Config.LogVerboseOut = fdLH + + ctx._logPrint("Logging file for HTTP requests: %s\n", logFileHTTPReq) + } + + // Create syncthing instance when section "syncthing" is present in config.json + if ctx.Config.FileConf.SThgConf != nil { + ctx.SThg = st.NewSyncThing(ctx.Config, ctx.Log) + } + + // Start local instance of Syncthing and Syncthing-notify + if ctx.SThg != nil { + ctx.Log.Infof("Starting Syncthing...") + ctx.SThgCmd, err = ctx.SThg.Start() + if err != nil { + return -4, err + } + ctx._logPrint("Syncthing started (PID %d)\n", ctx.SThgCmd.Process.Pid) + + ctx.Log.Infof("Starting Syncthing-inotify...") + ctx.SThgInotCmd, err = ctx.SThg.StartInotify() + if err != nil { + return -4, err + } + ctx._logPrint("Syncthing-inotify started (PID %d)\n", ctx.SThgInotCmd.Process.Pid) + + // Establish connection with local Syncthing (retry if connection fail) + ctx._logPrint("Establishing connection with Syncthing...\n") + time.Sleep(2 * time.Second) + maxRetry := 30 + retry := maxRetry + err = nil + for retry > 0 { + if err = ctx.SThg.Connect(); err == nil { + break + } + ctx.Log.Warningf("Establishing connection to Syncthing (retry %d/%d)", retry, maxRetry) + time.Sleep(time.Second) + retry-- + } + if err != nil || retry == 0 { + return -4, err + } + + // FIXME: do we still need Builder notion ? if no cleanup + if ctx.Config.Builder, err = xdsconfig.NewBuilderConfig(ctx.SThg.MyID); err != nil { + return -4, err + } + ctx.Config.SupportedSharing[xsapiv1.TypeCloudSync] = true + } + + // Init model folder + ctx.mfolders = FoldersNew(ctx) + + // Load initial folders config from disk + if err := ctx.mfolders.LoadConfig(); err != nil { + return -5, err + } + + // Init cross SDKs + ctx.sdks, err = NewSDKs(ctx) + if err != nil { + return -6, err + } + + // Create Web Server + ctx.WWWServer = NewWebServer(ctx) + + // Sessions manager + ctx.sessions = NewClientSessions(ctx, cookieMaxAge) + + // Run Web Server until exit requested (blocking call) + if err = ctx.WWWServer.Serve(); err != nil { + ctx.Log.Println(err) + return -7, err + } + + return -99, fmt.Errorf("Program exited ") +} + +// Helper function to log message on both stdout and logger +func (ctx *Context) _logPrint(format string, args ...interface{}) { + fmt.Printf(format, args...) + if ctx.Log.Out != os.Stdout { + ctx.Log.Infof(format, args...) + } +} + +// Handle exit and properly stop/close all stuff +func handlerSigTerm(ctx *Context) { + <-ctx.Exit + if ctx.SThg != nil { + ctx.Log.Infof("Stoping Syncthing... (PID %d)", ctx.SThgCmd.Process.Pid) + ctx.SThg.Stop() + ctx.Log.Infof("Stoping Syncthing-inotify... (PID %d)", ctx.SThgInotCmd.Process.Pid) + ctx.SThg.StopInotify() + } + if ctx.WWWServer != nil { + ctx.Log.Infof("Stoping Web server...") + ctx.WWWServer.Stop() + } + os.Exit(0) +} diff --git a/lib/xsapiv1/config.go b/lib/xsapiv1/config.go new file mode 100644 index 0000000..33bc116 --- /dev/null +++ b/lib/xsapiv1/config.go @@ -0,0 +1,18 @@ +package xsapiv1 + +// APIConfig parameters (json format) of /config command +type APIConfig struct { + 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"` +} + +// BuilderConfig represents the builder container configuration +type BuilderConfig struct { + IP string `json:"ip"` + Port string `json:"port"` + SyncThingID string `json:"syncThingID"` +} diff --git a/lib/xsapiv1/events.go b/lib/xsapiv1/events.go new file mode 100644 index 0000000..1304b3a --- /dev/null +++ b/lib/xsapiv1/events.go @@ -0,0 +1,31 @@ +package xsapiv1 + +// EventRegisterArgs Parameters (json format) of /events/register command +type EventRegisterArgs struct { + Name string `json:"name"` + ProjectID string `json:"filterProjectID"` +} + +// EventUnRegisterArgs Parameters of /events/unregister command +type EventUnRegisterArgs struct { + Name string `json:"name"` + ID int `json:"id"` +} + +// EventMsg Message send +type EventMsg struct { + Time string `json:"time"` + Type string `json:"type"` + Folder FolderConfig `json:"folder"` +} + +// EventEvent Event send in WS when an internal event (eg. Syncthing event is received) +const ( + // EventTypePrefix Used as event prefix + EventTypePrefix = "event:" // following by event type + + // Supported Events type + EVTAll = EventTypePrefix + "all" + EVTFolderChange = EventTypePrefix + "folder-change" // type EventMsg with Data type xsapiv1.??? + EVTFolderStateChange = EventTypePrefix + "folder-state-change" // type EventMsg with Data type xsapiv1.??? +) diff --git a/lib/xsapiv1/exec.go b/lib/xsapiv1/exec.go new file mode 100644 index 0000000..dce9eff --- /dev/null +++ b/lib/xsapiv1/exec.go @@ -0,0 +1,76 @@ +package xsapiv1 + +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 + CmdID string `json:"cmdID"` // command unique ID + Cmd string `json:"cmd" binding:"required"` + Args []string `json:"args"` + Env []string `json:"env"` + RPath string `json:"rpath"` // relative path into project + TTY bool `json:"tty"` // Use a tty, specific to gdb --tty option + TTYGdbserverFix bool `json:"ttyGdbserverFix"` // Set to true to activate gdbserver workaround about inferior output + ExitImmediate bool `json:"exitImmediate"` // when true, exit event sent immediately when command exited (IOW, don't wait file synchronization) + CmdTimeout int `json:"timeout"` // command completion timeout in Second + } + + // ExecResult JSON result of /exec command + ExecResult struct { + Status string `json:"status"` // status OK + CmdID string `json:"cmdID"` // command unique ID + } + + // ExecSigResult JSON result of /signal command + ExecSigResult struct { + Status string `json:"status"` // status OK + CmdID string `json:"cmdID"` // command unique ID + } + + // ExecInMsg Message used to received input characters (stdin) + ExecInMsg struct { + CmdID string `json:"cmdID"` + Timestamp string `json:"timestamp"` + Stdin string `json:"stdin"` + } + + // ExecOutMsg Message used to send output characters (stdout+stderr) + ExecOutMsg struct { + CmdID string `json:"cmdID"` + Timestamp string `json:"timestamp"` + Stdout string `json:"stdout"` + Stderr string `json:"stderr"` + } + + // ExecExitMsg Message sent when executed command exited + ExecExitMsg struct { + CmdID string `json:"cmdID"` + Timestamp string `json:"timestamp"` + Code int `json:"code"` + Error error `json:"error"` + } + + // ExecSignalArgs JSON parameters of /exec/signal command + ExecSignalArgs struct { + CmdID string `json:"cmdID" binding:"required"` // command id + Signal string `json:"signal" binding:"required"` // signal number + } +) + +const ( + // ExecInEvent Event send in WS when characters are sent (stdin) + ExecInEvent = "exec:input" + + // ExecOutEvent Event send in WS when characters are received (stdout or stderr) + ExecOutEvent = "exec:output" + + // ExecExitEvent Event send in WS when program exited + ExecExitEvent = "exec:exit" + + // ExecInferiorInEvent Event send in WS when characters are sent to an inferior (used by gdb inferior/tty) + ExecInferiorInEvent = "exec:inferior-input" + + // ExecInferiorOutEvent Event send in WS when characters are received by an inferior + ExecInferiorOutEvent = "exec:inferior-output" +) diff --git a/lib/folder/folder-interface.go b/lib/xsapiv1/folders.go index 3208869..9506a1d 100644 --- a/lib/folder/folder-interface.go +++ b/lib/xsapiv1/folders.go @@ -1,4 +1,4 @@ -package folder +package xsapiv1 // FolderType definition type FolderType string @@ -18,25 +18,6 @@ const ( StatusSyncing = "Syncing" ) -type EventCBData map[string]interface{} -type EventCB func(cfg *FolderConfig, data *EventCBData) - -// IFOLDER Folder interface -type IFOLDER interface { - NewUID(suffix string) string // Get a new folder UUID - Add(cfg FolderConfig) (*FolderConfig, error) // Add a new folder - GetConfig() FolderConfig // Get folder public configuration - GetFullPath(dir string) string // Get folder full path - ConvPathCli2Svr(s string) string // Convert path from Client to Server - ConvPathSvr2Cli(s string) string // Convert path from Server to Client - Remove() error // Remove a folder - Update(cfg FolderConfig) (*FolderConfig, error) // Update a new folder - RegisterEventChange(cb *EventCB, data *EventCBData) error // Request events registration (sent through WS) - UnRegisterEventChange() error // Un-register events - Sync() error // Force folder files synchronization - IsInSync() (bool, error) // Check if folder files are in-sync -} - // FolderConfig is the config for one folder type FolderConfig struct { ID string `json:"id"` diff --git a/lib/xsapiv1/sdks.go b/lib/xsapiv1/sdks.go new file mode 100644 index 0000000..5ca4dd5 --- /dev/null +++ b/lib/xsapiv1/sdks.go @@ -0,0 +1,14 @@ +package xsapiv1 + +// SDK Define a cross tool chain used to build application +type SDK struct { + ID string `json:"id" binding:"required"` + Name string `json:"name"` + Profile string `json:"profile"` + Version string `json:"version"` + Arch string `json:"arch"` + Path string `json:"path"` + + // Not exported fields + EnvFile string `json:"-"` +} diff --git a/lib/xsapiv1/version.go b/lib/xsapiv1/version.go new file mode 100644 index 0000000..8c3a742 --- /dev/null +++ b/lib/xsapiv1/version.go @@ -0,0 +1,9 @@ +package xsapiv1 + +// XDS server Version +type Version struct { + ID string `json:"id"` + Version string `json:"version"` + APIVersion string `json:"apiVersion"` + VersionGitTag string `json:"gitTag"` +} |