Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Brad Rydzewski 2016-03-03 14:40:50 -08:00
commit 6769eed65d
20 changed files with 722 additions and 146 deletions

8
ISSUE_TEMPLATE.md Normal file
View file

@ -0,0 +1,8 @@
Thank you for taking the time to use Drone and file an issue or feature request. Before filing an issue please ensure the following boxes are checked, if applicable:
- [ ] I have searched for existing issues
- [ ] I have discussed the issue with the community at https://gitter.im/drone/drone
- [ ] I have provided a sample `.drone.yml` file to help the team reproduce
- [ ] I have provided details from the build logs
- [ ] I have provided details from the server logs by running `docker logs drone`
- [ ] I am not using the issue tracker to ask why my build failed

View file

@ -33,6 +33,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. * `open=false` allows users to self-register. Defaults to false for security reasons.
* `orgs=drone&orgs=docker` restricts access to these GitLab organizations. **Optional** * `orgs=drone&orgs=docker` restricts access to these GitLab organizations. **Optional**
* `skip_verify=false` skip ca verification if self-signed certificate. Defaults to false for security reasons. * `skip_verify=false` skip ca verification if self-signed certificate. Defaults to false for security reasons.
* `hide_archives=false` hide projects archived in GitLab from the listing.
* `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 ) * `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 ## Gitlab registration

View file

@ -329,6 +329,11 @@ func (e *engine) runJob(c context.Context, r *Task, updater *updater, client doc
info, builderr := docker.Wait(client, name) info, builderr := docker.Wait(client, name)
switch { switch {
case info.State.Running:
// A build unblocked before actually being completed.
log.Errorf("incomplete build: %s", name)
r.Job.ExitCode = 1
r.Job.Status = model.StatusError
case info.State.ExitCode == 128: case info.State.ExitCode == 128:
r.Job.ExitCode = info.State.ExitCode r.Job.ExitCode = info.State.ExitCode
r.Job.Status = model.StatusKilled r.Job.Status = model.StatusKilled

View file

@ -0,0 +1,53 @@
package client
import (
"encoding/json"
"strconv"
)
const (
groupsUrl = "/groups"
)
// Get a list of all projects owned by the authenticated user.
func (g *Client) AllGroups() ([]*Namespace, error) {
var perPage = 100
var groups []*Namespace
for i := 1; true; i++ {
contents, err := g.Groups(i, perPage)
if err != nil {
return groups, err
}
for _, value := range contents {
groups = append(groups, value)
}
if len(groups) == 0 {
break
}
if len(groups)/i < perPage {
break
}
}
return groups, nil
}
func (g *Client) Groups(page, perPage int) ([]*Namespace, error) {
url, opaque := g.ResourceUrl(groupsUrl, nil, QMap{
"page": strconv.Itoa(page),
"per_page": strconv.Itoa(perPage),
})
var groups []*Namespace
contents, err := g.Do("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &groups)
}
return groups, err
}

View file

@ -15,12 +15,12 @@ const (
) )
// Get a list of all projects owned by the authenticated user. // Get a list of all projects owned by the authenticated user.
func (g *Client) AllProjects() ([]*Project, error) { func (g *Client) AllProjects(hide_archives bool) ([]*Project, error) {
var per_page = 100 var per_page = 100
var projects []*Project var projects []*Project
for i := 1; true; i++ { for i := 1; true; i++ {
contents, err := g.Projects(i, per_page) contents, err := g.Projects(i, per_page, hide_archives)
if err != nil { if err != nil {
return projects, err return projects, err
} }
@ -42,12 +42,17 @@ func (g *Client) AllProjects() ([]*Project, error) {
} }
// Get a list of projects owned by the authenticated user. // Get a list of projects owned by the authenticated user.
func (c *Client) Projects(page int, per_page int) ([]*Project, error) { func (c *Client) Projects(page int, per_page int, hide_archives bool) ([]*Project, error) {
projectsOptions := QMap{
url, opaque := c.ResourceUrl(projectsUrl, nil, QMap{
"page": strconv.Itoa(page), "page": strconv.Itoa(page),
"per_page": strconv.Itoa(per_page), "per_page": strconv.Itoa(per_page),
}) }
if hide_archives {
projectsOptions["archived"] = "false"
}
url, opaque := c.ResourceUrl(projectsUrl, nil, projectsOptions)
var projects []*Project var projects []*Project

View file

@ -48,12 +48,14 @@ type Project struct {
SshRepoUrl string `json:"ssh_url_to_repo"` SshRepoUrl string `json:"ssh_url_to_repo"`
HttpRepoUrl string `json:"http_url_to_repo"` HttpRepoUrl string `json:"http_url_to_repo"`
Url string `json:"web_url"` Url string `json:"web_url"`
AvatarUrl string `json:"avatar_url"`
Permissions *Permissions `json:"permissions,omitempty"` Permissions *Permissions `json:"permissions,omitempty"`
} }
type Namespace struct { type Namespace struct {
Id int `json:"id,omitempty"` Id int `json:"id,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Path string `json:"path,omitempty"`
} }
type Person struct { type Person struct {
@ -62,12 +64,17 @@ type Person struct {
} }
type hProject struct { type hProject struct {
Name string `json:"name"` Name string `json:"name"`
SshUrl string `json:"ssh_url"` SshUrl string `json:"ssh_url"`
HttpUrl string `json:"http_url"` HttpUrl string `json:"http_url"`
VisibilityLevel int `json:"visibility_level"` GitSshUrl string `json:"git_ssh_url"`
WebUrl string `json:"web_url"` GitHttpUrl string `json:"git_http_url"`
Namespace string `json:"namespace"` AvatarUrl string `json:"avatar_url"`
VisibilityLevel int `json:"visibility_level"`
WebUrl string `json:"web_url"`
PathWithNamespace string `json:"path_with_namespace"`
DefaultBranch string `json:"default_branch"`
Namespace string `json:"namespace"`
} }
type hRepository struct { type hRepository struct {
@ -122,6 +129,7 @@ type HookPayload struct {
UserId int `json:"user_id,omitempty"` UserId int `json:"user_id,omitempty"`
UserName string `json:"user_name,omitempty"` UserName string `json:"user_name,omitempty"`
ProjectId int `json:"project_id,omitempty"` ProjectId int `json:"project_id,omitempty"`
Project *hProject `json:"project,omitempty"`
Repository *hRepository `json:"repository,omitempty"` Repository *hRepository `json:"repository,omitempty"`
Commits []hCommit `json:"commits,omitempty"` Commits []hCommit `json:"commits,omitempty"`
TotalCommitsCount int `json:"total_commits_count,omitempty"` TotalCommitsCount int `json:"total_commits_count,omitempty"`

View file

@ -5,8 +5,18 @@ import (
"strings" "strings"
) )
var encodeMap = map[string]string{
".": "%252E",
}
func encodeParameter(value string) string { func encodeParameter(value string) string {
return strings.Replace(url.QueryEscape(value), "/", "%2F", 0) value = url.QueryEscape(value)
for before, after := range encodeMap {
value = strings.Replace(value, before, after, -1)
}
return value
} }
// Tag returns current tag for push event hook payload // Tag returns current tag for push event hook payload

View file

@ -23,15 +23,16 @@ const (
) )
type Gitlab struct { type Gitlab struct {
URL string URL string
Client string Client string
Secret string Secret string
AllowedOrgs []string AllowedOrgs []string
CloneMode string CloneMode string
Open bool Open bool
PrivateMode bool PrivateMode bool
SkipVerify bool SkipVerify bool
Search bool HideArchives bool
Search bool
} }
func Load(env envconfig.Env) *Gitlab { func Load(env envconfig.Env) *Gitlab {
@ -50,6 +51,7 @@ func Load(env envconfig.Env) *Gitlab {
gitlab.Secret = params.Get("client_secret") gitlab.Secret = params.Get("client_secret")
gitlab.AllowedOrgs = params["orgs"] gitlab.AllowedOrgs = params["orgs"]
gitlab.SkipVerify, _ = strconv.ParseBool(params.Get("skip_verify")) gitlab.SkipVerify, _ = strconv.ParseBool(params.Get("skip_verify"))
gitlab.HideArchives, _ = strconv.ParseBool(params.Get("hide_archives"))
gitlab.Open, _ = strconv.ParseBool(params.Get("open")) gitlab.Open, _ = strconv.ParseBool(params.Get("open"))
switch params.Get("clone_mode") { switch params.Get("clone_mode") {
@ -101,6 +103,28 @@ func (g *Gitlab) Login(res http.ResponseWriter, req *http.Request) (*model.User,
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
if len(g.AllowedOrgs) != 0 {
groups, err := client.AllGroups()
if err != nil {
return nil, false, fmt.Errorf("Could not check org membership. %s", err)
}
var member bool
for _, group := range groups {
for _, allowedOrg := range g.AllowedOrgs {
if group.Path == allowedOrg {
member = true
break
}
}
}
if !member {
return nil, false, fmt.Errorf("User does not belong to correct group. Must belong to %v", g.AllowedOrgs)
}
}
user := &model.User{} user := &model.User{}
user.Login = login.Username user.Login = login.Username
user.Email = login.Email user.Email = login.Email
@ -113,7 +137,7 @@ func (g *Gitlab) Login(res http.ResponseWriter, req *http.Request) (*model.User,
user.Avatar = g.URL + "/" + login.AvatarUrl user.Avatar = g.URL + "/" + login.AvatarUrl
} }
return user, true, nil return user, g.Open, nil
} }
func (g *Gitlab) Auth(token, secret string) (string, error) { func (g *Gitlab) Auth(token, secret string) (string, error) {
@ -145,6 +169,12 @@ func (g *Gitlab) Repo(u *model.User, owner, name string) (*model.Repo, error) {
repo.Clone = repo_.HttpRepoUrl repo.Clone = repo_.HttpRepoUrl
repo.Branch = "master" repo.Branch = "master"
repo.Avatar = repo_.AvatarUrl
if len(repo.Avatar) != 0 && !strings.HasPrefix(repo.Avatar, "http") {
repo.Avatar = fmt.Sprintf("%s/%s", g.URL, repo.Avatar)
}
if repo_.DefaultBranch != "" { if repo_.DefaultBranch != "" {
repo.Branch = repo_.DefaultBranch repo.Branch = repo_.DefaultBranch
} }
@ -164,7 +194,7 @@ func (g *Gitlab) Repos(u *model.User) ([]*model.RepoLite, error) {
var repos = []*model.RepoLite{} var repos = []*model.RepoLite{}
all, err := client.AllProjects() all, err := client.AllProjects(g.HideArchives)
if err != nil { if err != nil {
return repos, err return repos, err
} }
@ -173,15 +203,20 @@ func (g *Gitlab) Repos(u *model.User) ([]*model.RepoLite, error) {
var parts = strings.Split(repo.PathWithNamespace, "/") var parts = strings.Split(repo.PathWithNamespace, "/")
var owner = parts[0] var owner = parts[0]
var name = parts[1] var name = parts[1]
var avatar = repo.AvatarUrl
if len(avatar) != 0 && !strings.HasPrefix(avatar, "http") {
avatar = fmt.Sprintf("%s/%s", g.URL, avatar)
}
repos = append(repos, &model.RepoLite{ repos = append(repos, &model.RepoLite{
Owner: owner, Owner: owner,
Name: name, Name: name,
FullName: repo.PathWithNamespace, FullName: repo.PathWithNamespace,
Avatar: avatar,
}) })
// TODO: add repo.AvatarUrl
} }
return repos, err return repos, err
} }
@ -201,7 +236,7 @@ func (g *Gitlab) Perm(u *model.User, owner, name string) (*model.Perm, error) {
// repo owner is granted full access // repo owner is granted full access
if repo.Owner != nil && repo.Owner.Username == u.Login { if repo.Owner != nil && repo.Owner.Username == u.Login {
return &model.Perm{true, true, true}, nil return &model.Perm{true, true, true}, nil
} }
// check permission for current user // check permission for current user
@ -337,58 +372,136 @@ func (g *Gitlab) Hook(req *http.Request) (*model.Repo, *model.Build, error) {
func mergeRequest(parsed *client.HookPayload, req *http.Request) (*model.Repo, *model.Build, error) { func mergeRequest(parsed *client.HookPayload, req *http.Request) (*model.Repo, *model.Build, error) {
repo := &model.Repo{} repo := &model.Repo{}
repo.Owner = req.FormValue("owner")
repo.Name = req.FormValue("name") obj := parsed.ObjectAttributes
repo.FullName = fmt.Sprintf("%s/%s", repo.Owner, repo.Name) if obj == nil {
repo.Link = parsed.ObjectAttributes.Target.WebUrl return nil, nil, fmt.Errorf("object_attributes key expected in merge request hook")
repo.Clone = parsed.ObjectAttributes.Target.HttpUrl }
repo.Branch = "master"
target := obj.Target
source := obj.Source
if target == nil && source == nil {
return nil, nil, fmt.Errorf("target and source keys expected in merge request hook")
} else if target == nil {
return nil, nil, fmt.Errorf("target key expected in merge request hook")
} else if source == nil {
return nil, nil, fmt.Errorf("source key exptected in merge request hook")
}
if target.PathWithNamespace != "" {
var err error
if repo.Owner, repo.Name, err = ExtractFromPath(target.PathWithNamespace); err != nil {
return nil, nil, err
}
repo.FullName = target.PathWithNamespace
} else {
repo.Owner = req.FormValue("owner")
repo.Name = req.FormValue("name")
repo.FullName = fmt.Sprintf("%s/%s", repo.Owner, repo.Name)
}
repo.Link = target.WebUrl
if target.GitHttpUrl != "" {
repo.Clone = target.GitHttpUrl
} else {
repo.Clone = target.HttpUrl
}
if target.DefaultBranch != "" {
repo.Branch = target.DefaultBranch
} else {
repo.Branch = "master"
}
if target.AvatarUrl != "" {
repo.Avatar = target.AvatarUrl
}
build := &model.Build{} build := &model.Build{}
build.Event = "pull_request" build.Event = "pull_request"
build.Message = parsed.ObjectAttributes.LastCommit.Message
build.Commit = parsed.ObjectAttributes.LastCommit.Id
//build.Remote = parsed.ObjectAttributes.Source.HttpUrl
if parsed.ObjectAttributes.SourceProjectId == parsed.ObjectAttributes.TargetProjectId { lastCommit := obj.LastCommit
build.Ref = fmt.Sprintf("refs/heads/%s", parsed.ObjectAttributes.SourceBranch) if lastCommit == nil {
} else { return nil, nil, fmt.Errorf("last_commit key expected in merge request hook")
build.Ref = fmt.Sprintf("refs/merge-requests/%d/head", parsed.ObjectAttributes.IId)
} }
build.Branch = parsed.ObjectAttributes.SourceBranch build.Message = lastCommit.Message
// build.Timestamp = parsed.ObjectAttributes.LastCommit.Timestamp build.Commit = lastCommit.Id
//build.Remote = parsed.ObjectAttributes.Source.HttpUrl
build.Author = parsed.ObjectAttributes.LastCommit.Author.Name if obj.SourceProjectId == obj.TargetProjectId {
build.Email = parsed.ObjectAttributes.LastCommit.Author.Email build.Ref = fmt.Sprintf("refs/heads/%s", obj.SourceBranch)
build.Title = parsed.ObjectAttributes.Title } else {
build.Link = parsed.ObjectAttributes.Url build.Ref = fmt.Sprintf("refs/merge-requests/%d/head", obj.IId)
}
build.Branch = obj.SourceBranch
author := lastCommit.Author
if author == nil {
return nil, nil, fmt.Errorf("author key expected in merge request hook")
}
build.Author = author.Name
build.Email = author.Email
if len(build.Email) != 0 {
build.Avatar = GetUserAvatar(build.Email)
}
build.Title = obj.Title
build.Link = obj.Url
return repo, build, nil return repo, build, nil
} }
func push(parsed *client.HookPayload, req *http.Request) (*model.Repo, *model.Build, error) { func push(parsed *client.HookPayload, req *http.Request) (*model.Repo, *model.Build, error) {
var cloneUrl = parsed.Repository.GitHttpUrl
repo := &model.Repo{} repo := &model.Repo{}
repo.Owner = req.FormValue("owner")
repo.Name = req.FormValue("name")
repo.FullName = fmt.Sprintf("%s/%s", repo.Owner, repo.Name)
repo.Link = parsed.Repository.URL
repo.Clone = cloneUrl
repo.Branch = "master"
switch parsed.Repository.VisibilityLevel { // Since gitlab 8.5, used project instead repository key
case 0: // see https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/web_hooks/web_hooks.md#web-hooks
repo.IsPrivate = true if project := parsed.Project; project != nil {
case 10: var err error
repo.IsPrivate = true if repo.Owner, repo.Name, err = ExtractFromPath(project.PathWithNamespace); err != nil {
case 20: return nil, nil, err
repo.IsPrivate = false }
repo.Avatar = project.AvatarUrl
repo.Link = project.WebUrl
repo.Clone = project.GitHttpUrl
repo.FullName = project.PathWithNamespace
repo.Branch = project.DefaultBranch
switch project.VisibilityLevel {
case 0:
repo.IsPrivate = true
case 10:
repo.IsPrivate = true
case 20:
repo.IsPrivate = false
}
} else if repository := parsed.Repository; repository != nil {
repo.Owner = req.FormValue("owner")
repo.Name = req.FormValue("name")
repo.Link = repository.URL
repo.Clone = repository.GitHttpUrl
repo.Branch = "master"
repo.FullName = fmt.Sprintf("%s/%s", req.FormValue("owner"), req.FormValue("name"))
switch repository.VisibilityLevel {
case 0:
repo.IsPrivate = true
case 10:
repo.IsPrivate = true
case 20:
repo.IsPrivate = false
}
} else {
return nil, nil, fmt.Errorf("No project/repository keys given")
} }
repo.FullName = fmt.Sprintf("%s/%s", req.FormValue("owner"), req.FormValue("name"))
build := &model.Build{} build := &model.Build{}
build.Event = model.EventPush build.Event = model.EventPush
build.Commit = parsed.After build.Commit = parsed.After
@ -406,6 +519,9 @@ func push(parsed *client.HookPayload, req *http.Request) (*model.Repo, *model.Bu
case head.Author != nil: case head.Author != nil:
build.Email = head.Author.Email build.Email = head.Author.Email
build.Author = parsed.UserName build.Author = parsed.UserName
if len(build.Email) != 0 {
build.Avatar = GetUserAvatar(build.Email)
}
case head.Author == nil: case head.Author == nil:
build.Author = parsed.UserName build.Author = parsed.UserName
} }
@ -436,25 +552,6 @@ func (g *Gitlab) Oauth2Transport(r *http.Request) *oauth2.Transport {
} }
} }
// Accessor method, to allowed remote organizations field.
func (g *Gitlab) GetOrgs() []string {
return g.AllowedOrgs
}
// Accessor method, to open field.
func (g *Gitlab) GetOpen() bool {
return g.Open
}
// return default scope for GitHub
func (g *Gitlab) Scope() string {
return DefaultScope
}
func (g *Gitlab) String() string {
return "gitlab"
}
const ( const (
StatusPending = "pending" StatusPending = "pending"
StatusRunning = "running" StatusRunning = "running"

View file

@ -32,6 +32,25 @@ func Test_Gitlab(t *testing.T) {
g := goblin.Goblin(t) g := goblin.Goblin(t)
g.Describe("Gitlab Plugin", func() { g.Describe("Gitlab Plugin", func() {
// Test projects method
g.Describe("AllProjects", func() {
g.It("Should return only non-archived projects is hidden", func() {
gitlab.HideArchives = true
_projects, err := gitlab.Repos(&user)
g.Assert(err == nil).IsTrue()
g.Assert(len(_projects)).Equal(1)
})
g.It("Should return all the projects", func() {
gitlab.HideArchives = false
_projects, err := gitlab.Repos(&user)
g.Assert(err == nil).IsTrue()
g.Assert(len(_projects)).Equal(2)
})
})
// Test repository method // Test repository method
g.Describe("Repo", func() { g.Describe("Repo", func() {
g.It("Should return valid repo", func() { g.It("Should return valid repo", func() {
@ -115,52 +134,114 @@ func Test_Gitlab(t *testing.T) {
// Test hook method // Test hook method
g.Describe("Hook", func() { g.Describe("Hook", func() {
g.It("Should parse push hoook", func() { g.Describe("Push hook", func() {
req, _ := http.NewRequest( g.It("Should parse actual push hoook", func() {
"POST", req, _ := http.NewRequest(
"http://example.com/api/hook?owner=diaspora&name=diaspora-client", "POST",
bytes.NewReader(testdata.PushHook), "http://example.com/api/hook?owner=diaspora&name=diaspora-client",
) bytes.NewReader(testdata.PushHook),
)
repo, build, err := gitlab.Hook(req) repo, build, err := gitlab.Hook(req)
g.Assert(err == nil).IsTrue() g.Assert(err == nil).IsTrue()
g.Assert(repo.Owner).Equal("diaspora") g.Assert(repo.Owner).Equal("mike")
g.Assert(repo.Name).Equal("diaspora-client") g.Assert(repo.Name).Equal("diaspora")
g.Assert(build.Ref).Equal("refs/heads/master") g.Assert(repo.Avatar).Equal("http://example.com/uploads/project/avatar/555/Outh-20-Logo.jpg")
g.Assert(repo.Branch).Equal("develop")
g.Assert(build.Ref).Equal("refs/heads/master")
})
g.It("Should parse legacy push hoook", func() {
req, _ := http.NewRequest(
"POST",
"http://example.com/api/hook?owner=diaspora&name=diaspora-client",
bytes.NewReader(testdata.LegacyPushHook),
)
repo, build, err := gitlab.Hook(req)
g.Assert(err == nil).IsTrue()
g.Assert(repo.Owner).Equal("diaspora")
g.Assert(repo.Name).Equal("diaspora-client")
g.Assert(repo.Avatar).Equal("")
g.Assert(repo.Branch).Equal("master")
g.Assert(build.Ref).Equal("refs/heads/master")
})
}) })
g.It("Should parse tag push hook", func() { g.Describe("Tag push hook", func() {
req, _ := http.NewRequest( g.It("Should parse tag push hook", func() {
"POST", req, _ := http.NewRequest(
"http://example.com/api/hook?owner=diaspora&name=diaspora-client", "POST",
bytes.NewReader(testdata.TagHook), "http://example.com/api/hook?owner=diaspora&name=diaspora-client",
) bytes.NewReader(testdata.TagHook),
)
repo, build, err := gitlab.Hook(req) repo, build, err := gitlab.Hook(req)
g.Assert(err == nil).IsTrue() g.Assert(err == nil).IsTrue()
g.Assert(repo.Owner).Equal("diaspora") g.Assert(repo.Owner).Equal("jsmith")
g.Assert(repo.Name).Equal("diaspora-client") g.Assert(repo.Name).Equal("example")
g.Assert(build.Ref).Equal("refs/tags/v1.0.0") g.Assert(repo.Avatar).Equal("http://example.com/uploads/project/avatar/555/Outh-20-Logo.jpg")
g.Assert(repo.Branch).Equal("develop")
g.Assert(build.Ref).Equal("refs/tags/v1.0.0")
})
g.It("Should parse legacy tag push hook", func() {
req, _ := http.NewRequest(
"POST",
"http://example.com/api/hook?owner=diaspora&name=diaspora-client",
bytes.NewReader(testdata.LegacyTagHook),
)
repo, build, err := gitlab.Hook(req)
g.Assert(err == nil).IsTrue()
g.Assert(repo.Owner).Equal("diaspora")
g.Assert(repo.Name).Equal("diaspora-client")
g.Assert(build.Ref).Equal("refs/tags/v1.0.0")
})
}) })
g.It("Should parse merge request hook", func() { g.Describe("Merge request hook", func() {
req, _ := http.NewRequest( g.It("Should parse merge request hook", func() {
"POST", req, _ := http.NewRequest(
"http://example.com/api/hook?owner=diaspora&name=diaspora-client", "POST",
bytes.NewReader(testdata.MergeRequestHook), "http://example.com/api/hook?owner=diaspora&name=diaspora-client",
) bytes.NewReader(testdata.MergeRequestHook),
)
repo, build, err := gitlab.Hook(req) repo, build, err := gitlab.Hook(req)
g.Assert(err == nil).IsTrue() g.Assert(err == nil).IsTrue()
g.Assert(repo.Owner).Equal("diaspora") g.Assert(repo.Avatar).Equal("http://example.com/uploads/project/avatar/555/Outh-20-Logo.jpg")
g.Assert(repo.Name).Equal("diaspora-client") g.Assert(repo.Branch).Equal("develop")
g.Assert(repo.Owner).Equal("awesome_space")
g.Assert(repo.Name).Equal("awesome_project")
g.Assert(build.Title).Equal("MS-Viewport") g.Assert(build.Title).Equal("MS-Viewport")
})
g.It("Should parse legacy merge request hook", func() {
req, _ := http.NewRequest(
"POST",
"http://example.com/api/hook?owner=diaspora&name=diaspora-client",
bytes.NewReader(testdata.LegacyMergeRequestHook),
)
repo, build, err := gitlab.Hook(req)
g.Assert(err == nil).IsTrue()
g.Assert(repo.Owner).Equal("diaspora")
g.Assert(repo.Name).Equal("diaspora-client")
g.Assert(build.Title).Equal("MS-Viewport")
})
}) })
}) })
}) })

View file

@ -1,13 +1,20 @@
package gitlab package gitlab
import ( import (
"crypto/md5"
"encoding/hex"
"fmt" "fmt"
"net/url" "net/url"
"strconv" "strconv"
"strings"
"github.com/drone/drone/remote/gitlab/client" "github.com/drone/drone/remote/gitlab/client"
) )
const (
gravatarBase = "https://www.gravatar.com/avatar"
)
// NewClient is a helper function that returns a new GitHub // NewClient is a helper function that returns a new GitHub
// client using the provided OAuth token. // client using the provided OAuth token.
func NewClient(url, accessToken string, skipVerify bool) *client.Client { func NewClient(url, accessToken string, skipVerify bool) *client.Client {
@ -79,6 +86,26 @@ func ns(owner, name string) string {
return fmt.Sprintf("%s%%2F%s", owner, name) return fmt.Sprintf("%s%%2F%s", owner, name)
} }
func GetUserAvatar(email string) string {
hasher := md5.New()
hasher.Write([]byte(email))
return fmt.Sprintf(
"%s/%v.jpg?s=%s",
gravatarBase,
hex.EncodeToString(hasher.Sum(nil)),
"128",
)
}
func ExtractFromPath(str string) (string, string, error) {
s := strings.Split(str, "/")
if len(s) < 2 {
return "", "", fmt.Errorf("Minimum match not found")
}
return s[0], s[1], nil
}
func GetUserEmail(c *client.Client, defaultURL string) (*client.Client, error) { func GetUserEmail(c *client.Client, defaultURL string) (*client.Client, error) {
return c, nil return c, nil
} }

View file

@ -1,6 +1,46 @@
package testdata package testdata
var TagHook = []byte(` var TagHook = []byte(`
{
"object_kind": "tag_push",
"ref": "refs/tags/v1.0.0",
"before": "0000000000000000000000000000000000000000",
"after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
"user_id": 1,
"user_name": "John Smith",
"user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s=80",
"project_id": 1,
"project":{
"name":"Example",
"description":"",
"web_url":"http://example.com/jsmith/example",
"avatar_url":"http://example.com/uploads/project/avatar/555/Outh-20-Logo.jpg",
"git_ssh_url":"git@example.com:jsmith/example.git",
"git_http_url":"http://example.com/jsmith/example.git",
"namespace":"Jsmith",
"visibility_level":0,
"path_with_namespace":"jsmith/example",
"default_branch":"develop",
"homepage":"http://example.com/jsmith/example",
"url":"git@example.com:jsmith/example.git",
"ssh_url":"git@example.com:jsmith/example.git",
"http_url":"http://example.com/jsmith/example.git"
},
"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": [],
"total_commits_count": 0
}
`)
var LegacyTagHook = []byte(`
{ {
"object_kind": "tag_push", "object_kind": "tag_push",
"ref": "refs/tags/v1.0.0", "ref": "refs/tags/v1.0.0",
@ -45,6 +85,86 @@ var TagHook = []byte(`
`) `)
var MergeRequestHook = []byte(` 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",
"description":"Aut reprehenderit ut est.",
"web_url":"http://example.com/awesome_space/awesome_project",
"avatar_url":"http://example.com/uploads/project/avatar/555/Outh-20-Logo.jpg",
"git_ssh_url":"git@example.com:awesome_space/awesome_project.git",
"git_http_url":"http://example.com/awesome_space/awesome_project.git",
"namespace":"Awesome Space",
"visibility_level":20,
"path_with_namespace":"awesome_space/awesome_project",
"default_branch":"master",
"homepage":"http://example.com/awesome_space/awesome_project",
"url":"http://example.com/awesome_space/awesome_project.git",
"ssh_url":"git@example.com:awesome_space/awesome_project.git",
"http_url":"http://example.com/awesome_space/awesome_project.git"
},
"target": {
"name":"Awesome Project",
"description":"Aut reprehenderit ut est.",
"web_url":"http://example.com/awesome_space/awesome_project",
"avatar_url":"http://example.com/uploads/project/avatar/555/Outh-20-Logo.jpg",
"git_ssh_url":"git@example.com:awesome_space/awesome_project.git",
"git_http_url":"http://example.com/awesome_space/awesome_project.git",
"namespace":"Awesome Space",
"visibility_level":20,
"path_with_namespace":"awesome_space/awesome_project",
"default_branch":"develop",
"homepage":"http://example.com/awesome_space/awesome_project",
"url":"http://example.com/awesome_space/awesome_project.git",
"ssh_url":"git@example.com:awesome_space/awesome_project.git",
"http_url":"http://example.com/awesome_space/awesome_project.git"
},
"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)"
}
},
"work_in_progress": false,
"url": "http://example.com/diaspora/merge_requests/1",
"action": "open",
"assignee": {
"name": "User1",
"username": "user1",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
}
}
}
`)
var LegacyMergeRequestHook = []byte(`
{ {
"object_kind": "merge_request", "object_kind": "merge_request",
"user": { "user": {
@ -101,6 +221,74 @@ var MergeRequestHook = []byte(`
`) `)
var PushHook = []byte(` 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",
"user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
"project_id": 15,
"project":{
"name":"Diaspora",
"description":"",
"web_url":"http://example.com/mike/diaspora",
"avatar_url":"http://example.com/uploads/project/avatar/555/Outh-20-Logo.jpg",
"git_ssh_url":"git@example.com:mike/diaspora.git",
"git_http_url":"http://example.com/mike/diaspora.git",
"namespace":"Mike",
"visibility_level":0,
"path_with_namespace":"mike/diaspora",
"default_branch":"develop",
"homepage":"http://example.com/mike/diaspora",
"url":"git@example.com:mike/diasporadiaspora.git",
"ssh_url":"git@example.com:mike/diaspora.git",
"http_url":"http://example.com/mike/diaspora.git"
},
"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"
},
"added": ["CHANGELOG"],
"modified": ["app/controller/application.rb"],
"removed": []
},
{
"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)"
},
"added": ["CHANGELOG"],
"modified": ["app/controller/application.rb"],
"removed": []
}
],
"total_commits_count": 4
}
`)
var LegacyPushHook = []byte(`
{ {
"object_kind": "push", "object_kind": "push",
"before": "95790bf891e76fee5e1747ab589903a6a1f80f22", "before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
@ -114,7 +302,7 @@ var PushHook = []byte(`
"name": "Diaspora", "name": "Diaspora",
"url": "git@example.com:mike/diasporadiaspora.git", "url": "git@example.com:mike/diasporadiaspora.git",
"description": "", "description": "",
"homepage": "http://example.com/mike/diaspora", "homepage": "http://example.com/mike/diaspora",
"git_http_url":"http://example.com/mike/diaspora.git", "git_http_url":"http://example.com/mike/diaspora.git",
"git_ssh_url":"git@example.com:mike/diaspora.git", "git_ssh_url":"git@example.com:mike/diaspora.git",
"visibility_level":0 "visibility_level":0

View file

@ -1,7 +1,7 @@
package testdata package testdata
// sample repository list // sample repository list
var projectsPayload = []byte(` var allProjectsPayload = []byte(`
[ [
{ {
"id": 4, "id": 4,
@ -73,6 +73,47 @@ var projectsPayload = []byte(`
"path": "brightbox", "path": "brightbox",
"updated_at": "2013-09-30T13:46:02Z" "updated_at": "2013-09-30T13:46:02Z"
}, },
"archived": true
}
]
`)
var notArchivedProjectsPayload = []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",
"username": "some_user",
"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 "archived": false
} }
] ]

View file

@ -16,7 +16,12 @@ func NewServer() *httptest.Server {
// evaluate the path to serve a dummy data file // evaluate the path to serve a dummy data file
switch r.URL.Path { switch r.URL.Path {
case "/api/v3/projects": case "/api/v3/projects":
w.Write(projectsPayload) if r.URL.Query().Get("archived") == "false" {
w.Write(notArchivedProjectsPayload)
} else {
w.Write(allProjectsPayload)
}
return return
case "/api/v3/projects/diaspora/diaspora-client": case "/api/v3/projects/diaspora/diaspora-client":
w.Write(project4Paylod) w.Write(project4Paylod)

View file

@ -25,8 +25,9 @@ func Options(c *gin.Context) {
if c.Request.Method != "OPTIONS" { if c.Request.Method != "OPTIONS" {
c.Next() c.Next()
} else { } else {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS")
c.Header("Access-Control-Allow-Headers", "Authorization") c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept")
c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS")
c.Header("Content-Type", "application/json") c.Header("Content-Type", "application/json")
c.AbortWithStatus(200) c.AbortWithStatus(200)

View file

@ -2,6 +2,7 @@ package session
import ( import (
"net/http" "net/http"
"os"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/remote" "github.com/drone/drone/remote"
@ -104,6 +105,8 @@ func Perm(c *gin.Context) *model.Perm {
} }
func SetPerm() gin.HandlerFunc { func SetPerm() gin.HandlerFunc {
PUBLIC_MODE := os.Getenv("PUBLIC_MODE")
return func(c *gin.Context) { return func(c *gin.Context) {
user := User(c) user := User(c)
repo := Repo(c) repo := Repo(c)
@ -164,6 +167,11 @@ func SetPerm() gin.HandlerFunc {
} }
} }
// all build logs are visible in public mode
if PUBLIC_MODE != "" {
perm.Pull = true
}
if user != nil { if user != nil {
log.Debugf("%s granted %+v permission to %s", log.Debugf("%s granted %+v permission to %s",
user.Login, perm, repo.FullName) user.Login, perm, repo.FullName)

View file

@ -0,0 +1,44 @@
package session
import (
"os"
"testing"
"github.com/drone/drone/model"
"github.com/franela/goblin"
"github.com/gin-gonic/gin"
)
func TestSetPerm(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("SetPerm", func() {
g.BeforeEach(func() {
os.Unsetenv("PUBLIC_MODE")
})
g.It("Should set pull to false (private repo, user not logged in)", func() {
c := gin.Context{}
c.Set("repo", &model.Repo{
IsPrivate: true,
})
SetPerm()(&c)
v, ok := c.Get("perm")
g.Assert(ok).IsTrue("perm was not set")
p, ok := v.(*model.Perm)
g.Assert(ok).IsTrue("perm was the wrong type")
g.Assert(p.Pull).IsFalse("pull should be false")
})
g.It("Should set pull to true (private repo, user not logged in, public mode)", func() {
os.Setenv("PUBLIC_MODE", "true")
c := gin.Context{}
c.Set("repo", &model.Repo{
IsPrivate: true,
})
SetPerm()(&c)
v, ok := c.Get("perm")
g.Assert(ok).IsTrue("perm was not set")
p, ok := v.(*model.Perm)
g.Assert(ok).IsTrue("perm was the wrong type")
g.Assert(p.Pull).IsTrue("pull should be true")
})
})
}

View file

@ -1,9 +1,9 @@
package docker package docker
import ( import (
"io" "errors"
"io/ioutil"
log "github.com/Sirupsen/logrus"
"github.com/samalba/dockerclient" "github.com/samalba/dockerclient"
) )
@ -77,33 +77,21 @@ func Wait(client dockerclient.Client, name string) (*dockerclient.ContainerInfo,
client.KillContainer(name, "9") client.KillContainer(name, "9")
}() }()
errc := make(chan error, 1) for attempts := 0; attempts < 5; attempts++ {
infoc := make(chan *dockerclient.ContainerInfo, 1) done := client.Wait(name)
go func() { <-done
// blocks and waits for the container to finish
// by streaming the logs (to /dev/null). Ideally
// we could use the `wait` function instead
rc, err := client.ContainerLogs(name, LogOptsTail)
if err != nil {
errc <- err
return
}
io.Copy(ioutil.Discard, rc)
rc.Close()
info, err := client.InspectContainer(name) info, err := client.InspectContainer(name)
if err != nil { if err != nil {
errc <- err return nil, err
return
} }
infoc <- info
}()
select { if !info.State.Running {
case info := <-infoc: return info, nil
return info, nil }
case err := <-errc:
return nil, err log.Debugf("attempting to resume waiting after %d attempts.\n", attempts)
} }
return nil, errors.New("reached maximum wait attempts")
} }

BIN
static/images/dummy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -32,7 +32,10 @@ block content
each $build in $group.Builds each $build in $group.Builds
a.card[href=$repo.Name+"/"+$build.Number][data-build=$build.Number] a.card[href=$repo.Name+"/"+$build.Number][data-build=$build.Number]
div.card-header div.card-header
img[src=$build.Avatar] if $build.Avatar != ""
img[src=$build.Avatar]
else
img[src="/static/images/dummy.png"]
div.card-block div.card-block
div div
div.status[class=$build.Status] #{$build.Status} div.status[class=$build.Status] #{$build.Status}

View file

@ -20,7 +20,10 @@ block content
div.col-sm-4 div.col-sm-4
a.card[href="/"+$repo.FullName] a.card[href="/"+$repo.FullName]
div.card-header div.card-header
img.avatar[src=$repo.Avatar] if $repo.Avatar != ""
img.avatar[src=$repo.Avatar]
else
img.avatar[src="/static/images/dummy.png"]
div.card-block div.card-block
h3.login #{$repo.Name} h3.login #{$repo.Name}
div.full_name.hidden #{$repo.FullName} div.full_name.hidden #{$repo.FullName}