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) }
// GetRepoEvents will upgrade the connection to a Websocket and will stream // event updates to the browser. func GetRepoEvents(c *gin.Context) { engine_ := context.Engine(c) repo := session.Repo(c) c.Writer.Header().Set("Content-Type", "text/event-stream") eventc := make(chan *engine.Event, 1) engine_.Subscribe(eventc) defer func() { engine_.Unsubscribe(eventc) close(eventc) log.Infof("closed event stream") }() c.Stream(func(w io.Writer) bool { select { case event := <-eventc: if event == nil { log.Infof("nil event received") return false } if event.Name == repo.FullName { log.Debugf("received message %s", event.Name) sse.Encode(w, sse.Event{ Event: "message", Data: string(event.Msg), }) } case <-c.Writer.CloseNotify(): return false } return true }) }
func PostRepoKey(c *gin.Context) { repo := session.Repo(c) keys, err := store.GetKey(c, repo) if err != nil { c.String(404, "Error fetching repository key") return } body, err := ioutil.ReadAll(c.Request.Body) if err != nil { c.String(500, "Error reading private key from body. %s", err) return } pkey := crypto.UnmarshalPrivateKey(body) if pkey == nil { c.String(500, "Cannot unmarshal private key. Invalid format.") return } keys.Public = string(crypto.MarshalPublicKey(&pkey.PublicKey)) keys.Private = string(crypto.MarshalPrivateKey(pkey)) err = store.UpdateKey(c, keys) if err != nil { c.String(500, "Error updating repository key") return } c.String(201, keys.Public) }
func GetCommit(c *gin.Context) { repo := session.Repo(c) parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) { return repo.Hash, nil }) if err != nil { c.AbortWithError(http.StatusBadRequest, err) return } if parsed.Text != repo.FullName { c.AbortWithStatus(http.StatusUnauthorized) return } commit := c.Param("sha") branch := c.Query("branch") if len(branch) == 0 { branch = repo.Branch } build, err := store.GetBuildCommit(c, repo, commit, branch) if err != nil { c.AbortWithError(http.StatusNotFound, err) return } c.JSON(http.StatusOK, build) }
func ShowRepo(c *gin.Context) { user := session.User(c) repo := session.Repo(c) builds, _ := store.GetBuildList(c, repo) groups := []*model.BuildGroup{} var curr *model.BuildGroup for _, build := range builds { date := time.Unix(build.Created, 0).Format("Jan 2 2006") if curr == nil || curr.Date != date { curr = &model.BuildGroup{} curr.Date = date groups = append(groups, curr) } curr.Builds = append(curr.Builds, build) } httputil.SetCookie(c.Writer, c.Request, "user_last", repo.FullName) c.HTML(200, "repo.html", gin.H{ "User": user, "Repo": repo, "Builds": builds, "Groups": groups, }) }
func GetBuild(c *gin.Context) { if c.Param("number") == "latest" { GetBuildLast(c) return } repo := session.Repo(c) num, err := strconv.Atoi(c.Param("number")) if err != nil { c.AbortWithError(http.StatusBadRequest, err) return } build, err := store.GetBuildNumber(c, repo, num) if err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } jobs, _ := store.GetJobList(c, build) out := struct { *model.Build Jobs []*model.Job `json:"jobs"` }{build, jobs} c.IndentedJSON(http.StatusOK, &out) }
func GetRepoKey(c *gin.Context) { repo := session.Repo(c) keys, err := store.GetKey(c, repo) if err != nil { c.AbortWithError(http.StatusNotFound, err) } else { c.String(http.StatusOK, keys.Public) } }
func GetRepoKey(c *gin.Context) { repo := session.Repo(c) keys, err := store.GetKey(c, repo) if err != nil { c.String(404, "Error fetching repository key") } else { c.String(http.StatusOK, keys.Public) } }
func GetBuilds(c *gin.Context) { repo := session.Repo(c) builds, err := store.GetBuildList(c, repo) if err != nil { c.AbortWithStatus(http.StatusInternalServerError) return } c.IndentedJSON(http.StatusOK, builds) }
func ShowRepoBadges(c *gin.Context) { user := session.User(c) repo := session.Repo(c) c.HTML(200, "repo_badge.html", gin.H{ "User": user, "Repo": repo, "Link": httputil.GetURL(c.Request), }) }
func ChownRepo(c *gin.Context) { repo := session.Repo(c) user := session.User(c) repo.UserID = user.ID err := store.UpdateRepo(c, repo) if err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } c.JSON(http.StatusOK, repo) }
func RedirectPullRequest(c *gin.Context) { repo := session.Repo(c) refs := fmt.Sprintf("refs/pull/%s/head", c.Param("number")) build, err := store.GetBuildRef(c, repo, refs) if err != nil { c.AbortWithError(http.StatusNotFound, err) return } path := fmt.Sprintf("/%s/%s/%d", repo.Owner, repo.Name, build.Number) c.Redirect(http.StatusSeeOther, path) }
func DeleteRepo(c *gin.Context) { remote := remote.FromContext(c) repo := session.Repo(c) user := session.User(c) err := store.DeleteRepo(c, repo) if err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } remote.Deactivate(user, repo, httputil.GetURL(c.Request)) c.Writer.WriteHeader(http.StatusOK) }
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 ShowRepoEncrypt(c *gin.Context) { user := session.User(c) repo := session.Repo(c) token, _ := token.New( token.CsrfToken, user.Login, ).Sign(user.Hash) c.HTML(200, "repo_secret.html", gin.H{ "User": user, "Repo": repo, "Csrf": token, }) }
func ShowBuild(c *gin.Context) { user := session.User(c) repo := session.Repo(c) num, _ := strconv.Atoi(c.Param("number")) seq, _ := strconv.Atoi(c.Param("job")) if seq == 0 { seq = 1 } build, err := store.GetBuildNumber(c, repo, num) if err != nil { c.AbortWithError(404, err) return } jobs, err := store.GetJobList(c, build) if err != nil { c.AbortWithError(404, err) return } var job *model.Job for _, j := range jobs { if j.Number == seq { job = j break } } httputil.SetCookie(c.Writer, c.Request, "user_last", repo.FullName) var csrf string if user != nil { csrf, _ = token.New( token.CsrfToken, user.Login, ).Sign(user.Hash) } c.HTML(200, "build.html", gin.H{ "User": user, "Repo": repo, "Build": build, "Jobs": jobs, "Job": job, "Csrf": csrf, }) }
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 GetSecrets(c *gin.Context) { repo := session.Repo(c) secrets, err := store.GetSecretList(c, repo) if err != nil { c.AbortWithStatus(http.StatusInternalServerError) return } var list []*model.Secret for _, s := range secrets { list = append(list, s.Clone()) } c.JSON(http.StatusOK, list) }
func DeleteSecret(c *gin.Context) { repo := session.Repo(c) name := c.Param("secret") secret, err := store.GetSecret(c, repo, name) if err != nil { c.String(404, "Cannot find secret %s.", name) return } err = store.DeleteSecret(c, secret) if err != nil { c.String(500, "Unable to delete secret. %s", err.Error()) return } c.String(200, "") }
func DeleteSecret(c *gin.Context) { repo := session.Repo(c) name := c.Param("secret") secret, err := store.GetSecret(c, repo, name) if err != nil { c.String(http.StatusNotFound, "Cannot find secret %s.", name) return } err = store.DeleteSecret(c, secret) if err != nil { c.String(http.StatusInternalServerError, "Unable to delete secret. %s", err.Error()) return } c.String(http.StatusOK, "") }
func ShowRepoConf(c *gin.Context) { user := session.User(c) repo := session.Repo(c) token, _ := token.New( token.CsrfToken, user.Login, ).Sign(user.Hash) c.HTML(200, "repo_config.html", gin.H{ "User": user, "Repo": repo, "Csrf": token, "Link": httputil.GetURL(c.Request), }) }
func GetBuildLast(c *gin.Context) { repo := session.Repo(c) branch := c.DefaultQuery("branch", repo.Branch) build, err := store.GetBuildLast(c, repo, branch) if err != nil { c.String(http.StatusInternalServerError, err.Error()) return } jobs, _ := store.GetJobList(c, build) out := struct { *model.Build Jobs []*model.Job `json:"jobs"` }{build, jobs} c.IndentedJSON(http.StatusOK, &out) }
func RedirectSha(c *gin.Context) { repo := session.Repo(c) commit := c.Param("sha") branch := c.Query("branch") if len(branch) == 0 { branch = repo.Branch } build, err := store.GetBuildCommit(c, repo, commit, branch) if err != nil { c.AbortWithError(http.StatusNotFound, err) return } path := fmt.Sprintf("/%s/%s/%d", repo.Owner, repo.Name, build.Number) c.Redirect(http.StatusSeeOther, path) }
func PatchRepo(c *gin.Context) { repo := session.Repo(c) user := session.User(c) in := &struct { IsTrusted *bool `json:"trusted,omitempty"` Timeout *int64 `json:"timeout,omitempty"` AllowPull *bool `json:"allow_pr,omitempty"` AllowPush *bool `json:"allow_push,omitempty"` AllowDeploy *bool `json:"allow_deploy,omitempty"` AllowTag *bool `json:"allow_tag,omitempty"` }{} if err := c.Bind(in); err != nil { c.AbortWithError(http.StatusBadRequest, err) return } if in.AllowPush != nil { repo.AllowPush = *in.AllowPush } if in.AllowPull != nil { repo.AllowPull = *in.AllowPull } if in.AllowDeploy != nil { repo.AllowDeploy = *in.AllowDeploy } if in.AllowTag != nil { repo.AllowTag = *in.AllowTag } if in.IsTrusted != nil && user.Admin { repo.IsTrusted = *in.IsTrusted } if in.Timeout != nil && user.Admin { repo.Timeout = *in.Timeout } err := store.UpdateRepo(c, repo) if err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } c.JSON(http.StatusOK, repo) }
// GetRepoEvents will upgrade the connection to a Websocket and will stream // event updates to the browser. func GetRepoEvents(c *gin.Context) { repo := session.Repo(c) c.Writer.Header().Set("Content-Type", "text/event-stream") eventc := make(chan *bus.Event, 1) bus.Subscribe(c, eventc) defer func() { bus.Unsubscribe(c, eventc) close(eventc) log.Infof("closed event stream") }() c.Stream(func(w io.Writer) bool { select { case event := <-eventc: if event == nil { log.Infof("nil event received") return false } // TODO(bradrydzewski) This is a super hacky workaround until we improve // the actual bus. Having a per-call database event is just plain stupid. if event.Repo.FullName == repo.FullName { var payload = struct { model.Build Jobs []*model.Job `json:"jobs"` }{} payload.Build = event.Build payload.Jobs, _ = store.GetJobList(c, &event.Build) data, _ := json.Marshal(&payload) sse.Encode(w, sse.Event{ Event: "message", Data: string(data), }) } case <-c.Writer.CloseNotify(): return false } return true }) }
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 PostSecret(c *gin.Context) { repo := session.Repo(c) in := &model.Secret{} err := c.Bind(in) if err != nil { c.String(400, "Invalid JSON input. %s", err.Error()) return } in.ID = 0 in.RepoID = repo.ID err = store.SetSecret(c, in) if err != nil { c.String(500, "Unable to persist secret. %s", err.Error()) return } c.String(200, "") }
func PostSecret(c *gin.Context) { repo := session.Repo(c) in := &model.Secret{} err := c.Bind(in) if err != nil { c.String(http.StatusBadRequest, "Invalid JSON input. %s", err.Error()) return } in.ID = 0 in.RepoID = repo.ID err = store.SetSecret(c, in) if err != nil { c.String(http.StatusInternalServerError, "Unable to persist secret. %s", err.Error()) return } c.String(http.StatusOK, "") }
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, "") }