Merge remote-tracking branch 'upstream/0.4.0' into 0.4.0

This commit is contained in:
Brad Rydzewski 2015-09-02 16:14:58 -07:00
commit b1150ce6fb
42 changed files with 1421 additions and 186 deletions

View file

@ -9,9 +9,12 @@ script:
- go get golang.org/x/tools/cmd/cover
- go get golang.org/x/tools/cmd/vet
- go get -u github.com/jteeuwen/go-bindata/...
- make bindata deps
- go run make.go bindata
- go run make.go build
- go run make.go vet test
- go run make.go vet
- go run make.go test
- make dist
notify:

1
.gitignore vendored
View file

@ -14,6 +14,7 @@ drone.sublime-workspace
*.rice-box.go
*.db
*.txt
*.min.css
*.min.js
*_bindata.go
*.toml

2
Godeps/Godeps.json generated
View file

@ -12,7 +12,7 @@
},
{
"ImportPath": "github.com/Bugagazavr/go-gitlab-client",
"Rev": "912567bb7e65212c910733b3bfa178b11049a70e"
"Rev": "e5999f934dc45c41073fc57998a1224a75ff7d50"
},
{
"ImportPath": "github.com/BurntSushi/migration",

View file

@ -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)

View file

@ -37,15 +37,23 @@ 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")
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")
flag.Usage = func() {
fmt.Printf("Usage:\n")
flag.PrintDefaults()
@ -151,11 +159,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…")

View file

@ -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)
}
}

View file

@ -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 {

View file

@ -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,omiyempty"`
Source *hProject `json:"source,omitempty"`
Target *hProject `json:"target,omitempty"`
LastCommit *hCommit `json:"last_commit,omitempty"`
@ -37,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"`
}

View file

@ -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()
}

View file

@ -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
}
}

View file

@ -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")

View file

@ -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
*/

View file

@ -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()
}

View file

@ -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
}

View file

@ -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"
}
]

View file

@ -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"
}
]

View file

@ -3,16 +3,6 @@
SHA := $(shell git rev-parse --short HEAD)
VERSION := 0.4.0-alpha
all: concat bindata build
deps:
go get github.com/jteeuwen/go-bindata/...
test:
go vet github.com/drone/drone/pkg/...
go vet github.com/drone/drone/cmd/...
go test -cover -short github.com/drone/drone/pkg/...
# Execute the database test suite against mysql 5.5
#
# You can launch a mysql container locally for testing:
@ -22,38 +12,14 @@ test_mysql:
TEST_DRIVER="mysql" TEST_DATASOURCE="root@tcp(127.0.0.1:3306)/test" go test -short github.com/drone/drone/pkg/store/builtin
mysql -P 3306 --protocol=tcp -u root -e 'drop database test;'
build:
go build -o bin/drone -ldflags "-X main.revision=$(SHA) -X main.version=$(VERSION).$(SHA)" github.com/drone/drone/cmd/drone-server
go build -o bin/drone-agent -ldflags "-X main.revision=$(SHA) -X main.version=$(VERSION).$(SHA)" github.com/drone/drone/cmd/drone-agent
run:
bin/drone-server --debug
clean:
find . -name "*.out" -delete
find . -name "*_bindata.go" -delete
rm -f bin/drone*
concat:
cat cmd/drone-server/static/scripts/drone.js \
cmd/drone-server/static/scripts/services/*.js \
cmd/drone-server/static/scripts/filters/*.js \
cmd/drone-server/static/scripts/controllers/*.js \
cmd/drone-server/static/scripts/term.js > cmd/drone-server/static/scripts/drone.min.js
bin/drone --debug
# installs the drone binaries into bin
install:
install -t /usr/local/bin bin/drone
install -t /usr/local/bin bin/drone-agent
# embeds all the static files directly
# into the drone binary file
bindata:
$$GOPATH/bin/go-bindata -o="cmd/drone-server/drone_bindata.go" cmd/drone-server/static/...
bindata_debug:
$$GOPATH/bin/go-bindata --debug -o="cmd/drone-server/drone_bindata.go" cmd/drone-server/static/...
docker:
docker build --file=cmd/drone-build/Dockerfile.alpine --rm=true -t drone/drone-build .

View file

@ -72,7 +72,7 @@ var conf = struct {
func main() {
flag.StringVar(&conf.docker.host, "docker-host", "unix:///var/run/docker/docker.sock", "")
flag.StringVar(&conf.docker.host, "docker-host", "unix:///var/run/docker.sock", "")
flag.StringVar(&conf.docker.cert, "docker-cert", "", "")
flag.StringVar(&conf.docker.key, "docker-key", "", "")
flag.StringVar(&conf.docker.ca, "docker-ca", "", "")
@ -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)
@ -236,6 +236,21 @@ func main() {
auth.POST("", server.GetLogin)
}
gitlab := r.Group("/gitlab/:owner/:name")
{
gitlab.Use(server.SetDatastore(store))
gitlab.Use(server.SetRepo())
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())
r.NoRoute(func(c *gin.Context) {
c.HTML(200, "index.html", nil)

View file

@ -6,10 +6,10 @@
$scope.user = payload.data;
});
$scope.number = $stateParams.number || undefined;
$scope.owner = $stateParams.owner || undefined;
$scope.name = $stateParams.name || undefined;
$scope.full_name = $scope.owner + '/' + $scope.name;
$scope.number = $stateParams.number || undefined;
$scope.owner = $stateParams.owner || undefined;
$scope.name = $stateParams.name || undefined;
$scope.full_name = $scope.owner + '/' + $scope.name;
}
function UserLoginCtrl($scope, $window) {
@ -18,6 +18,18 @@
$scope.error = $window.location.hash.substr(7);
}
function UserLogoutCtrl($scope, $window, $state) {
// Remove login information from the local
// storage and redirect to login page
if (localStorage.hasOwnProperty("access_token")) {
localStorage.removeItem("access_token");
}
$state.go("login", {}, {
location: "replace"
});
}
/**
* UserCtrl is responsible for managing user settings.
*/
@ -115,6 +127,7 @@
.module('drone')
.controller('UserHeaderCtrl', UserHeaderCtrl)
.controller('UserLoginCtrl', UserLoginCtrl)
.controller('UserLogoutCtrl', UserLogoutCtrl)
.controller('UserCtrl', UserCtrl)
.controller('UsersCtrl', UsersCtrl);
})();

View file

@ -89,6 +89,17 @@
},
title: 'Login'
})
.state('logout', {
url: '/logout',
views: {
'layout': {
templateUrl: '/static/scripts/views/login.html',
controller: 'UserLogoutCtrl',
resolve: resolveUser
}
},
title: 'Logout'
})
.state('app.profile', {
url: '/profile',
views: {

View file

@ -15,6 +15,7 @@
<ul>
<li><a href="/profile">Profile</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/logout">Logout</a></li>
</ul>
</div>
</li>
@ -71,4 +72,4 @@ header li {
.dropdown li > a:hover {
background:#F5F7F9;
}
</style>
</style>

View file

@ -31,6 +31,7 @@ This section lists all connection options used in the connection string format.
* `open=false` allows users to self-register. Defaults to false for security reasons.
* `orgs=drone,docker` restricts access to these GitLab organizations. **Optional**
* `skip_verify=false` skip ca verification if self-signed certificate. Defaults to false for security reasons.
* `clone_mode=token` a strategy for clone authorization, by default use repo token, but can be changed to `oauth` ( This is not secure, because your user token, with full access to your gitlab account will be written to .netrc, and it can be read by all who have access to project builds )
## Gitlab registration

View file

@ -33,6 +33,8 @@ Here are some of the Docker options, explained:
Drone uses environment variables for runtime settings and configuration, such as GitHub, GitLab, plugins and more. These settings can be provided to Docker using an `--env-file` as seen above.
Once you have your drone container created, then you can start/stop/restart it in below ways.
## Starting, Stopping, Logs
Commands to start, stop and restart Drone:
@ -60,12 +62,8 @@ start on filesystem and started docker
stop on runlevel [!2345]
respawn
pre-start script
/usr/bin/docker rm -f drone
end script
script
/usr/bin/docker run -a drone
/usr/bin/docker start -a drone
end script
```

View file

@ -18,7 +18,7 @@ SERVER_ADDR=":80"
## Server SSL
Drone uses the `ListAndServerTLS` function in the Go standard library to accept `https` connections. If you experience any issues configuring `https` please contact us on [gitter](https://gitter.im/drone/drone). Please do not log an issue saying `https` is broken in Drone (it isn't).
Drone uses the `ListenAndServeTLS` function in the Go standard library to accept `https` connections. If you experience any issues configuring `https` please contact us on [gitter](https://gitter.im/drone/drone). Please do not log an issue saying `https` is broken in Drone (it isn't).
This example accepts `HTTPS` connections:

121
make.go
View file

@ -9,6 +9,7 @@ package main
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
@ -34,6 +35,7 @@ var steps = map[string]step{
"build": build,
"test": test,
"image": image,
"clean": clean,
}
func main() {
@ -62,14 +64,84 @@ func embed() error {
// scripts step concatinates all javascript files.
func scripts() error {
// concatinate scripts
files := []string{
"cmd/drone-server/static/scripts/term.js",
"cmd/drone-server/static/scripts/drone.js",
"cmd/drone-server/static/scripts/controllers/repos.js",
"cmd/drone-server/static/scripts/controllers/builds.js",
"cmd/drone-server/static/scripts/controllers/users.js",
"cmd/drone-server/static/scripts/services/repos.js",
"cmd/drone-server/static/scripts/services/builds.js",
"cmd/drone-server/static/scripts/services/users.js",
"cmd/drone-server/static/scripts/services/logs.js",
"cmd/drone-server/static/scripts/services/tokens.js",
"cmd/drone-server/static/scripts/services/feed.js",
"cmd/drone-server/static/scripts/filters/filter.js",
"cmd/drone-server/static/scripts/filters/gravatar.js",
"cmd/drone-server/static/scripts/filters/time.js",
}
f, err := os.OpenFile(
"cmd/drone-server/static/scripts/drone.min.js",
os.O_CREATE|os.O_RDWR|os.O_TRUNC,
0660)
defer f.Close()
if err != nil {
fmt.Println("Failed to open output file")
return err
}
for _, input := range files {
content, err := ioutil.ReadFile(input)
if err != nil {
return err
}
f.Write(content)
}
return nil
}
// styles step concatinates the css files.
// styles step concatinates the stylesheet files.
func styles() error {
// concatinate styles
// inject css variables?
files := []string{
"cmd/drone-server/static/styles/reset.css",
"cmd/drone-server/static/styles/fonts.css",
"cmd/drone-server/static/styles/alert.css",
"cmd/drone-server/static/styles/blankslate.css",
"cmd/drone-server/static/styles/list.css",
"cmd/drone-server/static/styles/label.css",
"cmd/drone-server/static/styles/range.css",
"cmd/drone-server/static/styles/switch.css",
"cmd/drone-server/static/styles/main.css",
}
f, err := os.OpenFile(
"cmd/drone-server/static/styles/drone.min.css",
os.O_CREATE|os.O_RDWR|os.O_TRUNC,
0660)
defer f.Close()
if err != nil {
fmt.Println("Failed to open output file")
return err
}
for _, input := range files {
content, err := ioutil.ReadFile(input)
if err != nil {
return err
}
f.Write(content)
}
return nil
}
@ -170,6 +242,47 @@ func image() error {
return nil
}
func clean() error {
err := filepath.Walk(".", func(path string, f os.FileInfo, err error) error {
suffixes := []string{
".out",
"_bindata.go",
}
for _, suffix := range suffixes {
if strings.HasSuffix(path, suffix) {
if err := os.Remove(path); err != nil {
return err
}
}
}
return nil
})
if err != nil {
return err
}
files := []string{
"bin/drone",
"bin/drone-agent",
"bin/drone-build",
}
for _, file := range files {
if _, err := os.Stat(file); err != nil {
continue
}
if err := os.Remove(file); err != nil {
return err
}
}
return nil
}
// trace is a helper fucntion that writes a command
// to stdout similar to bash +x
func trace(args []string) {

12
pkg/hash/hash.go Normal file
View file

@ -0,0 +1,12 @@
package hash
import (
"crypto/sha256"
"encoding/hex"
)
func New(text, salt string) string {
hasher := sha256.New()
hasher.Write([]byte(text + salt))
return hex.EncodeToString(hasher.Sum(nil))
}

View file

@ -170,7 +170,7 @@ func (g *GitHub) Script(u *common.User, r *common.Repo, b *common.Build) ([]byte
// Netrc returns a .netrc file that can be used to clone
// private repositories from a remote system.
func (g *GitHub) Netrc(u *common.User) (*common.Netrc, error) {
func (g *GitHub) Netrc(u *common.User, r *common.Repo) (*common.Netrc, error) {
url_, err := url.Parse(g.URL)
if err != nil {
return nil, err

View file

@ -11,6 +11,7 @@ import (
"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"
"github.com/drone/drone/pkg/hash"
"github.com/drone/drone/pkg/oauth2"
"github.com/drone/drone/pkg/remote"
common "github.com/drone/drone/pkg/types"
@ -26,6 +27,7 @@ type Gitlab struct {
Client string
Secret string
AllowedOrgs []string
CloneMode string
Open bool
PrivateMode bool
SkipVerify bool
@ -54,6 +56,13 @@ func NewDriver(config string) (remote.Remote, error) {
gitlab.SkipVerify, _ = strconv.ParseBool(params.Get("skip_verify"))
gitlab.Open, _ = strconv.ParseBool(params.Get("open"))
switch params.Get("clone_mode") {
case "oauth":
gitlab.CloneMode = "oauth"
default:
gitlab.CloneMode = "token"
}
// this is a temp workaround
gitlab.Search, _ = strconv.ParseBool(params.Get("search"))
@ -64,8 +73,8 @@ func NewDriver(config string) (remote.Remote, error) {
return &gitlab, err
}
func (r *Gitlab) Login(token, secret string) (*common.User, error) {
client := NewClient(r.URL, token, r.SkipVerify)
func (g *Gitlab) Login(token, secret string) (*common.User, error) {
client := NewClient(g.URL, token, g.SkipVerify)
var login, err = client.CurrentUser()
if err != nil {
return nil, err
@ -79,20 +88,20 @@ func (r *Gitlab) Login(token, secret string) (*common.User, error) {
if strings.HasPrefix(login.AvatarUrl, "http") {
user.Avatar = login.AvatarUrl
} else {
user.Avatar = r.URL + "/" + login.AvatarUrl
user.Avatar = g.URL + "/" + login.AvatarUrl
}
return &user, nil
}
// Orgs fetches the organizations for the given user.
func (r *Gitlab) Orgs(u *common.User) ([]string, error) {
func (g *Gitlab) Orgs(u *common.User) ([]string, error) {
return nil, nil
}
// Repo fetches the named repository from the remote system.
func (r *Gitlab) Repo(u *common.User, owner, name string) (*common.Repo, error) {
client := NewClient(r.URL, u.Token, r.SkipVerify)
id, err := GetProjectId(r, client, owner, name)
func (g *Gitlab) Repo(u *common.User, owner, name string) (*common.Repo, error) {
client := NewClient(g.URL, u.Token, g.SkipVerify)
id, err := GetProjectId(g, client, owner, name)
if err != nil {
return nil, err
}
@ -113,7 +122,7 @@ func (r *Gitlab) Repo(u *common.User, owner, name string) (*common.Repo, error)
repo.Branch = repo_.DefaultBranch
}
if r.PrivateMode {
if g.PrivateMode {
repo.Private = true
} else {
repo.Private = !repo_.Public
@ -123,15 +132,15 @@ func (r *Gitlab) Repo(u *common.User, owner, name string) (*common.Repo, error)
}
// Perm fetches the named repository from the remote system.
func (r *Gitlab) Perm(u *common.User, owner, name string) (*common.Perm, error) {
func (g *Gitlab) Perm(u *common.User, owner, name string) (*common.Perm, error) {
key := fmt.Sprintf("%s/%s/%s", u.Login, owner, name)
val, ok := r.cache.Get(key)
val, ok := g.cache.Get(key)
if ok {
return val.(*common.Perm), nil
}
client := NewClient(r.URL, u.Token, r.SkipVerify)
id, err := GetProjectId(r, client, owner, name)
client := NewClient(g.URL, u.Token, g.SkipVerify)
id, err := GetProjectId(g, client, owner, name)
if err != nil {
return nil, err
}
@ -144,15 +153,15 @@ func (r *Gitlab) Perm(u *common.User, owner, name string) (*common.Perm, error)
m.Admin = IsAdmin(repo)
m.Pull = IsRead(repo)
m.Push = IsWrite(repo)
r.cache.Add(key, m)
g.cache.Add(key, m)
return m, nil
}
// GetScript fetches the build script (.drone.yml) from the remote
// repository and returns in string format.
func (r *Gitlab) Script(user *common.User, repo *common.Repo, build *common.Build) ([]byte, error) {
var client = NewClient(r.URL, user.Token, r.SkipVerify)
id, err := GetProjectId(r, client, repo.Owner, repo.Name)
func (g *Gitlab) Script(user *common.User, repo *common.Repo, build *common.Build) ([]byte, error) {
var client = NewClient(g.URL, user.Token, g.SkipVerify)
id, err := GetProjectId(g, client, repo.Owner, repo.Name)
if err != nil {
return nil, err
}
@ -163,95 +172,71 @@ func (r *Gitlab) Script(user *common.User, repo *common.Repo, build *common.Buil
// NOTE Currently gitlab doesn't support status for commits and events,
// also if we want get MR status in gitlab we need implement a special plugin for gitlab,
// gitlab uses API to fetch build status on client side. But for now we skip this.
func (r *Gitlab) Status(u *common.User, repo *common.Repo, b *common.Build) error {
func (g *Gitlab) Status(u *common.User, repo *common.Repo, b *common.Build) error {
return nil
}
// Netrc returns a .netrc file that can be used to clone
// private repositories from a remote system.
func (r *Gitlab) Netrc(u *common.User) (*common.Netrc, error) {
url_, err := url.Parse(r.URL)
func (g *Gitlab) Netrc(u *common.User, r *common.Repo) (*common.Netrc, error) {
url_, err := url.Parse(g.URL)
if err != nil {
return nil, err
}
netrc := &common.Netrc{}
netrc.Login = "oauth2"
netrc.Password = u.Token
switch g.CloneMode {
case "oauth":
netrc.Login = "oauth2"
netrc.Password = u.Token
case "token":
netrc.Login = "drone-ci-token"
netrc.Password = hash.New(r.FullName, r.Hash)
}
netrc.Machine = url_.Host
return netrc, nil
}
// Activate activates a repository by adding a Post-commit hook and
// a Public Deploy key, if applicable.
func (r *Gitlab) Activate(user *common.User, repo *common.Repo, k *common.Keypair, link string) error {
var client = NewClient(r.URL, user.Token, r.SkipVerify)
id, err := GetProjectId(r, client, repo.Owner, repo.Name)
func (g *Gitlab) Activate(user *common.User, repo *common.Repo, k *common.Keypair, link string) error {
var client = NewClient(g.URL, user.Token, g.SkipVerify)
id, err := GetProjectId(g, client, repo.Owner, repo.Name)
if err != nil {
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")
ssl_verify := strconv.FormatBool(!g.SkipVerify)
// 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,
"enable_ssl_verification": ssl_verify,
})
}
// Deactivate removes a repository by removing all the post-commit hooks
// which are equal to link and removing the SSH deploy key.
func (r *Gitlab) Deactivate(user *common.User, repo *common.Repo, link string) error {
var client = NewClient(r.URL, user.Token, r.SkipVerify)
id, err := GetProjectId(r, client, repo.Owner, repo.Name)
func (g *Gitlab) Deactivate(user *common.User, repo *common.Repo, link string) error {
var client = NewClient(g.URL, user.Token, g.SkipVerify)
id, err := GetProjectId(g, client, repo.Owner, repo.Name)
if err != nil {
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
// and returns the required data in a standard format.
func (r *Gitlab) Hook(req *http.Request) (*common.Hook, error) {
func (g *Gitlab) Hook(req *http.Request) (*common.Hook, error) {
defer req.Body.Close()
var payload, _ = ioutil.ReadAll(req.Body)
var parsed, err = gogitlab.ParseHook(payload)
@ -259,20 +244,54 @@ 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":
return mergeRequest(parsed, req)
case "tag_push", "push":
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)
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 = parsed.ObjectAttributes.Target.WebUrl
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.SourceProjectId == parsed.ObjectAttributes.TargetProjectId {
hook.Commit.Ref = fmt.Sprintf("refs/heads/%s", parsed.ObjectAttributes.SourceBranch)
} else {
hook.Commit.Ref = fmt.Sprintf("refs/merge-requests/%d/head", parsed.ObjectAttributes.IId)
}
if len(parsed.After) == 0 {
return nil, nil
}
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.Title
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)
@ -338,16 +357,16 @@ func (g *Gitlab) Oauth2Transport(r *http.Request) *oauth2.Transport {
}
// Accessor method, to allowed remote organizations field.
func (r *Gitlab) GetOrgs() []string {
return r.AllowedOrgs
func (g *Gitlab) GetOrgs() []string {
return g.AllowedOrgs
}
// Accessor method, to open field.
func (r *Gitlab) GetOpen() bool {
return r.Open
func (g *Gitlab) GetOpen() bool {
return g.Open
}
// return default scope for GitHub
func (r *Gitlab) Scope() string {
func (g *Gitlab) Scope() string {
return DefaultScope
}

View file

@ -0,0 +1,167 @@
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.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()
})
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("MS-Viewport")
})
})
})
}

View file

@ -0,0 +1,146 @@
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",
"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
}
`)

View file

@ -0,0 +1,3 @@
package testdata
var accessTokenPayload = []byte(`access_token=sekret&scope=api&token_type=bearer`)

View file

@ -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
}
}
}
`)

View file

@ -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
}

View file

@ -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
}
`)

View file

@ -64,7 +64,7 @@ type Remote interface {
// Netrc returns a .netrc file that can be used to clone
// private repositories from a remote system.
Netrc(u *types.User) (*types.Netrc, error)
Netrc(u *types.User, r *types.Repo) (*types.Netrc, error)
// Activate activates a repository by creating the post-commit hook and
// adding the SSH deploy key, if applicable.

View file

@ -21,7 +21,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"))
@ -47,7 +47,7 @@ func GetCommit(c *gin.Context) {
//
// GET /api/repos/:owner/:name/builds
//
func GetCommits(c *gin.Context) {
func GetBuilds(c *gin.Context) {
store := ToDatastore(c)
repo := ToRepo(c)
builds, err := store.BuildList(repo, 20, 0)
@ -167,7 +167,7 @@ func RunBuild(c *gin.Context) {
return
}
netrc, err := remote.Netrc(user)
netrc, err := remote.Netrc(user, repo)
if err != nil {
c.Fail(500, err)
return

146
pkg/server/gitlab.go Normal file
View file

@ -0,0 +1,146 @@
package server
import (
"fmt"
"strconv"
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
"github.com/drone/drone/pkg/hash"
)
// 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.New(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.New(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
}

View file

@ -5,6 +5,7 @@ import (
log "github.com/drone/drone/Godeps/_workspace/src/github.com/Sirupsen/logrus"
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
"github.com/drone/drone/pkg/hash"
"github.com/drone/drone/pkg/queue"
common "github.com/drone/drone/pkg/types"
"github.com/drone/drone/pkg/utils/httputil"
@ -57,7 +58,7 @@ func PostHook(c *gin.Context) {
}
// get the token and verify the hook is authorized
if c.Request.FormValue("access_token") != hash(repo.FullName, repo.Hash) {
if c.Request.FormValue("access_token") != hash.New(repo.FullName, repo.Hash) {
log.Errorf("invalid token sent with hook.")
c.AbortWithStatus(403)
return
@ -68,11 +69,11 @@ func PostHook(c *gin.Context) {
log.Warnf("ignoring hook. repo %s has no owner.", repo.FullName)
c.Writer.WriteHeader(204)
return
case !repo.Hooks.Push && hook.PullRequest != nil:
case !repo.Hooks.Push && hook.Commit != nil:
log.Infof("ignoring hook. repo %s is disabled.", repo.FullName)
c.Writer.WriteHeader(204)
return
case !repo.Hooks.PullRequest && hook.PullRequest == nil:
case !repo.Hooks.PullRequest && hook.PullRequest != nil:
log.Warnf("ignoring hook. repo %s is disabled for pull requests.", repo.FullName)
c.Writer.WriteHeader(204)
return
@ -129,7 +130,7 @@ func PostHook(c *gin.Context) {
})
}
netrc, err := remote.Netrc(user)
netrc, err := remote.Netrc(user, repo)
if err != nil {
log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err)
c.Fail(500, err)

View file

@ -1,14 +1,13 @@
package server
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin/binding"
"github.com/drone/drone/pkg/hash"
"github.com/drone/drone/pkg/remote"
common "github.com/drone/drone/pkg/types"
"github.com/drone/drone/pkg/utils/httputil"
@ -209,7 +208,7 @@ func PostRepo(c *gin.Context) {
link := fmt.Sprintf(
"%s/api/hook?access_token=%s",
httputil.GetURL(c.Request),
hash(r.FullName, r.Hash),
hash.New(r.FullName, r.Hash),
)
// generate an RSA key and add to the repo
@ -316,9 +315,3 @@ func perms(remote remote.Remote, u *common.User, r *common.Repo) *common.Perm {
}
return p
}
func hash(text, salt string) string {
hasher := sha256.New()
hasher.Write([]byte(text + salt))
return hex.EncodeToString(hasher.Sum(nil))
}

View file

@ -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) {
@ -100,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

View file

@ -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)

View file

@ -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)