d015d2b4b18f9f6772d11ce5df9b6350ff0e66d8
[src/xds/xds-server.git] / lib / apiv1 / make.go
1 package apiv1
2
3 import (
4         "net/http"
5         "strings"
6
7         "time"
8
9         "strconv"
10
11         "github.com/gin-gonic/gin"
12         "github.com/iotbzh/xds-server/lib/common"
13 )
14
15 // MakeArgs is the parameters (json format) of /make command
16 type MakeArgs struct {
17         ID         string   `json:"id"`
18         SdkID      string   `json:"sdkid"` // sdk ID to use for setting env
19         Args       []string `json:"args"`  // args to pass to make command
20         Env        []string `json:"env"`
21         RPath      string   `json:"rpath"`   // relative path into project
22         CmdTimeout int      `json:"timeout"` // command completion timeout in Second
23 }
24
25 // MakeOutMsg Message send on each output (stdout+stderr) of make command
26 type MakeOutMsg struct {
27         CmdID     string `json:"cmdID"`
28         Timestamp string `json:"timestamp"`
29         Stdout    string `json:"stdout"`
30         Stderr    string `json:"stderr"`
31 }
32
33 // MakeExitMsg Message send on make command exit
34 type MakeExitMsg struct {
35         CmdID     string `json:"cmdID"`
36         Timestamp string `json:"timestamp"`
37         Code      int    `json:"code"`
38         Error     error  `json:"error"`
39 }
40
41 // MakeOutEvent Event send in WS when characters are received on stdout/stderr
42 const MakeOutEvent = "make:output"
43
44 // MakeExitEvent Event send in WS when command exited
45 const MakeExitEvent = "make:exit"
46
47 var makeCommandID = 1
48
49 func (s *APIService) buildMake(c *gin.Context) {
50         var args MakeArgs
51
52         if c.BindJSON(&args) != nil {
53                 common.APIError(c, "Invalid arguments")
54                 return
55         }
56
57         sess := s.sessions.Get(c)
58         if sess == nil {
59                 common.APIError(c, "Unknown sessions")
60                 return
61         }
62         sop := sess.IOSocket
63         if sop == nil {
64                 common.APIError(c, "Websocket not established")
65                 return
66         }
67
68         // Allow to pass id in url (/make/:id) or as JSON argument
69         id := c.Param("id")
70         if id == "" {
71                 id = args.ID
72         }
73         if id == "" {
74                 common.APIError(c, "Invalid id")
75                 return
76         }
77
78         prj := s.mfolder.GetFolderFromID(id)
79         if prj == nil {
80                 common.APIError(c, "Unknown id")
81                 return
82         }
83
84         execTmo := args.CmdTimeout
85         if execTmo == 0 {
86                 // TODO get default timeout from config.json file
87                 execTmo = 24 * 60 * 60 // 1 day
88         }
89
90         // Define callback for output
91         var oCB common.EmitOutputCB
92         oCB = func(sid string, id int, stdout, stderr string, data *map[string]interface{}) {
93                 // IO socket can be nil when disconnected
94                 so := s.sessions.IOSocketGet(sid)
95                 if so == nil {
96                         s.log.Infof("%s not emitted: WS closed - sid: %s - msg id:%d", MakeOutEvent, sid, id)
97                         return
98                 }
99
100                 // Retrieve project ID and RootPath
101                 prjID := (*data)["ID"].(string)
102                 prjRootPath := (*data)["RootPath"].(string)
103
104                 // Cleanup any references to internal rootpath in stdout & stderr
105                 stdout = strings.Replace(stdout, prjRootPath, "", -1)
106                 stderr = strings.Replace(stderr, prjRootPath, "", -1)
107
108                 s.log.Debugf("%s emitted - WS sid %s - id:%d - prjID:%s", MakeOutEvent, sid, id, prjID)
109
110                 // FIXME replace by .BroadcastTo a room
111                 err := (*so).Emit(MakeOutEvent, MakeOutMsg{
112                         CmdID:     strconv.Itoa(id),
113                         Timestamp: time.Now().String(),
114                         Stdout:    stdout,
115                         Stderr:    stderr,
116                 })
117                 if err != nil {
118                         s.log.Errorf("WS Emit : %v", err)
119                 }
120         }
121
122         // Define callback for output
123         eCB := func(sid string, id int, code int, err error, data *map[string]interface{}) {
124                 s.log.Debugf("Command [Cmd ID %d] exited: code %d, error: %v", id, code, err)
125
126                 // IO socket can be nil when disconnected
127                 so := s.sessions.IOSocketGet(sid)
128                 if so == nil {
129                         s.log.Infof("%s not emitted - WS closed (id:%d", MakeExitEvent, id)
130                         return
131                 }
132
133                 // Retrieve project ID and RootPath
134                 prjID := (*data)["ID"].(string)
135
136                 // XXX - workaround to be sure that Syncthing detected all changes
137                 if err := s.mfolder.ForceSync(prjID); err != nil {
138                         s.log.Errorf("Error while syncing folder %s: %v", prjID, err)
139                 }
140
141                 // FIXME replace by .BroadcastTo a room
142                 e := (*so).Emit(MakeExitEvent, MakeExitMsg{
143                         CmdID:     strconv.Itoa(id),
144                         Timestamp: time.Now().String(),
145                         Code:      code,
146                         Error:     err,
147                 })
148                 if e != nil {
149                         s.log.Errorf("WS Emit : %v", e)
150                 }
151         }
152
153         cmdID := makeCommandID
154         makeCommandID++
155         cmd := []string{}
156
157         // Retrieve env command regarding Sdk ID
158         if envCmd := s.sdks.GetEnvCmd(args.SdkID, prj.DefaultSdk); len(envCmd) > 0 {
159                 cmd = append(cmd, envCmd...)
160                 cmd = append(cmd, "&&")
161         }
162
163         cmd = append(cmd, "cd", prj.GetFullPath(args.RPath), "&&", "make")
164         if len(args.Args) > 0 {
165                 cmd = append(cmd, args.Args...)
166         }
167
168         s.log.Debugf("Execute [Cmd ID %d]: %v", cmdID, cmd)
169
170         data := make(map[string]interface{})
171         data["ID"] = prj.ID
172         data["RootPath"] = prj.RootPath
173
174         err := common.ExecPipeWs(cmd, args.Env, sop, sess.ID, cmdID, execTmo, s.log, oCB, eCB, nil)
175         if err != nil {
176                 common.APIError(c, err.Error())
177                 return
178         }
179
180         c.JSON(http.StatusOK,
181                 gin.H{
182                         "status": "OK",
183                         "cmdID":  cmdID,
184                 })
185 }