github.com/kvattikuti/drone@v0.2.1-0.20140603034306-d400229a327a/pkg/queue/worker.go (about)

     1  package queue
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"github.com/drone/drone/pkg/build/git"
     7  	r "github.com/drone/drone/pkg/build/repo"
     8  	"github.com/drone/drone/pkg/channel"
     9  	"github.com/drone/drone/pkg/database"
    10  	. "github.com/drone/drone/pkg/model"
    11  	"github.com/drone/drone/pkg/plugin/notify"
    12  	"github.com/drone/go-github/github"
    13  	"io"
    14  	//"log"
    15  	"net/url"
    16  	"path/filepath"
    17  	"time"
    18  )
    19  
    20  type worker struct {
    21  	runner BuildRunner
    22  }
    23  
    24  // work is a function that will infinitely
    25  // run in the background waiting for tasks that
    26  // it can pull off the queue and execute.
    27  func (w *worker) work(queue <-chan *BuildTask) {
    28  	var task *BuildTask
    29  	for {
    30  		// get work item (pointer) from the queue
    31  		task = <-queue
    32  		if task == nil {
    33  			continue
    34  		}
    35  
    36  		// execute the task
    37  		var err = w.execute(task)
    38  		if err != nil {
    39  			println(err.Error())
    40  		}
    41  	}
    42  }
    43  
    44  // execute will execute the build task and persist
    45  // the results to the datastore.
    46  func (w *worker) execute(task *BuildTask) error {
    47  
    48  	println("build task execute called")
    49  	// we need to be sure that we can recover
    50  	// from any sort panic that could occur
    51  	// to avoid brining down the entire application
    52  	defer func() {
    53  		if e := recover(); e != nil {
    54  			task.Build.Finished = time.Now().UTC()
    55  			task.Commit.Finished = time.Now().UTC()
    56  			task.Build.Duration = task.Build.Finished.Unix() - task.Build.Started.Unix()
    57  			task.Commit.Duration = task.Build.Finished.Unix() - task.Build.Started.Unix()
    58  			task.Commit.Status = "Error"
    59  			task.Build.Status = "Error"
    60  			database.SaveBuild(task.Build)
    61  			database.SaveCommit(task.Commit)
    62  		}
    63  	}()
    64  
    65  	println("build task started")
    66  	// update commit and build status
    67  	task.Commit.Status = "Started"
    68  	task.Build.Status = "Started"
    69  	task.Build.Started = time.Now().UTC()
    70  	task.Commit.Started = time.Now().UTC()
    71  
    72  	// persist the commit to the database
    73  	if err := database.SaveCommit(task.Commit); err != nil {
    74  		return err
    75  	}
    76  	println("commit saved")
    77  
    78  	// persist the build to the database
    79  	if err := database.SaveBuild(task.Build); err != nil {
    80  		return err
    81  	}
    82  	println("build saved")
    83  
    84  	// get settings
    85  	settings, _ := database.GetSettings()
    86  
    87  	// notification context
    88  	context := &notify.Context{
    89  		Repo:   task.Repo,
    90  		Commit: task.Commit,
    91  		Host:   settings.URL().String(),
    92  	}
    93  	println(context)
    94  
    95  	/*// send all "started" notifications
    96  	if task.Script.Notifications != nil {
    97  		task.Script.Notifications.Send(context)
    98  	}
    99  
   100  	// Send "started" notification to Github
   101  	if err := updateGitHubStatus(task.Repo, task.Commit); err != nil {
   102  		log.Printf("error updating github status: %s\n", err.Error())
   103  	}
   104  	println("github status updated")*/
   105  
   106  	// make sure a channel exists for the repository,
   107  	// the commit, and the commit output (TODO)
   108  	reposlug := fmt.Sprintf("%s/%s/%s", task.Repo.Host, task.Repo.Owner, task.Repo.Name)
   109  	commitslug := fmt.Sprintf("%s/%s/%s/commit/%s/%s", task.Repo.Host, task.Repo.Owner, task.Repo.Name, task.Commit.Branch, task.Commit.Hash)
   110  	consoleslug := fmt.Sprintf("%s/%s/%s/commit/%s/%s/builds/%s", task.Repo.Host, task.Repo.Owner, task.Repo.Name, task.Commit.Branch, task.Commit.Hash, task.Build.Slug)
   111  	println(reposlug)
   112  	println(commitslug)
   113  	println(consoleslug)
   114  	channel.Create(reposlug)
   115  	channel.Create(commitslug)
   116  	channel.CreateStream(consoleslug)
   117  
   118  	// notify the channels that the commit and build started
   119  	channel.SendJSON(reposlug, task.Commit)
   120  	channel.SendJSON(commitslug, task.Build)
   121  
   122  	var buf = &bufferWrapper{channel: consoleslug}
   123  
   124  	// append private parameters to the environment
   125  	// variable section of the .drone.yml file, iff
   126  	// this is not a pull request (for security purposes)
   127  	if task.Repo.Params != nil && len(task.Commit.PullRequest) == 0 {
   128  		for k, v := range task.Repo.Params {
   129  			task.Script.Env = append(task.Script.Env, k+"="+v)
   130  		}
   131  	}
   132  
   133  	/*defer func() {
   134  		// update the status of the commit using the
   135  		// GitHub status API.
   136  		if err := updateGitHubStatus(task.Repo, task.Commit); err != nil {
   137  			log.Printf("error updating github status: %s\n", err.Error())
   138  		}
   139  	}()*/
   140  
   141  	println("running build")
   142  	// execute the build
   143  	passed, buildErr := w.runBuild(task, buf)
   144  	println("build completed");
   145  
   146  	task.Build.Finished = time.Now().UTC()
   147  	task.Commit.Finished = time.Now().UTC()
   148  	task.Build.Duration = task.Build.Finished.UnixNano() - task.Build.Started.UnixNano()
   149  	task.Commit.Duration = task.Build.Finished.UnixNano() - task.Build.Started.UnixNano()
   150  	task.Commit.Status = "Success"
   151  	task.Build.Status = "Success"
   152  	task.Build.Stdout = buf.buf.String()
   153  
   154  	// if exit code != 0 set to failure
   155  	if passed {
   156  		task.Commit.Status = "Failure"
   157  		task.Build.Status = "Failure"
   158  		if buildErr != nil && task.Build.Stdout == "" {
   159  			// TODO: If you wanted to have very friendly error messages, you could do that here
   160  			task.Build.Stdout = buildErr.Error() + "\n"
   161  		}
   162  	}
   163  
   164  	println("build status is ", task.Commit.Status)
   165  	println("build err is ", buildErr)
   166  	if passed {
   167  		println(buildErr.Error())
   168  	}
   169  	// persist the build to the database
   170  	if err := database.SaveBuild(task.Build); err != nil {
   171  		return err
   172  	}
   173  	println("build saved")
   174  
   175  	// persist the commit to the database
   176  	if err := database.SaveCommit(task.Commit); err != nil {
   177  		return err
   178  	}
   179  	println("commit saved")
   180  
   181  	// notify the channels that the commit and build finished
   182  	channel.SendJSON(reposlug, task.Commit)
   183  	channel.SendJSON(commitslug, task.Build)
   184  	channel.Close(consoleslug)
   185  
   186  	// send all "finished" notifications
   187  	if task.Script.Notifications != nil {
   188  		task.Script.Notifications.Send(context)
   189  	}
   190  	println("notifications sent")
   191  
   192  	return nil
   193  }
   194  
   195  func (w *worker) runBuild(task *BuildTask, buf io.Writer) (bool, error) {
   196  	repo := &r.Repo{
   197  		Name:   task.Repo.Slug,
   198  		Path:   task.Repo.URL,
   199  		Branch: task.Commit.Branch,
   200  		Commit: task.Commit.Hash,
   201  		PR:     task.Commit.PullRequest,
   202  		Dir:    filepath.Join("/var/cache/drone/src", task.Repo.Slug),
   203  		Depth:  git.GitDepth(task.Script.Git),
   204  	}
   205  
   206  	return w.runner.Run(
   207  		task.Script,
   208  		repo,
   209  		[]byte(task.Repo.PrivateKey),
   210  		task.Repo.Privileged,
   211  		buf,
   212  	)
   213  }
   214  
   215  // updateGitHubStatus is a helper function that will send
   216  // the build status to GitHub using the Status API.
   217  // see https://github.com/blog/1227-commit-status-api
   218  func updateGitHubStatus(repo *Repo, commit *Commit) error {
   219  
   220  	// convert from drone status to github status
   221  	var message, status string
   222  	switch commit.Status {
   223  	case "Success":
   224  		status = "success"
   225  		message = "The build succeeded on drone.io"
   226  	case "Failure":
   227  		status = "failure"
   228  		message = "The build failed on drone.io"
   229  	case "Started":
   230  		status = "pending"
   231  		message = "The build is pending on drone.io"
   232  	default:
   233  		status = "error"
   234  		message = "The build errored on drone.io"
   235  	}
   236  
   237  	// get the system settings
   238  	settings, _ := database.GetSettings()
   239  
   240  	// get the user from the database
   241  	// since we need his / her GitHub token
   242  	user, err := database.GetUser(repo.UserID)
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	client := github.New(user.GithubToken)
   248  	client.ApiUrl = settings.GitHubApiUrl
   249  	buildUrl := getBuildUrl(settings.URL().String(), repo, commit)
   250  
   251  	return client.Repos.CreateStatus(repo.Owner, repo.Name, status, buildUrl, message, commit.Hash)
   252  }
   253  
   254  func getBuildUrl(host string, repo *Repo, commit *Commit) string {
   255  	branchQuery := url.Values{}
   256  	branchQuery.Set("branch", commit.Branch)
   257  	buildUrl := fmt.Sprintf("%s/%s/commit/%s?%s", host, repo.Slug, commit.Hash, branchQuery.Encode())
   258  	return buildUrl
   259  }
   260  
   261  type bufferWrapper struct {
   262  	buf bytes.Buffer
   263  
   264  	// name of the channel
   265  	channel string
   266  }
   267  
   268  func (b *bufferWrapper) Write(p []byte) (n int, err error) {
   269  	n, err = b.buf.Write(p)
   270  	channel.SendBytes(b.channel, p)
   271  	return
   272  }