github.com/marinho/drone@v0.2.1-0.20140504195434-d3ba962e89a7/pkg/handler/github.go (about) 1 package handler 2 3 import ( 4 "database/sql" 5 "net/http" 6 "strconv" 7 "time" 8 9 "github.com/drone/drone/pkg/build/script" 10 "github.com/drone/drone/pkg/database" 11 . "github.com/drone/drone/pkg/model" 12 "github.com/drone/drone/pkg/queue" 13 "github.com/drone/go-github/github" 14 ) 15 16 type GithubHandler struct { 17 queue *queue.Queue 18 } 19 20 func NewGithubHandler(queue *queue.Queue) *GithubHandler { 21 return &GithubHandler{ 22 queue: queue, 23 } 24 } 25 26 // Processes a generic POST-RECEIVE GitHub hook and 27 // attempts to trigger a build. 28 func (h *GithubHandler) Hook(w http.ResponseWriter, r *http.Request) error { 29 // handle github ping 30 if r.Header.Get("X-Github-Event") == "ping" { 31 return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) 32 } 33 34 // if this is a pull request route 35 // to a different handler 36 if r.Header.Get("X-Github-Event") == "pull_request" { 37 h.PullRequestHook(w, r) 38 return nil 39 } 40 41 // get the payload of the message 42 // this should contain a json representation of the 43 // repository and commit details 44 payload := r.FormValue("payload") 45 46 // parse the github Hook payload 47 hook, err := github.ParseHook([]byte(payload)) 48 if err != nil { 49 println("could not parse hook:", err.Error()) 50 return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 51 } 52 53 // make sure this is being triggered because of a commit 54 // and not something like a tag deletion or whatever 55 if hook.IsTag() || hook.IsGithubPages() || 56 hook.IsHead() == false || hook.IsDeleted() { 57 return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) 58 } 59 60 // get the repo from the URL 61 repoId := r.FormValue("id") 62 63 // get the repo from the database, return error if not found 64 repo, err := database.GetRepoSlug(repoId) 65 if err != nil { 66 return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) 67 } 68 69 // Get the user that owns the repository 70 user, err := database.GetUser(repo.UserID) 71 if err != nil { 72 return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 73 } 74 75 // Verify that the commit doesn't already exist. 76 // We should never build the same commit twice. 77 _, err = database.GetCommitBranchHash(hook.Branch(), hook.Head.Id, repo.ID) 78 if err != nil && err != sql.ErrNoRows { 79 println("commit already exists") 80 return RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway) 81 } 82 83 // we really only need: 84 // * repo owner 85 // * repo name 86 // * repo host (github) 87 // * commit hash 88 // * commit timestamp 89 // * commit branch 90 // * commit message 91 // * commit author 92 // * pull request 93 94 // once we have this data we could just send directly to the queue 95 // and let it handle everything else 96 97 commit := &Commit{} 98 commit.RepoID = repo.ID 99 commit.Branch = hook.Branch() 100 commit.Hash = hook.Head.Id 101 commit.Status = "Pending" 102 commit.Created = time.Now().UTC() 103 104 // extract the author and message from the commit 105 // this is kind of experimental, since I don't know 106 // what I'm doing here. 107 if hook.Head != nil && hook.Head.Author != nil { 108 commit.Message = hook.Head.Message 109 commit.Timestamp = hook.Head.Timestamp 110 commit.SetAuthor(hook.Head.Author.Email) 111 } else if hook.Commits != nil && len(hook.Commits) > 0 && hook.Commits[0].Author != nil { 112 commit.Message = hook.Commits[0].Message 113 commit.Timestamp = hook.Commits[0].Timestamp 114 commit.SetAuthor(hook.Commits[0].Author.Email) 115 } 116 117 // get the github settings from the database 118 settings := database.SettingsMust() 119 120 // get the drone.yml file from GitHub 121 client := github.New(user.GithubToken) 122 client.ApiUrl = settings.GitHubApiUrl 123 124 content, err := client.Contents.FindRef(repo.Owner, repo.Name, ".drone.yml", commit.Hash) 125 if err != nil { 126 msg := "No .drone.yml was found in this repository. You need to add one.\n" 127 if err := saveFailedBuild(commit, msg); err != nil { 128 return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 129 } 130 return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 131 } 132 133 // decode the content. Note: Not sure this will ever happen...it basically means a GitHub API issue 134 raw, err := content.DecodeContent() 135 if err != nil { 136 msg := "Could not decode the yaml from GitHub. Check that your .drone.yml is a valid yaml file.\n" 137 if err := saveFailedBuild(commit, msg); err != nil { 138 return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 139 } 140 return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 141 } 142 143 // parse the build script 144 buildscript, err := script.ParseBuild(raw, repo.Params) 145 if err != nil { 146 msg := "Could not parse your .drone.yml file. It needs to be a valid drone yaml file.\n\n" + err.Error() + "\n" 147 if err := saveFailedBuild(commit, msg); err != nil { 148 return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 149 } 150 return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 151 } 152 153 // save the commit to the database 154 if err := database.SaveCommit(commit); err != nil { 155 return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 156 } 157 158 // save the build to the database 159 build := &Build{} 160 build.Slug = "1" // TODO 161 build.CommitID = commit.ID 162 build.Created = time.Now().UTC() 163 build.Status = "Pending" 164 if err := database.SaveBuild(build); err != nil { 165 return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 166 } 167 168 // notify websocket that a new build is pending 169 //realtime.CommitPending(repo.UserID, repo.TeamID, repo.ID, commit.ID, repo.Private) 170 //realtime.BuildPending(repo.UserID, repo.TeamID, repo.ID, commit.ID, build.ID, repo.Private) 171 172 h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) //Push(repo, commit, build, buildscript) 173 174 // OK! 175 return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) 176 } 177 178 func (h *GithubHandler) PullRequestHook(w http.ResponseWriter, r *http.Request) { 179 // get the payload of the message 180 // this should contain a json representation of the 181 // repository and commit details 182 payload := r.FormValue("payload") 183 184 println("GOT PR HOOK") 185 println(payload) 186 187 hook, err := github.ParsePullRequestHook([]byte(payload)) 188 if err != nil { 189 RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 190 return 191 } 192 193 // ignore these 194 if hook.Action != "opened" && hook.Action != "synchronize" { 195 RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) 196 return 197 } 198 199 // get the repo from the URL 200 repoId := r.FormValue("id") 201 202 // get the repo from the database, return error if not found 203 repo, err := database.GetRepoSlug(repoId) 204 if err != nil { 205 RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) 206 return 207 } 208 209 // Get the user that owns the repository 210 user, err := database.GetUser(repo.UserID) 211 if err != nil { 212 RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 213 return 214 } 215 216 // Verify that the commit doesn't already exist. 217 // We should enver build the same commit twice. 218 _, err = database.GetCommitHash(hook.PullRequest.Head.Sha, repo.ID) 219 if err != nil && err != sql.ErrNoRows { 220 RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway) 221 return 222 } 223 224 /////////////////////////////////////////////////////// 225 226 commit := &Commit{} 227 commit.RepoID = repo.ID 228 commit.Branch = hook.PullRequest.Head.Ref 229 commit.Hash = hook.PullRequest.Head.Sha 230 commit.Status = "Pending" 231 commit.Created = time.Now().UTC() 232 commit.Gravatar = hook.PullRequest.User.GravatarId 233 commit.Author = hook.PullRequest.User.Login 234 commit.PullRequest = strconv.Itoa(hook.Number) 235 commit.Message = hook.PullRequest.Title 236 // label := p.PullRequest.Head.Labe 237 238 // get the github settings from the database 239 settings := database.SettingsMust() 240 241 // get the drone.yml file from GitHub 242 client := github.New(user.GithubToken) 243 client.ApiUrl = settings.GitHubApiUrl 244 245 content, err := client.Contents.FindRef(repo.Owner, repo.Name, ".drone.yml", commit.Hash) // TODO should this really be the hash?? 246 if err != nil { 247 println(err.Error()) 248 RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 249 return 250 } 251 252 // decode the content 253 raw, err := content.DecodeContent() 254 if err != nil { 255 RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 256 return 257 } 258 259 // parse the build script 260 buildscript, err := script.ParseBuild(raw, repo.Params) 261 if err != nil { 262 // TODO if the YAML is invalid we should create a commit record 263 // with an ERROR status so that the user knows why a build wasn't 264 // triggered in the system 265 RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 266 return 267 } 268 269 // save the commit to the database 270 if err := database.SaveCommit(commit); err != nil { 271 RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 272 return 273 } 274 275 // save the build to the database 276 build := &Build{} 277 build.Slug = "1" // TODO 278 build.CommitID = commit.ID 279 build.Created = time.Now().UTC() 280 build.Status = "Pending" 281 if err := database.SaveBuild(build); err != nil { 282 RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 283 return 284 } 285 286 // notify websocket that a new build is pending 287 // TODO we should, for consistency, just put this inside Queue.Add() 288 h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) 289 290 // OK! 291 RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) 292 }