func DeleteBuild(c *gin.Context) { engine_ := context.Engine(c) repo := session.Repo(c) // parse the build number and job sequence number from // the repquest parameter. num, _ := strconv.Atoi(c.Params.ByName("number")) seq, _ := strconv.Atoi(c.Params.ByName("job")) build, err := store.GetBuildNumber(c, repo, num) if err != nil { c.AbortWithError(404, err) return } job, err := store.GetJobNumber(c, build, seq) if err != nil { c.AbortWithError(404, err) return } node, err := store.GetNode(c, job.NodeID) if err != nil { c.AbortWithError(404, err) return } engine_.Cancel(build.ID, job.ID, node) }
func GetStream(c *gin.Context) { repo := session.Repo(c) buildn, _ := strconv.Atoi(c.Param("build")) jobn, _ := strconv.Atoi(c.Param("number")) c.Writer.Header().Set("Content-Type", "text/event-stream") build, err := store.GetBuildNumber(c, repo, buildn) if err != nil { log.Debugln("stream cannot get build number.", err) c.AbortWithError(404, err) return } job, err := store.GetJobNumber(c, build, jobn) if err != nil { log.Debugln("stream cannot get job number.", err) c.AbortWithError(404, err) return } rc, err := stream.Reader(c, stream.ToKey(job.ID)) if err != nil { c.AbortWithError(404, err) return } go func() { <-c.Writer.CloseNotify() rc.Close() }() var line int var scanner = bufio.NewScanner(rc) for scanner.Scan() { line++ var err = sse.Encode(c.Writer, sse.Event{ Id: strconv.Itoa(line), Event: "message", Data: scanner.Text(), }) if err != nil { break } c.Writer.Flush() } log.Debugf("Closed stream %s#%d", repo.FullName, build.Number) }
func GetStream(c *gin.Context) { engine_ := context.Engine(c) repo := session.Repo(c) buildn, _ := strconv.Atoi(c.Param("build")) jobn, _ := strconv.Atoi(c.Param("number")) c.Writer.Header().Set("Content-Type", "text/event-stream") build, err := store.GetBuildNumber(c, repo, buildn) if err != nil { log.Debugln("stream cannot get build number.", err) c.AbortWithError(404, err) return } job, err := store.GetJobNumber(c, build, jobn) if err != nil { log.Debugln("stream cannot get job number.", err) c.AbortWithError(404, err) return } node, err := store.GetNode(c, job.NodeID) if err != nil { log.Debugln("stream cannot get node.", err) c.AbortWithError(404, err) return } rc, err := engine_.Stream(build.ID, job.ID, node) if err != nil { c.AbortWithError(404, err) return } defer func() { rc.Close() }() go func() { <-c.Writer.CloseNotify() rc.Close() }() rw := &StreamWriter{c.Writer, 0} stdcopy.StdCopy(rw, rw, rc) }
func DeleteBuild(c *gin.Context) { repo := session.Repo(c) // parse the build number and job sequence number from // the repquest parameter. num, _ := strconv.Atoi(c.Params.ByName("number")) seq, _ := strconv.Atoi(c.Params.ByName("job")) build, err := store.GetBuildNumber(c, repo, num) if err != nil { c.AbortWithError(404, err) return } job, err := store.GetJobNumber(c, build, seq) if err != nil { c.AbortWithError(404, err) return } if job.Status != model.StatusRunning { c.String(400, "Cannot cancel a non-running build") return } job.Status = model.StatusKilled job.Finished = time.Now().Unix() if job.Started == 0 { job.Started = job.Finished } job.ExitCode = 137 store.UpdateBuildJob(c, build, job) client := stomp.MustFromContext(c) client.SendJSON("/topic/cancel", model.Event{ Type: model.Cancelled, Repo: *repo, Build: *build, Job: *job, }, stomp.WithHeader("job-id", strconv.FormatInt(job.ID, 10))) c.String(204, "") }
func GetBuildLogs(c *gin.Context) { repo := session.Repo(c) // the user may specify to stream the full logs, // or partial logs, capped at 2MB. full, _ := strconv.ParseBool(c.DefaultQuery("full", "false")) // parse the build number and job sequence number from // the repquest parameter. num, _ := strconv.Atoi(c.Params.ByName("number")) seq, _ := strconv.Atoi(c.Params.ByName("job")) build, err := store.GetBuildNumber(c, repo, num) if err != nil { c.AbortWithError(404, err) return } job, err := store.GetJobNumber(c, build, seq) if err != nil { c.AbortWithError(404, err) return } r, err := store.ReadLog(c, job) if err != nil { c.AbortWithError(404, err) return } defer r.Close() if full { // TODO implement limited streaming to avoid crashing the browser } c.Header("Content-Type", "application/json") stream.Copy(c.Writer, r) }
func DeleteBuild(c *gin.Context) { repo := session.Repo(c) // parse the build number and job sequence number from // the repquest parameter. num, _ := strconv.Atoi(c.Params.ByName("number")) seq, _ := strconv.Atoi(c.Params.ByName("job")) build, err := store.GetBuildNumber(c, repo, num) if err != nil { c.AbortWithError(404, err) return } job, err := store.GetJobNumber(c, build, seq) if err != nil { c.AbortWithError(404, err) return } bus.Publish(c, bus.NewEvent(bus.Cancelled, repo, build, job)) c.String(204, "") }
func GetBuildLogs(c *gin.Context) { repo := session.Repo(c) // the user may specify to stream the full logs, // or partial logs, capped at 2MB. full, _ := strconv.ParseBool(c.Params.ByName("full")) // parse the build number and job sequence number from // the repquest parameter. num, _ := strconv.Atoi(c.Params.ByName("number")) seq, _ := strconv.Atoi(c.Params.ByName("job")) build, err := store.GetBuildNumber(c, repo, num) if err != nil { c.AbortWithError(404, err) return } job, err := store.GetJobNumber(c, build, seq) if err != nil { c.AbortWithError(404, err) return } r, err := store.ReadLog(c, job) if err != nil { c.AbortWithError(404, err) return } defer r.Close() if full { io.Copy(c.Writer, r) } else { io.Copy(c.Writer, io.LimitReader(r, 2000000)) } }
func DeleteBuild(c *gin.Context) { repo := session.Repo(c) // parse the build number and job sequence number from // the repquest parameter. num, _ := strconv.Atoi(c.Params.ByName("number")) seq, _ := strconv.Atoi(c.Params.ByName("job")) build, err := store.GetBuildNumber(c, repo, num) if err != nil { c.AbortWithError(404, err) return } job, err := store.GetJobNumber(c, build, seq) if err != nil { c.AbortWithError(404, err) return } if job.Status != model.StatusRunning { c.String(400, "Cannot cancel a non-running build") return } job.Status = model.StatusKilled job.Finished = time.Now().Unix() if job.Started == 0 { job.Started = job.Finished } job.ExitCode = 137 store.UpdateBuildJob(c, build, job) bus.Publish(c, bus.NewEvent(bus.Cancelled, repo, build, job)) c.String(204, "") }
// LogStream streams the build log output to the client. func LogStream(c *gin.Context) { repo := session.Repo(c) buildn, _ := strconv.Atoi(c.Param("build")) jobn, _ := strconv.Atoi(c.Param("number")) c.Writer.Header().Set("Content-Type", "text/event-stream") build, err := store.GetBuildNumber(c, repo, buildn) if err != nil { logrus.Debugln("stream cannot get build number.", err) c.AbortWithError(404, err) return } job, err := store.GetJobNumber(c, build, jobn) if err != nil { logrus.Debugln("stream cannot get job number.", err) c.AbortWithError(404, err) return } if job.Status != model.StatusRunning { logrus.Debugln("stream not found.") c.AbortWithStatus(404) return } ws, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { if _, ok := err.(websocket.HandshakeError); !ok { logrus.Errorf("Cannot upgrade websocket. %s", err) } return } logrus.Debugf("Successfull upgraded websocket") ticker := time.NewTicker(pingPeriod) defer ticker.Stop() logs := make(chan []byte) done := make(chan bool) dest := fmt.Sprintf("/topic/logs.%d", job.ID) client, _ := stomp.FromContext(c) sub, err := client.Subscribe(dest, stomp.HandlerFunc(func(m *stomp.Message) { if m.Header.GetBool("eof") { done <- true } else { logs <- m.Body } m.Release() })) if err != nil { logrus.Errorf("Unable to read logs from broker. %s", err) return } defer func() { client.Unsubscribe(sub) close(done) close(logs) }() for { select { case buf := <-logs: ws.SetWriteDeadline(time.Now().Add(writeWait)) ws.WriteMessage(websocket.TextMessage, buf) case <-done: return case <-ticker.C: err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait)) if err != nil { return } } } }