github.com/marinho/drone@v0.2.1-0.20140504195434-d3ba962e89a7/pkg/handler/gitlab.go (about) 1 package handler 2 3 import ( 4 "database/sql" 5 "fmt" 6 "io/ioutil" 7 "net/http" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/drone/drone/pkg/build/script" 13 "github.com/drone/drone/pkg/database" 14 . "github.com/drone/drone/pkg/model" 15 "github.com/drone/drone/pkg/queue" 16 "github.com/plouc/go-gitlab-client" 17 ) 18 19 type GitlabHandler struct { 20 queue *queue.Queue 21 apiPath string 22 } 23 24 func NewGitlabHandler(queue *queue.Queue) *GitlabHandler { 25 return &GitlabHandler{ 26 queue: queue, 27 apiPath: "/api/v3", 28 } 29 } 30 31 func (g *GitlabHandler) Add(w http.ResponseWriter, r *http.Request, u *User) error { 32 settings := database.SettingsMust() 33 teams, err := database.ListTeams(u.ID) 34 if err != nil { 35 return err 36 } 37 data := struct { 38 User *User 39 Teams []*Team 40 Settings *Settings 41 }{u, teams, settings} 42 // if the user hasn't linked their GitLab account 43 // render a different template 44 if len(u.GitlabToken) == 0 { 45 return RenderTemplate(w, "gitlab_link.html", &data) 46 } 47 // otherwise display the template for adding 48 // a new GitLab repository. 49 return RenderTemplate(w, "gitlab_add.html", &data) 50 } 51 52 func (g *GitlabHandler) Link(w http.ResponseWriter, r *http.Request, u *User) error { 53 token := strings.TrimSpace(r.FormValue("token")) 54 55 if len(u.GitlabToken) == 0 || token != u.GitlabToken && len(token) > 0 { 56 u.GitlabToken = token 57 settings := database.SettingsMust() 58 gl := gogitlab.NewGitlab(settings.GitlabApiUrl, g.apiPath, u.GitlabToken) 59 _, err := gl.CurrentUser() 60 if err != nil { 61 return fmt.Errorf("Private Token is not valid: %q", err) 62 } 63 if err := database.SaveUser(u); err != nil { 64 return RenderError(w, err, http.StatusBadRequest) 65 } 66 } 67 68 http.Redirect(w, r, "/new/gitlab", http.StatusSeeOther) 69 return nil 70 } 71 72 func (g *GitlabHandler) ReLink(w http.ResponseWriter, r *http.Request, u *User) error { 73 data := struct { 74 User *User 75 }{u} 76 return RenderTemplate(w, "gitlab_link.html", &data) 77 } 78 79 func (g *GitlabHandler) Create(w http.ResponseWriter, r *http.Request, u *User) error { 80 teamName := r.FormValue("team") 81 owner := r.FormValue("owner") 82 name := r.FormValue("name") 83 84 repo, err := g.newGitlabRepo(u, owner, name) 85 if err != nil { 86 return err 87 } 88 89 if len(teamName) > 0 { 90 team, err := database.GetTeamSlug(teamName) 91 if err != nil { 92 return fmt.Errorf("Unable to find Team %s.", teamName) 93 } 94 95 // user must be an admin member of the team 96 if ok, _ := database.IsMemberAdmin(u.ID, team.ID); !ok { 97 return fmt.Errorf("Invalid permission to access Team %s.", teamName) 98 } 99 repo.TeamID = team.ID 100 } 101 102 // Save to the database 103 if err := database.SaveRepo(repo); err != nil { 104 return fmt.Errorf("Error saving repository to the database. %s", err) 105 } 106 107 return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) 108 } 109 110 func (g *GitlabHandler) newGitlabRepo(u *User, owner, name string) (*Repo, error) { 111 settings := database.SettingsMust() 112 gl := gogitlab.NewGitlab(settings.GitlabApiUrl, g.apiPath, u.GitlabToken) 113 114 project, err := gl.Project(ns(owner, name)) 115 if err != nil { 116 return nil, err 117 } 118 119 var cloneUrl string 120 if project.Public { 121 cloneUrl = project.HttpRepoUrl 122 } else { 123 cloneUrl = project.SshRepoUrl 124 } 125 126 repo, err := NewRepo(settings.GitlabDomain, owner, name, ScmGit, cloneUrl) 127 if err != nil { 128 return nil, err 129 } 130 131 repo.UserID = u.ID 132 repo.Private = !project.Public 133 if repo.Private { 134 // name the key 135 keyName := fmt.Sprintf("%s@%s", repo.Owner, settings.Domain) 136 137 // TODO: (fudanchii) check if we already opted to use UserKey 138 139 // create the github key, or update if one already exists 140 if err := gl.AddProjectDeployKey(ns(owner, name), keyName, repo.PublicKey); err != nil { 141 return nil, fmt.Errorf("Unable to add Public Key to your GitLab repository.") 142 } 143 } 144 145 link := fmt.Sprintf("%s://%s/hook/gitlab?id=%s", settings.Scheme, settings.Domain, repo.Slug) 146 if err := gl.AddProjectHook(ns(owner, name), link, true, false, true); err != nil { 147 return nil, fmt.Errorf("Unable to add Hook to your GitLab repository.") 148 } 149 150 return repo, err 151 } 152 153 func (g *GitlabHandler) Hook(w http.ResponseWriter, r *http.Request) error { 154 rID := r.FormValue("id") 155 repo, err := database.GetRepoSlug(rID) 156 if err != nil { 157 return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) 158 } 159 160 user, err := database.GetUser(repo.UserID) 161 if err != nil { 162 return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) 163 } 164 165 payload, _ := ioutil.ReadAll(r.Body) 166 parsed, err := gogitlab.ParseHook(payload) 167 if err != nil { 168 return err 169 } 170 if parsed.ObjectKind == "merge_request" { 171 fmt.Println(string(payload)) 172 if err := g.PullRequestHook(parsed, repo, user); err != nil { 173 return err 174 } 175 return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) 176 } 177 178 if len(parsed.After) == 0 { 179 return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) 180 } 181 182 _, err = database.GetCommitHash(parsed.After, repo.ID) 183 if err != nil && err != sql.ErrNoRows { 184 fmt.Println("commit already exists") 185 return RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway) 186 } 187 188 commit := &Commit{} 189 commit.RepoID = repo.ID 190 commit.Branch = parsed.Branch() 191 commit.Hash = parsed.After 192 commit.Status = "Pending" 193 commit.Created = time.Now().UTC() 194 195 head := parsed.Head() 196 commit.Message = head.Message 197 commit.Timestamp = head.Timestamp 198 if head.Author != nil { 199 commit.SetAuthor(head.Author.Email) 200 } else { 201 commit.SetAuthor(parsed.UserName) 202 } 203 204 // get the github settings from the database 205 settings := database.SettingsMust() 206 207 // get the drone.yml file from GitHub 208 client := gogitlab.NewGitlab(settings.GitlabApiUrl, g.apiPath, user.GitlabToken) 209 210 content, err := client.RepoRawFile(ns(repo.Owner, repo.Name), commit.Hash, ".drone.yml") 211 if err != nil { 212 msg := "No .drone.yml was found in this repository. You need to add one.\n" 213 if err := saveFailedBuild(commit, msg); err != nil { 214 return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 215 } 216 return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 217 } 218 219 // parse the build script 220 buildscript, err := script.ParseBuild(content, repo.Params) 221 if err != nil { 222 msg := "Could not parse your .drone.yml file. It needs to be a valid drone yaml file.\n\n" + err.Error() + "\n" 223 if err := saveFailedBuild(commit, msg); err != nil { 224 return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 225 } 226 return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 227 } 228 229 // save the commit to the database 230 if err := database.SaveCommit(commit); err != nil { 231 return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 232 } 233 234 // save the build to the database 235 build := &Build{} 236 build.Slug = "1" // TODO 237 build.CommitID = commit.ID 238 build.Created = time.Now().UTC() 239 build.Status = "Pending" 240 if err := database.SaveBuild(build); err != nil { 241 return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 242 } 243 244 g.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) 245 246 // OK! 247 return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) 248 249 } 250 251 func (g *GitlabHandler) PullRequestHook(p *gogitlab.HookPayload, repo *Repo, user *User) error { 252 obj := p.ObjectAttributes 253 254 // Gitlab may trigger multiple hooks upon updating merge requests status 255 // only build when it was just opened and the merge hasn't been checked yet. 256 if !(obj.State == "opened" && obj.MergeStatus == "unchecked") { 257 fmt.Println("Ignore GitLab Merge Requests") 258 return nil 259 } 260 261 settings := database.SettingsMust() 262 263 client := gogitlab.NewGitlab(settings.GitlabApiUrl, g.apiPath, user.GitlabToken) 264 265 // GitLab merge-requests hook doesn't include repository data. 266 // Have to fetch it manually 267 src, err := client.RepoBranch(strconv.Itoa(obj.SourceProjectId), obj.SourceBranch) 268 if err != nil { 269 return err 270 } 271 272 _, err = database.GetCommitHash(src.Commit.Id, repo.ID) 273 if err != nil && err != sql.ErrNoRows { 274 fmt.Println("commit already exists") 275 return err 276 } 277 278 commit := &Commit{} 279 commit.RepoID = repo.ID 280 commit.Branch = src.Name 281 commit.Hash = src.Commit.Id 282 commit.Status = "Pending" 283 commit.Created = time.Now().UTC() 284 commit.PullRequest = strconv.Itoa(obj.IId) 285 286 commit.Message = src.Commit.Message 287 commit.Timestamp = src.Commit.AuthoredDateRaw 288 commit.SetAuthor(src.Commit.Author.Email) 289 290 content, err := client.RepoRawFile(strconv.Itoa(obj.SourceProjectId), commit.Hash, ".drone.yml") 291 if err != nil { 292 msg := "No .drone.yml was found in this repository. You need to add one.\n" 293 if err := saveFailedBuild(commit, msg); err != nil { 294 return fmt.Errorf("Failed to save build: %q", err) 295 } 296 return fmt.Errorf("Error to fetch build script: %q", err) 297 } 298 299 // parse the build script 300 buildscript, err := script.ParseBuild(content, repo.Params) 301 if err != nil { 302 msg := "Could not parse your .drone.yml file. It needs to be a valid drone yaml file.\n\n" + err.Error() + "\n" 303 if err := saveFailedBuild(commit, msg); err != nil { 304 return fmt.Errorf("Failed to save build: %q", err) 305 } 306 return fmt.Errorf("Failed to parse build script: %q", err) 307 } 308 309 // save the commit to the database 310 if err := database.SaveCommit(commit); err != nil { 311 return fmt.Errorf("Failed to save commit: %q", err) 312 } 313 314 // save the build to the database 315 build := &Build{} 316 build.Slug = "1" // TODO 317 build.CommitID = commit.ID 318 build.Created = time.Now().UTC() 319 build.Status = "Pending" 320 if err := database.SaveBuild(build); err != nil { 321 return fmt.Errorf("Failed to save build: %q", err) 322 } 323 324 g.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) 325 326 return nil 327 } 328 329 // ns namespaces user and repo. 330 // Returns user%2Frepo 331 func ns(user, repo string) string { 332 return fmt.Sprintf("%s%%2F%s", user, repo) 333 }