// execute will execute the build task and persist // the results to the datastore. func (b *BuildTask) execute() error { // we need to be sure that we can recover // from any sort panic that could occur // to avoid brining down the entire application defer func() { if e := recover(); e != nil { b.Build.Finished = time.Now().UTC() b.Commit.Finished = time.Now().UTC() b.Build.Duration = b.Build.Finished.Unix() - b.Build.Started.Unix() b.Commit.Duration = b.Build.Finished.Unix() - b.Build.Started.Unix() b.Commit.Status = "Error" b.Build.Status = "Error" database.SaveBuild(b.Build) database.SaveCommit(b.Commit) } }() // update commit and build status b.Commit.Status = "Started" b.Build.Status = "Started" b.Build.Started = time.Now().UTC() b.Commit.Started = time.Now().UTC() // persist the commit to the database if err := database.SaveCommit(b.Commit); err != nil { return err } // persist the build to the database if err := database.SaveBuild(b.Build); err != nil { return err } // get settings settings, _ := database.GetSettings() // notification context context := ¬ification.Context{ Repo: b.Repo, Commit: b.Commit, Host: settings.URL().String(), } // send all "started" notifications if b.Script.Notifications != nil { b.Script.Notifications.Send(context) } // make sure a channel exists for the repository, // the commit, and the commit output (TODO) reposlug := fmt.Sprintf("%s/%s/%s", b.Repo.Host, b.Repo.Owner, b.Repo.Name) commitslug := fmt.Sprintf("%s/%s/%s/commit/%s", b.Repo.Host, b.Repo.Owner, b.Repo.Name, b.Commit.Hash) consoleslug := fmt.Sprintf("%s/%s/%s/commit/%s/builds/%s", b.Repo.Host, b.Repo.Owner, b.Repo.Name, b.Commit.Hash, b.Build.Slug) channel.Create(reposlug) channel.Create(commitslug) channel.CreateStream(consoleslug) // notify the channels that the commit and build started channel.SendJSON(reposlug, b.Commit) channel.SendJSON(commitslug, b.Build) var buf = &bufferWrapper{channel: consoleslug} // append private parameters to the environment // variable section of the .drone.yml file if b.Repo.Params != nil { for k, v := range b.Repo.Params { b.Script.Env = append(b.Script.Env, k+"="+v) } } // execute the build builder := bldr.Builder{} builder.Build = b.Script builder.Repo = &r.Repo{Path: b.Repo.URL, Branch: b.Commit.Branch, Commit: b.Commit.Hash, PR: b.Commit.PullRequest, Dir: filepath.Join("/var/cache/drone/src", b.Repo.Slug)} builder.Key = []byte(b.Repo.PrivateKey) builder.Stdout = buf builder.Timeout = 300 * time.Minute defer func() { // update the status of the commit using the // GitHub status API. if err := updateGitHubStatus(b.Repo, b.Commit); err != nil { log.Printf("error updating github status: %s\n", err.Error()) } }() buildErr := builder.Run() b.Build.Finished = time.Now().UTC() b.Commit.Finished = time.Now().UTC() b.Build.Duration = b.Build.Finished.UnixNano() - b.Build.Started.UnixNano() b.Commit.Duration = b.Build.Finished.UnixNano() - b.Build.Started.UnixNano() b.Commit.Status = "Success" b.Build.Status = "Success" b.Build.Stdout = buf.buf.String() // if exit code != 0 set to failure if builder.BuildState == nil || builder.BuildState.ExitCode != 0 { b.Commit.Status = "Failure" b.Build.Status = "Failure" if buildErr != nil && b.Build.Stdout == "" { // TODO: If you wanted to have very friendly error messages, you could do that here b.Build.Stdout = buildErr.Error() + "\n" } } // persist the build to the database if err := database.SaveBuild(b.Build); err != nil { return err } // persist the commit to the database if err := database.SaveCommit(b.Commit); err != nil { return err } // notify the channels that the commit and build finished channel.SendJSON(reposlug, b.Commit) channel.SendJSON(commitslug, b.Build) channel.Close(consoleslug) // add the smtp address to the notificaitons //if b.Script.Notifications != nil && b.Script.Notifications.Email != nil { // b.Script.Notifications.Email.SetServer(settings.SmtpServer, settings.SmtpPort, // settings.SmtpUsername, settings.SmtpPassword, settings.SmtpAddress) //} // send all "finished" notifications if b.Script.Notifications != nil { b.sendEmail(context) // send email from queue, not from inside /build/script package b.Script.Notifications.Send(context) } return nil }
func run(path string) { // parse the Drone yml file s, err := script.ParseBuildFile(path) if err != nil { log.Err(err.Error()) os.Exit(1) return } // get the repository root directory dir := filepath.Dir(path) code := repo.Repo{Path: dir} // does the local repository match the // $GOPATH/src/{package} pattern? This is // important so we know the target location // where the code should be copied inside // the container. if gopath, ok := getRepoPath(dir); ok { code.Dir = gopath } else if gopath, ok := getGoPath(dir); ok { // in this case we found a GOPATH and // reverse engineered the package path code.Dir = gopath } else { // otherwise just use directory name code.Dir = filepath.Base(dir) } // this is where the code gets uploaded to the container // TODO move this code to the build package code.Dir = filepath.Join("/var/cache/drone/src", filepath.Clean(code.Dir)) // track all build results var builders []*build.Builder // ssh key to import into container var key []byte if len(*identity) != 0 { key, err = ioutil.ReadFile(*identity) if err != nil { fmt.Printf("[Error] Could not find or read identity file %s\n", *identity) os.Exit(1) return } } builds := []*script.Build{s} // loop through and create builders for _, b := range builds { //script.Builds { builder := build.Builder{} builder.Build = b builder.Repo = &code builder.Key = key builder.Stdout = os.Stdout builder.Timeout = *timeout if *parallel == true { var buf bytes.Buffer builder.Stdout = &buf } builders = append(builders, &builder) } switch *parallel { case false: runSequential(builders) case true: runParallel(builders) } // if in parallel mode, print out the buffer // if we had a failure for _, builder := range builders { if builder.BuildState.ExitCode == 0 { continue } if buf, ok := builder.Stdout.(*bytes.Buffer); ok { log.Noticef("printing stdout for failed build %s", builder.Build.Name) println(buf.String()) } } // this exit code is initially 0 and will // be set to an error code if any of the // builds fail. var exit int fmt.Printf("\nDrone Build Results \033[90m(%v)\033[0m\n", len(builders)) // loop through and print results for _, builder := range builders { build := builder.Build res := builder.BuildState duration := time.Duration(res.Finished - res.Started) switch { case builder.BuildState.ExitCode == 0: fmt.Printf(" \033[32m\u2713\033[0m %v \033[90m(%v)\033[0m\n", build.Name, humanizeDuration(duration*time.Second)) case builder.BuildState.ExitCode != 0: fmt.Printf(" \033[31m\u2717\033[0m %v \033[90m(%v)\033[0m\n", build.Name, humanizeDuration(duration*time.Second)) exit = builder.BuildState.ExitCode } } os.Exit(exit) }