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