summaryrefslogtreecommitdiffstats
path: root/lib/apiv1
diff options
context:
space:
mode:
authorSebastien Douheret <sebastien.douheret@iot.bzh>2017-08-26 11:29:56 +0200
committerSebastien Douheret <sebastien.douheret@iot.bzh>2017-08-26 11:29:56 +0200
commit5e120c466686880c5bf6b94043dd01edc261fef9 (patch)
tree9d3071f226bd0b234540bd20bdab0c77d3bca164 /lib/apiv1
parente113bbc75f88457d29f11823af0ff902e7c2ac8b (diff)
parent0367a6f9f2f868d785f197a052840ec621a681a6 (diff)
Merge remote-tracking branch 'origin/wip'
Diffstat (limited to 'lib/apiv1')
-rw-r--r--lib/apiv1/agent.go3
-rw-r--r--lib/apiv1/apiv1.go12
-rw-r--r--lib/apiv1/config.go7
-rw-r--r--lib/apiv1/events.go147
-rw-r--r--lib/apiv1/exec.go322
-rw-r--r--lib/apiv1/folders.go50
-rw-r--r--lib/apiv1/make.go28
7 files changed, 448 insertions, 121 deletions
diff --git a/lib/apiv1/agent.go b/lib/apiv1/agent.go
index 651f246..925f12b 100644
--- a/lib/apiv1/agent.go
+++ b/lib/apiv1/agent.go
@@ -11,6 +11,7 @@ import (
common "github.com/iotbzh/xds-common/golib"
)
+// XDSAgentTarball .
type XDSAgentTarball struct {
OS string `json:"os"`
Arch string `json:"arch"`
@@ -18,6 +19,8 @@ type XDSAgentTarball struct {
RawVersion string `json:"raw-version"`
FileURL string `json:"fileUrl"`
}
+
+// XDSAgentInfo .
type XDSAgentInfo struct {
Tarballs []XDSAgentTarball `json:"tarballs"`
}
diff --git a/lib/apiv1/apiv1.go b/lib/apiv1/apiv1.go
index 7fa69e9..262f513 100644
--- a/lib/apiv1/apiv1.go
+++ b/lib/apiv1/apiv1.go
@@ -16,19 +16,19 @@ type APIService struct {
apiRouter *gin.RouterGroup
sessions *session.Sessions
cfg *xdsconfig.Config
- mfolder *model.Folder
+ 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, mfolder *model.Folder, sdks *crosssdk.SDKs) *APIService {
+func New(r *gin.Engine, sess *session.Sessions, cfg *xdsconfig.Config, mfolders *model.Folders, sdks *crosssdk.SDKs) *APIService {
s := &APIService{
router: r,
sessions: sess,
apiRouter: r.Group("/api/v1"),
cfg: cfg,
- mfolder: mfolder,
+ mfolders: mfolders,
sdks: sdks,
log: cfg.Log,
}
@@ -42,6 +42,7 @@ func New(r *gin.Engine, sess *session.Sessions, cfg *xdsconfig.Config, mfolder *
s.apiRouter.GET("/folders", s.getFolders)
s.apiRouter.GET("/folder/:id", s.getFolder)
s.apiRouter.POST("/folder", s.addFolder)
+ s.apiRouter.POST("/folder/sync/:id", s.syncFolder)
s.apiRouter.DELETE("/folder/:id", s.delFolder)
s.apiRouter.GET("/sdks", s.getSdks)
@@ -52,6 +53,11 @@ func New(r *gin.Engine, sess *session.Sessions, cfg *xdsconfig.Config, mfolder *
s.apiRouter.POST("/exec", s.execCmd)
s.apiRouter.POST("/exec/:id", s.execCmd)
+ s.apiRouter.POST("/signal", s.execSignalCmd)
+
+ s.apiRouter.GET("/events", s.eventsList)
+ s.apiRouter.POST("/events/register", s.eventsRegister)
+ s.apiRouter.POST("/events/unregister", s.eventsUnRegister)
return s
}
diff --git a/lib/apiv1/config.go b/lib/apiv1/config.go
index 662ec8e..4b53217 100644
--- a/lib/apiv1/config.go
+++ b/lib/apiv1/config.go
@@ -36,10 +36,5 @@ func (s *APIService) setConfig(c *gin.Context) {
s.log.Debugln("SET config: ", cfgArg)
- if err := s.mfolder.UpdateAll(cfgArg); err != nil {
- common.APIError(c, err.Error())
- return
- }
-
- c.JSON(http.StatusOK, s.cfg)
+ common.APIError(c, "Not Supported")
}
diff --git a/lib/apiv1/events.go b/lib/apiv1/events.go
new file mode 100644
index 0000000..da8298c
--- /dev/null
+++ b/lib/apiv1/events.go
@@ -0,0 +1,147 @@
+package apiv1
+
+import (
+ "net/http"
+ "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 EventEventAll = "event:all"
+const EventEventType = "event:" // following by event type
+
+// eventsList Registering for events that will be send over a WS
+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
+
+ 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
+ }
+
+ evType := "FolderStateChanged"
+ if args.Name != evType {
+ common.APIError(c, "Unsupported event name")
+ return
+ }
+
+ /* XXX - to be removed if no plan to support "generic" event
+ var cbFunc st.EventsCB
+ cbFunc = func(ev st.Event, data *st.EventsCBData) {
+
+ evid, _ := strconv.Atoi((*data)["id"].(string))
+ ssid := (*data)["sid"].(string)
+ so := s.sessions.IOSocketGet(ssid)
+ if so == nil {
+ s.log.Infof("Event %s not emitted - sid: %s", ev.Type, ssid)
+
+ // Consider that client disconnected, so unregister this event
+ s.mfolders.SThg.Events.UnRegister(ev.Type, evid)
+ return
+ }
+
+ msg := EventMsg{
+ Time: ev.Time,
+ Type: ev.Type,
+ Data: ev.Data,
+ }
+
+ if err := (*so).Emit(EventEventAll, msg); err != nil {
+ s.log.Errorf("WS Emit Event : %v", err)
+ }
+
+ if err := (*so).Emit(EventEventType+ev.Type, msg); err != nil {
+ s.log.Errorf("WS Emit Event : %v", err)
+ }
+ }
+
+ data := make(st.EventsCBData)
+ data["sid"] = sess.ID
+
+ 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) {
+ ssid := (*data)["sid"].(string)
+ so := s.sessions.IOSocketGet(ssid)
+ if so == nil {
+ //s.log.Infof("Event %s not emitted - sid: %s", ev.Type, ssid)
+
+ // Consider that client disconnected, so unregister this event
+ // SEB FIXMEs.mfolders.RegisterEventChange(ev.Type)
+ return
+ }
+
+ msg := EventMsg{
+ Time: time.Now().String(),
+ Type: evType,
+ Folder: *cfg,
+ }
+
+ if err := (*so).Emit(EventEventType+evType, msg); err != nil {
+ s.log.Errorf("WS Emit Folder StateChanged event : %v", err)
+ }
+ }
+ data := make(folder.EventCBData)
+ data["sid"] = sess.ID
+
+ err := s.mfolders.RegisterEventChange(args.ProjectID, &cbFunc, &data)
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{"status": "OK"})
+}
+
+// eventsRegister Registering for events that will be send over a WS
+func (s *APIService) eventsUnRegister(c *gin.Context) {
+ var args EventUnRegisterArgs
+
+ if c.BindJSON(&args) != nil || args.Name == "" || args.ID < 0 {
+ common.APIError(c, "Invalid arguments")
+ return
+ }
+ /* TODO
+ if err := s.mfolders.SThg.Events.UnRegister(args.Name, args.ID); err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+ c.JSON(http.StatusOK, gin.H{"status": "OK"})
+ */
+ common.APIError(c, "Not implemented yet")
+}
diff --git a/lib/apiv1/exec.go b/lib/apiv1/exec.go
index 654ff64..6300dba 100644
--- a/lib/apiv1/exec.go
+++ b/lib/apiv1/exec.go
@@ -1,53 +1,88 @@
package apiv1
import (
+ "fmt"
"net/http"
+ "os"
+ "regexp"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
common "github.com/iotbzh/xds-common/golib"
+ "github.com/iotbzh/xds-common/golib/eows"
+ "github.com/kr/pty"
)
-// ExecArgs JSON parameters of /exec command
-type ExecArgs struct {
- ID string `json:"id" binding:"required"`
- SdkID string `json:"sdkid"` // sdk ID to use for setting env
- Cmd string `json:"cmd" binding:"required"`
- Args []string `json:"args"`
- 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
-}
+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
+ 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
+ }
-// 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"`
-}
+ // ExecInMsg Message used to received input characters (stdin)
+ ExecInMsg struct {
+ CmdID string `json:"cmdID"`
+ Timestamp string `json:"timestamp"`
+ Stdin string `json:"stdin"`
+ }
-// 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"`
-}
+ // 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
+ }
+)
-// ExecOutEvent Event send in WS when characters are received
-const ExecOutEvent = "exec:output"
+const (
+ // ExecInEvent Event send in WS when characters are sent (stdin)
+ ExecInEvent = "exec:input"
-// ExecExitEvent Event send in WS when program exited
-const ExecExitEvent = "exec:exit"
+ // 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
if c.BindJSON(&args) != nil {
common.APIError(c, "Invalid arguments")
@@ -78,41 +113,123 @@ func (s *APIService) execCmd(c *gin.Context) {
return
}
- prj := s.mfolder.GetFolderFromID(id)
- if prj == nil {
+ f := s.mfolders.Get(id)
+ if f == nil {
common.APIError(c, "Unknown id")
return
}
+ folder := *f
+ prj := folder.GetConfig()
- execTmo := args.CmdTimeout
- if execTmo == 0 {
+ // Build command line
+ cmd := []string{}
+ // Setup env var regarding Sdk ID (used for example to setup cross toolchain)
+ if envCmd := s.sdks.GetEnvCmd(args.SdkID, prj.DefaultSdk); len(envCmd) > 0 {
+ cmd = append(cmd, envCmd...)
+ cmd = append(cmd, "&&")
+ } else {
+ // It's an error if no envcmd found while a sdkid has been provided
+ if args.SdkID != "" {
+ common.APIError(c, "Unknown sdkid")
+ return
+ }
+ }
+
+ cmd = append(cmd, "cd", folder.GetFullPath(args.RPath))
+ // FIXME - add 'exec' prevents to use syntax:
+ // xds-exec -l debug -c xds-config.env -- "cd build && cmake .."
+ // but exec is mandatory to allow to pass correctly signals
+ // As workaround, exec is set for now on client side (eg. in xds-gdb)
+ //cmd = append(cmd, "&&", "exec", args.Cmd)
+ cmd = append(cmd, "&&", args.Cmd)
+
+ // Process command arguments
+ cmdArgs := make([]string, len(args.Args)+1)
+ copy(cmdArgs, args.Args)
+
+ // Allocate pts if tty if used
+ if args.TTY {
+ gdbPty, gdbTty, err = pty.Open()
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
+ }
+
+ s.log.Debugf("Client command tty: %v %v\n", gdbTty.Name(), gdbTty.Name())
+ cmdArgs = append(cmdArgs, "--tty="+gdbTty.Name())
+ }
+
+ // Unique ID for each commands
+ cmdID := strconv.Itoa(execCommandID)
+ execCommandID++
+
+ // Create new execution over WS context
+ execWS := eows.New(strings.Join(cmd, " "), cmdArgs, sop, sess.ID, cmdID)
+ execWS.Log = s.log
+
+ // Append client project dir to environment
+ execWS.Env = append(args.Env, "CLIENT_PROJECT_DIR="+prj.ClientPath)
+
+ // Set command execution timeout
+ if args.CmdTimeout == 0 {
+ // 0 : default timeout
// TODO get default timeout from config.json file
- execTmo = 24 * 60 * 60 // 1 day
+ execWS.CmdExecTimeout = 24 * 60 * 60 // 1 day
+ } else {
+ execWS.CmdExecTimeout = args.CmdTimeout
}
- // Define callback for output
- var oCB common.EmitOutputCB
- oCB = func(sid string, id int, stdout, stderr string, data *map[string]interface{}) {
+ // Define callback for input (stdin)
+ execWS.InputEvent = ExecInEvent
+ execWS.InputCB = func(e *eows.ExecOverWS, stdin string) (string, error) {
+ s.log.Debugf("STDIN <<%v>>", strings.Replace(stdin, "\n", "\\n", -1))
+
+ // Handle Ctrl-D
+ if len(stdin) == 1 && stdin == "\x04" {
+ // Close stdin
+ errMsg := fmt.Errorf("close stdin: %v", stdin)
+ return "", errMsg
+ }
+
+ // Set correct path
+ data := e.UserData
+ rootPath := (*data)["RootPath"].(string)
+ clientPath := (*data)["ClientPath"].(string)
+ stdin = strings.Replace(stdin, clientPath, rootPath+"/"+clientPath, -1)
+
+ return stdin, nil
+ }
+
+ // Define callback for output (stdout+stderr)
+ execWS.OutputCB = func(e *eows.ExecOverWS, stdout, stderr string) {
// IO socket can be nil when disconnected
- so := s.sessions.IOSocketGet(sid)
+ so := s.sessions.IOSocketGet(e.Sid)
if so == nil {
- s.log.Infof("%s not emitted: WS closed - sid: %s - msg id:%d", ExecOutEvent, sid, id)
+ s.log.Infof("%s not emitted: WS closed (sid:%s, msgid:%s)", ExecOutEvent, e.Sid, e.CmdID)
return
}
// Retrieve project ID and RootPath
+ data := e.UserData
prjID := (*data)["ID"].(string)
prjRootPath := (*data)["RootPath"].(string)
+ gdbServerTTY := (*data)["gdbServerTTY"].(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", ExecOutEvent, sid, id, prjID)
+ s.log.Debugf("%s emitted - WS sid[4:] %s - id:%s - prjID:%s", ExecOutEvent, e.Sid[4:], e.CmdID, prjID)
+ if stdout != "" {
+ s.log.Debugf("STDOUT <<%v>>", strings.Replace(stdout, "\n", "\\n", -1))
+ }
+ if stderr != "" {
+ s.log.Debugf("STDERR <<%v>>", strings.Replace(stderr, "\n", "\\n", -1))
+ }
// FIXME replace by .BroadcastTo a room
err := (*so).Emit(ExecOutEvent, ExecOutMsg{
- CmdID: strconv.Itoa(id),
+ CmdID: e.CmdID,
Timestamp: time.Now().String(),
Stdout: stdout,
Stderr: stderr,
@@ -120,25 +237,58 @@ func (s *APIService) execCmd(c *gin.Context) {
if err != nil {
s.log.Errorf("WS Emit : %v", err)
}
+
+ // XXX - Workaround due to gdbserver bug that doesn't redirect
+ // inferior output (https://bugs.eclipse.org/bugs/show_bug.cgi?id=437532#c13)
+ if gdbServerTTY == "workaround" && len(stdout) > 1 && stdout[0] == '&' {
+
+ // Extract and cleanup string like &"bla bla\n"
+ re := regexp.MustCompile("&\"(.*)\"")
+ rer := re.FindAllStringSubmatch(stdout, -1)
+ out := ""
+ if rer != nil && len(rer) > 0 {
+ for _, o := range rer {
+ if len(o) >= 1 {
+ out = strings.Replace(o[1], "\\n", "\n", -1)
+ 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{
+ CmdID: e.CmdID,
+ Timestamp: time.Now().String(),
+ Stdout: out,
+ Stderr: "",
+ })
+ if err != nil {
+ s.log.Errorf("WS Emit : %v", err)
+ }
+ }
+ }
+ } else {
+ s.log.Errorf("INFERIOR out parsing error: stdout=<%v>", stdout)
+ }
+ }
}
// Define callback for output
- eCB := func(sid string, id int, code int, err error, data *map[string]interface{}) {
- s.log.Debugf("Command [Cmd ID %d] exited: code %d, error: %v", id, code, err)
+ 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)
// IO socket can be nil when disconnected
- so := s.sessions.IOSocketGet(sid)
+ so := s.sessions.IOSocketGet(e.Sid)
if so == nil {
- s.log.Infof("%s not emitted - WS closed (id:%d", ExecExitEvent, id)
+ s.log.Infof("%s not emitted - WS closed (id:%s)", ExecExitEvent, e.CmdID)
return
}
// Retrieve project ID and RootPath
+ data := e.UserData
prjID := (*data)["ID"].(string)
exitImm := (*data)["ExitImmediate"].(bool)
// XXX - workaround to be sure that Syncthing detected all changes
- if err := s.mfolder.ForceSync(prjID); err != nil {
+ if err := s.mfolders.ForceSync(prjID); err != nil {
s.log.Errorf("Error while syncing folder %s: %v", prjID, err)
}
if !exitImm {
@@ -146,8 +296,8 @@ func (s *APIService) execCmd(c *gin.Context) {
// 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.mfolder.IsFolderInSync(prjID); sync || err != nil {
+ 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)
}
@@ -157,44 +307,73 @@ func (s *APIService) execCmd(c *gin.Context) {
}
}
+ // Close client tty
+ if gdbPty != nil {
+ gdbPty.Close()
+ }
+ if gdbTty != nil {
+ gdbTty.Close()
+ }
+
// FIXME replace by .BroadcastTo a room
- e := (*so).Emit(ExecExitEvent, ExecExitMsg{
- CmdID: strconv.Itoa(id),
+ errSoEmit := (*so).Emit(ExecExitEvent, ExecExitMsg{
+ CmdID: e.CmdID,
Timestamp: time.Now().String(),
Code: code,
Error: err,
})
- if e != nil {
- s.log.Errorf("WS Emit : %v", e)
+ if errSoEmit != nil {
+ s.log.Errorf("WS Emit : %v", errSoEmit)
}
}
- cmdID := execCommandID
- execCommandID++
- cmd := []string{}
-
- // Setup env var regarding Sdk ID (used for example to setup cross toolchain)
- if envCmd := s.sdks.GetEnvCmd(args.SdkID, prj.DefaultSdk); len(envCmd) > 0 {
- cmd = append(cmd, envCmd...)
- cmd = append(cmd, "&&")
+ // User data (used within callbacks)
+ data := make(map[string]interface{})
+ data["ID"] = prj.ID
+ data["RootPath"] = prj.RootPath
+ data["ClientPath"] = prj.ClientPath
+ data["ExitImmediate"] = args.ExitImmediate
+ if args.TTY && args.TTYGdbserverFix {
+ data["gdbServerTTY"] = "workaround"
+ } else {
+ data["gdbServerTTY"] = ""
}
+ execWS.UserData = &data
- cmd = append(cmd, "cd", prj.GetFullPath(args.RPath), "&&", args.Cmd)
- if len(args.Args) > 0 {
- cmd = append(cmd, args.Args...)
+ // Start command execution
+ s.log.Debugf("Execute [Cmd ID %s]: %v %v", execWS.CmdID, execWS.Cmd, execWS.Args)
+
+ err = execWS.Start()
+ if err != nil {
+ common.APIError(c, err.Error())
+ return
}
- // Append client project dir to environment
- args.Env = append(args.Env, "CLIENT_PROJECT_DIR="+prj.RelativePath)
+ c.JSON(http.StatusOK,
+ gin.H{
+ "status": "OK",
+ "cmdID": execWS.CmdID,
+ })
+}
- s.log.Debugf("Execute [Cmd ID %d]: %v", cmdID, cmd)
+// ExecCmd executes remotely a command
+func (s *APIService) execSignalCmd(c *gin.Context) {
+ var args ExecSignalArgs
- data := make(map[string]interface{})
- data["ID"] = prj.ID
- data["RootPath"] = prj.RootPath
- data["ExitImmediate"] = args.ExitImmediate
+ if c.BindJSON(&args) != nil {
+ common.APIError(c, "Invalid arguments")
+ return
+ }
+
+ s.log.Debugf("Signal %s for command ID %s", args.Signal, args.CmdID)
+
+ e := eows.GetEows(args.CmdID)
+ if e == nil {
+ common.APIError(c, "unknown cmdID")
+ return
+ }
- err := common.ExecPipeWs(cmd, args.Env, sop, sess.ID, cmdID, execTmo, s.log, oCB, eCB, &data)
+ err := e.Signal(args.Signal)
if err != nil {
common.APIError(c, err.Error())
return
@@ -203,6 +382,5 @@ func (s *APIService) execCmd(c *gin.Context) {
c.JSON(http.StatusOK,
gin.H{
"status": "OK",
- "cmdID": cmdID,
})
}
diff --git a/lib/apiv1/folders.go b/lib/apiv1/folders.go
index 44bda24..cf56c3f 100644
--- a/lib/apiv1/folders.go
+++ b/lib/apiv1/folders.go
@@ -2,49 +2,39 @@ package apiv1
import (
"net/http"
- "strconv"
"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/folder"
)
// getFolders returns all folders configuration
func (s *APIService) getFolders(c *gin.Context) {
- confMut.Lock()
- defer confMut.Unlock()
-
- c.JSON(http.StatusOK, s.cfg.Folders)
+ c.JSON(http.StatusOK, s.mfolders.GetConfigArr())
}
// 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) {
+ f := s.mfolders.Get(c.Param("id"))
+ if f == nil {
common.APIError(c, "Invalid id")
return
}
- confMut.Lock()
- defer confMut.Unlock()
-
- c.JSON(http.StatusOK, s.cfg.Folders[id])
+ c.JSON(http.StatusOK, (*f).GetConfig())
}
// addFolder adds a new folder to server config
func (s *APIService) addFolder(c *gin.Context) {
- var cfgArg xdsconfig.FolderConfig
+ var cfgArg folder.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.mfolder.UpdateFolder(cfgArg)
+ newFld, err := s.mfolders.Add(cfgArg)
if err != nil {
common.APIError(c, err.Error())
return
@@ -53,25 +43,31 @@ func (s *APIService) addFolder(c *gin.Context) {
c.JSON(http.StatusOK, newFld)
}
-// delFolder deletes folder from server config
-func (s *APIService) delFolder(c *gin.Context) {
+// syncFolder force synchronization of folder files
+func (s *APIService) syncFolder(c *gin.Context) {
id := c.Param("id")
- if id == "" {
- common.APIError(c, "Invalid id")
+
+ s.log.Debugln("Sync folder id: ", id)
+
+ err := s.mfolders.ForceSync(id)
+ if err != nil {
+ common.APIError(c, err.Error())
return
}
- confMut.Lock()
- defer confMut.Unlock()
+ c.JSON(http.StatusOK, "")
+}
+
+// delFolder deletes folder from server config
+func (s *APIService) delFolder(c *gin.Context) {
+ id := c.Param("id")
s.log.Debugln("Delete folder id ", id)
- var delEntry xdsconfig.FolderConfig
- var err error
- if delEntry, err = s.mfolder.DeleteFolder(id); err != nil {
+ delEntry, err := s.mfolders.Delete(id)
+ if 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
index 5cd98c6..cf76476 100644
--- a/lib/apiv1/make.go
+++ b/lib/apiv1/make.go
@@ -76,11 +76,13 @@ func (s *APIService) buildMake(c *gin.Context) {
return
}
- prj := s.mfolder.GetFolderFromID(id)
- if prj == nil {
+ 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 {
@@ -92,11 +94,11 @@ func (s *APIService) buildMake(c *gin.Context) {
// Define callback for output
var oCB common.EmitOutputCB
- oCB = func(sid string, id int, stdout, stderr string, data *map[string]interface{}) {
+ 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:%d", MakeOutEvent, sid, id)
+ s.log.Infof("%s not emitted: WS closed - sid: %s - msg id:%s", MakeOutEvent, sid, cmdID)
return
}
@@ -112,7 +114,7 @@ func (s *APIService) buildMake(c *gin.Context) {
// FIXME replace by .BroadcastTo a room
err := (*so).Emit(MakeOutEvent, MakeOutMsg{
- CmdID: strconv.Itoa(id),
+ CmdID: cmdID,
Timestamp: time.Now().String(),
Stdout: stdout,
Stderr: stderr,
@@ -123,13 +125,13 @@ func (s *APIService) buildMake(c *gin.Context) {
}
// Define callback for output
- eCB := func(sid string, id int, code int, err error, data *map[string]interface{}) {
- s.log.Debugf("Command [Cmd ID %d] exited: code %d, error: %v", id, code, err)
+ 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:%d", MakeExitEvent, id)
+ s.log.Infof("%s not emitted - WS closed (id:%s", MakeExitEvent, cmdID)
return
}
@@ -138,7 +140,7 @@ func (s *APIService) buildMake(c *gin.Context) {
exitImm := (*data)["ExitImmediate"].(bool)
// XXX - workaround to be sure that Syncthing detected all changes
- if err := s.mfolder.ForceSync(prjID); err != nil {
+ if err := s.mfolders.ForceSync(prjID); err != nil {
s.log.Errorf("Error while syncing folder %s: %v", prjID, err)
}
if !exitImm {
@@ -147,7 +149,7 @@ func (s *APIService) buildMake(c *gin.Context) {
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.mfolder.IsFolderInSync(prjID); sync || err != nil {
+ if sync, err := s.mfolders.IsFolderInSync(prjID); sync || err != nil {
if err != nil {
s.log.Errorf("ERROR IsFolderInSync (%s): %v", prjID, err)
}
@@ -159,7 +161,7 @@ func (s *APIService) buildMake(c *gin.Context) {
// FIXME replace by .BroadcastTo a room
e := (*so).Emit(MakeExitEvent, MakeExitMsg{
- CmdID: strconv.Itoa(id),
+ CmdID: id,
Timestamp: time.Now().String(),
Code: code,
Error: err,
@@ -169,7 +171,7 @@ func (s *APIService) buildMake(c *gin.Context) {
}
}
- cmdID := makeCommandID
+ cmdID := strconv.Itoa(makeCommandID)
makeCommandID++
cmd := []string{}
@@ -179,7 +181,7 @@ func (s *APIService) buildMake(c *gin.Context) {
cmd = append(cmd, "&&")
}
- cmd = append(cmd, "cd", prj.GetFullPath(args.RPath), "&&", "make")
+ cmd = append(cmd, "cd", folder.GetFullPath(args.RPath), "&&", "make")
if len(args.Args) > 0 {
cmd = append(cmd, args.Args...)
}