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