New dashboard improvements.
[src/xds/xds-agent.git] / lib / agent / apiv1-exec.go
1 package agent
2
3 import (
4         "encoding/json"
5         "io/ioutil"
6         "net/http"
7
8         "github.com/franciscocpg/reflectme"
9         "github.com/gin-gonic/gin"
10         "github.com/iotbzh/xds-agent/lib/apiv1"
11         common "github.com/iotbzh/xds-common/golib"
12         uuid "github.com/satori/go.uuid"
13 )
14
15 // ExecCmd executes remotely a command
16 func (s *APIService) execCmd(c *gin.Context) {
17         s._execRequest("/exec", c)
18 }
19
20 // execSignalCmd executes remotely a command
21 func (s *APIService) execSignalCmd(c *gin.Context) {
22         s._execRequest("/signal", c)
23 }
24
25 func (s *APIService) _execRequest(cmd string, c *gin.Context) {
26         data, err := c.GetRawData()
27         if err != nil {
28                 common.APIError(c, err.Error())
29         }
30
31         args := apiv1.ExecArgs{}
32         // XXX - we cannot use c.BindJSON, so directly unmarshall it
33         // (see https://github.com/gin-gonic/gin/issues/1078)
34         if err := json.Unmarshal(data, &args); err != nil {
35                 common.APIError(c, "Invalid arguments")
36                 return
37         }
38
39         // First get Project ID to retrieve Server ID and send command to right server
40         iid := c.Param("id")
41         if iid == "" {
42                 iid = args.ID
43         }
44         id, err := s.projects.ResolveID(iid)
45         if err != nil {
46                 common.APIError(c, err.Error())
47                 return
48         }
49         prj := s.projects.Get(id)
50         if prj == nil {
51                 common.APIError(c, "Unknown id")
52                 return
53         }
54
55         svr := (*prj).GetServer()
56         if svr == nil {
57                 common.APIError(c, "Cannot identify XDS Server")
58                 return
59         }
60
61         // Retrieve session info
62         sess := s.sessions.Get(c)
63         if sess == nil {
64                 common.APIError(c, "Unknown sessions")
65                 return
66         }
67         sock := sess.IOSocket
68         if sock == nil {
69                 common.APIError(c, "Websocket not established")
70                 return
71         }
72
73         // Forward XDSServer WS events to client WS
74         // TODO removed static event name list and get it from XDSServer
75         evtList := []string{
76                 apiv1.ExecInEvent,
77                 apiv1.ExecOutEvent,
78                 apiv1.ExecInferiorInEvent,
79                 apiv1.ExecInferiorOutEvent,
80         }
81
82         var fwdFuncID []uuid.UUID
83         for _, evName := range evtList {
84                 evN := evName
85                 fwdFunc := func(pData interface{}, evData interface{}) error {
86                         sid := pData.(string)
87                         // IO socket can be nil when disconnected
88                         so := s.sessions.IOSocketGet(sid)
89                         if so == nil {
90                                 s.Log.Infof("%s not emitted: WS closed (sid:%s)", evN, sid)
91                                 return nil
92                         }
93
94                         // Add sessionID to event Data
95                         reflectme.SetField(evData, "sessionID", sid)
96
97                         // Forward event to Client/Dashboard
98                         (*so).Emit(evN, evData)
99                         return nil
100                 }
101                 id, err := svr.EventOn(evN, sess.ID, fwdFunc)
102                 if err != nil {
103                         common.APIError(c, err.Error())
104                         return
105                 }
106                 fwdFuncID = append(fwdFuncID, id)
107         }
108
109         // Handle Exit event separately to cleanup registered listener
110         var exitFuncID uuid.UUID
111         exitFunc := func(pData interface{}, evData interface{}) error {
112                 evN := apiv1.ExecExitEvent
113                 sid := pData.(string)
114
115                 // Add sessionID to event Data
116                 reflectme.SetField(evData, "sessionID", sid)
117
118                 // IO socket can be nil when disconnected
119                 so := s.sessions.IOSocketGet(sid)
120                 if so != nil {
121                         (*so).Emit(evN, evData)
122                 } else {
123                         s.Log.Infof("%s not emitted: WS closed (sid:%s)", evN, sid)
124                 }
125
126                 // cleanup listener
127                 for i, evName := range evtList {
128                         svr.EventOff(evName, fwdFuncID[i])
129                 }
130                 svr.EventOff(evN, exitFuncID)
131
132                 return nil
133         }
134         exitFuncID, err = svr.EventOn(apiv1.ExecExitEvent, sess.ID, exitFunc)
135         if err != nil {
136                 common.APIError(c, err.Error())
137                 return
138         }
139
140         // Forward back command to right server
141         response, err := svr.SendCommand(cmd, data)
142         if err != nil {
143                 common.APIError(c, err.Error())
144                 return
145         }
146
147         // Decode response
148         body, err := ioutil.ReadAll(response.Body)
149         if err != nil {
150                 common.APIError(c, "Cannot read response body")
151                 return
152         }
153         c.JSON(http.StatusOK, string(body))
154 }