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