Fixed /exec input stream and /signal.
[src/xds/xds-agent.git] / lib / agent / apiv1-exec.go
1 package agent
2
3 import (
4         "net/http"
5
6         "github.com/franciscocpg/reflectme"
7         "github.com/gin-gonic/gin"
8         "github.com/iotbzh/xds-agent/lib/xaapiv1"
9         common "github.com/iotbzh/xds-common/golib"
10         "github.com/iotbzh/xds-server/lib/xsapiv1"
11         uuid "github.com/satori/go.uuid"
12 )
13
14 // ExecCmd executes remotely a command
15 func (s *APIService) execCmd(c *gin.Context) {
16
17         args := xaapiv1.ExecArgs{}
18         if err := c.BindJSON(&args); err != nil {
19                 s.Log.Warningf("/exec invalid args, err=%v", err)
20                 common.APIError(c, "Invalid arguments")
21                 return
22         }
23
24         // First get Project ID to retrieve Server ID and send command to right server
25         iid := c.Param("id")
26         if iid == "" {
27                 iid = args.ID
28         }
29         id, err := s.projects.ResolveID(iid)
30         if err != nil {
31                 common.APIError(c, err.Error())
32                 return
33         }
34         prj := s.projects.Get(id)
35         if prj == nil {
36                 common.APIError(c, "Unknown id")
37                 return
38         }
39
40         svr := (*prj).GetServer()
41         if svr == nil {
42                 common.APIError(c, "Cannot identify XDS Server")
43                 return
44         }
45
46         // Retrieve session info
47         sess := s.sessions.Get(c)
48         if sess == nil {
49                 common.APIError(c, "Unknown sessions")
50                 return
51         }
52         sock := sess.IOSocket
53         if sock == nil {
54                 common.APIError(c, "Websocket not established")
55                 return
56         }
57
58         // Forward input events from client to XDSServer through WS
59         // TODO use XDSServer events names definition
60         evtInList := []string{
61                 xaapiv1.ExecInEvent,
62                 xaapiv1.ExecInferiorInEvent,
63         }
64         for _, evName := range evtInList {
65                 evN := evName
66                 err := (*sock).On(evN, func(stdin string) {
67                         if s.LogLevelSilly {
68                                 s.Log.Debugf("EXEC EVENT IN (%s) <<%v>>", evN, stdin)
69                         }
70                         svr.EventEmit(evN, stdin)
71                 })
72                 if err != nil {
73                         msgErr := "Error while registering WS for " + evN + " event"
74                         s.Log.Errorf(msgErr, ", err: %v", err)
75                         common.APIError(c, msgErr)
76                         return
77                 }
78         }
79
80         // Forward output events from XDSServer to client through WS
81         // TODO use XDSServer events names definition
82         var fwdFuncID []uuid.UUID
83         evtOutList := []string{
84                 xaapiv1.ExecOutEvent,
85                 xaapiv1.ExecInferiorOutEvent,
86         }
87         for _, evName := range evtOutList {
88                 evN := evName
89                 fwdFunc := func(pData interface{}, evData interface{}) error {
90                         sid := pData.(string)
91                         // IO socket can be nil when disconnected
92                         so := s.sessions.IOSocketGet(sid)
93                         if so == nil {
94                                 s.Log.Infof("%s not emitted: WS closed (sid:%s)", evN, sid)
95                                 return nil
96                         }
97
98                         // Add sessionID to event Data
99                         reflectme.SetField(evData, "sessionID", sid)
100
101                         if s.LogLevelSilly {
102                                 s.Log.Debugf("EXEC EVENT OUT (%s) <<%v>>", evN, evData)
103                         }
104
105                         // Forward event to Client/Dashboard
106                         (*so).Emit(evN, evData)
107                         return nil
108                 }
109                 id, err := svr.EventOn(evN, sess.ID, fwdFunc)
110                 if err != nil {
111                         common.APIError(c, err.Error())
112                         return
113                 }
114                 fwdFuncID = append(fwdFuncID, id)
115         }
116
117         // Handle Exit event separately to cleanup registered listener
118         var exitFuncID uuid.UUID
119         exitFunc := func(privD interface{}, evData interface{}) error {
120                 evN := xaapiv1.ExecExitEvent
121
122                 pData := privD.(map[string]string)
123                 sid := pData["sessID"]
124                 prjID := pData["prjID"]
125
126                 // Add sessionID to event Data
127                 reflectme.SetField(evData, "sessionID", sid)
128
129                 // IO socket can be nil when disconnected
130                 so := s.sessions.IOSocketGet(sid)
131                 if so != nil {
132                         (*so).Emit(evN, evData)
133                 } else {
134                         s.Log.Infof("%s not emitted: WS closed (sid:%s)", evN, sid)
135                 }
136
137                 prj := s.projects.Get(prjID)
138                 if prj != nil {
139                         evD := evData.(map[string]interface{})
140                         cmdIDData, cmdIDExist := evD["cmdID"]
141                         svr := (*prj).GetServer()
142                         if svr != nil && cmdIDExist {
143                                 svr.CommandDelete(cmdIDData.(string))
144                         } else {
145                                 s.Log.Infof("%s: cannot retrieve server for sid=%s, prjID=%s, evD=%v", evN, sid, prjID, evD)
146                         }
147                 } else {
148                         s.Log.Infof("%s: cannot retrieve project for sid=%s, prjID=%s", evN, sid, prjID)
149                 }
150
151                 // cleanup listener
152                 for i, evName := range evtOutList {
153                         svr.EventOff(evName, fwdFuncID[i])
154                 }
155                 svr.EventOff(evN, exitFuncID)
156
157                 return nil
158         }
159
160         prjCfg := (*prj).GetProject()
161         privData := map[string]string{"sessID": sess.ID, "prjID": prjCfg.ID}
162         exitFuncID, err = svr.EventOn(xaapiv1.ExecExitEvent, privData, exitFunc)
163         if err != nil {
164                 common.APIError(c, err.Error())
165                 return
166         }
167
168         // Forward back command to right server
169         res := xsapiv1.ExecResult{}
170         xsArgs := &xsapiv1.ExecArgs{
171                 ID:              args.ID,
172                 SdkID:           args.SdkID,
173                 CmdID:           args.CmdID,
174                 Cmd:             args.Cmd,
175                 Args:            args.Args,
176                 Env:             args.Env,
177                 RPath:           args.RPath,
178                 TTY:             args.TTY,
179                 TTYGdbserverFix: args.TTYGdbserverFix,
180                 ExitImmediate:   args.ExitImmediate,
181                 CmdTimeout:      args.CmdTimeout,
182         }
183         if err := svr.CommandExec(xsArgs, &res); err != nil {
184                 common.APIError(c, err.Error())
185                 return
186         }
187
188         // Add command to running commands list
189         if err := svr.CommandAdd(res.CmdID, xsArgs); err != nil {
190                 common.APIError(c, err.Error())
191                 return
192         }
193
194         c.JSON(http.StatusOK, xaapiv1.ExecResult{Status: res.Status, CmdID: res.CmdID})
195 }
196
197 // execSignalCmd executes remotely the signal command
198 func (s *APIService) execSignalCmd(c *gin.Context) {
199
200         args := xaapiv1.ExecSignalArgs{}
201         if err := c.BindJSON(&args); err != nil {
202                 s.Log.Warningf("/signal invalid args, err=%v", err)
203                 common.APIError(c, "Invalid arguments")
204                 return
205         }
206
207         // Retrieve on which xds-server the command is running
208         var svr *XdsServer
209         var dataCmd interface{}
210         for _, svr = range s.xdsServers {
211                 dataCmd = svr.CommandGet(args.CmdID)
212                 if dataCmd != nil {
213                         break
214                 }
215         }
216         if dataCmd == nil {
217                 common.APIError(c, "Cannot retrieve XDS Server for this cmdID")
218                 return
219         }
220
221         // Forward back command to right server
222         res := xsapiv1.ExecSigResult{}
223         xsArgs := &xsapiv1.ExecSignalArgs{
224                 CmdID:  args.CmdID,
225                 Signal: args.Signal,
226         }
227         if err := svr.CommandSignal(xsArgs, &res); err != nil {
228                 common.APIError(c, err.Error())
229                 return
230         }
231
232         c.JSON(http.StatusOK, xaapiv1.ExecSignalResult{Status: res.Status, CmdID: res.CmdID})
233 }