From fd98e26575836729b1a8a2d03126b65404ce2174 Mon Sep 17 00:00:00 2001 From: Kirill Zaitsev Date: Sat, 22 Aug 2015 06:32:22 +0300 Subject: [PATCH 1/7] GitLab service interface --- Godeps/Godeps.json | 2 +- .../Bugagazavr/go-gitlab-client/README.md | 3 + .../examples/projects/main.go | 84 ++++++++++++-- .../examples/repositories/main.go | 32 ++++- .../Bugagazavr/go-gitlab-client/gitlab.go | 29 +++++ .../go-gitlab-client/hook_payload.go | 1 + .../Bugagazavr/go-gitlab-client/hooks.go | 16 ++- .../Bugagazavr/go-gitlab-client/projects.go | 65 +++++++++-- .../go-gitlab-client/projects_test.go | 15 +++ .../go-gitlab-client/repositories.go | 89 +++++++++++++- .../go-gitlab-client/repositories_test.go | 9 ++ .../Bugagazavr/go-gitlab-client/services.go | 19 +++ .../stubs/commits/comments/index.json | 16 +++ .../projects/merge_requests/notes/index.json | 16 +++ cmd/drone-server/drone.go | 16 +++ pkg/remote/builtin/gitlab/gitlab.go | 109 ++++++++++-------- pkg/server/commits.go | 34 ++++++ pkg/server/redirect.go | 61 ++++++++++ pkg/store/builtin/build.go | 12 ++ pkg/store/builtin/build_sql.go | 73 ++++++++++++ pkg/store/store.go | 8 ++ 21 files changed, 629 insertions(+), 80 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/services.go create mode 100644 Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/stubs/commits/comments/index.json create mode 100644 Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/stubs/projects/merge_requests/notes/index.json create mode 100644 pkg/server/redirect.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 14f6037e4..089d4c11e 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -12,7 +12,7 @@ }, { "ImportPath": "github.com/Bugagazavr/go-gitlab-client", - "Rev": "912567bb7e65212c910733b3bfa178b11049a70e" + "Rev": "fa361f26087a2ff8fbb267fbe2d82037fc35e51a" }, { "ImportPath": "github.com/BurntSushi/migration", diff --git a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/README.md b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/README.md index 4e716ee32..7920c5c4b 100644 --- a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/README.md +++ b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/README.md @@ -18,6 +18,9 @@ go-gitlab-client is a simple client written in golang to consume gitlab API. ###Projects [gitlab api doc](http://doc.gitlab.com/ce/api/projects.html) * list projects * get single project + * list project merge requests + * list notes on merge requests + * add comments to merge requests * ###Repositories [gitlab api doc](http://doc.gitlab.com/ce/api/repositories.html) diff --git a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/examples/projects/main.go b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/examples/projects/main.go index 26325c19d..80a3d478d 100644 --- a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/examples/projects/main.go +++ b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/examples/projects/main.go @@ -37,15 +37,30 @@ func main() { var method string flag.StringVar(&method, "m", "", "Specify method to retrieve projects infos, available methods:\n"+ " > -m projects\n"+ - " > -m project -id PROJECT_ID\n"+ - " > -m hooks -id PROJECT_ID\n"+ - " > -m branches -id PROJECT_ID\n"+ - " > -m merge_requests -id PROJECT_ID\n"+ - " > -m team -id PROJECT_ID") + " > -m project -id PROJECT_ID\n"+ + " > -m hooks -id PROJECT_ID\n"+ + " > -m branches -id PROJECT_ID\n"+ + " > -m merge_requests -id PROJECT_ID\n"+ + " > -m merge_request_notes -id PROJECT_ID -merge_id MERGE_REQUEST_ID\n"+ + " > -m merge_request_comment -id PROJECT_ID -merge_id MERGE_REQUEST_ID -comment COMMENT_BODY\n"+ + " > -m team -id PROJECT_ID\n"+ + " > -m add_drone -id PROJECT_ID\n -token DRONE_TOKEN -url DRONE_URL") var id string flag.StringVar(&id, "id", "", "Specify repository id") + var merge_id string + flag.StringVar(&merge_id, "merge_id", "", "Specify merge request id") + + var comment string + flag.StringVar(&comment, "comment", "", "The body of the new comment") + + var drone_token string + flag.StringVar(&drone_token, "drone_token", "", "Drone service token") + + var drone_url string + flag.StringVar(&drone_url, "drone_url", "", "Drone service url") + flag.Usage = func() { fmt.Printf("Usage:\n") flag.PrintDefaults() @@ -151,11 +166,54 @@ func main() { if mr.Assignee != nil { assignee = mr.Assignee.Username } - fmt.Printf(" %s -> %s [%s] author[%s] assignee[%s]\n", - mr.SourceBranch, mr.TargetBranch, mr.State, + fmt.Printf(" (#%d) %s -> %s [%s] author[%s] assignee[%s]\n", + mr.Id, mr.SourceBranch, mr.TargetBranch, mr.State, author, assignee) } + case "merge_request_notes": + fmt.Println("Fetching merge_request notes…") + + if id == "" { + flag.Usage() + return + } + + notes, err := gitlab.MergeRequestNotes(id, merge_id, 0, 30) + if err != nil { + fmt.Println(err.Error()) + return + } + + for _, note := range notes { + author := "" + if note.Author != nil { + author = note.Author.Username + } + fmt.Printf(" [%d] author: %s <%s> %s\n", + note.Id, author, note.CreatedAt, note.Body) + } + + case "merge_request_comment": + fmt.Println("Sending new merge_request comment…") + + if id == "" { + flag.Usage() + return + } + + note, err := gitlab.SendMergeRequestComment(id, merge_id, comment) + if err != nil { + fmt.Println(err.Error()) + return + } + author := "" + if note.Author != nil { + author = note.Author.Username + } + fmt.Printf(" [%d] author: %s <%s> %s\n", + note.Id, author, note.CreatedAt, note.Body) + case "hooks": fmt.Println("Fetching project hooks…") @@ -191,5 +249,17 @@ func main() { for _, member := range members { fmt.Printf("> [%d] %s (%s) since %s\n", member.Id, member.Username, member.Name, member.CreatedAt) } + case "add_drone": + fmt.Println("Adding drone service to project") + + if id == "" || drone_token == "" || drone_url == "" { + flag.Usage() + return + } + + if err := gitlab.AddDroneService(id, map[string]string{"token": drone_token, "drone_url": drone_url}); err != nil { + fmt.Println(err) + return + } } } diff --git a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/examples/repositories/main.go b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/examples/repositories/main.go index e6591bb91..8a250204c 100644 --- a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/examples/repositories/main.go +++ b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/examples/repositories/main.go @@ -36,11 +36,19 @@ func main() { " > branches\n"+ " > branch\n"+ " > tags\n"+ - " > commits") + " > commits\n"+ + " > commit_comments -sha COMMIT_SHA\n"+ + " > comment_a_commit -sha COMMIT_SHA -comment COMMENT_BODY") var id string flag.StringVar(&id, "id", "", "Specify repository id") + var sha string + flag.StringVar(&sha, "sha", "", "Specify commit sha") + + var comment string + flag.StringVar(&comment, "comment", "", "The body of the new comment") + flag.Usage = func() { fmt.Printf("Usage:\n") flag.PrintDefaults() @@ -90,7 +98,27 @@ func main() { } for _, commit := range commits { - fmt.Printf("%s > [%s] %s\n", commit.CreatedAt.Format("Mon 02 Jan 15:04"), commit.Author_Name, commit.Title) + fmt.Printf("(%s) %s > [%s] %s\n", commit.Id, commit.CreatedAt.Format("Mon 02 Jan 15:04"), commit.Author_Name, commit.Title) } + case "commit_comments": + fmt.Println("Fetching comments on a repository commit…") + + comments, err := gitlab.RepoCommitComments(id, sha) + if err != nil { + fmt.Println(err.Error()) + } + + for _, c := range comments { + fmt.Printf("[%s] %s\n", c.Author.Username, c.Note) + } + case "comment_a_commit": + fmt.Println("Sending a new comment on a repository commit…") + + c, err := gitlab.SendRepoCommitComment(id, sha, comment) + if err != nil { + fmt.Println(err.Error()) + } + + fmt.Printf("[%s] %s\n", c.Author.Username, c.Note) } } diff --git a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/gitlab.go b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/gitlab.go index 415dc5ed6..dceb17541 100644 --- a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/gitlab.go +++ b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/gitlab.go @@ -128,6 +128,35 @@ func (g *Gitlab) ResourceUrlQuery(u string, params, query map[string]string) str } +func (g *Gitlab) ResourceUrlQueryRaw(u string, params, query map[string]string) (string, string) { + if params != nil { + for key, val := range params { + u = strings.Replace(u, key, encodeParameter(val), -1) + } + } + + query_params := url.Values{} + if !g.Bearer { + query_params.Add("private_token", g.Token) + } + + if query != nil { + for key, val := range query { + query_params.Set(key, val) + } + } + + u = g.BaseUrl + g.ApiPath + u + "?" + query_params.Encode() + p, err := url.Parse(u) + if err != nil { + return u, "" + } + + opaque := "//" + p.Host + p.Path + return u, opaque + +} + func (g *Gitlab) ResourceUrlRaw(u string, params map[string]string) (string, string) { if params != nil { diff --git a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/hook_payload.go b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/hook_payload.go index 8bb7e9add..e4c33c0a2 100644 --- a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/hook_payload.go +++ b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/hook_payload.go @@ -27,6 +27,7 @@ type HookObjAttr struct { StDiffs string `json:"st_diffs,omitempty"` MergeStatus string `json:"merge_status,omitempty"` TargetProjectId int `json:"target_project_id,omitempty"` + URL string `json:"url,omitempty"` Source *hProject `json:"source,omitempty"` Target *hProject `json:"target,omitempty"` LastCommit *hCommit `json:"last_commit,omitempty"` diff --git a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/hooks.go b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/hooks.go index 9dd0fe4bb..3f19de7eb 100644 --- a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/hooks.go +++ b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/hooks.go @@ -88,13 +88,13 @@ Parameters: merge_requests_events Trigger hook on merge_requests events */ -func (g *Gitlab) AddProjectHook(id, hook_url string, push_events, issues_events, merge_requests_events bool) error { +func (g *Gitlab) AddProjectHook(id, hook_url string, push_events, issues_events, merge_requests_events, tag_events bool) error { url, opaque := g.ResourceUrlRaw(project_url_hooks, map[string]string{":id": id}) var err error - body := buildHookQuery(hook_url, push_events, issues_events, merge_requests_events) + body := buildHookQuery(hook_url, push_events, issues_events, merge_requests_events, tag_events) _, err = g.buildAndExecRequestRaw("POST", url, opaque, []byte(body)) return err @@ -115,7 +115,7 @@ Parameters: merge_requests_events Trigger hook on merge_requests events */ -func (g *Gitlab) EditProjectHook(id, hook_id, hook_url string, push_events, issues_events, merge_requests_events bool) error { +func (g *Gitlab) EditProjectHook(id, hook_id, hook_url string, push_events, issues_events, merge_requests_events, tag_events bool) error { url, opaque := g.ResourceUrlRaw(project_url_hook, map[string]string{ ":id": id, @@ -124,7 +124,7 @@ func (g *Gitlab) EditProjectHook(id, hook_id, hook_url string, push_events, issu var err error - body := buildHookQuery(hook_url, push_events, issues_events, merge_requests_events) + body := buildHookQuery(hook_url, push_events, issues_events, merge_requests_events, tag_events) _, err = g.buildAndExecRequestRaw("PUT", url, opaque, []byte(body)) return err @@ -158,7 +158,7 @@ func (g *Gitlab) RemoveProjectHook(id, hook_id string) error { /* Build HTTP query to add or edit hook */ -func buildHookQuery(hook_url string, push_events, issues_events, merge_requests_events bool) string { +func buildHookQuery(hook_url string, push_events, issues_events, merge_requests_events, tag_events bool) string { v := url.Values{} v.Set("url", hook_url) @@ -178,6 +178,10 @@ func buildHookQuery(hook_url string, push_events, issues_events, merge_requests_ } else { v.Set("merge_requests_events", "false") } - + if tag_events { + v.Set("tag_push_events", "true") + } else { + v.Set("tag_push_events", "false") + } return v.Encode() } diff --git a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/projects.go b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/projects.go index 9de030ec0..b470f57e3 100644 --- a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/projects.go +++ b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/projects.go @@ -2,19 +2,21 @@ package gogitlab import ( "encoding/json" + "fmt" "strconv" "strings" ) const ( - projects_url = "/projects" // Get a list of projects owned by the authenticated user - projects_search_url = "/projects/search/:query" // Search for projects by name - project_url = "/projects/:id" // Get a specific project, identified by project ID or NAME - project_url_events = "/projects/:id/events" // Get project events - project_url_branches = "/projects/:id/repository/branches" // Lists all branches of a project - project_url_members = "/projects/:id/members" // List project team members - project_url_member = "/projects/:id/members/:user_id" // Get project team member - project_url_merge_requests = "/projects/:id/merge_requests" // List all merge requests of a project + projects_url = "/projects" // Get a list of projects owned by the authenticated user + projects_search_url = "/projects/search/:query" // Search for projects by name + project_url = "/projects/:id" // Get a specific project, identified by project ID or NAME + project_url_events = "/projects/:id/events" // Get project events + project_url_branches = "/projects/:id/repository/branches" // Lists all branches of a project + project_url_members = "/projects/:id/members" // List project team members + project_url_member = "/projects/:id/members/:user_id" // Get project team member + project_url_merge_requests = "/projects/:id/merge_requests" // List all merge requests of a project + merge_request_url_notes = "/projects/:id/merge_requests/:merge_request_id/notes" // Manage comments for a given merge request ) type Member struct { @@ -89,6 +91,14 @@ type MergeRequest struct { Description string `json:"description,omitempty"` } +type MergeRequestNote struct { + Attachment interface{} `json:"attachment"` + Body string `json:"body"` + CreatedAt string `json:"created_at"` + Id int `json:"id"` + Author *Member `json:"author"` +} + /* Get a list of all projects owned by the authenticated user. */ @@ -209,6 +219,43 @@ func (g *Gitlab) ProjectMergeRequests(id string, page int, per_page int, state s return mr, err } +/* +Lists all comments on merge request. +*/ +func (g *Gitlab) MergeRequestNotes(id string, merge_request_id string, page int, per_page int) ([]*MergeRequestNote, error) { + par := map[string]string{":id": id, ":merge_request_id": merge_request_id} + qry := map[string]string{ + "page": strconv.Itoa(page), + "per_page": strconv.Itoa(per_page)} + url := g.ResourceUrlQuery(merge_request_url_notes, par, qry) + + var mr []*MergeRequestNote + + contents, err := g.buildAndExecRequest("GET", url, nil) + if err == nil { + err = json.Unmarshal(contents, &mr) + } + + return mr, err +} + +/* +Creates a new comment on a merge request. +*/ +func (g *Gitlab) SendMergeRequestComment(id string, merge_request_id string, comment string) (*MergeRequestNote, error) { + par := map[string]string{":id": id, ":merge_request_id": merge_request_id} + url := g.ResourceUrlQuery(merge_request_url_notes, par, map[string]string{}) + + var mr *MergeRequestNote + + contents, err := g.buildAndExecRequest("POST", url, []byte(fmt.Sprintf("body=%s", comment))) + if err == nil { + err = json.Unmarshal(contents, &mr) + } + + return mr, err +} + /* Get single project id. @@ -236,7 +283,7 @@ func (g *Gitlab) SearchProjectId(namespace string, name string) (id int, err err } for _, project := range projects { - if project.Namespace.Name == namespace { + if project.Namespace.Name == namespace && strings.ToLower(project.Name) == strings.ToLower(name) { id = project.Id } } diff --git a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/projects_test.go b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/projects_test.go index 2484d6f48..6fcbda844 100644 --- a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/projects_test.go +++ b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/projects_test.go @@ -57,6 +57,21 @@ func TestProjectMergeRequests(t *testing.T) { } } +func TestMergeRequestNotes(t *testing.T) { + ts, gitlab := Stub("stubs/projects/merge_requests/notes/index.json") + defer ts.Close() + notes, err := gitlab.MergeRequestNotes("1", "1", 0, 30) + + assert.Equal(t, err, nil) + assert.Equal(t, len(notes), 1) + + if len(notes) > 0 { + assert.Equal(t, notes[0].Id, 301) + assert.Equal(t, notes[0].Body, "Comment for MR") + assert.Equal(t, notes[0].Author.Username, "pipin") + } +} + func TestSearchProjectId(t *testing.T) { ts, gitlab := Stub("stubs/projects/index.json") diff --git a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/repositories.go b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/repositories.go index 694100e0e..47d54b706 100644 --- a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/repositories.go +++ b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/repositories.go @@ -2,17 +2,19 @@ package gogitlab import ( "encoding/json" + "fmt" "net/url" "time" ) const ( - repo_url_branches = "/projects/:id/repository/branches" // List repository branches - repo_url_branch = "/projects/:id/repository/branches/:branch" // Get a specific branch of a project. - repo_url_tags = "/projects/:id/repository/tags" // List project repository tags - repo_url_commits = "/projects/:id/repository/commits" // List repository commits - repo_url_tree = "/projects/:id/repository/tree" // List repository tree - repo_url_raw_file = "/projects/:id/repository/blobs/:sha" // Get raw file content for specific commit/branch + repo_url_branches = "/projects/:id/repository/branches" // List repository branches + repo_url_branch = "/projects/:id/repository/branches/:branch" // Get a specific branch of a project. + repo_url_tags = "/projects/:id/repository/tags" // List project repository tags + repo_url_commits = "/projects/:id/repository/commits" // List repository commits + repo_url_commit_comments = "/projects/:id/repository/commits/:sha/comments" // New comment or list of commit comments + repo_url_tree = "/projects/:id/repository/tree" // List repository tree + repo_url_raw_file = "/projects/:id/repository/blobs/:sha" // Get raw file content for specific commit/branch ) type BranchCommit struct { @@ -62,6 +64,14 @@ type File struct { Children []*File } +type CommitComment struct { + Author *Member `json:"author,omitempty"` + Line int `json:"line,omitempty"` + LineType string `json:"line_type,omitempty"` + Note string `json:"note,omitempty"` + Path string `json:"path,omitempty"` +} + /* Get a list of repository branches from a project, sorted by name alphabetically. @@ -195,6 +205,73 @@ func (g *Gitlab) RepoCommits(id string) ([]*Commit, error) { return commits, err } +/* +Get a list of comments in a repository commit. + + GET /projects/:id/repository/commits/:sha/comments + +Parameters: + + id The ID of a project + sha The sha of the commit + +Usage: + + comments, err := gitlab.RepoCommitComments("your_projet_id", "commit_sha") + if err != nil { + fmt.Println(err.Error()) + } + for _, comment := range comments { + fmt.Printf("%+v\n", comment) + } +*/ +func (g *Gitlab) RepoCommitComments(id string, sha string) ([]*CommitComment, error) { + + url, opaque := g.ResourceUrlRaw(repo_url_commit_comments, map[string]string{":id": id, ":sha": sha}) + + var comments []*CommitComment + + contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil) + if err == nil { + err = json.Unmarshal(contents, &comments) + } + + return comments, err +} + +/* +Create a comment in a repository commit. + + POST /projects/:id/repository/commits/:sha/comments + +Parameters: + + id The ID of a project + sha The sha of the commit + body The body of the comment + +Usage: + + comment, err := gitlab.SendRepoCommitComment("your_projet_id", "commit_sha", "your comment goes here") + if err != nil { + fmt.Println(err.Error()) + } + fmt.Printf("%+v\n", comment) +*/ +func (g *Gitlab) SendRepoCommitComment(id string, sha string, body string) (*CommitComment, error) { + + url, opaque := g.ResourceUrlRaw(repo_url_commit_comments, map[string]string{":id": id, ":sha": sha}) + + var comment *CommitComment + + contents, err := g.buildAndExecRequestRaw("POST", url, opaque, []byte(fmt.Sprintf("note=%s", body))) + if err == nil { + err = json.Unmarshal(contents, &comment) + } + + return comment, err +} + /* Get Raw file content */ diff --git a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/repositories_test.go b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/repositories_test.go index ab445fbc5..36a4d7bda 100644 --- a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/repositories_test.go +++ b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/repositories_test.go @@ -41,3 +41,12 @@ func TestRepoCommits(t *testing.T) { assert.Equal(t, len(commits), 2) defer ts.Close() } + +func TestRepoCommitComments(t *testing.T) { + ts, gitlab := Stub("stubs/commits/comments/index.json") + comments, err := gitlab.RepoCommitComments("1", "a9e6a5io4e695923c995ed2e836789b50oi77e0b") + + assert.Equal(t, err, nil) + assert.Equal(t, len(comments), 1) + defer ts.Close() +} diff --git a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/services.go b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/services.go new file mode 100644 index 000000000..6ef79c283 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/services.go @@ -0,0 +1,19 @@ +package gogitlab + +const ( + drone_service_url = "/projects/:id/services/drone-ci" +) + +func (g *Gitlab) AddDroneService(id string, params map[string]string) error { + url, opaque := g.ResourceUrlQueryRaw(drone_service_url, map[string]string{":id": id}, params) + + _, err := g.buildAndExecRequestRaw("PUT", url, opaque, nil) + return err +} + +func (g *Gitlab) DeleteDroneService(id string) error { + url, opaque := g.ResourceUrlQueryRaw(drone_service_url, map[string]string{":id": id}, nil) + + _, err := g.buildAndExecRequestRaw("DELETE", url, opaque, nil) + return err +} diff --git a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/stubs/commits/comments/index.json b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/stubs/commits/comments/index.json new file mode 100644 index 000000000..f3886f5c4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/stubs/commits/comments/index.json @@ -0,0 +1,16 @@ +[ + { + "author": { + "id": 1, + "username": "admin", + "email": "admin@local.host", + "name": "Administrator", + "blocked": false, + "created_at": "2012-04-29T08:46:00Z" + }, + "note": "text1", + "path": "example.rb", + "line": 5, + "line_type": "new" + } +] \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/stubs/projects/merge_requests/notes/index.json b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/stubs/projects/merge_requests/notes/index.json new file mode 100644 index 000000000..52f57e809 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/stubs/projects/merge_requests/notes/index.json @@ -0,0 +1,16 @@ +[ + { + "id": 301, + "body": "Comment for MR", + "attachment": null, + "author": { + "id": 1, + "username": "pipin", + "email": "admin@example.com", + "name": "Pip", + "state": "active", + "created_at": "2013-09-30T13:46:01Z" + }, + "created_at": "2013-10-02T08:57:14Z" + } +] \ No newline at end of file diff --git a/cmd/drone-server/drone.go b/cmd/drone-server/drone.go index 737fa78ba..16001ae55 100644 --- a/cmd/drone-server/drone.go +++ b/cmd/drone-server/drone.go @@ -185,6 +185,14 @@ func main() { repo.GET("/logs/:number/:task", server.GetLogs) // repo.POST("/status/:number", server.PostBuildStatus) } + + // Routes for external services + repoExternal := repos.Group("") + { + repoExternal.Use(server.SetRepo()) + + repoExternal.GET("/pr/:number", server.GetPullRequest) + } } badges := api.Group("/badges/:owner/:name") @@ -236,6 +244,14 @@ func main() { auth.POST("", server.GetLogin) } + redirects := r.Group("/redirect") + { + redirects.Use(server.SetDatastore(store)) + redirects.Use(server.SetRepo()) + + redirects.GET("/:owner/:name/commit/:sha", server.RedirectSha) + } + r.SetHTMLTemplate(index()) r.NoRoute(func(c *gin.Context) { c.HTML(200, "index.html", nil) diff --git a/pkg/remote/builtin/gitlab/gitlab.go b/pkg/remote/builtin/gitlab/gitlab.go index a79809bfa..e5c0560dc 100644 --- a/pkg/remote/builtin/gitlab/gitlab.go +++ b/pkg/remote/builtin/gitlab/gitlab.go @@ -6,8 +6,10 @@ import ( "io/ioutil" "net/http" "net/url" + "regexp" "strconv" "strings" + "time" "github.com/drone/drone/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client" "github.com/drone/drone/Godeps/_workspace/src/github.com/hashicorp/golang-lru" @@ -190,25 +192,15 @@ func (r *Gitlab) Activate(user *common.User, repo *common.Repo, k *common.Keypai return err } - title, err := GetKeyTitle(link) + uri, err := url.Parse(link) if err != nil { return err } - // if the repository is private we'll need - // to upload a github key to the repository - if repo.Private { - if err := client.AddProjectDeployKey(id, title, k.Public); err != nil { - return err - } - } + droneUrl := fmt.Sprintf("%s://%s", uri.Scheme, uri.Host) + droneToken := uri.Query().Get("access_token") - // append the repo owner / name to the hook url since gitlab - // doesn't send this detail in the post-commit hook - link += "&owner=" + repo.Owner + "&name=" + repo.Name - - // add the hook - return client.AddProjectHook(id, link, true, false, true) + return client.AddDroneService(id, map[string]string{"token": droneToken, "drone_url": droneUrl}) } // Deactivate removes a repository by removing all the post-commit hooks @@ -220,33 +212,7 @@ func (r *Gitlab) Deactivate(user *common.User, repo *common.Repo, link string) e return err } - keys, err := client.ProjectDeployKeys(id) - if err != nil { - return err - } - var pubkey = strings.TrimSpace(repo.Keys.Public) - for _, k := range keys { - if pubkey == strings.TrimSpace(k.Key) { - if err := client.RemoveProjectDeployKey(id, strconv.Itoa(k.Id)); err != nil { - return err - } - break - } - } - hooks, err := client.ProjectHooks(id) - if err != nil { - return err - } - link += "&owner=" + repo.Owner + "&name=" + repo.Name - for _, h := range hooks { - if link == h.Url { - if err := client.RemoveProjectHook(id, strconv.Itoa(h.Id)); err != nil { - return err - } - break - } - } - return nil + return client.DeleteDroneService(id) } // ParseHook parses the post-commit hook from the Request body @@ -259,20 +225,65 @@ func (r *Gitlab) Hook(req *http.Request) (*common.Hook, error) { return nil, err } - if len(parsed.After) == 0 || parsed.TotalCommitsCount == 0 { + switch parsed.ObjectKind { + case "merge_request": + if (parsed.ObjectAttributes.State != "reopened" || parsed.ObjectAttributes.State != "opened") && parsed.ObjectAttributes.MergeStatus != "unchecked" { + return nil, nil + } + + return mergeRequest(parsed, req) + case "push": + if len(parsed.After) == 0 || parsed.TotalCommitsCount == 0 { + return nil, nil + } + + return push(parsed, req) + default: return nil, nil } +} - if parsed.ObjectKind == "merge_request" { - // NOTE: in gitlab 8.0, gitlab will get same MR models as github - // https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/981/diffs - return nil, nil +func mergeRequest(parsed *gogitlab.HookPayload, req *http.Request) (*common.Hook, error) { + var hook = new(common.Hook) + + re := regexp.MustCompile(".git$") + + hook.Repo = &common.Repo{} + hook.Repo.Owner = req.FormValue("owner") + hook.Repo.Name = req.FormValue("name") + hook.Repo.FullName = fmt.Sprintf("%s/%s", hook.Repo.Owner, hook.Repo.Name) + hook.Repo.Link = re.ReplaceAllString(parsed.ObjectAttributes.Target.HttpUrl, "$1") + hook.Repo.Clone = parsed.ObjectAttributes.Target.HttpUrl + hook.Repo.Branch = "master" + + hook.Commit = &common.Commit{} + hook.Commit.Message = parsed.ObjectAttributes.LastCommit.Message + hook.Commit.Sha = parsed.ObjectAttributes.LastCommit.Id + hook.Commit.Remote = parsed.ObjectAttributes.Source.HttpUrl + + if parsed.ObjectAttributes.Source.HttpUrl == parsed.ObjectAttributes.Target.HttpUrl { + hook.Commit.Ref = fmt.Sprintf("refs/heads/%s", parsed.ObjectAttributes.SourceBranch) + hook.Commit.Branch = parsed.ObjectAttributes.SourceBranch + hook.Commit.Timestamp = time.Now().UTC().Format("2006-01-02 15:04:05.000000000 +0000 MST") + } else { + hook.Commit.Ref = fmt.Sprintf("refs/merge-requests/%d/head", parsed.ObjectAttributes.IId) + hook.Commit.Branch = parsed.ObjectAttributes.SourceBranch + hook.Commit.Timestamp = time.Now().UTC().Format("2006-01-02 15:04:05.000000000 +0000 MST") } - if len(parsed.After) == 0 { - return nil, nil - } + hook.Commit.Author = &common.Author{} + hook.Commit.Author.Login = parsed.ObjectAttributes.LastCommit.Author.Name + hook.Commit.Author.Email = parsed.ObjectAttributes.LastCommit.Author.Email + hook.PullRequest = &common.PullRequest{} + hook.PullRequest.Number = parsed.ObjectAttributes.IId + hook.PullRequest.Title = parsed.ObjectAttributes.Description + hook.PullRequest.Link = parsed.ObjectAttributes.URL + + return hook, nil +} + +func push(parsed *gogitlab.HookPayload, req *http.Request) (*common.Hook, error) { var cloneUrl = parsed.Repository.GitHttpUrl var hook = new(common.Hook) diff --git a/pkg/server/commits.go b/pkg/server/commits.go index bf9b647e1..cead75400 100644 --- a/pkg/server/commits.go +++ b/pkg/server/commits.go @@ -41,6 +41,40 @@ func GetCommit(c *gin.Context) { } } +// GetPullRequest accepts a requests to retvie a pull request +// from the datastore for the given repository and +// pull request number +// +// GET /api/repos/:owner/:name/pr/:number +// +func GetPullRequest(c *gin.Context) { + store := ToDatastore(c) + repo := ToRepo(c) + + // get the token and verify the hook is authorized + if c.Request.FormValue("access_token") != hash(repo.FullName, repo.Hash) { + c.AbortWithStatus(403) + return + } + + num, err := strconv.Atoi(c.Params.ByName("number")) + if err != nil { + c.Fail(400, err) + return + } + build, err := store.BuildPullRequestNumber(repo, num) + if err != nil { + c.Fail(404, err) + return + } + build.Jobs, err = store.JobList(build) + if err != nil { + c.Fail(404, err) + } else { + c.JSON(200, build) + } +} + // GetCommits accepts a request to retrieve a list // of commits from the datastore for the given repository. // diff --git a/pkg/server/redirect.go b/pkg/server/redirect.go new file mode 100644 index 000000000..59a0608f4 --- /dev/null +++ b/pkg/server/redirect.go @@ -0,0 +1,61 @@ +package server + +import ( + "fmt" + "strconv" + + "github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin" +) + +// RedirectSha accepts a request to retvie a redirect +// to job from the datastore for the given repository +// and commit sha +// +// GET /redirect/:owner/:name/commit/:sha +// +func RedirectSha(c *gin.Context) { + var branch string + + store := ToDatastore(c) + repo := ToRepo(c) + sha := c.Params.ByName("sha") + + branch = c.Request.FormValue("branch") + if branch == "" { + branch = repo.Branch + } + + build, err := store.BuildSha(repo, sha, branch) + if err != nil { + c.Redirect(301, "/") + return + } + + c.Redirect(301, fmt.Sprintf("/%s/%s/%d", repo.Owner, repo.Name, build.ID)) + return +} + +// RedirectSha accepts a request to retvie a redirect +// to job from the datastore for the given repository +// and pull request number +// +// GET /redirect/:owner/:name/pr/:number +// +func RedirectPullRequest(c *gin.Context) { + store := ToDatastore(c) + repo := ToRepo(c) + num, err := strconv.Atoi(c.Params.ByName("number")) + if err != nil { + c.Redirect(301, "/") + return + } + + build, err := store.BuildPullRequestNumber(repo, num) + if err != nil { + c.Redirect(301, "/") + return + } + + c.Redirect(301, fmt.Sprintf("/%s/%s/%d", repo.Owner, repo.Name, build.ID)) + return +} diff --git a/pkg/store/builtin/build.go b/pkg/store/builtin/build.go index e2b139e53..ba14b19e1 100644 --- a/pkg/store/builtin/build.go +++ b/pkg/store/builtin/build.go @@ -25,6 +25,18 @@ func (db *Buildstore) BuildNumber(repo *types.Repo, seq int) (*types.Build, erro return getBuild(db, rebind(stmtBuildSelectBuildNumber), repo.ID, seq) } +// BuildPullRequest gets the specific build for the +// named repository and pull request number +func (db *Buildstore) BuildPullRequestNumber(repo *types.Repo, seq int) (*types.Build, error) { + return getBuild(db, rebind(stmtBuildSelectPullRequestNumber), repo.ID, seq) +} + +// BuildSha gets the specific build for the +// named repository and sha +func (db *Buildstore) BuildSha(repo *types.Repo, sha, branch string) (*types.Build, error) { + return getBuild(db, rebind(stmtBuildSelectSha), repo.ID, sha, branch) +} + // BuildLast gets the last executed build for the // named repository. func (db *Buildstore) BuildLast(repo *types.Repo, branch string) (*types.Build, error) { diff --git a/pkg/store/builtin/build_sql.go b/pkg/store/builtin/build_sql.go index eabc37285..32b7ce98c 100644 --- a/pkg/store/builtin/build_sql.go +++ b/pkg/store/builtin/build_sql.go @@ -596,6 +596,79 @@ WHERE build_repo_id = ? AND build_number = ? ` +const stmtBuildSelectPullRequestNumber = ` +SELECT + build_id +,build_repo_id +,build_number +,build_status +,build_started +,build_finished +,build_commit_sha +,build_commit_ref +,build_commit_link +,build_commit_branch +,build_commit_message +,build_commit_timestamp +,build_commit_remote +,build_commit_author_login +,build_commit_author_email +,build_pull_request_number +,build_pull_request_title +,build_pull_request_link +,build_pull_request_base_sha +,build_pull_request_base_ref +,build_pull_request_base_link +,build_pull_request_base_branch +,build_pull_request_base_message +,build_pull_request_base_timestamp +,build_pull_request_base_remote +,build_pull_request_base_author_login +,build_pull_request_base_author_email +FROM builds +WHERE build_repo_id = ? +AND build_pull_request_number = ? +ORDER BY build_number DESC +LIMIT 1 +` + +const stmtBuildSelectSha = ` +SELECT + build_id +,build_repo_id +,build_number +,build_status +,build_started +,build_finished +,build_commit_sha +,build_commit_ref +,build_commit_link +,build_commit_branch +,build_commit_message +,build_commit_timestamp +,build_commit_remote +,build_commit_author_login +,build_commit_author_email +,build_pull_request_number +,build_pull_request_title +,build_pull_request_link +,build_pull_request_base_sha +,build_pull_request_base_ref +,build_pull_request_base_link +,build_pull_request_base_branch +,build_pull_request_base_message +,build_pull_request_base_timestamp +,build_pull_request_base_remote +,build_pull_request_base_author_login +,build_pull_request_base_author_email +FROM builds +WHERE build_repo_id = ? +AND build_commit_sha = ? +AND build_commit_branch = ? +ORDER BY build_number DESC +LIMIT 1 +` + const stmtBuildSelectCommitBranch = ` SELECT build_id diff --git a/pkg/store/store.go b/pkg/store/store.go index 92470571f..8b2db4ee2 100644 --- a/pkg/store/store.go +++ b/pkg/store/store.go @@ -131,6 +131,14 @@ type Store interface { // named repository and build number BuildNumber(*types.Repo, int) (*types.Build, error) + // BuildPullRequestNumber gets the specified build number for the + // named repository and build number + BuildPullRequestNumber(*types.Repo, int) (*types.Build, error) + + // BuildSha gets the specified build number for the + // named repository and sha + BuildSha(*types.Repo, string, string) (*types.Build, error) + // BuildLast gets the last executed build for the // named repository and branch BuildLast(*types.Repo, string) (*types.Build, error) From 7716c702be587b5b3c526ae72dab3edcc706e7c5 Mon Sep 17 00:00:00 2001 From: Kirill Zaitsev Date: Sat, 22 Aug 2015 18:06:58 +0300 Subject: [PATCH 2/7] FIxes tests --- pkg/remote/builtin/gitlab/gitlab.go | 3 ++- pkg/store/mock/mock.go | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/pkg/remote/builtin/gitlab/gitlab.go b/pkg/remote/builtin/gitlab/gitlab.go index e5c0560dc..fc6713163 100644 --- a/pkg/remote/builtin/gitlab/gitlab.go +++ b/pkg/remote/builtin/gitlab/gitlab.go @@ -227,7 +227,8 @@ func (r *Gitlab) Hook(req *http.Request) (*common.Hook, error) { switch parsed.ObjectKind { case "merge_request": - if (parsed.ObjectAttributes.State != "reopened" || parsed.ObjectAttributes.State != "opened") && parsed.ObjectAttributes.MergeStatus != "unchecked" { + if parsed.ObjectAttributes.State != "reopened" && parsed.ObjectAttributes.MergeStatus != "unchecked" || + parsed.ObjectAttributes.State != "opened" && parsed.ObjectAttributes.MergeStatus != "unchecked" { return nil, nil } diff --git a/pkg/store/mock/mock.go b/pkg/store/mock/mock.go index b199d5066..603d46d5f 100644 --- a/pkg/store/mock/mock.go +++ b/pkg/store/mock/mock.go @@ -229,6 +229,28 @@ func (m *Store) BuildNumber(_a0 *types.Repo, _a1 int) (*types.Build, error) { return r0, r1 } +func (m *Store) BuildPullRequestNumber(_a0 *types.Repo, _a1 int) (*types.Build, error) { + ret := m.Called(_a0, _a1) + + var r0 *types.Build + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Build) + } + r1 := ret.Error(1) + + return r0, r1 +} +func (m *Store) BuildSha(_a0 *types.Repo, _a1, _a2 string) (*types.Build, error) { + ret := m.Called(_a0, _a1, _a2) + + var r0 *types.Build + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Build) + } + r1 := ret.Error(1) + + return r0, r1 +} func (m *Store) BuildLast(_a0 *types.Repo, _a1 string) (*types.Build, error) { ret := m.Called(_a0, _a1) From 00284dd87992227d7438fe1c6b2df465ffdc84ca Mon Sep 17 00:00:00 2001 From: Kirill Zaitsev Date: Sat, 22 Aug 2015 22:06:56 +0300 Subject: [PATCH 3/7] Gitlab testing --- pkg/remote/builtin/gitlab/gitlab_test.go | 149 +++++++++++++++ pkg/remote/builtin/gitlab/testdata/hooks.go | 102 +++++++++++ pkg/remote/builtin/gitlab/testdata/oauth.go | 3 + .../builtin/gitlab/testdata/projects.go | 173 ++++++++++++++++++ .../builtin/gitlab/testdata/testdata.go | 56 ++++++ pkg/remote/builtin/gitlab/testdata/users.go | 24 +++ 6 files changed, 507 insertions(+) create mode 100644 pkg/remote/builtin/gitlab/gitlab_test.go create mode 100644 pkg/remote/builtin/gitlab/testdata/hooks.go create mode 100644 pkg/remote/builtin/gitlab/testdata/oauth.go create mode 100644 pkg/remote/builtin/gitlab/testdata/projects.go create mode 100644 pkg/remote/builtin/gitlab/testdata/testdata.go create mode 100644 pkg/remote/builtin/gitlab/testdata/users.go diff --git a/pkg/remote/builtin/gitlab/gitlab_test.go b/pkg/remote/builtin/gitlab/gitlab_test.go new file mode 100644 index 000000000..832507195 --- /dev/null +++ b/pkg/remote/builtin/gitlab/gitlab_test.go @@ -0,0 +1,149 @@ +package gitlab + +import ( + "bytes" + "fmt" + "net/http" + "testing" + + "github.com/drone/drone/Godeps/_workspace/src/github.com/franela/goblin" + "github.com/drone/drone/pkg/remote/builtin/gitlab/testdata" + "github.com/drone/drone/pkg/types" +) + +func Test_Gitlab(t *testing.T) { + // setup a dummy github server + var server = testdata.NewServer() + defer server.Close() + + var gitlab, err = NewDriver(server.URL + "?client_id=test&client_secret=test") + if err != nil { + panic(err) + } + + var user = types.User{ + Login: "test_user", + Token: "e3b0c44298fc1c149afbf4c8996fb", + } + + var repo = types.Repo{ + Name: "diaspora-client", + Owner: "diaspora", + } + + g := goblin.Goblin(t) + g.Describe("Gitlab Plugin", func() { + // Test repository method + g.Describe("Repo", func() { + g.It("Should return valid repo", func() { + _repo, err := gitlab.Repo(&user, "diaspora", "diaspora-client") + + g.Assert(err == nil).IsTrue() + g.Assert(_repo.Name).Equal("diaspora-client") + g.Assert(_repo.Owner).Equal("diaspora") + g.Assert(_repo.Private).Equal(true) + }) + + g.It("Should return error, when repo not exist", func() { + _, err := gitlab.Repo(&user, "not-existed", "not-existed") + + g.Assert(err != nil).IsTrue() + }) + }) + + // Test permissions method + g.Describe("Perm", func() { + g.It("Should return repo permissions", func() { + perm, err := gitlab.Perm(&user, "diaspora", "diaspora-client") + + fmt.Println(gitlab.(*Gitlab), err) + g.Assert(err == nil).IsTrue() + g.Assert(perm.Admin).Equal(true) + g.Assert(perm.Pull).Equal(true) + g.Assert(perm.Push).Equal(true) + }) + + g.It("Should return error, when repo is not exist", func() { + _, err := gitlab.Perm(&user, "not-existed", "not-existed") + + g.Assert(err != nil).IsTrue() + }) + }) + + // Test activate method + g.Describe("Activate", func() { + g.It("Should be success", func() { + err := gitlab.Activate(&user, &repo, &types.Keypair{}, "http://example.com/api/hook/test/test?access_token=token") + + g.Assert(err == nil).IsTrue() + }) + + g.It("Should be failed, when token not given", func() { + err := gitlab.Activate(&user, &repo, &types.Keypair{}, "http://example.com/api/hook/test/test") + + g.Assert(err != nil).IsTrue() + }) + }) + + // Test deactivate method + g.Describe("Deactivate", func() { + g.It("Should be success", func() { + err := gitlab.Deactivate(&user, &repo, "http://example.com/api/hook/test/test?access_token=token") + + g.Assert(err == nil).IsTrue() + }) + }) + + // Test login method + g.Describe("Login", func() { + g.It("Should return user", func() { + user, err := gitlab.Login("valid_token", "") + + g.Assert(err == nil).IsTrue() + g.Assert(user == nil).IsFalse() + }) + + g.It("Should return error, when token is invalid", func() { + _, err := gitlab.Login("invalid_token", "") + + g.Assert(err != nil).IsTrue() + }) + }) + + // Test hook method + g.Describe("Hook", func() { + g.It("Should parse push hoook", func() { + req, _ := http.NewRequest( + "POST", + "http://example.com/api/hook?owner=diaspora&name=diaspora-client", + bytes.NewReader(testdata.PushHook), + ) + + hook, err := gitlab.Hook(req) + + g.Assert(err == nil).IsTrue() + g.Assert(hook.Repo.Owner).Equal("diaspora") + g.Assert(hook.Repo.Name).Equal("diaspora-client") + + g.Assert(hook.PullRequest == nil).IsTrue() + }) + + g.It("Should parse merge request hook", func() { + req, _ := http.NewRequest( + "POST", + "http://example.com/api/hook?owner=diaspora&name=diaspora-client", + bytes.NewReader(testdata.MergeRequestHook), + ) + + hook, err := gitlab.Hook(req) + + g.Assert(err == nil).IsTrue() + g.Assert(hook.Repo.Owner).Equal("diaspora") + g.Assert(hook.Repo.Name).Equal("diaspora-client") + + g.Assert(hook.PullRequest.Number).Equal(1) + g.Assert(hook.PullRequest.Title).Equal("") + }) + }) + }) +} diff --git a/pkg/remote/builtin/gitlab/testdata/hooks.go b/pkg/remote/builtin/gitlab/testdata/hooks.go new file mode 100644 index 000000000..836d29831 --- /dev/null +++ b/pkg/remote/builtin/gitlab/testdata/hooks.go @@ -0,0 +1,102 @@ +package testdata + +var MergeRequestHook = []byte(` +{ + "object_kind": "merge_request", + "user": { + "name": "Administrator", + "username": "root", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" + }, + "object_attributes": { + "id": 99, + "target_branch": "master", + "source_branch": "ms-viewport", + "source_project_id": 14, + "author_id": 51, + "assignee_id": 6, + "title": "MS-Viewport", + "created_at": "2013-12-03T17:23:34Z", + "updated_at": "2013-12-03T17:23:34Z", + "st_commits": null, + "st_diffs": null, + "milestone_id": null, + "state": "opened", + "merge_status": "unchecked", + "target_project_id": 14, + "iid": 1, + "description": "", + "source": { + "name": "awesome_project", + "ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git", + "http_url": "http://example.com/awesome_space/awesome_project.git", + "visibility_level": 20, + "namespace": "awesome_space" + }, + "target": { + "name": "awesome_project", + "ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git", + "http_url": "http://example.com/awesome_space/awesome_project.git", + "visibility_level": 20, + "namespace": "awesome_space" + }, + "last_commit": { + "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "message": "fixed readme", + "timestamp": "2012-01-03T23:36:29+02:00", + "url": "http://example.com/awesome_space/awesome_project/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "author": { + "name": "GitLab dev user", + "email": "gitlabdev@dv6700.(none)" + } + }, + "url": "http://example.com/diaspora/merge_requests/1", + "action": "open" + } +} +`) + +var PushHook = []byte(` +{ + "object_kind": "push", + "before": "95790bf891e76fee5e1747ab589903a6a1f80f22", + "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "ref": "refs/heads/master", + "user_id": 4, + "user_name": "John Smith", + "user_email": "john@example.com", + "project_id": 15, + "repository": { + "name": "Diaspora", + "url": "git@example.com:mike/diasporadiaspora.git", + "description": "", + "homepage": "http://example.com/mike/diaspora", + "git_http_url":"http://example.com/mike/diaspora.git", + "git_ssh_url":"git@example.com:mike/diaspora.git", + "visibility_level":0 + }, + "commits": [ + { + "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", + "message": "Update Catalan translation to e38cb41.", + "timestamp": "2011-12-12T14:27:31+02:00", + "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", + "author": { + "name": "Jordi Mallach", + "email": "jordi@softcatala.org" + } + }, + { + "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "message": "fixed readme", + "timestamp": "2012-01-03T23:36:29+02:00", + "url": "http://example.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "author": { + "name": "GitLab dev user", + "email": "gitlabdev@dv6700.(none)" + } + } + ], + "total_commits_count": 4 +} +`) diff --git a/pkg/remote/builtin/gitlab/testdata/oauth.go b/pkg/remote/builtin/gitlab/testdata/oauth.go new file mode 100644 index 000000000..95119d70a --- /dev/null +++ b/pkg/remote/builtin/gitlab/testdata/oauth.go @@ -0,0 +1,3 @@ +package testdata + +var accessTokenPayload = []byte(`access_token=sekret&scope=api&token_type=bearer`) diff --git a/pkg/remote/builtin/gitlab/testdata/projects.go b/pkg/remote/builtin/gitlab/testdata/projects.go new file mode 100644 index 000000000..09bec7366 --- /dev/null +++ b/pkg/remote/builtin/gitlab/testdata/projects.go @@ -0,0 +1,173 @@ +package testdata + +// sample repository list +var projectsPayload = []byte(` +[ + { + "id": 4, + "description": null, + "default_branch": "master", + "public": false, + "visibility_level": 0, + "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git", + "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git", + "web_url": "http://example.com/diaspora/diaspora-client", + "owner": { + "id": 3, + "name": "Diaspora", + "created_at": "2013-09-30T13: 46: 02Z" + }, + "name": "Diaspora Client", + "name_with_namespace": "Diaspora / Diaspora Client", + "path": "diaspora-client", + "path_with_namespace": "diaspora/diaspora-client", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "snippets_enabled": false, + "created_at": "2013-09-30T13: 46: 02Z", + "last_activity_at": "2013-09-30T13: 46: 02Z", + "namespace": { + "created_at": "2013-09-30T13: 46: 02Z", + "description": "", + "id": 3, + "name": "Diaspora", + "owner_id": 1, + "path": "diaspora", + "updated_at": "2013-09-30T13: 46: 02Z" + }, + "archived": false + }, + { + "id": 6, + "description": null, + "default_branch": "master", + "public": false, + "visibility_level": 0, + "ssh_url_to_repo": "git@example.com:brightbox/puppet.git", + "http_url_to_repo": "http://example.com/brightbox/puppet.git", + "web_url": "http://example.com/brightbox/puppet", + "owner": { + "id": 4, + "name": "Brightbox", + "created_at": "2013-09-30T13:46:02Z" + }, + "name": "Puppet", + "name_with_namespace": "Brightbox / Puppet", + "path": "puppet", + "path_with_namespace": "brightbox/puppet", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "snippets_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", + "namespace": { + "created_at": "2013-09-30T13:46:02Z", + "description": "", + "id": 4, + "name": "Brightbox", + "owner_id": 1, + "path": "brightbox", + "updated_at": "2013-09-30T13:46:02Z" + }, + "archived": false + } +] +`) + +var project4Paylod = []byte(` +{ + "id": 4, + "description": null, + "default_branch": "master", + "public": false, + "visibility_level": 0, + "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git", + "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git", + "web_url": "http://example.com/diaspora/diaspora-client", + "owner": { + "id": 3, + "name": "Diaspora", + "created_at": "2013-09-30T13: 46: 02Z" + }, + "name": "Diaspora Client", + "name_with_namespace": "Diaspora / Diaspora Client", + "path": "diaspora-client", + "path_with_namespace": "diaspora/diaspora-client", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "snippets_enabled": false, + "created_at": "2013-09-30T13: 46: 02Z", + "last_activity_at": "2013-09-30T13: 46: 02Z", + "namespace": { + "created_at": "2013-09-30T13: 46: 02Z", + "description": "", + "id": 3, + "name": "Diaspora", + "owner_id": 1, + "path": "diaspora", + "updated_at": "2013-09-30T13: 46: 02Z" + }, + "archived": false, + "permissions": { + "project_access": { + "access_level": 10, + "notification_level": 3 + }, + "group_access": { + "access_level": 50, + "notification_level": 3 + } + } +} +`) + +var project6Paylod = []byte(` +{ + "id": 6, + "description": null, + "default_branch": "master", + "public": false, + "visibility_level": 0, + "ssh_url_to_repo": "git@example.com:brightbox/puppet.git", + "http_url_to_repo": "http://example.com/brightbox/puppet.git", + "web_url": "http://example.com/brightbox/puppet", + "owner": { + "id": 4, + "name": "Brightbox", + "created_at": "2013-09-30T13:46:02Z" + }, + "name": "Puppet", + "name_with_namespace": "Brightbox / Puppet", + "path": "puppet", + "path_with_namespace": "brightbox/puppet", + "issues_enabled": true, + "merge_requests_enabled": true, + "wiki_enabled": true, + "snippets_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", + "namespace": { + "created_at": "2013-09-30T13:46:02Z", + "description": "", + "id": 4, + "name": "Brightbox", + "owner_id": 1, + "path": "brightbox", + "updated_at": "2013-09-30T13:46:02Z" + }, + "archived": false, + "permissions": { + "project_access": { + "access_level": 10, + "notification_level": 3 + }, + "group_access": { + "access_level": 50, + "notification_level": 3 + } + } +} +`) diff --git a/pkg/remote/builtin/gitlab/testdata/testdata.go b/pkg/remote/builtin/gitlab/testdata/testdata.go new file mode 100644 index 000000000..de36846e1 --- /dev/null +++ b/pkg/remote/builtin/gitlab/testdata/testdata.go @@ -0,0 +1,56 @@ +package testdata + +import ( + "net/http" + "net/http/httptest" +) + +// setup a mock server for testing purposes. +func NewServer() *httptest.Server { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + + // handle requests and serve mock data + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + //println(r.URL.Path + " " + r.Method) + // evaluate the path to serve a dummy data file + switch r.URL.Path { + case "/api/v3/projects": + w.Write(projectsPayload) + return + case "/api/v3/projects/diaspora/diaspora-client": + w.Write(project4Paylod) + return + case "/api/v3/projects/diaspora/diaspora-client/services/drone-ci": + switch r.Method { + case "PUT": + if r.FormValue("token") == "" { + w.WriteHeader(404) + } else { + w.WriteHeader(201) + } + case "DELETE": + w.WriteHeader(201) + } + + return + case "/oauth/token": + w.Write(accessTokenPayload) + return + case "/api/v3/user": + if r.Header.Get("Authorization") == "Bearer valid_token" { + w.Write(currentUserPayload) + } else { + w.WriteHeader(401) + } + return + } + + // else return a 404 + http.NotFound(w, r) + }) + + // return the server to the client which + // will need to know the base URL path + return server +} diff --git a/pkg/remote/builtin/gitlab/testdata/users.go b/pkg/remote/builtin/gitlab/testdata/users.go new file mode 100644 index 000000000..e7d563697 --- /dev/null +++ b/pkg/remote/builtin/gitlab/testdata/users.go @@ -0,0 +1,24 @@ +package testdata + +var currentUserPayload = []byte(` +{ + "id": 1, + "username": "john_smith", + "email": "john@example.com", + "name": "John Smith", + "private_token": "dd34asd13as", + "state": "active", + "created_at": "2012-05-23T08:00:58Z", + "bio": null, + "skype": "", + "linkedin": "", + "twitter": "", + "website_url": "", + "theme_id": 1, + "color_scheme_id": 2, + "is_admin": false, + "can_create_group": true, + "can_create_project": true, + "projects_limit": 100 +} +`) From ce50e0fd4b5bedfd3e5a7548e268830923f6bfca Mon Sep 17 00:00:00 2001 From: Kirill Zaitsev Date: Sat, 22 Aug 2015 22:55:08 +0300 Subject: [PATCH 4/7] Added push tag event support --- pkg/remote/builtin/gitlab/gitlab.go | 2 +- pkg/remote/builtin/gitlab/gitlab_test.go | 18 +++++++++ pkg/remote/builtin/gitlab/testdata/hooks.go | 44 +++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/pkg/remote/builtin/gitlab/gitlab.go b/pkg/remote/builtin/gitlab/gitlab.go index fc6713163..c6b1bacf3 100644 --- a/pkg/remote/builtin/gitlab/gitlab.go +++ b/pkg/remote/builtin/gitlab/gitlab.go @@ -233,7 +233,7 @@ func (r *Gitlab) Hook(req *http.Request) (*common.Hook, error) { } return mergeRequest(parsed, req) - case "push": + case "tag_push", "push": if len(parsed.After) == 0 || parsed.TotalCommitsCount == 0 { return nil, nil } diff --git a/pkg/remote/builtin/gitlab/gitlab_test.go b/pkg/remote/builtin/gitlab/gitlab_test.go index 832507195..1421f711b 100644 --- a/pkg/remote/builtin/gitlab/gitlab_test.go +++ b/pkg/remote/builtin/gitlab/gitlab_test.go @@ -124,6 +124,24 @@ func Test_Gitlab(t *testing.T) { g.Assert(err == nil).IsTrue() g.Assert(hook.Repo.Owner).Equal("diaspora") g.Assert(hook.Repo.Name).Equal("diaspora-client") + g.Assert(hook.Commit.Ref).Equal("refs/heads/master") + + g.Assert(hook.PullRequest == nil).IsTrue() + }) + + g.It("Should parse tag push hook", func() { + req, _ := http.NewRequest( + "POST", + "http://example.com/api/hook?owner=diaspora&name=diaspora-client", + bytes.NewReader(testdata.TagHook), + ) + + hook, err := gitlab.Hook(req) + + g.Assert(err == nil).IsTrue() + g.Assert(hook.Repo.Owner).Equal("diaspora") + g.Assert(hook.Repo.Name).Equal("diaspora-client") + g.Assert(hook.Commit.Ref).Equal("refs/tags/v1.0.0") g.Assert(hook.PullRequest == nil).IsTrue() }) diff --git a/pkg/remote/builtin/gitlab/testdata/hooks.go b/pkg/remote/builtin/gitlab/testdata/hooks.go index 836d29831..9f4a5cea1 100644 --- a/pkg/remote/builtin/gitlab/testdata/hooks.go +++ b/pkg/remote/builtin/gitlab/testdata/hooks.go @@ -1,5 +1,49 @@ package testdata +var TagHook = []byte(` +{ + "object_kind": "tag_push", + "ref": "refs/tags/v1.0.0", + "before": "0000000000000000000000000000000000000000", + "after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7", + "user_id": 1, + "user_name": "John Smith", + "project_id": 1, + "repository": { + "name": "jsmith", + "url": "ssh://git@example.com/jsmith/example.git", + "description": "", + "homepage": "http://example.com/jsmith/example", + "git_http_url":"http://example.com/jsmith/example.git", + "git_ssh_url":"git@example.com:jsmith/example.git", + "visibility_level":0 + }, + "commits": [ + { + "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", + "message": "Update Catalan translation to e38cb41.", + "timestamp": "2011-12-12T14:27:31+02:00", + "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", + "author": { + "name": "Jordi Mallach", + "email": "jordi@softcatala.org" + } + }, + { + "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "message": "fixed readme", + "timestamp": "2012-01-03T23:36:29+02:00", + "url": "http://example.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "author": { + "name": "GitLab dev user", + "email": "gitlabdev@dv6700.(none)" + } + } + ], + "total_commits_count": 4 +} +`) + var MergeRequestHook = []byte(` { "object_kind": "merge_request", From 6854d1343eb11ce86235260532ae316566fd5d61 Mon Sep 17 00:00:00 2001 From: Kirilll Zaitsev Date: Sun, 30 Aug 2015 01:18:45 +0300 Subject: [PATCH 5/7] Additional fixes --- cmd/drone-server/drone.go | 10 ++-- pkg/remote/builtin/gitlab/gitlab.go | 7 ++- pkg/server/commits.go | 60 ++++++++++++++++++++---- pkg/server/redirect.go | 11 +++-- pkg/store/builtin/build.go | 15 ++++++ pkg/store/builtin/build_sql.go | 73 ----------------------------- 6 files changed, 87 insertions(+), 89 deletions(-) diff --git a/cmd/drone-server/drone.go b/cmd/drone-server/drone.go index 16001ae55..ea5374b9d 100644 --- a/cmd/drone-server/drone.go +++ b/cmd/drone-server/drone.go @@ -178,8 +178,8 @@ func main() { repo.POST("/watch", server.Subscribe) repo.DELETE("/unwatch", server.Unsubscribe) - repo.GET("/builds", server.GetCommits) - repo.GET("/builds/:number", server.GetCommit) + repo.GET("/builds", server.GetBuilds) + repo.GET("/builds/:number", server.GetBuild) repo.POST("/builds/:number", server.RunBuild) repo.DELETE("/builds/:number", server.KillBuild) repo.GET("/logs/:number/:task", server.GetLogs) @@ -191,7 +191,8 @@ func main() { { repoExternal.Use(server.SetRepo()) - repoExternal.GET("/pr/:number", server.GetPullRequest) + repoExternal.GET("/commits/:sha", server.GetCommit) + repoExternal.GET("/pulls/:number", server.GetPullRequest) } } @@ -249,7 +250,8 @@ func main() { redirects.Use(server.SetDatastore(store)) redirects.Use(server.SetRepo()) - redirects.GET("/:owner/:name/commit/:sha", server.RedirectSha) + redirects.GET("/:owner/:name/commits/:sha", server.RedirectSha) + redirects.GET("/:owner/:name/pulls/:number", server.RedirectPullRequest) } r.SetHTMLTemplate(index()) diff --git a/pkg/remote/builtin/gitlab/gitlab.go b/pkg/remote/builtin/gitlab/gitlab.go index c6b1bacf3..7b67a53c9 100644 --- a/pkg/remote/builtin/gitlab/gitlab.go +++ b/pkg/remote/builtin/gitlab/gitlab.go @@ -199,8 +199,13 @@ func (r *Gitlab) Activate(user *common.User, repo *common.Repo, k *common.Keypai droneUrl := fmt.Sprintf("%s://%s", uri.Scheme, uri.Host) droneToken := uri.Query().Get("access_token") + ssl_verify := strconv.FormatBool(!r.SkipVerify) - return client.AddDroneService(id, map[string]string{"token": droneToken, "drone_url": droneUrl}) + return client.AddDroneService(id, map[string]string{ + "token": droneToken, + "drone_url": droneUrl, + "enable_ssl_verification": ssl_verify, + }) } // Deactivate removes a repository by removing all the post-commit hooks diff --git a/pkg/server/commits.go b/pkg/server/commits.go index cead75400..8455f0b54 100644 --- a/pkg/server/commits.go +++ b/pkg/server/commits.go @@ -20,7 +20,7 @@ import ( // // GET /api/repos/:owner/:name/:number // -func GetCommit(c *gin.Context) { +func GetBuild(c *gin.Context) { store := ToDatastore(c) repo := ToRepo(c) num, err := strconv.Atoi(c.Params.ByName("number")) @@ -41,12 +41,30 @@ func GetCommit(c *gin.Context) { } } +// GetCommits accepts a request to retrieve a list +// of commits from the datastore for the given repository. +// +// GET /api/repos/:owner/:name/builds +// +func GetBuilds(c *gin.Context) { + store := ToDatastore(c) + repo := ToRepo(c) + builds, err := store.BuildList(repo, 20, 0) + if err != nil { + c.Fail(404, err) + } else { + c.JSON(200, builds) + } +} + // GetPullRequest accepts a requests to retvie a pull request // from the datastore for the given repository and // pull request number // -// GET /api/repos/:owner/:name/pr/:number +// GET /api/repos/:owner/:name/pulls/:number // +// REASON: It required by GitLab, becuase we get only +// sha and ref name, but drone uses build numbers func GetPullRequest(c *gin.Context) { store := ToDatastore(c) repo := ToRepo(c) @@ -75,20 +93,46 @@ func GetPullRequest(c *gin.Context) { } } -// GetCommits accepts a request to retrieve a list -// of commits from the datastore for the given repository. +// GetCommit accepts a requests to retvie a sha and branch +// from the datastore for the given repository and +// pull request number // -// GET /api/repos/:owner/:name/builds +// GET /api/repos/:owner/:name/commits/:sha // -func GetCommits(c *gin.Context) { +// REASON: It required by GitLab, becuase we get only +// sha and ref name, but drone uses build numbers +func GetCommit(c *gin.Context) { + var branch string + store := ToDatastore(c) repo := ToRepo(c) - builds, err := store.BuildList(repo, 20, 0) + sha := c.Params.ByName("sha") + + // get the token and verify the hook is authorized + if c.Request.FormValue("access_token") != hash(repo.FullName, repo.Hash) { + c.AbortWithStatus(403) + return + } + + branch = c.Request.FormValue("branch") + if branch == "" { + branch = repo.Branch + } + + build, err := store.BuildSha(repo, sha, branch) + if err != nil { + c.Fail(404, err) + return + } + + build.Jobs, err = store.JobList(build) if err != nil { c.Fail(404, err) } else { - c.JSON(200, builds) + c.JSON(200, build) } + + return } // GetLogs accepts a request to retrieve logs from the diff --git a/pkg/server/redirect.go b/pkg/server/redirect.go index 59a0608f4..cb5657438 100644 --- a/pkg/server/redirect.go +++ b/pkg/server/redirect.go @@ -11,8 +11,10 @@ import ( // to job from the datastore for the given repository // and commit sha // -// GET /redirect/:owner/:name/commit/:sha +// GET /redirect/:owner/:name/commits/:sha // +// REASON: It required by GitLab, becuase we get only +// sha and ref name, but drone uses build numbers func RedirectSha(c *gin.Context) { var branch string @@ -35,12 +37,15 @@ func RedirectSha(c *gin.Context) { return } -// RedirectSha accepts a request to retvie a redirect +// RedirectPullRequest accepts a request to retvie a redirect // to job from the datastore for the given repository // and pull request number // -// GET /redirect/:owner/:name/pr/:number +// GET /redirect/:owner/:name/pulls/:number // +// REASON: It required by GitLab, because we get only +// internal merge request id/ref/sha, but drone uses +// build numbers func RedirectPullRequest(c *gin.Context) { store := ToDatastore(c) repo := ToRepo(c) diff --git a/pkg/store/builtin/build.go b/pkg/store/builtin/build.go index ba14b19e1..5e9ace3eb 100644 --- a/pkg/store/builtin/build.go +++ b/pkg/store/builtin/build.go @@ -112,6 +112,21 @@ func (db *Buildstore) KillBuilds() error { return err2 } +const stmtBuildSelectPullRequestNumber = stmtBuildSelectList + ` +WHERE build_repo_id = ? +AND build_pull_request_number = ? +ORDER BY build_number DESC +LIMIT 1 +` + +const stmtBuildSelectSha = stmtBuildSelectList + ` +WHERE build_repo_id = ? +AND build_commit_sha = ? +AND build_commit_branch = ? +ORDER BY build_number DESC +LIMIT 1 +` + // SQL query to retrieve the latest builds across all branches. const buildListQuery = ` SELECT diff --git a/pkg/store/builtin/build_sql.go b/pkg/store/builtin/build_sql.go index 32b7ce98c..eabc37285 100644 --- a/pkg/store/builtin/build_sql.go +++ b/pkg/store/builtin/build_sql.go @@ -596,79 +596,6 @@ WHERE build_repo_id = ? AND build_number = ? ` -const stmtBuildSelectPullRequestNumber = ` -SELECT - build_id -,build_repo_id -,build_number -,build_status -,build_started -,build_finished -,build_commit_sha -,build_commit_ref -,build_commit_link -,build_commit_branch -,build_commit_message -,build_commit_timestamp -,build_commit_remote -,build_commit_author_login -,build_commit_author_email -,build_pull_request_number -,build_pull_request_title -,build_pull_request_link -,build_pull_request_base_sha -,build_pull_request_base_ref -,build_pull_request_base_link -,build_pull_request_base_branch -,build_pull_request_base_message -,build_pull_request_base_timestamp -,build_pull_request_base_remote -,build_pull_request_base_author_login -,build_pull_request_base_author_email -FROM builds -WHERE build_repo_id = ? -AND build_pull_request_number = ? -ORDER BY build_number DESC -LIMIT 1 -` - -const stmtBuildSelectSha = ` -SELECT - build_id -,build_repo_id -,build_number -,build_status -,build_started -,build_finished -,build_commit_sha -,build_commit_ref -,build_commit_link -,build_commit_branch -,build_commit_message -,build_commit_timestamp -,build_commit_remote -,build_commit_author_login -,build_commit_author_email -,build_pull_request_number -,build_pull_request_title -,build_pull_request_link -,build_pull_request_base_sha -,build_pull_request_base_ref -,build_pull_request_base_link -,build_pull_request_base_branch -,build_pull_request_base_message -,build_pull_request_base_timestamp -,build_pull_request_base_remote -,build_pull_request_base_author_login -,build_pull_request_base_author_email -FROM builds -WHERE build_repo_id = ? -AND build_commit_sha = ? -AND build_commit_branch = ? -ORDER BY build_number DESC -LIMIT 1 -` - const stmtBuildSelectCommitBranch = ` SELECT build_id From 9ded21d7922a9be473779b5916e4717df31f294a Mon Sep 17 00:00:00 2001 From: Kirilll Zaitsev Date: Sun, 30 Aug 2015 03:34:05 +0300 Subject: [PATCH 6/7] Some optimizations --- Godeps/Godeps.json | 2 +- .../examples/projects/main.go | 21 +------------- .../go-gitlab-client/hook_payload.go | 3 +- pkg/remote/builtin/gitlab/gitlab.go | 28 +++++-------------- pkg/remote/builtin/gitlab/gitlab_test.go | 2 +- 5 files changed, 12 insertions(+), 44 deletions(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 089d4c11e..b2596e4e4 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -12,7 +12,7 @@ }, { "ImportPath": "github.com/Bugagazavr/go-gitlab-client", - "Rev": "fa361f26087a2ff8fbb267fbe2d82037fc35e51a" + "Rev": "e5999f934dc45c41073fc57998a1224a75ff7d50" }, { "ImportPath": "github.com/BurntSushi/migration", diff --git a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/examples/projects/main.go b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/examples/projects/main.go index 80a3d478d..4eb0b0a6e 100644 --- a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/examples/projects/main.go +++ b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/examples/projects/main.go @@ -43,8 +43,7 @@ func main() { " > -m merge_requests -id PROJECT_ID\n"+ " > -m merge_request_notes -id PROJECT_ID -merge_id MERGE_REQUEST_ID\n"+ " > -m merge_request_comment -id PROJECT_ID -merge_id MERGE_REQUEST_ID -comment COMMENT_BODY\n"+ - " > -m team -id PROJECT_ID\n"+ - " > -m add_drone -id PROJECT_ID\n -token DRONE_TOKEN -url DRONE_URL") + " > -m team -id PROJECT_ID") var id string flag.StringVar(&id, "id", "", "Specify repository id") @@ -55,12 +54,6 @@ func main() { var comment string flag.StringVar(&comment, "comment", "", "The body of the new comment") - var drone_token string - flag.StringVar(&drone_token, "drone_token", "", "Drone service token") - - var drone_url string - flag.StringVar(&drone_url, "drone_url", "", "Drone service url") - flag.Usage = func() { fmt.Printf("Usage:\n") flag.PrintDefaults() @@ -249,17 +242,5 @@ func main() { for _, member := range members { fmt.Printf("> [%d] %s (%s) since %s\n", member.Id, member.Username, member.Name, member.CreatedAt) } - case "add_drone": - fmt.Println("Adding drone service to project") - - if id == "" || drone_token == "" || drone_url == "" { - flag.Usage() - return - } - - if err := gitlab.AddDroneService(id, map[string]string{"token": drone_token, "drone_url": drone_url}); err != nil { - fmt.Println(err) - return - } } } diff --git a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/hook_payload.go b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/hook_payload.go index e4c33c0a2..b778e1fb9 100644 --- a/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/hook_payload.go +++ b/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client/hook_payload.go @@ -27,7 +27,7 @@ type HookObjAttr struct { StDiffs string `json:"st_diffs,omitempty"` MergeStatus string `json:"merge_status,omitempty"` TargetProjectId int `json:"target_project_id,omitempty"` - URL string `json:"url,omitempty"` + Url string `json:"url,omiyempty"` Source *hProject `json:"source,omitempty"` Target *hProject `json:"target,omitempty"` LastCommit *hCommit `json:"last_commit,omitempty"` @@ -38,6 +38,7 @@ type hProject struct { SshUrl string `json:"ssh_url"` HttpUrl string `json:"http_url"` VisibilityLevel int `json:"visibility_level"` + WebUrl string `json:"web_url"` Namespace string `json:"namespace"` } diff --git a/pkg/remote/builtin/gitlab/gitlab.go b/pkg/remote/builtin/gitlab/gitlab.go index 7b67a53c9..74c644851 100644 --- a/pkg/remote/builtin/gitlab/gitlab.go +++ b/pkg/remote/builtin/gitlab/gitlab.go @@ -6,10 +6,8 @@ import ( "io/ioutil" "net/http" "net/url" - "regexp" "strconv" "strings" - "time" "github.com/drone/drone/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client" "github.com/drone/drone/Godeps/_workspace/src/github.com/hashicorp/golang-lru" @@ -232,17 +230,8 @@ func (r *Gitlab) Hook(req *http.Request) (*common.Hook, error) { switch parsed.ObjectKind { case "merge_request": - if parsed.ObjectAttributes.State != "reopened" && parsed.ObjectAttributes.MergeStatus != "unchecked" || - parsed.ObjectAttributes.State != "opened" && parsed.ObjectAttributes.MergeStatus != "unchecked" { - return nil, nil - } - return mergeRequest(parsed, req) case "tag_push", "push": - if len(parsed.After) == 0 || parsed.TotalCommitsCount == 0 { - return nil, nil - } - return push(parsed, req) default: return nil, nil @@ -252,13 +241,11 @@ func (r *Gitlab) Hook(req *http.Request) (*common.Hook, error) { func mergeRequest(parsed *gogitlab.HookPayload, req *http.Request) (*common.Hook, error) { var hook = new(common.Hook) - re := regexp.MustCompile(".git$") - hook.Repo = &common.Repo{} hook.Repo.Owner = req.FormValue("owner") hook.Repo.Name = req.FormValue("name") hook.Repo.FullName = fmt.Sprintf("%s/%s", hook.Repo.Owner, hook.Repo.Name) - hook.Repo.Link = re.ReplaceAllString(parsed.ObjectAttributes.Target.HttpUrl, "$1") + hook.Repo.Link = parsed.ObjectAttributes.Target.WebUrl hook.Repo.Clone = parsed.ObjectAttributes.Target.HttpUrl hook.Repo.Branch = "master" @@ -267,24 +254,23 @@ func mergeRequest(parsed *gogitlab.HookPayload, req *http.Request) (*common.Hook hook.Commit.Sha = parsed.ObjectAttributes.LastCommit.Id hook.Commit.Remote = parsed.ObjectAttributes.Source.HttpUrl - if parsed.ObjectAttributes.Source.HttpUrl == parsed.ObjectAttributes.Target.HttpUrl { + if parsed.ObjectAttributes.SourceProjectId == parsed.ObjectAttributes.TargetProjectId { hook.Commit.Ref = fmt.Sprintf("refs/heads/%s", parsed.ObjectAttributes.SourceBranch) - hook.Commit.Branch = parsed.ObjectAttributes.SourceBranch - hook.Commit.Timestamp = time.Now().UTC().Format("2006-01-02 15:04:05.000000000 +0000 MST") } else { hook.Commit.Ref = fmt.Sprintf("refs/merge-requests/%d/head", parsed.ObjectAttributes.IId) - hook.Commit.Branch = parsed.ObjectAttributes.SourceBranch - hook.Commit.Timestamp = time.Now().UTC().Format("2006-01-02 15:04:05.000000000 +0000 MST") } + hook.Commit.Branch = parsed.ObjectAttributes.SourceBranch + hook.Commit.Timestamp = parsed.ObjectAttributes.LastCommit.Timestamp + hook.Commit.Author = &common.Author{} hook.Commit.Author.Login = parsed.ObjectAttributes.LastCommit.Author.Name hook.Commit.Author.Email = parsed.ObjectAttributes.LastCommit.Author.Email hook.PullRequest = &common.PullRequest{} hook.PullRequest.Number = parsed.ObjectAttributes.IId - hook.PullRequest.Title = parsed.ObjectAttributes.Description - hook.PullRequest.Link = parsed.ObjectAttributes.URL + hook.PullRequest.Title = parsed.ObjectAttributes.Title + hook.PullRequest.Link = parsed.ObjectAttributes.Url return hook, nil } diff --git a/pkg/remote/builtin/gitlab/gitlab_test.go b/pkg/remote/builtin/gitlab/gitlab_test.go index 1421f711b..e694ab930 100644 --- a/pkg/remote/builtin/gitlab/gitlab_test.go +++ b/pkg/remote/builtin/gitlab/gitlab_test.go @@ -160,7 +160,7 @@ func Test_Gitlab(t *testing.T) { g.Assert(hook.Repo.Name).Equal("diaspora-client") g.Assert(hook.PullRequest.Number).Equal(1) - g.Assert(hook.PullRequest.Title).Equal("") + g.Assert(hook.PullRequest.Title).Equal("MS-Viewport") }) }) }) From 654dd31cbd377c4e67862fea0273100c14b512f7 Mon Sep 17 00:00:00 2001 From: Kirilll Zaitsev Date: Wed, 2 Sep 2015 05:19:11 +0300 Subject: [PATCH 7/7] Move gitlab routes to gitlab group --- cmd/drone-server/drone.go | 25 +++---- pkg/server/commits.go | 78 --------------------- pkg/server/gitlab.go | 144 ++++++++++++++++++++++++++++++++++++++ pkg/server/redirect.go | 66 ----------------- 4 files changed, 155 insertions(+), 158 deletions(-) create mode 100644 pkg/server/gitlab.go delete mode 100644 pkg/server/redirect.go diff --git a/cmd/drone-server/drone.go b/cmd/drone-server/drone.go index ea5374b9d..3e5977926 100644 --- a/cmd/drone-server/drone.go +++ b/cmd/drone-server/drone.go @@ -185,15 +185,6 @@ func main() { repo.GET("/logs/:number/:task", server.GetLogs) // repo.POST("/status/:number", server.PostBuildStatus) } - - // Routes for external services - repoExternal := repos.Group("") - { - repoExternal.Use(server.SetRepo()) - - repoExternal.GET("/commits/:sha", server.GetCommit) - repoExternal.GET("/pulls/:number", server.GetPullRequest) - } } badges := api.Group("/badges/:owner/:name") @@ -245,13 +236,19 @@ func main() { auth.POST("", server.GetLogin) } - redirects := r.Group("/redirect") + gitlab := r.Group("/gitlab/:owner/:name") { - redirects.Use(server.SetDatastore(store)) - redirects.Use(server.SetRepo()) + gitlab.Use(server.SetDatastore(store)) + gitlab.Use(server.SetRepo()) - redirects.GET("/:owner/:name/commits/:sha", server.RedirectSha) - redirects.GET("/:owner/:name/pulls/:number", server.RedirectPullRequest) + gitlab.GET("/commits/:sha", server.GetCommit) + gitlab.GET("/pulls/:number", server.GetPullRequest) + + redirects := gitlab.Group("/redirect") + { + redirects.GET("/commits/:sha", server.RedirectSha) + redirects.GET("/pulls/:number", server.RedirectPullRequest) + } } r.SetHTMLTemplate(index()) diff --git a/pkg/server/commits.go b/pkg/server/commits.go index 8455f0b54..3c75faad5 100644 --- a/pkg/server/commits.go +++ b/pkg/server/commits.go @@ -57,84 +57,6 @@ func GetBuilds(c *gin.Context) { } } -// GetPullRequest accepts a requests to retvie a pull request -// from the datastore for the given repository and -// pull request number -// -// GET /api/repos/:owner/:name/pulls/:number -// -// REASON: It required by GitLab, becuase we get only -// sha and ref name, but drone uses build numbers -func GetPullRequest(c *gin.Context) { - store := ToDatastore(c) - repo := ToRepo(c) - - // get the token and verify the hook is authorized - if c.Request.FormValue("access_token") != hash(repo.FullName, repo.Hash) { - c.AbortWithStatus(403) - return - } - - num, err := strconv.Atoi(c.Params.ByName("number")) - if err != nil { - c.Fail(400, err) - return - } - build, err := store.BuildPullRequestNumber(repo, num) - if err != nil { - c.Fail(404, err) - return - } - build.Jobs, err = store.JobList(build) - if err != nil { - c.Fail(404, err) - } else { - c.JSON(200, build) - } -} - -// GetCommit accepts a requests to retvie a sha and branch -// from the datastore for the given repository and -// pull request number -// -// GET /api/repos/:owner/:name/commits/:sha -// -// REASON: It required by GitLab, becuase we get only -// sha and ref name, but drone uses build numbers -func GetCommit(c *gin.Context) { - var branch string - - store := ToDatastore(c) - repo := ToRepo(c) - sha := c.Params.ByName("sha") - - // get the token and verify the hook is authorized - if c.Request.FormValue("access_token") != hash(repo.FullName, repo.Hash) { - c.AbortWithStatus(403) - return - } - - branch = c.Request.FormValue("branch") - if branch == "" { - branch = repo.Branch - } - - build, err := store.BuildSha(repo, sha, branch) - if err != nil { - c.Fail(404, err) - return - } - - build.Jobs, err = store.JobList(build) - if err != nil { - c.Fail(404, err) - } else { - c.JSON(200, build) - } - - return -} - // GetLogs accepts a request to retrieve logs from the // datastore for the given repository, build and task // number. diff --git a/pkg/server/gitlab.go b/pkg/server/gitlab.go new file mode 100644 index 000000000..69a4caf1f --- /dev/null +++ b/pkg/server/gitlab.go @@ -0,0 +1,144 @@ +package server + +import ( + "fmt" + "strconv" + + "github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin" +) + +// RedirectSha accepts a request to retvie a redirect +// to job from the datastore for the given repository +// and commit sha +// +// GET /gitlab/:owner/:name/redirect/commits/:sha +// +// REASON: It required by GitLab, becuase we get only +// sha and ref name, but drone uses build numbers +func RedirectSha(c *gin.Context) { + var branch string + + store := ToDatastore(c) + repo := ToRepo(c) + sha := c.Params.ByName("sha") + + branch = c.Request.FormValue("branch") + if branch == "" { + branch = repo.Branch + } + + build, err := store.BuildSha(repo, sha, branch) + if err != nil { + c.Redirect(301, "/") + return + } + + c.Redirect(301, fmt.Sprintf("/%s/%s/%d", repo.Owner, repo.Name, build.ID)) + return +} + +// RedirectPullRequest accepts a request to retvie a redirect +// to job from the datastore for the given repository +// and pull request number +// +// GET /gitlab/:owner/:name/redirect/pulls/:number +// +// REASON: It required by GitLab, because we get only +// internal merge request id/ref/sha, but drone uses +// build numbers +func RedirectPullRequest(c *gin.Context) { + store := ToDatastore(c) + repo := ToRepo(c) + num, err := strconv.Atoi(c.Params.ByName("number")) + if err != nil { + c.Redirect(301, "/") + return + } + + build, err := store.BuildPullRequestNumber(repo, num) + if err != nil { + c.Redirect(301, "/") + return + } + + c.Redirect(301, fmt.Sprintf("/%s/%s/%d", repo.Owner, repo.Name, build.ID)) + return +} + +// GetPullRequest accepts a requests to retvie a pull request +// from the datastore for the given repository and +// pull request number +// +// GET /gitlab/:owner/:name/pulls/:number +// +// REASON: It required by GitLab, becuase we get only +// sha and ref name, but drone uses build numbers +func GetPullRequest(c *gin.Context) { + store := ToDatastore(c) + repo := ToRepo(c) + + // get the token and verify the hook is authorized + if c.Request.FormValue("access_token") != hash(repo.FullName, repo.Hash) { + c.AbortWithStatus(403) + return + } + + num, err := strconv.Atoi(c.Params.ByName("number")) + if err != nil { + c.Fail(400, err) + return + } + build, err := store.BuildPullRequestNumber(repo, num) + if err != nil { + c.Fail(404, err) + return + } + build.Jobs, err = store.JobList(build) + if err != nil { + c.Fail(404, err) + } else { + c.JSON(200, build) + } +} + +// GetCommit accepts a requests to retvie a sha and branch +// from the datastore for the given repository and +// pull request number +// +// GET /gitlab/:owner/:name/commits/:sha +// +// REASON: It required by GitLab, becuase we get only +// sha and ref name, but drone uses build numbers +func GetCommit(c *gin.Context) { + var branch string + + store := ToDatastore(c) + repo := ToRepo(c) + sha := c.Params.ByName("sha") + + // get the token and verify the hook is authorized + if c.Request.FormValue("access_token") != hash(repo.FullName, repo.Hash) { + c.AbortWithStatus(403) + return + } + + branch = c.Request.FormValue("branch") + if branch == "" { + branch = repo.Branch + } + + build, err := store.BuildSha(repo, sha, branch) + if err != nil { + c.Fail(404, err) + return + } + + build.Jobs, err = store.JobList(build) + if err != nil { + c.Fail(404, err) + } else { + c.JSON(200, build) + } + + return +} diff --git a/pkg/server/redirect.go b/pkg/server/redirect.go deleted file mode 100644 index cb5657438..000000000 --- a/pkg/server/redirect.go +++ /dev/null @@ -1,66 +0,0 @@ -package server - -import ( - "fmt" - "strconv" - - "github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin" -) - -// RedirectSha accepts a request to retvie a redirect -// to job from the datastore for the given repository -// and commit sha -// -// GET /redirect/:owner/:name/commits/:sha -// -// REASON: It required by GitLab, becuase we get only -// sha and ref name, but drone uses build numbers -func RedirectSha(c *gin.Context) { - var branch string - - store := ToDatastore(c) - repo := ToRepo(c) - sha := c.Params.ByName("sha") - - branch = c.Request.FormValue("branch") - if branch == "" { - branch = repo.Branch - } - - build, err := store.BuildSha(repo, sha, branch) - if err != nil { - c.Redirect(301, "/") - return - } - - c.Redirect(301, fmt.Sprintf("/%s/%s/%d", repo.Owner, repo.Name, build.ID)) - return -} - -// RedirectPullRequest accepts a request to retvie a redirect -// to job from the datastore for the given repository -// and pull request number -// -// GET /redirect/:owner/:name/pulls/:number -// -// REASON: It required by GitLab, because we get only -// internal merge request id/ref/sha, but drone uses -// build numbers -func RedirectPullRequest(c *gin.Context) { - store := ToDatastore(c) - repo := ToRepo(c) - num, err := strconv.Atoi(c.Params.ByName("number")) - if err != nil { - c.Redirect(301, "/") - return - } - - build, err := store.BuildPullRequestNumber(repo, num) - if err != nil { - c.Redirect(301, "/") - return - } - - c.Redirect(301, fmt.Sprintf("/%s/%s/%d", repo.Owner, repo.Name, build.ID)) - return -}