summaryrefslogtreecommitdiffstats
path: root/lib/apiv1
diff options
context:
space:
mode:
Diffstat (limited to 'lib/apiv1')
-rw-r--r--lib/apiv1/apiv1.go49
-rw-r--r--lib/apiv1/config.go45
-rw-r--r--lib/apiv1/exec.go154
-rw-r--r--lib/apiv1/folders.go77
-rw-r--r--lib/apiv1/make.go151
-rw-r--r--lib/apiv1/version.go24
6 files changed, 500 insertions, 0 deletions
diff --git a/lib/apiv1/apiv1.go b/lib/apiv1/apiv1.go
new file mode 100644
index 0000000..56c7503
--- /dev/null
+++ b/lib/apiv1/apiv1.go
@@ -0,0 +1,49 @@
+package apiv1
+
+import (
+ "github.com/Sirupsen/logrus"
+ "github.com/gin-gonic/gin"
+
+ "github.com/iotbzh/xds-server/lib/session"
+ "github.com/iotbzh/xds-server/lib/xdsconfig"
+)
+
+// APIService .
+type APIService struct {
+ router *gin.Engine
+ apiRouter *gin.RouterGroup
+ sessions *session.Sessions
+ cfg xdsconfig.Config
+ log *logrus.Logger
+}
+
+// New creates a new instance of API service
+func New(sess *session.Sessions, cfg xdsconfig.Config, r *gin.Engine) *APIService {
+ s := &APIService{
+ router: r,
+ sessions: sess,
+ apiRouter: r.Group("/api/v1"),
+ cfg: cfg,
+ log: cfg.Log,
+ }
+
+ s.apiRouter.GET("/version", s.getVersion)
+
+ 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.DELETE("/folder/:id", s.delFolder)
+
+ s.apiRouter.POST("/make", s.buildMake)
+ s.apiRouter.POST("/make/:id", s.buildMake)
+
+ /* TODO: to be tested and then enabled
+ s.apiRouter.POST("/exec", s.execCmd)
+ s.apiRouter.POST("/exec/:id", s.execCmd)
+ */
+
+ return s
+}
diff --git a/lib/apiv1/config.go b/lib/apiv1/config.go
new file mode 100644
index 0000000..a2817a0
--- /dev/null
+++ b/lib/apiv1/config.go
@@ -0,0 +1,45 @@
+package apiv1
+
+import (
+ "net/http"
+ "sync"
+
+ "github.com/gin-gonic/gin"
+ "github.com/iotbzh/xds-server/lib/common"
+ "github.com/iotbzh/xds-server/lib/xdsconfig"
+)
+
+var confMut sync.Mutex
+
+// GetConfig returns server configuration
+func (s *APIService) getConfig(c *gin.Context) {
+ confMut.Lock()
+ defer confMut.Unlock()
+
+ c.JSON(http.StatusOK, s.cfg)
+}
+
+// SetConfig sets server configuration
+func (s *APIService) setConfig(c *gin.Context) {
+ // FIXME - must be tested
+ c.JSON(http.StatusNotImplemented, "Not implemented")
+
+ var cfgArg xdsconfig.Config
+
+ if c.BindJSON(&cfgArg) != nil {
+ common.APIError(c, "Invalid arguments")
+ return
+ }
+
+ confMut.Lock()
+ defer confMut.Unlock()
+
+ s.log.Debugln("SET config: ", cfgArg)
+
+ if err := s.cfg.UpdateAll(cfgArg); err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusOK, s.cfg)
+}
diff --git a/lib/apiv1/exec.go b/lib/apiv1/exec.go
new file mode 100644
index 0000000..f7beea6
--- /dev/null
+++ b/lib/apiv1/exec.go
@@ -0,0 +1,154 @@
+package apiv1
+
+import (
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/iotbzh/xds-server/lib/common"
+)
+
+// ExecArgs JSON parameters of /exec command
+type ExecArgs struct {
+ ID string `json:"id"`
+ RPath string `json:"rpath"` // relative path into project
+ Cmd string `json:"cmd" binding:"required"`
+ Args []string `json:"args"`
+ CmdTimeout int `json:"timeout"` // command completion timeout in Second
+}
+
+// ExecOutMsg Message send on each output (stdout+stderr) of executed command
+type ExecOutMsg struct {
+ CmdID string `json:"cmdID"`
+ Timestamp string `json:timestamp`
+ Stdout string `json:"stdout"`
+ Stderr string `json:"stderr"`
+}
+
+// ExecExitMsg Message send when executed command exited
+type ExecExitMsg struct {
+ CmdID string `json:"cmdID"`
+ Timestamp string `json:timestamp`
+ Code int `json:"code"`
+ Error error `json:"error"`
+}
+
+// Event name send in WS
+const ExecOutEvent = "exec:output"
+const ExecExitEvent = "exec:exit"
+
+var execCommandID = 1
+
+// ExecCmd executes remotely a command
+func (s *APIService) execCmd(c *gin.Context) {
+ var args ExecArgs
+ if c.BindJSON(&args) != nil {
+ common.APIError(c, "Invalid arguments")
+ return
+ }
+
+ // TODO: add permission
+
+ // Retrieve session info
+ 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 (/exec/:id) or as JSON argument
+ id := c.Param("id")
+ if id == "" {
+ id = args.ID
+ }
+ if id == "" {
+ common.APIError(c, "Invalid id")
+ return
+ }
+
+ prj := s.cfg.GetFolderFromID(id)
+ if prj == nil {
+ common.APIError(c, "Unknown id")
+ return
+ }
+
+ execTmo := args.CmdTimeout
+ if execTmo == 0 {
+ // TODO get default timeout from config.json file
+ execTmo = 24 * 60 * 60 // 1 day
+ }
+
+ // Define callback for output
+ var oCB common.EmitOutputCB
+ oCB = func(sid string, id int, stdout, stderr string) {
+ // 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:%d", ExecOutEvent, sid, id)
+ return
+ }
+ s.log.Debugf("%s emitted - WS sid %s - id:%d", ExecOutEvent, sid, id)
+
+ // FIXME replace by .BroadcastTo a room
+ err := (*so).Emit(ExecOutEvent, ExecOutMsg{
+ CmdID: strconv.Itoa(id),
+ 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, id int, code int, err error) {
+ s.log.Debugf("Command [Cmd ID %d] exited: code %d, error: %v", id, 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:%d", ExecExitEvent, id)
+ return
+ }
+
+ // FIXME replace by .BroadcastTo a room
+ e := (*so).Emit(ExecExitEvent, ExecExitMsg{
+ CmdID: strconv.Itoa(id),
+ Timestamp: time.Now().String(),
+ Code: code,
+ Error: err,
+ })
+ if e != nil {
+ s.log.Errorf("WS Emit : %v", e)
+ }
+ }
+
+ cmdID := execCommandID
+ execCommandID++
+
+ cmd := "cd " + prj.GetFullPath(args.RPath) + " && " + args.Cmd
+ if len(args.Args) > 0 {
+ cmd += " " + strings.Join(args.Args, " ")
+ }
+
+ s.log.Debugf("Execute [Cmd ID %d]: %v %v", cmdID, cmd)
+ err := common.ExecPipeWs(cmd, sop, sess.ID, cmdID, execTmo, s.log, oCB, eCB)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusOK,
+ gin.H{
+ "status": "OK",
+ "cmdID": cmdID,
+ })
+}
diff --git a/lib/apiv1/folders.go b/lib/apiv1/folders.go
new file mode 100644
index 0000000..b1864a2
--- /dev/null
+++ b/lib/apiv1/folders.go
@@ -0,0 +1,77 @@
+package apiv1
+
+import (
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/iotbzh/xds-server/lib/common"
+ "github.com/iotbzh/xds-server/lib/xdsconfig"
+)
+
+// getFolders returns all folders configuration
+func (s *APIService) getFolders(c *gin.Context) {
+ confMut.Lock()
+ defer confMut.Unlock()
+
+ c.JSON(http.StatusOK, s.cfg.Folders)
+}
+
+// getFolder returns a specific folder configuration
+func (s *APIService) getFolder(c *gin.Context) {
+ id, err := strconv.Atoi(c.Param("id"))
+ if err != nil || id < 0 || id > len(s.cfg.Folders) {
+ common.APIError(c, "Invalid id")
+ return
+ }
+
+ confMut.Lock()
+ defer confMut.Unlock()
+
+ c.JSON(http.StatusOK, s.cfg.Folders[id])
+}
+
+// addFolder adds a new folder to server config
+func (s *APIService) addFolder(c *gin.Context) {
+ var cfgArg xdsconfig.FolderConfig
+ if c.BindJSON(&cfgArg) != nil {
+ common.APIError(c, "Invalid arguments")
+ return
+ }
+
+ confMut.Lock()
+ defer confMut.Unlock()
+
+ s.log.Debugln("Add folder config: ", cfgArg)
+
+ newFld, err := s.cfg.UpdateFolder(cfgArg)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusOK, newFld)
+}
+
+// delFolder deletes folder from server config
+func (s *APIService) delFolder(c *gin.Context) {
+ id := c.Param("id")
+ if id == "" {
+ common.APIError(c, "Invalid id")
+ return
+ }
+
+ confMut.Lock()
+ defer confMut.Unlock()
+
+ s.log.Debugln("Delete folder id ", id)
+
+ var delEntry xdsconfig.FolderConfig
+ var err error
+ if delEntry, err = s.cfg.DeleteFolder(id); err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+ c.JSON(http.StatusOK, delEntry)
+
+}
diff --git a/lib/apiv1/make.go b/lib/apiv1/make.go
new file mode 100644
index 0000000..eac6210
--- /dev/null
+++ b/lib/apiv1/make.go
@@ -0,0 +1,151 @@
+package apiv1
+
+import (
+ "net/http"
+
+ "time"
+
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/iotbzh/xds-server/lib/common"
+)
+
+// MakeArgs is the parameters (json format) of /make command
+type MakeArgs struct {
+ ID string `json:"id"`
+ RPath string `json:"rpath"` // relative path into project
+ Args string `json:"args"`
+ 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"`
+}
+
+// Event name send in WS
+const MakeOutEvent = "make:output"
+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
+ id := c.Param("id")
+ if id == "" {
+ id = args.ID
+ }
+ if id == "" {
+ common.APIError(c, "Invalid id")
+ return
+ }
+
+ prj := s.cfg.GetFolderFromID(id)
+ if prj == nil {
+ common.APIError(c, "Unknown id")
+ return
+ }
+
+ execTmo := args.CmdTimeout
+ if execTmo == 0 {
+ // TODO get default timeout from config.json file
+ execTmo = 24 * 60 * 60 // 1 day
+ }
+
+ cmd := "cd " + prj.GetFullPath(args.RPath) + " && make"
+ if args.Args != "" {
+ cmd += " " + args.Args
+ }
+
+ // Define callback for output
+ var oCB common.EmitOutputCB
+ oCB = func(sid string, id int, stdout, stderr string) {
+ // 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:%d", MakeOutEvent, sid, id)
+ return
+ }
+ s.log.Debugf("%s emitted - WS sid %s - id:%d", MakeOutEvent, sid, id)
+
+ // FIXME replace by .BroadcastTo a room
+ err := (*so).Emit(MakeOutEvent, MakeOutMsg{
+ CmdID: strconv.Itoa(id),
+ 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, id int, code int, err error) {
+ s.log.Debugf("Command [Cmd ID %d] exited: code %d, error: %v", id, 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:%d", MakeExitEvent, id)
+ return
+ }
+
+ // FIXME replace by .BroadcastTo a room
+ e := (*so).Emit(MakeExitEvent, MakeExitMsg{
+ CmdID: strconv.Itoa(id),
+ Timestamp: time.Now().String(),
+ Code: code,
+ Error: err,
+ })
+ if e != nil {
+ s.log.Errorf("WS Emit : %v", e)
+ }
+ }
+
+ cmdID := makeCommandID
+ makeCommandID++
+
+ s.log.Debugf("Execute [Cmd ID %d]: %v", cmdID, cmd)
+ err := common.ExecPipeWs(cmd, sop, sess.ID, cmdID, execTmo, s.log, oCB, eCB)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusOK,
+ gin.H{
+ "status": "OK",
+ "cmdID": cmdID,
+ })
+}
diff --git a/lib/apiv1/version.go b/lib/apiv1/version.go
new file mode 100644
index 0000000..e022441
--- /dev/null
+++ b/lib/apiv1/version.go
@@ -0,0 +1,24 @@
+package apiv1
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+)
+
+type version struct {
+ 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{
+ Version: s.cfg.Version,
+ APIVersion: s.cfg.APIVersion,
+ VersionGitTag: s.cfg.VersionGitTag,
+ }
+
+ c.JSON(http.StatusOK, response)
+}