9 "github.com/gin-gonic/gin"
10 "github.com/iotbzh/xds-server/lib/common"
13 // ExecArgs JSON parameters of /exec command
14 type ExecArgs struct {
15 ID string `json:"id" binding:"required"`
16 SdkID string `json:"sdkid"` // sdk ID to use for setting env
17 Cmd string `json:"cmd" binding:"required"`
18 Args []string `json:"args"`
19 Env []string `json:"env"`
20 RPath string `json:"rpath"` // relative path into project
21 CmdTimeout int `json:"timeout"` // command completion timeout in Second
24 // ExecOutMsg Message send on each output (stdout+stderr) of executed command
25 type ExecOutMsg struct {
26 CmdID string `json:"cmdID"`
27 Timestamp string `json:"timestamp"`
28 Stdout string `json:"stdout"`
29 Stderr string `json:"stderr"`
32 // ExecExitMsg Message send when executed command exited
33 type ExecExitMsg struct {
34 CmdID string `json:"cmdID"`
35 Timestamp string `json:"timestamp"`
36 Code int `json:"code"`
37 Error error `json:"error"`
40 // ExecOutEvent Event send in WS when characters are received
41 const ExecOutEvent = "exec:output"
43 // ExecExitEvent Event send in WS when program exited
44 const ExecExitEvent = "exec:exit"
48 // ExecCmd executes remotely a command
49 func (s *APIService) execCmd(c *gin.Context) {
51 if c.BindJSON(&args) != nil {
52 common.APIError(c, "Invalid arguments")
56 // TODO: add permission ?
58 // Retrieve session info
59 sess := s.sessions.Get(c)
61 common.APIError(c, "Unknown sessions")
66 common.APIError(c, "Websocket not established")
70 // Allow to pass id in url (/exec/:id) or as JSON argument
76 common.APIError(c, "Invalid id")
80 prj := s.mfolder.GetFolderFromID(id)
82 common.APIError(c, "Unknown id")
86 execTmo := args.CmdTimeout
88 // TODO get default timeout from config.json file
89 execTmo = 24 * 60 * 60 // 1 day
92 // Define callback for output
93 var oCB common.EmitOutputCB
94 oCB = func(sid string, id int, stdout, stderr string, data *map[string]interface{}) {
95 // IO socket can be nil when disconnected
96 so := s.sessions.IOSocketGet(sid)
98 s.log.Infof("%s not emitted: WS closed - sid: %s - msg id:%d", ExecOutEvent, sid, id)
102 // Retrieve project ID and RootPath
103 prjID := (*data)["ID"].(string)
104 prjRootPath := (*data)["RootPath"].(string)
106 // Cleanup any references to internal rootpath in stdout & stderr
107 stdout = strings.Replace(stdout, prjRootPath, "", -1)
108 stderr = strings.Replace(stderr, prjRootPath, "", -1)
110 s.log.Debugf("%s emitted - WS sid %s - id:%d - prjID:%s", ExecOutEvent, sid, id, prjID)
112 // FIXME replace by .BroadcastTo a room
113 err := (*so).Emit(ExecOutEvent, ExecOutMsg{
114 CmdID: strconv.Itoa(id),
115 Timestamp: time.Now().String(),
120 s.log.Errorf("WS Emit : %v", err)
124 // Define callback for output
125 eCB := func(sid string, id int, code int, err error) {
126 s.log.Debugf("Command [Cmd ID %d] exited: code %d, error: %v", id, code, err)
128 // IO socket can be nil when disconnected
129 so := s.sessions.IOSocketGet(sid)
131 s.log.Infof("%s not emitted - WS closed (id:%d", ExecExitEvent, id)
135 // FIXME replace by .BroadcastTo a room
136 e := (*so).Emit(ExecExitEvent, ExecExitMsg{
137 CmdID: strconv.Itoa(id),
138 Timestamp: time.Now().String(),
143 s.log.Errorf("WS Emit : %v", e)
147 cmdID := execCommandID
151 // Setup env var regarding Sdk ID (used for example to setup cross toolchain)
152 if envCmd := s.sdks.GetEnvCmd(args.SdkID, prj.DefaultSdk); len(envCmd) > 0 {
153 cmd = append(cmd, envCmd...)
154 cmd = append(cmd, "&&")
157 cmd = append(cmd, "cd", prj.GetFullPath(args.RPath), "&&", args.Cmd)
158 if len(args.Args) > 0 {
159 cmd = append(cmd, args.Args...)
162 s.log.Debugf("Execute [Cmd ID %d]: %v", cmdID, cmd)
164 data := make(map[string]interface{})
166 data["RootPath"] = prj.RootPath
168 err := common.ExecPipeWs(cmd, args.Env, sop, sess.ID, cmdID, execTmo, s.log, oCB, eCB, &data)
170 common.APIError(c, err.Error())
174 c.JSON(http.StatusOK,