func (e *engine) Schedule(c context.Context, req *Task) { node := <-e.pool.reserve() // since we are probably running in a go-routine // make sure we recover from any panics so that // a bug doesn't crash the whole system. defer func() { if err := recover(); err != nil { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] log.Errorf("panic running build: %v\n%s", err, string(buf)) } e.pool.release(node) }() // update the node that was allocated to each job func(id int64) { for _, job := range req.Jobs { job.NodeID = id store.UpdateJob(c, job) } }(node.ID) // run the full build! client, err := newDockerClient(node.Addr, node.Cert, node.Key, node.CA) if err != nil { log.Errorln("error creating docker client", err) } // update the build state if any of the sub-tasks // had a non-success status req.Build.Started = time.Now().UTC().Unix() req.Build.Status = model.StatusRunning e.updater.SetBuild(c, req) // run all bulid jobs for _, job := range req.Jobs { req.Job = job e.runJob(c, req, e.updater, client) } // update overall status based on each job req.Build.Status = model.StatusSuccess for _, job := range req.Jobs { if job.Status != model.StatusSuccess { req.Build.Status = job.Status break } } req.Build.Finished = time.Now().UTC().Unix() err = e.updater.SetBuild(c, req) if err != nil { log.Errorf("error updating build completion status. %s", err) } // run notifications err = e.runJobNotify(req, client) if err != nil { log.Errorf("error executing notification step. %s", err) } }
func PostBuild(c *gin.Context) { remote_ := remote.FromContext(c) repo := session.Repo(c) fork := c.DefaultQuery("fork", "false") num, err := strconv.Atoi(c.Param("number")) if err != nil { c.AbortWithError(http.StatusBadRequest, err) return } user, err := store.GetUser(c, repo.UserID) if err != nil { log.Errorf("failure to find repo owner %s. %s", repo.FullName, err) c.AbortWithError(500, err) return } build, err := store.GetBuildNumber(c, repo, num) if err != nil { log.Errorf("failure to get build %d. %s", num, err) c.AbortWithError(404, err) return } // if the remote has a refresh token, the current access token // may be stale. Therefore, we should refresh prior to dispatching // the job. if refresher, ok := remote_.(remote.Refresher); ok { ok, _ := refresher.Refresh(user) if ok { store.UpdateUser(c, user) } } // fetch the .drone.yml file from the database raw, sec, err := remote_.Script(user, repo, build) if err != nil { log.Errorf("failure to get .drone.yml for %s. %s", repo.FullName, err) c.AbortWithError(404, err) return } key, _ := store.GetKey(c, repo) netrc, err := remote_.Netrc(user, repo) if err != nil { log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err) c.AbortWithError(500, err) return } jobs, err := store.GetJobList(c, build) if err != nil { log.Errorf("failure to get build %d jobs. %s", build.Number, err) c.AbortWithError(404, err) return } // must not restart a running build if build.Status == model.StatusPending || build.Status == model.StatusRunning { c.String(409, "Cannot re-start a started build") return } // forking the build creates a duplicate of the build // and then executes. This retains prior build history. if forkit, _ := strconv.ParseBool(fork); forkit { build.ID = 0 build.Number = 0 for _, job := range jobs { job.ID = 0 job.NodeID = 0 } err := store.CreateBuild(c, build, jobs...) if err != nil { c.String(500, err.Error()) return } } // todo move this to database tier // and wrap inside a transaction build.Status = model.StatusPending build.Started = 0 build.Finished = 0 build.Enqueued = time.Now().UTC().Unix() for _, job := range jobs { job.Status = model.StatusPending job.Started = 0 job.Finished = 0 job.ExitCode = 0 job.NodeID = 0 job.Enqueued = build.Enqueued store.UpdateJob(c, job) } err = store.UpdateBuild(c, build) if err != nil { c.AbortWithStatus(500) return } c.JSON(202, build) // get the previous build so taht we can send // on status change notifications last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID) engine_ := context.Engine(c) go engine_.Schedule(c.Copy(), &engine.Task{ User: user, Repo: repo, Build: build, BuildPrev: last, Jobs: jobs, Keys: key, Netrc: netrc, Config: string(raw), Secret: string(sec), System: &model.System{ Link: httputil.GetURL(c.Request), Plugins: strings.Split(os.Getenv("PLUGIN_FILTER"), " "), Globals: strings.Split(os.Getenv("PLUGIN_PARAMS"), " "), }, }) }