Merge pull request #1608 from bradrydzewski/master
refactoring github package to increase test coverage
This commit is contained in:
commit
abeb9bc3c6
20 changed files with 876 additions and 874 deletions
13
README.md
13
README.md
|
@ -18,6 +18,13 @@ Drone documentation is organized into several categories:
|
|||
* [CLI Reference](http://readme.drone.io/devs/cli/)
|
||||
* [API Reference](http://readme.drone.io/devs/api/builds)
|
||||
|
||||
### Documentation for 0.5 (unstable)
|
||||
|
||||
If you are using the 0.5 unstable release (master branch) please see the updated documentation:
|
||||
|
||||
* [Setup Guide](http://readme.drone.io/0.5/manage/server/)
|
||||
* [Build Guide](http://readme.drone.io/0.5/usage/overview/)
|
||||
|
||||
### Community, Help
|
||||
|
||||
Contributions, questions, and comments are welcomed and encouraged. Drone developers hang out in the [drone/drone](https://gitter.im/drone/drone) room on gitter. We ask that you please post your questions to [gitter](https://gitter.im/drone/drone) before creating an issue.
|
||||
|
@ -51,10 +58,4 @@ make gen # Generate code
|
|||
make build # Build the binary
|
||||
```
|
||||
|
||||
If you are seeing slow compile times please install the following:
|
||||
|
||||
```sh
|
||||
go install github.com/mattn/go-sqlite3
|
||||
```
|
||||
|
||||
If you are having trouble building this project please reference its `.drone.yml` file. Everything you need to know about building Drone is defined in that file.
|
||||
|
|
|
@ -168,7 +168,7 @@ func start(c *cli.Context) {
|
|||
for {
|
||||
if err := r.run(); err != nil {
|
||||
dur := c.Duration("backoff")
|
||||
logrus.Warnf("Attempting to reconnect in %v", dur)
|
||||
logrus.Warnf("reconnect in %v. %s", dur, err.Error())
|
||||
time.Sleep(dur)
|
||||
}
|
||||
}
|
||||
|
|
135
drone/daemon.go
135
drone/daemon.go
|
@ -320,141 +320,6 @@ func start(c *cli.Context) error {
|
|||
)
|
||||
}
|
||||
|
||||
//
|
||||
// func setupCache(c *cli.Context) cache.Cache {
|
||||
// return cache.NewTTL(
|
||||
// c.Duration("cache-ttl"),
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// func setupBus(c *cli.Context) bus.Bus {
|
||||
// return bus.New()
|
||||
// }
|
||||
//
|
||||
// func setupQueue(c *cli.Context) queue.Queue {
|
||||
// return queue.New()
|
||||
// }
|
||||
//
|
||||
// func setupStream(c *cli.Context) stream.Stream {
|
||||
// return stream.New()
|
||||
// }
|
||||
//
|
||||
// func setupStore(c *cli.Context) store.Store {
|
||||
// return datastore.New(
|
||||
// c.String("driver"),
|
||||
// c.String("datasource"),
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// func setupRemote(c *cli.Context) remote.Remote {
|
||||
// var remote remote.Remote
|
||||
// var err error
|
||||
// switch {
|
||||
// case c.Bool("github"):
|
||||
// remote, err = setupGithub(c)
|
||||
// case c.Bool("gitlab"):
|
||||
// remote, err = setupGitlab(c)
|
||||
// case c.Bool("bitbucket"):
|
||||
// remote, err = setupBitbucket(c)
|
||||
// case c.Bool("stash"):
|
||||
// remote, err = setupStash(c)
|
||||
// case c.Bool("gogs"):
|
||||
// remote, err = setupGogs(c)
|
||||
// default:
|
||||
// err = fmt.Errorf("version control system not configured")
|
||||
// }
|
||||
// if err != nil {
|
||||
// logrus.Fatalln(err)
|
||||
// }
|
||||
// return remote
|
||||
// }
|
||||
//
|
||||
// func setupBitbucket(c *cli.Context) (remote.Remote, error) {
|
||||
// return bitbucket.New(
|
||||
// c.String("bitbucket-client"),
|
||||
// c.String("bitbucket-server"),
|
||||
// ), nil
|
||||
// }
|
||||
//
|
||||
// func setupGogs(c *cli.Context) (remote.Remote, error) {
|
||||
// return gogs.New(gogs.Opts{
|
||||
// URL: c.String("gogs-server"),
|
||||
// Username: c.String("gogs-git-username"),
|
||||
// Password: c.String("gogs-git-password"),
|
||||
// PrivateMode: c.Bool("gogs-private-mode"),
|
||||
// SkipVerify: c.Bool("gogs-skip-verify"),
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// func setupStash(c *cli.Context) (remote.Remote, error) {
|
||||
// return bitbucketserver.New(bitbucketserver.Opts{
|
||||
// URL: c.String("stash-server"),
|
||||
// Username: c.String("stash-git-username"),
|
||||
// Password: c.String("stash-git-password"),
|
||||
// ConsumerKey: c.String("stash-consumer-key"),
|
||||
// ConsumerRSA: c.String("stash-consumer-rsa"),
|
||||
// SkipVerify: c.Bool("stash-skip-verify"),
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// func setupGitlab(c *cli.Context) (remote.Remote, error) {
|
||||
// return gitlab.New(gitlab.Opts{
|
||||
// URL: c.String("gitlab-server"),
|
||||
// Client: c.String("gitlab-client"),
|
||||
// Secret: c.String("gitlab-sercret"),
|
||||
// Username: c.String("gitlab-git-username"),
|
||||
// Password: c.String("gitlab-git-password"),
|
||||
// PrivateMode: c.Bool("gitlab-private-mode"),
|
||||
// SkipVerify: c.Bool("gitlab-skip-verify"),
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// func setupGithub(c *cli.Context) (remote.Remote, error) {
|
||||
// return github.New(
|
||||
// c.String("github-server"),
|
||||
// c.String("github-client"),
|
||||
// c.String("github-sercret"),
|
||||
// c.StringSlice("github-scope"),
|
||||
// c.Bool("github-private-mode"),
|
||||
// c.Bool("github-skip-verify"),
|
||||
// c.BoolT("github-merge-ref"),
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// func setupConfig(c *cli.Context) *server.Config {
|
||||
// return &server.Config{
|
||||
// Open: c.Bool("open"),
|
||||
// Yaml: c.String("yaml"),
|
||||
// Secret: c.String("agent-secret"),
|
||||
// Admins: sliceToMap(c.StringSlice("admin")),
|
||||
// Orgs: sliceToMap(c.StringSlice("orgs")),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func sliceToMap(s []string) map[string]bool {
|
||||
// v := map[string]bool{}
|
||||
// for _, ss := range s {
|
||||
// v[ss] = true
|
||||
// }
|
||||
// return v
|
||||
// }
|
||||
//
|
||||
// func printSecret(c *cli.Context) error {
|
||||
// secret := c.String("agent-secret")
|
||||
// if secret == "" {
|
||||
// return fmt.Errorf("missing DRONE_AGENT_SECRET configuration parameter")
|
||||
// }
|
||||
// t := token.New(secret, "")
|
||||
// s, err := t.Sign(secret)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("invalid value for DRONE_AGENT_SECRET. %s", s)
|
||||
// }
|
||||
//
|
||||
// logrus.Infof("using agent secret %s", secret)
|
||||
// logrus.Warnf("agents can connect with token %s", s)
|
||||
// return nil
|
||||
// }
|
||||
|
||||
var agreement = `
|
||||
---
|
||||
|
||||
|
|
115
remote/gerrit/gerrit.go
Normal file
115
remote/gerrit/gerrit.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
package gerrit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/remote"
|
||||
)
|
||||
|
||||
// IMPORTANT Gerrit support is not yet implemented. This file is a placeholder
|
||||
// for future implementaiton.
|
||||
|
||||
// Opts defines configuration options.
|
||||
type Opts struct {
|
||||
URL string // Gerrit server url.
|
||||
Username string // Optional machine account username.
|
||||
Password string // Optional machine account password.
|
||||
PrivateMode bool // Gerrit is running in private mode.
|
||||
SkipVerify bool // Skip ssl verification.
|
||||
}
|
||||
|
||||
type client struct {
|
||||
URL string
|
||||
Machine string
|
||||
Username string
|
||||
Password string
|
||||
PrivateMode bool
|
||||
SkipVerify bool
|
||||
}
|
||||
|
||||
// New returns a Remote implementation that integrates with Getter, an open
|
||||
// source Git hosting service and code review system.
|
||||
func New(opts Opts) (remote.Remote, error) {
|
||||
url, err := url.Parse(opts.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
host, _, err := net.SplitHostPort(url.Host)
|
||||
if err == nil {
|
||||
url.Host = host
|
||||
}
|
||||
return &client{
|
||||
URL: opts.URL,
|
||||
Machine: url.Host,
|
||||
Username: opts.Username,
|
||||
Password: opts.Password,
|
||||
PrivateMode: opts.PrivateMode,
|
||||
SkipVerify: opts.SkipVerify,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Login authenticates an account with Gerrit using oauth authenticaiton. The
|
||||
// Gerrit account details are returned when the user is successfully authenticated.
|
||||
func (c *client) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Auth is not supported by the Gerrit driver.
|
||||
func (c *client) Auth(token, secret string) (string, error) {
|
||||
return "", fmt.Errorf("Not Implemented")
|
||||
}
|
||||
|
||||
// Teams is not supported by the Gerrit driver.
|
||||
func (c *client) Teams(u *model.User) ([]*model.Team, error) {
|
||||
var empty []*model.Team
|
||||
return empty, nil
|
||||
}
|
||||
|
||||
// Repo is not supported by the Gerrit driver.
|
||||
func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Repos is not supported by the Gerrit driver.
|
||||
func (c *client) Repos(u *model.User) ([]*model.RepoLite, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Perm is not supported by the Gerrit driver.
|
||||
func (c *client) Perm(u *model.User, owner, name string) (*model.Perm, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// File is not supported by the Gerrit driver.
|
||||
func (c *client) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Status is not supported by the Gogs driver.
|
||||
func (c *client) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Netrc is not supported by the Gerrit driver.
|
||||
func (c *client) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Activate is not supported by the Gerrit driver.
|
||||
func (c *client) Activate(u *model.User, r *model.Repo, link string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deactivate is not supported by the Gogs driver.
|
||||
func (c *client) Deactivate(u *model.User, r *model.Repo, link string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Hook is not supported by the Gerrit driver.
|
||||
func (c *client) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||
return nil, nil, nil
|
||||
}
|
126
remote/github/convert.go
Normal file
126
remote/github/convert.go
Normal file
|
@ -0,0 +1,126 @@
|
|||
package github
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/model"
|
||||
|
||||
"github.com/google/go-github/github"
|
||||
)
|
||||
|
||||
const defaultBranch = "master"
|
||||
|
||||
const (
|
||||
statusPending = "pending"
|
||||
statusSuccess = "success"
|
||||
statusFailure = "failure"
|
||||
statusError = "error"
|
||||
)
|
||||
|
||||
const (
|
||||
descPending = "this build is pending"
|
||||
descSuccess = "the build was successful"
|
||||
descFailure = "the build failed"
|
||||
descError = "oops, something went wrong"
|
||||
)
|
||||
|
||||
// convertStatus is a helper function used to convert a Drone status to a
|
||||
// GitHub commit status.
|
||||
func convertStatus(status string) string {
|
||||
switch status {
|
||||
case model.StatusPending, model.StatusRunning:
|
||||
return statusPending
|
||||
case model.StatusSuccess:
|
||||
return statusSuccess
|
||||
case model.StatusFailure:
|
||||
return statusFailure
|
||||
default:
|
||||
return statusError
|
||||
}
|
||||
}
|
||||
|
||||
// convertDesc is a helper function used to convert a Drone status to a
|
||||
// GitHub status description.
|
||||
func convertDesc(status string) string {
|
||||
switch status {
|
||||
case model.StatusPending, model.StatusRunning:
|
||||
return descPending
|
||||
case model.StatusSuccess:
|
||||
return descSuccess
|
||||
case model.StatusFailure:
|
||||
return descFailure
|
||||
default:
|
||||
return descError
|
||||
}
|
||||
}
|
||||
|
||||
// convertRepo is a helper function used to convert a GitHub repository
|
||||
// structure to the common Drone repository structure.
|
||||
func convertRepo(from *github.Repository, private bool) *model.Repo {
|
||||
repo := &model.Repo{
|
||||
Owner: *from.Owner.Login,
|
||||
Name: *from.Name,
|
||||
FullName: *from.FullName,
|
||||
Link: *from.HTMLURL,
|
||||
IsPrivate: *from.Private,
|
||||
Clone: *from.CloneURL,
|
||||
Avatar: *from.Owner.AvatarURL,
|
||||
Kind: model.RepoGit,
|
||||
Branch: defaultBranch,
|
||||
}
|
||||
if from.DefaultBranch != nil {
|
||||
repo.Branch = *from.DefaultBranch
|
||||
}
|
||||
if private {
|
||||
repo.IsPrivate = true
|
||||
}
|
||||
return repo
|
||||
}
|
||||
|
||||
// convertPerm is a helper function used to convert a GitHub repository
|
||||
// permissions to the common Drone permissions structure.
|
||||
func convertPerm(from *github.Repository) *model.Perm {
|
||||
return &model.Perm{
|
||||
Admin: (*from.Permissions)["admin"],
|
||||
Push: (*from.Permissions)["push"],
|
||||
Pull: (*from.Permissions)["pull"],
|
||||
}
|
||||
}
|
||||
|
||||
// convertRepoList is a helper function used to convert a GitHub repository
|
||||
// list to the common Drone repository structure.
|
||||
func convertRepoList(from []github.Repository) []*model.RepoLite {
|
||||
var repos []*model.RepoLite
|
||||
for _, repo := range from {
|
||||
repos = append(repos, convertRepoLite(repo))
|
||||
}
|
||||
return repos
|
||||
}
|
||||
|
||||
// convertRepoLite is a helper function used to convert a GitHub repository
|
||||
// structure to the common Drone repository structure.
|
||||
func convertRepoLite(from github.Repository) *model.RepoLite {
|
||||
return &model.RepoLite{
|
||||
Owner: *from.Owner.Login,
|
||||
Name: *from.Name,
|
||||
FullName: *from.FullName,
|
||||
Avatar: *from.Owner.AvatarURL,
|
||||
}
|
||||
}
|
||||
|
||||
// convertTeamList is a helper function used to convert a GitHub team list to
|
||||
// the common Drone repository structure.
|
||||
func convertTeamList(from []github.Organization) []*model.Team {
|
||||
var teams []*model.Team
|
||||
for _, team := range from {
|
||||
teams = append(teams, convertTeam(team))
|
||||
}
|
||||
return teams
|
||||
}
|
||||
|
||||
// convertTeam is a helper function used to convert a GitHub team structure
|
||||
// to the common Drone repository structure.
|
||||
func convertTeam(from github.Organization) *model.Team {
|
||||
return &model.Team{
|
||||
Login: *from.Login,
|
||||
Avatar: *from.AvatarURL,
|
||||
}
|
||||
}
|
171
remote/github/convert_test.go
Normal file
171
remote/github/convert_test.go
Normal file
|
@ -0,0 +1,171 @@
|
|||
package github
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/google/go-github/github"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func Test_helper(t *testing.T) {
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("GitHub converter", func() {
|
||||
|
||||
g.It("should convert passing status", func() {
|
||||
g.Assert(convertStatus(model.StatusSuccess)).Equal(statusSuccess)
|
||||
})
|
||||
|
||||
g.It("should convert pending status", func() {
|
||||
g.Assert(convertStatus(model.StatusPending)).Equal(statusPending)
|
||||
g.Assert(convertStatus(model.StatusRunning)).Equal(statusPending)
|
||||
})
|
||||
|
||||
g.It("should convert failing status", func() {
|
||||
g.Assert(convertStatus(model.StatusFailure)).Equal(statusFailure)
|
||||
})
|
||||
|
||||
g.It("should convert error status", func() {
|
||||
g.Assert(convertStatus(model.StatusKilled)).Equal(statusError)
|
||||
g.Assert(convertStatus(model.StatusError)).Equal(statusError)
|
||||
})
|
||||
|
||||
g.It("should convert passing desc", func() {
|
||||
g.Assert(convertDesc(model.StatusSuccess)).Equal(descSuccess)
|
||||
})
|
||||
|
||||
g.It("should convert pending desc", func() {
|
||||
g.Assert(convertDesc(model.StatusPending)).Equal(descPending)
|
||||
g.Assert(convertDesc(model.StatusRunning)).Equal(descPending)
|
||||
})
|
||||
|
||||
g.It("should convert failing desc", func() {
|
||||
g.Assert(convertDesc(model.StatusFailure)).Equal(descFailure)
|
||||
})
|
||||
|
||||
g.It("should convert error desc", func() {
|
||||
g.Assert(convertDesc(model.StatusKilled)).Equal(descError)
|
||||
g.Assert(convertDesc(model.StatusError)).Equal(descError)
|
||||
})
|
||||
|
||||
g.It("should convert repository lite", func() {
|
||||
from := github.Repository{
|
||||
FullName: github.String("octocat/hello-world"),
|
||||
Name: github.String("hello-world"),
|
||||
Owner: &github.User{
|
||||
AvatarURL: github.String("http://..."),
|
||||
Login: github.String("octocat"),
|
||||
},
|
||||
}
|
||||
|
||||
to := convertRepoLite(from)
|
||||
g.Assert(to.Avatar).Equal("http://...")
|
||||
g.Assert(to.FullName).Equal("octocat/hello-world")
|
||||
g.Assert(to.Owner).Equal("octocat")
|
||||
g.Assert(to.Name).Equal("hello-world")
|
||||
})
|
||||
|
||||
g.It("should convert repository list", func() {
|
||||
from := []github.Repository{
|
||||
{
|
||||
FullName: github.String("octocat/hello-world"),
|
||||
Name: github.String("hello-world"),
|
||||
Owner: &github.User{
|
||||
AvatarURL: github.String("http://..."),
|
||||
Login: github.String("octocat"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
to := convertRepoList(from)
|
||||
g.Assert(to[0].Avatar).Equal("http://...")
|
||||
g.Assert(to[0].FullName).Equal("octocat/hello-world")
|
||||
g.Assert(to[0].Owner).Equal("octocat")
|
||||
g.Assert(to[0].Name).Equal("hello-world")
|
||||
})
|
||||
|
||||
g.It("should convert repository", func() {
|
||||
from := github.Repository{
|
||||
FullName: github.String("octocat/hello-world"),
|
||||
Name: github.String("hello-world"),
|
||||
HTMLURL: github.String("https://github.com/octocat/hello-world"),
|
||||
CloneURL: github.String("https://github.com/octocat/hello-world.git"),
|
||||
DefaultBranch: github.String("develop"),
|
||||
Private: github.Bool(true),
|
||||
Owner: &github.User{
|
||||
AvatarURL: github.String("http://..."),
|
||||
Login: github.String("octocat"),
|
||||
},
|
||||
}
|
||||
|
||||
to := convertRepo(&from, false)
|
||||
g.Assert(to.Avatar).Equal("http://...")
|
||||
g.Assert(to.FullName).Equal("octocat/hello-world")
|
||||
g.Assert(to.Owner).Equal("octocat")
|
||||
g.Assert(to.Name).Equal("hello-world")
|
||||
g.Assert(to.Branch).Equal("develop")
|
||||
g.Assert(to.Kind).Equal("git")
|
||||
g.Assert(to.IsPrivate).IsTrue()
|
||||
g.Assert(to.Clone).Equal("https://github.com/octocat/hello-world.git")
|
||||
g.Assert(to.Link).Equal("https://github.com/octocat/hello-world")
|
||||
})
|
||||
|
||||
g.It("should convert repository permissions", func() {
|
||||
from := &github.Repository{
|
||||
Permissions: &map[string]bool{
|
||||
"admin": true,
|
||||
"push": true,
|
||||
"pull": true,
|
||||
},
|
||||
}
|
||||
|
||||
to := convertPerm(from)
|
||||
g.Assert(to.Push).IsTrue()
|
||||
g.Assert(to.Pull).IsTrue()
|
||||
g.Assert(to.Admin).IsTrue()
|
||||
})
|
||||
|
||||
g.It("should convert team", func() {
|
||||
from := github.Organization{
|
||||
Login: github.String("octocat"),
|
||||
AvatarURL: github.String("http://..."),
|
||||
}
|
||||
to := convertTeam(from)
|
||||
g.Assert(to.Login).Equal("octocat")
|
||||
g.Assert(to.Avatar).Equal("http://...")
|
||||
})
|
||||
|
||||
g.It("should convert team list", func() {
|
||||
from := []github.Organization{
|
||||
{
|
||||
Login: github.String("octocat"),
|
||||
AvatarURL: github.String("http://..."),
|
||||
},
|
||||
}
|
||||
to := convertTeamList(from)
|
||||
g.Assert(to[0].Login).Equal("octocat")
|
||||
g.Assert(to[0].Avatar).Equal("http://...")
|
||||
})
|
||||
|
||||
//
|
||||
// g.It("should convert user", func() {
|
||||
// token := &oauth2.Token{
|
||||
// AccessToken: "foo",
|
||||
// RefreshToken: "bar",
|
||||
// Expiry: time.Now(),
|
||||
// }
|
||||
// user := &internal.Account{Login: "octocat"}
|
||||
// user.Links.Avatar.Href = "http://..."
|
||||
//
|
||||
// result := convertUser(user, token)
|
||||
// g.Assert(result.Avatar).Equal(user.Links.Avatar.Href)
|
||||
// g.Assert(result.Login).Equal(user.Login)
|
||||
// g.Assert(result.Token).Equal(token.AccessToken)
|
||||
// g.Assert(result.Token).Equal(token.AccessToken)
|
||||
// g.Assert(result.Secret).Equal(token.RefreshToken)
|
||||
// g.Assert(result.Expiry).Equal(token.Expiry.UTC().Unix())
|
||||
// })
|
||||
})
|
||||
}
|
|
@ -2,8 +2,9 @@ package github
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"encoding/base32"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
@ -13,104 +14,111 @@ import (
|
|||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/remote"
|
||||
"github.com/drone/drone/shared/httputil"
|
||||
"github.com/drone/drone/shared/oauth2"
|
||||
"github.com/gorilla/securecookie"
|
||||
|
||||
"github.com/google/go-github/github"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultURL = "https://github.com" // Default GitHub URL
|
||||
DefaultAPI = "https://api.github.com" // Default GitHub API URL
|
||||
defaultURL = "https://github.com" // Default GitHub URL
|
||||
defaultAPI = "https://api.github.com" // Default GitHub API URL
|
||||
)
|
||||
|
||||
type Github struct {
|
||||
// Opts defines configuration options.
|
||||
type Opts struct {
|
||||
URL string // GitHub server url.
|
||||
Client string // GitHub oauth client id.
|
||||
Secret string // GitHub oauth client secret.
|
||||
Scopes []string // GitHub oauth scopes
|
||||
Username string // Optional machine account username.
|
||||
Password string // Optional machine account password.
|
||||
PrivateMode bool // GitHub is running in private mode.
|
||||
SkipVerify bool // Skip ssl verification.
|
||||
MergeRef bool // Clone pull requests using the merge ref.
|
||||
}
|
||||
|
||||
// New returns a Remote implementation that integrates with a GitHub Cloud or
|
||||
// GitHub Enterprise version control hosting provider.
|
||||
func New(opts Opts) (remote.Remote, error) {
|
||||
url, err := url.Parse(opts.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
host, _, err := net.SplitHostPort(url.Host)
|
||||
if err == nil {
|
||||
url.Host = host
|
||||
}
|
||||
remote := &client{
|
||||
API: defaultAPI,
|
||||
URL: defaultURL,
|
||||
Client: opts.Client,
|
||||
Secret: opts.Secret,
|
||||
Scope: strings.Join(opts.Scopes, ","),
|
||||
PrivateMode: opts.PrivateMode,
|
||||
SkipVerify: opts.SkipVerify,
|
||||
MergeRef: opts.MergeRef,
|
||||
Machine: url.Host,
|
||||
Username: opts.Username,
|
||||
Password: opts.Password,
|
||||
}
|
||||
if opts.URL != defaultURL {
|
||||
remote.URL = strings.TrimSuffix(opts.URL, "/")
|
||||
remote.API = remote.URL + "/api/v3/"
|
||||
}
|
||||
return remote, nil
|
||||
}
|
||||
|
||||
type client struct {
|
||||
URL string
|
||||
API string
|
||||
Client string
|
||||
Secret string
|
||||
Scope string
|
||||
MergeRef string
|
||||
Machine string
|
||||
Username string
|
||||
Password string
|
||||
PrivateMode bool
|
||||
SkipVerify bool
|
||||
}
|
||||
|
||||
func New(url, client, secret string, scope []string, private, skipverify, mergeref bool) (remote.Remote, error) {
|
||||
remote := &Github{
|
||||
URL: strings.TrimSuffix(url, "/"),
|
||||
Client: client,
|
||||
Secret: secret,
|
||||
Scope: strings.Join(scope, ","),
|
||||
PrivateMode: private,
|
||||
SkipVerify: skipverify,
|
||||
MergeRef: "head",
|
||||
}
|
||||
|
||||
if remote.URL == DefaultURL {
|
||||
remote.API = DefaultAPI
|
||||
} else {
|
||||
remote.API = remote.URL + "/api/v3/"
|
||||
}
|
||||
if mergeref {
|
||||
remote.MergeRef = "merge"
|
||||
}
|
||||
|
||||
return remote, nil
|
||||
MergeRef bool
|
||||
}
|
||||
|
||||
// Login authenticates the session and returns the remote user details.
|
||||
func (g *Github) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) {
|
||||
func (c *client) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) {
|
||||
config := c.newConfig(httputil.GetURL(req))
|
||||
|
||||
var config = &oauth2.Config{
|
||||
ClientId: g.Client,
|
||||
ClientSecret: g.Secret,
|
||||
Scope: g.Scope,
|
||||
AuthURL: fmt.Sprintf("%s/login/oauth/authorize", g.URL),
|
||||
TokenURL: fmt.Sprintf("%s/login/oauth/access_token", g.URL),
|
||||
RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(req)),
|
||||
}
|
||||
|
||||
// get the OAuth code
|
||||
var code = req.FormValue("code")
|
||||
code := req.FormValue("code")
|
||||
if len(code) == 0 {
|
||||
var random = GetRandom()
|
||||
http.Redirect(res, req, config.AuthCodeURL(random), http.StatusSeeOther)
|
||||
rand := base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32))
|
||||
http.Redirect(res, req, config.AuthCodeURL(rand), http.StatusSeeOther)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var trans = &oauth2.Transport{
|
||||
Config: config,
|
||||
}
|
||||
if g.SkipVerify {
|
||||
trans.Transport = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
var token, err = trans.Exchange(code)
|
||||
// TODO(bradrydzewski) what is the best way to provide a SkipVerify flag
|
||||
// when exchanging the token?
|
||||
|
||||
token, err := config.Exchange(oauth2.NoContext, code)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error exchanging token. %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var client = NewClient(g.API, token.AccessToken, g.SkipVerify)
|
||||
var useremail, errr = GetUserEmail(client)
|
||||
if errr != nil {
|
||||
return nil, fmt.Errorf("Error retrieving user or verified email. %s", errr)
|
||||
client := c.newClientToken(token.AccessToken)
|
||||
useremail, err := GetUserEmail(client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user := model.User{}
|
||||
user.Login = *useremail.Login
|
||||
user.Email = *useremail.Email
|
||||
user.Token = token.AccessToken
|
||||
user.Avatar = *useremail.AvatarURL
|
||||
return &user, nil
|
||||
return &model.User{
|
||||
Login: *useremail.Login,
|
||||
Email: *useremail.Email,
|
||||
Token: token.AccessToken,
|
||||
Avatar: *useremail.AvatarURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Auth authenticates the session and returns the remote user
|
||||
// login for the given token and secret
|
||||
func (g *Github) Auth(token, secret string) (string, error) {
|
||||
client := NewClient(g.API, token, g.SkipVerify)
|
||||
// Auth returns the GitHub user login for the given access token.
|
||||
func (c *client) Auth(token, secret string) (string, error) {
|
||||
client := c.newClientToken(token)
|
||||
user, _, err := client.Users.Get("")
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -118,117 +126,150 @@ func (g *Github) Auth(token, secret string) (string, error) {
|
|||
return *user.Login, nil
|
||||
}
|
||||
|
||||
func (g *Github) Teams(u *model.User) ([]*model.Team, error) {
|
||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||
orgs, err := GetOrgs(client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Teams returns a list of all team membership for the GitHub account.
|
||||
func (c *client) Teams(u *model.User) ([]*model.Team, error) {
|
||||
client := c.newClientToken(u.Token)
|
||||
|
||||
opts := new(github.ListOptions)
|
||||
opts.Page = 1
|
||||
|
||||
var teams []*model.Team
|
||||
for _, org := range orgs {
|
||||
teams = append(teams, &model.Team{
|
||||
Login: *org.Login,
|
||||
Avatar: *org.AvatarURL,
|
||||
})
|
||||
for opts.Page > 0 {
|
||||
list, resp, err := client.Organizations.List("", opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
teams = append(teams, convertTeamList(list)...)
|
||||
opts.Page = resp.NextPage
|
||||
}
|
||||
return teams, nil
|
||||
}
|
||||
|
||||
// Repo fetches the named repository from the remote system.
|
||||
func (g *Github) Repo(u *model.User, owner, name string) (*model.Repo, error) {
|
||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||
r, err := GetRepo(client, owner, name)
|
||||
// Repo returns the named GitHub repository.
|
||||
func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) {
|
||||
client := c.newClientToken(u.Token)
|
||||
repo, _, err := client.Repositories.Get(owner, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repo := &model.Repo{
|
||||
Owner: owner,
|
||||
Name: name,
|
||||
FullName: *r.FullName,
|
||||
Link: *r.HTMLURL,
|
||||
IsPrivate: *r.Private,
|
||||
Clone: *r.CloneURL,
|
||||
Avatar: *r.Owner.AvatarURL,
|
||||
Kind: model.RepoGit,
|
||||
}
|
||||
|
||||
if r.DefaultBranch != nil {
|
||||
repo.Branch = *r.DefaultBranch
|
||||
} else {
|
||||
repo.Branch = "master"
|
||||
}
|
||||
|
||||
if g.PrivateMode {
|
||||
repo.IsPrivate = true
|
||||
}
|
||||
|
||||
return repo, err
|
||||
return convertRepo(repo, c.PrivateMode), nil
|
||||
}
|
||||
|
||||
// Repos fetches a list of repos from the remote system.
|
||||
func (g *Github) Repos(u *model.User) ([]*model.RepoLite, error) {
|
||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||
// Repos returns a list of all repositories for GitHub account, including
|
||||
// organization repositories.
|
||||
func (c *client) Repos(u *model.User) ([]*model.RepoLite, error) {
|
||||
client := c.newClientToken(u.Token)
|
||||
|
||||
all, err := GetUserRepos(client)
|
||||
opts := new(github.RepositoryListOptions)
|
||||
opts.PerPage = 100
|
||||
opts.Page = 1
|
||||
|
||||
var repos []*model.RepoLite
|
||||
for opts.Page > 0 {
|
||||
list, resp, err := client.Repositories.List("", opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repos = append(repos, convertRepoList(list)...)
|
||||
opts.Page = resp.NextPage
|
||||
}
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
// Perm returns the user permissions for the named GitHub repository.
|
||||
func (c *client) Perm(u *model.User, owner, name string) (*model.Perm, error) {
|
||||
client := c.newClientToken(u.Token)
|
||||
repo, _, err := client.Repositories.Get(owner, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var repos = []*model.RepoLite{}
|
||||
for _, repo := range all {
|
||||
repos = append(repos, &model.RepoLite{
|
||||
Owner: *repo.Owner.Login,
|
||||
Name: *repo.Name,
|
||||
FullName: *repo.FullName,
|
||||
Avatar: *repo.Owner.AvatarURL,
|
||||
})
|
||||
}
|
||||
return repos, err
|
||||
return convertPerm(repo), nil
|
||||
}
|
||||
|
||||
// Perm fetches the named repository permissions from
|
||||
// the remote system for the specified user.
|
||||
func (g *Github) Perm(u *model.User, owner, name string) (*model.Perm, error) {
|
||||
// File fetches the file from the Bitbucket repository and returns its contents.
|
||||
func (c *client) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
|
||||
client := c.newClientToken(u.Token)
|
||||
|
||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||
repo, err := GetRepo(client, owner, name)
|
||||
opts := new(github.RepositoryContentGetOptions)
|
||||
opts.Ref = b.Commit
|
||||
data, _, _, err := client.Repositories.GetContents(r.Owner, r.Name, f, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := &model.Perm{}
|
||||
m.Admin = (*repo.Permissions)["admin"]
|
||||
m.Push = (*repo.Permissions)["push"]
|
||||
m.Pull = (*repo.Permissions)["pull"]
|
||||
return m, nil
|
||||
return data.Decode()
|
||||
}
|
||||
|
||||
// File fetches a file from the remote repository and returns in string format.
|
||||
func (g *Github) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
|
||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||
cfg, err := GetFile(client, r.Owner, r.Name, f, b.Commit)
|
||||
return cfg, err
|
||||
// Netrc returns a netrc file capable of authenticating GitHub requests and
|
||||
// cloning GitHub repositories. The netrc will use the global machine account
|
||||
// when configured.
|
||||
func (c *client) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
||||
if c.Password != "" {
|
||||
return &model.Netrc{
|
||||
Login: c.Username,
|
||||
Password: c.Password,
|
||||
Machine: c.Machine,
|
||||
}, nil
|
||||
}
|
||||
return &model.Netrc{
|
||||
Login: u.Token,
|
||||
Password: "x-oauth-basic",
|
||||
Machine: c.Machine,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// helper function to return the bitbucket oauth2 config
|
||||
func (c *client) newConfig(redirect string) *oauth2.Config {
|
||||
return &oauth2.Config{
|
||||
ClientID: c.Client,
|
||||
ClientSecret: c.Secret,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: fmt.Sprintf("%s/login/oauth/authorize", c.URL),
|
||||
TokenURL: fmt.Sprintf("%s/login/oauth/access_token", c.URL),
|
||||
},
|
||||
RedirectURL: fmt.Sprintf("%s/authorize", redirect),
|
||||
}
|
||||
}
|
||||
|
||||
// helper function to return the bitbucket oauth2 client
|
||||
func (c *client) newClientToken(token string) *github.Client {
|
||||
ts := oauth2.StaticTokenSource(
|
||||
&oauth2.Token{AccessToken: token},
|
||||
)
|
||||
tc := oauth2.NewClient(oauth2.NoContext, ts)
|
||||
if c.SkipVerify {
|
||||
tc.Transport.(*oauth2.Transport).Base = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
github := github.NewClient(tc)
|
||||
github.BaseURL, _ = url.Parse(c.API)
|
||||
return github
|
||||
}
|
||||
|
||||
//
|
||||
// TODO(bradrydzewski) refactor below functions
|
||||
//
|
||||
|
||||
// Status sends the commit status to the remote system.
|
||||
// An example would be the GitHub pull request status.
|
||||
func (g *Github) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||
if b.Event == "deployment" {
|
||||
func (c *client) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
||||
client := c.newClientToken(u.Token)
|
||||
switch b.Event {
|
||||
case "deployment":
|
||||
return deploymentStatus(client, r, b, link)
|
||||
} else {
|
||||
default:
|
||||
return repoStatus(client, r, b, link)
|
||||
}
|
||||
}
|
||||
|
||||
func repoStatus(client *github.Client, r *model.Repo, b *model.Build, link string) error {
|
||||
status := getStatus(b.Status)
|
||||
desc := getDesc(b.Status)
|
||||
data := github.RepoStatus{
|
||||
Context: github.String("continuous-integration/drone"),
|
||||
State: github.String(status),
|
||||
Description: github.String(desc),
|
||||
State: github.String(convertStatus(b.Status)),
|
||||
Description: github.String(convertDesc(b.Status)),
|
||||
TargetURL: github.String(link),
|
||||
}
|
||||
_, _, err := client.Repositories.CreateStatus(r.Owner, r.Name, b.Commit, &data)
|
||||
|
@ -239,283 +280,46 @@ var reDeploy = regexp.MustCompile(".+/deployments/(\\d+)")
|
|||
|
||||
func deploymentStatus(client *github.Client, r *model.Repo, b *model.Build, link string) error {
|
||||
matches := reDeploy.FindStringSubmatch(b.Link)
|
||||
// if the deployment was not triggered from github, don't send a deployment status
|
||||
if len(matches) != 2 {
|
||||
return nil
|
||||
}
|
||||
// the deployment ID is only available in the the link to the build as the last element in the URL
|
||||
id, _ := strconv.Atoi(matches[1])
|
||||
status := getStatus(b.Status)
|
||||
desc := getDesc(b.Status)
|
||||
|
||||
data := github.DeploymentStatusRequest{
|
||||
State: github.String(status),
|
||||
Description: github.String(desc),
|
||||
State: github.String(convertStatus(b.Status)),
|
||||
Description: github.String(convertDesc(b.Status)),
|
||||
TargetURL: github.String(link),
|
||||
}
|
||||
_, _, err := client.Repositories.CreateDeploymentStatus(r.Owner, r.Name, id, &data)
|
||||
return err
|
||||
}
|
||||
|
||||
// Netrc returns a .netrc file that can be used to clone
|
||||
// private repositories from a remote system.
|
||||
func (g *Github) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
||||
url_, err := url.Parse(g.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
netrc := &model.Netrc{}
|
||||
netrc.Login = u.Token
|
||||
netrc.Password = "x-oauth-basic"
|
||||
netrc.Machine = url_.Host
|
||||
return netrc, nil
|
||||
}
|
||||
|
||||
// Activate activates a repository by creating the post-commit hook and
|
||||
// adding the SSH deploy key, if applicable.
|
||||
func (g *Github) Activate(u *model.User, r *model.Repo, link string) error {
|
||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||
func (c *client) Activate(u *model.User, r *model.Repo, link string) error {
|
||||
client := c.newClientToken(u.Token)
|
||||
_, err := CreateUpdateHook(client, r.Owner, r.Name, link)
|
||||
return err
|
||||
}
|
||||
|
||||
// Deactivate removes a repository by removing all the post-commit hooks
|
||||
// which are equal to link and removing the SSH deploy key.
|
||||
func (g *Github) Deactivate(u *model.User, r *model.Repo, link string) error {
|
||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||
func (c *client) Deactivate(u *model.User, r *model.Repo, link string) error {
|
||||
client := c.newClientToken(u.Token)
|
||||
return DeleteHook(client, r.Owner, r.Name, link)
|
||||
}
|
||||
|
||||
// Hook parses the post-commit hook from the Request body
|
||||
// and returns the required data in a standard format.
|
||||
func (g *Github) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||
|
||||
func (c *client) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||
switch r.Header.Get("X-Github-Event") {
|
||||
case "pull_request":
|
||||
return g.pullRequest(r)
|
||||
return c.pullRequest(r)
|
||||
case "push":
|
||||
return g.push(r)
|
||||
return c.push(r)
|
||||
case "deployment":
|
||||
return g.deployment(r)
|
||||
return c.deployment(r)
|
||||
default:
|
||||
return nil, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// push parses a hook with event type `push` and returns
|
||||
// the commit data.
|
||||
func (g *Github) push(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||
payload := GetPayload(r)
|
||||
hook := &pushHook{}
|
||||
err := json.Unmarshal(payload, hook)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if hook.Deleted {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
repo := &model.Repo{}
|
||||
repo.Owner = hook.Repo.Owner.Login
|
||||
if len(repo.Owner) == 0 {
|
||||
repo.Owner = hook.Repo.Owner.Name
|
||||
}
|
||||
repo.Name = hook.Repo.Name
|
||||
// Generating rather than using hook.Repo.FullName as it's
|
||||
// not always present
|
||||
repo.FullName = fmt.Sprintf("%s/%s", repo.Owner, repo.Name)
|
||||
repo.Link = hook.Repo.HTMLURL
|
||||
repo.IsPrivate = hook.Repo.Private
|
||||
repo.Clone = hook.Repo.CloneURL
|
||||
repo.Branch = hook.Repo.DefaultBranch
|
||||
repo.Kind = model.RepoGit
|
||||
|
||||
build := &model.Build{}
|
||||
build.Event = model.EventPush
|
||||
build.Commit = hook.Head.ID
|
||||
build.Ref = hook.Ref
|
||||
build.Link = hook.Head.URL
|
||||
build.Branch = strings.Replace(build.Ref, "refs/heads/", "", -1)
|
||||
build.Message = hook.Head.Message
|
||||
// build.Timestamp = hook.Head.Timestamp
|
||||
build.Email = hook.Head.Author.Email
|
||||
build.Avatar = hook.Sender.Avatar
|
||||
build.Author = hook.Sender.Login
|
||||
build.Remote = hook.Repo.CloneURL
|
||||
|
||||
if len(build.Author) == 0 {
|
||||
build.Author = hook.Head.Author.Username
|
||||
// default gravatar?
|
||||
}
|
||||
|
||||
if strings.HasPrefix(build.Ref, "refs/tags/") {
|
||||
// just kidding, this is actually a tag event
|
||||
build.Event = model.EventTag
|
||||
}
|
||||
|
||||
return repo, build, nil
|
||||
}
|
||||
|
||||
// pullRequest parses a hook with event type `pullRequest`
|
||||
// and returns the commit data.
|
||||
func (g *Github) pullRequest(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||
payload := GetPayload(r)
|
||||
hook := &struct {
|
||||
Action string `json:"action"`
|
||||
PullRequest *github.PullRequest `json:"pull_request"`
|
||||
Repo *github.Repository `json:"repository"`
|
||||
}{}
|
||||
err := json.Unmarshal(payload, hook)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// ignore these
|
||||
if hook.Action != "opened" && hook.Action != "synchronize" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
if *hook.PullRequest.State != "open" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
repo := &model.Repo{}
|
||||
repo.Owner = *hook.Repo.Owner.Login
|
||||
repo.Name = *hook.Repo.Name
|
||||
repo.FullName = *hook.Repo.FullName
|
||||
repo.Link = *hook.Repo.HTMLURL
|
||||
repo.IsPrivate = *hook.Repo.Private
|
||||
repo.Clone = *hook.Repo.CloneURL
|
||||
repo.Kind = model.RepoGit
|
||||
repo.Branch = "master"
|
||||
if hook.Repo.DefaultBranch != nil {
|
||||
repo.Branch = *hook.Repo.DefaultBranch
|
||||
}
|
||||
|
||||
build := &model.Build{}
|
||||
build.Event = model.EventPull
|
||||
build.Commit = *hook.PullRequest.Head.SHA
|
||||
build.Ref = fmt.Sprintf("refs/pull/%d/%s", *hook.PullRequest.Number, g.MergeRef)
|
||||
build.Link = *hook.PullRequest.HTMLURL
|
||||
build.Branch = *hook.PullRequest.Head.Ref
|
||||
build.Message = *hook.PullRequest.Title
|
||||
build.Author = *hook.PullRequest.User.Login
|
||||
build.Avatar = *hook.PullRequest.User.AvatarURL
|
||||
build.Remote = *hook.PullRequest.Base.Repo.CloneURL
|
||||
build.Title = *hook.PullRequest.Title
|
||||
// build.Timestamp = time.Now().UTC().Format("2006-01-02 15:04:05.000000000 +0000 MST")
|
||||
|
||||
return repo, build, nil
|
||||
}
|
||||
|
||||
func (g *Github) deployment(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||
payload := GetPayload(r)
|
||||
hook := &deployHook{}
|
||||
|
||||
err := json.Unmarshal(payload, hook)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// for older versions of GitHub. Remove.
|
||||
if hook.Deployment.ID == 0 {
|
||||
hook.Deployment.ID = hook.ID
|
||||
hook.Deployment.Sha = hook.Sha
|
||||
hook.Deployment.Ref = hook.Ref
|
||||
hook.Deployment.Task = hook.Name
|
||||
hook.Deployment.Env = hook.Env
|
||||
hook.Deployment.Desc = hook.Desc
|
||||
}
|
||||
|
||||
repo := &model.Repo{}
|
||||
repo.Owner = hook.Repo.Owner.Login
|
||||
if len(repo.Owner) == 0 {
|
||||
repo.Owner = hook.Repo.Owner.Name
|
||||
}
|
||||
repo.Name = hook.Repo.Name
|
||||
repo.FullName = fmt.Sprintf("%s/%s", repo.Owner, repo.Name)
|
||||
repo.Link = hook.Repo.HTMLURL
|
||||
repo.IsPrivate = hook.Repo.Private
|
||||
repo.Clone = hook.Repo.CloneURL
|
||||
repo.Branch = hook.Repo.DefaultBranch
|
||||
repo.Kind = model.RepoGit
|
||||
|
||||
// ref can be
|
||||
// branch, tag, or sha
|
||||
|
||||
build := &model.Build{}
|
||||
build.Event = model.EventDeploy
|
||||
build.Commit = hook.Deployment.Sha
|
||||
build.Link = hook.Deployment.Url
|
||||
build.Message = hook.Deployment.Desc
|
||||
build.Avatar = hook.Sender.Avatar
|
||||
build.Author = hook.Sender.Login
|
||||
build.Ref = hook.Deployment.Ref
|
||||
build.Branch = hook.Deployment.Ref
|
||||
build.Deploy = hook.Deployment.Env
|
||||
|
||||
// if the ref is a sha or short sha we need to manually
|
||||
// construct the ref.
|
||||
if strings.HasPrefix(build.Commit, build.Ref) || build.Commit == build.Ref {
|
||||
build.Branch = repo.Branch
|
||||
build.Ref = fmt.Sprintf("refs/heads/%s", repo.Branch)
|
||||
|
||||
}
|
||||
// if the ref is a branch we should make sure it has refs/heads prefix
|
||||
if !strings.HasPrefix(build.Ref, "refs/") { // branch or tag
|
||||
build.Ref = fmt.Sprintf("refs/heads/%s", build.Branch)
|
||||
|
||||
}
|
||||
|
||||
return repo, build, nil
|
||||
}
|
||||
|
||||
func (g *Github) String() string {
|
||||
return "github"
|
||||
}
|
||||
|
||||
const (
|
||||
StatusPending = "pending"
|
||||
StatusSuccess = "success"
|
||||
StatusFailure = "failure"
|
||||
StatusError = "error"
|
||||
)
|
||||
|
||||
const (
|
||||
DescPending = "this build is pending"
|
||||
DescSuccess = "the build was successful"
|
||||
DescFailure = "the build failed"
|
||||
DescError = "oops, something went wrong"
|
||||
)
|
||||
|
||||
// getStatus is a helper functin that converts a Drone
|
||||
// status to a GitHub status.
|
||||
func getStatus(status string) string {
|
||||
switch status {
|
||||
case model.StatusPending, model.StatusRunning:
|
||||
return StatusPending
|
||||
case model.StatusSuccess:
|
||||
return StatusSuccess
|
||||
case model.StatusFailure:
|
||||
return StatusFailure
|
||||
case model.StatusError, model.StatusKilled:
|
||||
return StatusError
|
||||
default:
|
||||
return StatusError
|
||||
}
|
||||
}
|
||||
|
||||
// getDesc is a helper function that generates a description
|
||||
// message for the build based on the status.
|
||||
func getDesc(status string) string {
|
||||
switch status {
|
||||
case model.StatusPending, model.StatusRunning:
|
||||
return DescPending
|
||||
case model.StatusSuccess:
|
||||
return DescSuccess
|
||||
case model.StatusFailure:
|
||||
return DescFailure
|
||||
case model.StatusError, model.StatusKilled:
|
||||
return DescError
|
||||
default:
|
||||
return DescError
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
func TestHook(t *testing.T) {
|
||||
var (
|
||||
github Github
|
||||
github client
|
||||
r *http.Request
|
||||
body *bytes.Buffer
|
||||
)
|
||||
|
@ -20,7 +20,7 @@ func TestHook(t *testing.T) {
|
|||
|
||||
g.Describe("Hook", func() {
|
||||
g.BeforeEach(func() {
|
||||
github = Github{}
|
||||
github = client{}
|
||||
body = bytes.NewBuffer([]byte{})
|
||||
r, _ = http.NewRequest("POST", "https://drone.com/hook", body)
|
||||
})
|
||||
|
@ -31,11 +31,11 @@ func TestHook(t *testing.T) {
|
|||
})
|
||||
|
||||
g.It("Should set build author to the pull request author", func() {
|
||||
hookJson, err := ioutil.ReadFile("fixtures/pull_request.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
hookJSON, ioerr := ioutil.ReadFile("fixtures/pull_request.json")
|
||||
if ioerr != nil {
|
||||
panic(ioerr)
|
||||
}
|
||||
body.Write(hookJson)
|
||||
body.Write(hookJSON)
|
||||
|
||||
_, build, err := github.Hook(r)
|
||||
g.Assert(err).Equal(nil)
|
||||
|
|
|
@ -1,40 +1,14 @@
|
|||
package github
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/base32"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/drone/drone/shared/oauth2"
|
||||
"github.com/google/go-github/github"
|
||||
"github.com/gorilla/securecookie"
|
||||
)
|
||||
|
||||
// NewClient is a helper function that returns a new GitHub
|
||||
// client using the provided OAuth token.
|
||||
func NewClient(uri, token string, skipVerify bool) *github.Client {
|
||||
t := &oauth2.Transport{
|
||||
Token: &oauth2.Token{AccessToken: token},
|
||||
}
|
||||
|
||||
// this is for GitHub enterprise users that are using
|
||||
// self-signed certificates.
|
||||
if skipVerify {
|
||||
t.Transport = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
}
|
||||
|
||||
c := github.NewClient(t.Client())
|
||||
c.BaseURL, _ = url.Parse(uri)
|
||||
return c
|
||||
}
|
||||
|
||||
// GetUserEmail is a heper function that retrieves the currently
|
||||
// authenticated user from GitHub + Email address.
|
||||
func GetUserEmail(client *github.Client) (*github.User, error) {
|
||||
|
@ -58,7 +32,7 @@ func GetUserEmail(client *github.Client) (*github.User, error) {
|
|||
// WARNING, HACK
|
||||
// for out-of-date github enterprise editions the primary
|
||||
// and verified fields won't exist.
|
||||
if !strings.HasPrefix(*user.URL, DefaultAPI) && len(emails) != 0 {
|
||||
if !strings.HasPrefix(*user.URL, defaultAPI) && len(emails) != 0 {
|
||||
user.Email = emails[0].Email
|
||||
return user, nil
|
||||
}
|
||||
|
@ -66,56 +40,6 @@ func GetUserEmail(client *github.Client) (*github.User, error) {
|
|||
return nil, fmt.Errorf("No verified Email address for GitHub account")
|
||||
}
|
||||
|
||||
// GetRepo is a helper function that returns a named repo
|
||||
func GetRepo(client *github.Client, owner, repo string) (*github.Repository, error) {
|
||||
r, _, err := client.Repositories.Get(owner, repo)
|
||||
return r, err
|
||||
}
|
||||
|
||||
// GetUserRepos is a helper function that returns a list of
|
||||
// all user repositories. Paginated results are aggregated into
|
||||
// a single list.
|
||||
func GetUserRepos(client *github.Client) ([]github.Repository, error) {
|
||||
var repos []github.Repository
|
||||
var opts = github.RepositoryListOptions{}
|
||||
opts.PerPage = 100
|
||||
opts.Page = 1
|
||||
|
||||
// loop through user repository list
|
||||
for opts.Page > 0 {
|
||||
list, resp, err := client.Repositories.List("", &opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repos = append(repos, list...)
|
||||
|
||||
// increment the next page to retrieve
|
||||
opts.Page = resp.NextPage
|
||||
}
|
||||
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
// GetOrgs is a helper function that returns a list of
|
||||
// all orgs that a user belongs to.
|
||||
func GetOrgs(client *github.Client) ([]github.Organization, error) {
|
||||
var orgs []github.Organization
|
||||
var opts = github.ListOptions{}
|
||||
opts.Page = 1
|
||||
|
||||
for opts.Page > 0 {
|
||||
list, resp, err := client.Organizations.List("", &opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
orgs = append(orgs, list...)
|
||||
|
||||
// increment the next page to retrieve
|
||||
opts.Page = resp.NextPage
|
||||
}
|
||||
return orgs, nil
|
||||
}
|
||||
|
||||
// GetHook is a heper function that retrieves a hook by
|
||||
// hostname. To do this, it will retrieve a list of all hooks
|
||||
// and iterate through the list.
|
||||
|
@ -136,6 +60,7 @@ func GetHook(client *github.Client, owner, name, url string) (*github.Hook, erro
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
// DeleteHook does exactly what you think it does.
|
||||
func DeleteHook(client *github.Client, owner, name, url string) error {
|
||||
hook, err := GetHook(client, owner, name, url)
|
||||
if err != nil {
|
||||
|
@ -179,24 +104,6 @@ func CreateUpdateHook(client *github.Client, owner, name, url string) (*github.H
|
|||
return CreateHook(client, owner, name, url)
|
||||
}
|
||||
|
||||
// GetFile is a heper function that retrieves a file from
|
||||
// GitHub and returns its contents in byte array format.
|
||||
func GetFile(client *github.Client, owner, name, path, ref string) ([]byte, error) {
|
||||
var opts = new(github.RepositoryContentGetOptions)
|
||||
opts.Ref = ref
|
||||
content, _, _, err := client.Repositories.GetContents(owner, name, path, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return content.Decode()
|
||||
}
|
||||
|
||||
// GetRandom is a helper function that generates a 32-bit random
|
||||
// key, base32 encoded as a string value.
|
||||
func GetRandom() string {
|
||||
return base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32))
|
||||
}
|
||||
|
||||
// GetPayload is a helper function that will parse the JSON payload. It will
|
||||
// first check for a `payload` parameter in a POST, but can fallback to a
|
||||
// raw JSON body as well.
|
||||
|
|
184
remote/github/parser.go
Normal file
184
remote/github/parser.go
Normal file
|
@ -0,0 +1,184 @@
|
|||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/google/go-github/github"
|
||||
)
|
||||
|
||||
// push parses a hook with event type `push` and returns
|
||||
// the commit data.
|
||||
func (c *client) push(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||
payload := GetPayload(r)
|
||||
hook := &pushHook{}
|
||||
err := json.Unmarshal(payload, hook)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if hook.Deleted {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
repo := &model.Repo{}
|
||||
repo.Owner = hook.Repo.Owner.Login
|
||||
if len(repo.Owner) == 0 {
|
||||
repo.Owner = hook.Repo.Owner.Name
|
||||
}
|
||||
repo.Name = hook.Repo.Name
|
||||
// Generating rather than using hook.Repo.FullName as it's
|
||||
// not always present
|
||||
repo.FullName = fmt.Sprintf("%s/%s", repo.Owner, repo.Name)
|
||||
repo.Link = hook.Repo.HTMLURL
|
||||
repo.IsPrivate = hook.Repo.Private
|
||||
repo.Clone = hook.Repo.CloneURL
|
||||
repo.Branch = hook.Repo.DefaultBranch
|
||||
repo.Kind = model.RepoGit
|
||||
|
||||
build := &model.Build{}
|
||||
build.Event = model.EventPush
|
||||
build.Commit = hook.Head.ID
|
||||
build.Ref = hook.Ref
|
||||
build.Link = hook.Head.URL
|
||||
build.Branch = strings.Replace(build.Ref, "refs/heads/", "", -1)
|
||||
build.Message = hook.Head.Message
|
||||
// build.Timestamp = hook.Head.Timestamp
|
||||
build.Email = hook.Head.Author.Email
|
||||
build.Avatar = hook.Sender.Avatar
|
||||
build.Author = hook.Sender.Login
|
||||
build.Remote = hook.Repo.CloneURL
|
||||
|
||||
if len(build.Author) == 0 {
|
||||
build.Author = hook.Head.Author.Username
|
||||
// default gravatar?
|
||||
}
|
||||
|
||||
if strings.HasPrefix(build.Ref, "refs/tags/") {
|
||||
// just kidding, this is actually a tag event
|
||||
build.Event = model.EventTag
|
||||
}
|
||||
|
||||
return repo, build, nil
|
||||
}
|
||||
|
||||
// pullRequest parses a hook with event type `pullRequest`
|
||||
// and returns the commit data.
|
||||
func (c *client) pullRequest(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||
payload := GetPayload(r)
|
||||
hook := &struct {
|
||||
Action string `json:"action"`
|
||||
PullRequest *github.PullRequest `json:"pull_request"`
|
||||
Repo *github.Repository `json:"repository"`
|
||||
}{}
|
||||
err := json.Unmarshal(payload, hook)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// ignore these
|
||||
if hook.Action != "opened" && hook.Action != "synchronize" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
if *hook.PullRequest.State != "open" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
repo := &model.Repo{}
|
||||
repo.Owner = *hook.Repo.Owner.Login
|
||||
repo.Name = *hook.Repo.Name
|
||||
repo.FullName = *hook.Repo.FullName
|
||||
repo.Link = *hook.Repo.HTMLURL
|
||||
repo.IsPrivate = *hook.Repo.Private
|
||||
repo.Clone = *hook.Repo.CloneURL
|
||||
repo.Kind = model.RepoGit
|
||||
repo.Branch = "master"
|
||||
if hook.Repo.DefaultBranch != nil {
|
||||
repo.Branch = *hook.Repo.DefaultBranch
|
||||
}
|
||||
|
||||
build := &model.Build{}
|
||||
build.Event = model.EventPull
|
||||
build.Commit = *hook.PullRequest.Head.SHA
|
||||
build.Link = *hook.PullRequest.HTMLURL
|
||||
build.Branch = *hook.PullRequest.Head.Ref
|
||||
build.Message = *hook.PullRequest.Title
|
||||
build.Author = *hook.PullRequest.User.Login
|
||||
build.Avatar = *hook.PullRequest.User.AvatarURL
|
||||
build.Remote = *hook.PullRequest.Base.Repo.CloneURL
|
||||
build.Title = *hook.PullRequest.Title
|
||||
|
||||
if c.MergeRef {
|
||||
build.Ref = fmt.Sprintf("refs/pull/%d/merge", *hook.PullRequest.Number)
|
||||
} else {
|
||||
build.Ref = fmt.Sprintf("refs/pull/%d/head", *hook.PullRequest.Number)
|
||||
}
|
||||
|
||||
// build.Timestamp = time.Now().UTC().Format("2006-01-02 15:04:05.000000000 +0000 MST")
|
||||
|
||||
return repo, build, nil
|
||||
}
|
||||
|
||||
func (c *client) deployment(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||
payload := GetPayload(r)
|
||||
hook := &deployHook{}
|
||||
|
||||
err := json.Unmarshal(payload, hook)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// for older versions of GitHub. Remove.
|
||||
if hook.Deployment.ID == 0 {
|
||||
hook.Deployment.ID = hook.ID
|
||||
hook.Deployment.Sha = hook.Sha
|
||||
hook.Deployment.Ref = hook.Ref
|
||||
hook.Deployment.Task = hook.Name
|
||||
hook.Deployment.Env = hook.Env
|
||||
hook.Deployment.Desc = hook.Desc
|
||||
}
|
||||
|
||||
repo := &model.Repo{}
|
||||
repo.Owner = hook.Repo.Owner.Login
|
||||
if len(repo.Owner) == 0 {
|
||||
repo.Owner = hook.Repo.Owner.Name
|
||||
}
|
||||
repo.Name = hook.Repo.Name
|
||||
repo.FullName = fmt.Sprintf("%s/%s", repo.Owner, repo.Name)
|
||||
repo.Link = hook.Repo.HTMLURL
|
||||
repo.IsPrivate = hook.Repo.Private
|
||||
repo.Clone = hook.Repo.CloneURL
|
||||
repo.Branch = hook.Repo.DefaultBranch
|
||||
repo.Kind = model.RepoGit
|
||||
|
||||
// ref can be
|
||||
// branch, tag, or sha
|
||||
|
||||
build := &model.Build{}
|
||||
build.Event = model.EventDeploy
|
||||
build.Commit = hook.Deployment.Sha
|
||||
build.Link = hook.Deployment.URL
|
||||
build.Message = hook.Deployment.Desc
|
||||
build.Avatar = hook.Sender.Avatar
|
||||
build.Author = hook.Sender.Login
|
||||
build.Ref = hook.Deployment.Ref
|
||||
build.Branch = hook.Deployment.Ref
|
||||
build.Deploy = hook.Deployment.Env
|
||||
|
||||
// if the ref is a sha or short sha we need to manually
|
||||
// construct the ref.
|
||||
if strings.HasPrefix(build.Commit, build.Ref) || build.Commit == build.Ref {
|
||||
build.Branch = repo.Branch
|
||||
build.Ref = fmt.Sprintf("refs/heads/%s", repo.Branch)
|
||||
|
||||
}
|
||||
// if the ref is a branch we should make sure it has refs/heads prefix
|
||||
if !strings.HasPrefix(build.Ref, "refs/") { // branch or tag
|
||||
build.Ref = fmt.Sprintf("refs/heads/%s", build.Branch)
|
||||
|
||||
}
|
||||
|
||||
return repo, build, nil
|
||||
}
|
1
remote/github/parser_test.go
Normal file
1
remote/github/parser_test.go
Normal file
|
@ -0,0 +1 @@
|
|||
package github
|
|
@ -1,8 +1,5 @@
|
|||
package github
|
||||
|
||||
type postHook struct {
|
||||
}
|
||||
|
||||
type pushHook struct {
|
||||
Ref string `json:"ref"`
|
||||
Deleted bool `json:"deleted"`
|
||||
|
@ -54,7 +51,7 @@ type deployHook struct {
|
|||
Ref string `json:"ref"`
|
||||
Task string `json:"task"`
|
||||
Env string `json:"environment"`
|
||||
Url string `json:"url"`
|
||||
URL string `json:"url"`
|
||||
Desc string `json:"description"`
|
||||
} `json:"deployment"`
|
||||
|
||||
|
@ -78,9 +75,8 @@ type deployHook struct {
|
|||
DefaultBranch string `json:"default_branch"`
|
||||
} `json:"repository"`
|
||||
|
||||
// these are legacy fields that have been moded
|
||||
// to the deployment section. They are here for
|
||||
// older versions of GitHub and will be removed
|
||||
// these are legacy fields that have been added to the deployment section.
|
||||
// They are here for older versions of GitHub and will be removed.
|
||||
|
||||
ID int64 `json:"id"`
|
||||
Sha string `json:"sha"`
|
||||
|
|
|
@ -90,13 +90,15 @@ func setupGitlab(c *cli.Context) (remote.Remote, error) {
|
|||
|
||||
// helper function to setup the GitHub remote from the CLI arguments.
|
||||
func setupGithub(c *cli.Context) (remote.Remote, error) {
|
||||
return github.New(
|
||||
c.String("github-server"),
|
||||
c.String("github-client"),
|
||||
c.String("github-sercret"),
|
||||
c.StringSlice("github-scope"),
|
||||
c.Bool("github-private-mode"),
|
||||
c.Bool("github-skip-verify"),
|
||||
c.BoolT("github-merge-ref"),
|
||||
)
|
||||
return github.New(github.Opts{
|
||||
URL: c.String("github-server"),
|
||||
Client: c.String("github-client"),
|
||||
Secret: c.String("github-sercret"),
|
||||
Scopes: c.StringSlice("github-scope"),
|
||||
Username: c.String("github-git-username"),
|
||||
Password: c.String("github-git-password"),
|
||||
PrivateMode: c.Bool("github-private-mode"),
|
||||
SkipVerify: c.Bool("github-skip-verify"),
|
||||
MergeRef: c.BoolT("github-merge-ref"),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -8,14 +8,18 @@ import (
|
|||
// AuthorizeAgent authorizes requsts from build agents to access the queue.
|
||||
func AuthorizeAgent(c *gin.Context) {
|
||||
secret := c.MustGet("agent").(string)
|
||||
if secret == "" {
|
||||
c.String(401, "invalid or empty token.")
|
||||
return
|
||||
}
|
||||
|
||||
parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
|
||||
return secret, nil
|
||||
})
|
||||
if err != nil {
|
||||
c.AbortWithError(403, err)
|
||||
c.String(500, "invalid or empty token. %s", err)
|
||||
} else if parsed.Kind != token.AgentToken {
|
||||
c.AbortWithStatus(403)
|
||||
c.String(403, "invalid token. please use an agent token")
|
||||
} else {
|
||||
c.Next()
|
||||
}
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/remote"
|
||||
"github.com/drone/drone/shared/crypto"
|
||||
"github.com/drone/drone/shared/httputil"
|
||||
"github.com/drone/drone/shared/token"
|
||||
"github.com/drone/drone/store"
|
||||
"github.com/gorilla/securecookie"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
@ -39,12 +40,23 @@ func GetLogin(c *gin.Context) {
|
|||
if err != nil {
|
||||
|
||||
// if self-registration is disabled we should return a not authorized error
|
||||
if !config.Open {
|
||||
if !config.Open && !config.IsAdmin(tmpuser) {
|
||||
logrus.Errorf("cannot register %s. registration closed", tmpuser.Login)
|
||||
c.Redirect(303, "/login?error=access_denied")
|
||||
return
|
||||
}
|
||||
|
||||
// if self-registration is enabled for whitelisted organizations we need to
|
||||
// check the user's organization membership.
|
||||
if len(config.Orgs) != 0 {
|
||||
teams, terr := remote.Teams(c, tmpuser)
|
||||
if terr != nil || config.IsMember(teams) == false {
|
||||
logrus.Errorf("cannot verify team membership for %s.", u.Login)
|
||||
c.Redirect(303, "/login?error=access_denied")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// create the user account
|
||||
u = &model.User{
|
||||
Login: tmpuser.Login,
|
||||
|
@ -52,7 +64,9 @@ func GetLogin(c *gin.Context) {
|
|||
Secret: tmpuser.Secret,
|
||||
Email: tmpuser.Email,
|
||||
Avatar: tmpuser.Avatar,
|
||||
Hash: crypto.Rand(),
|
||||
Hash: base32.StdEncoding.EncodeToString(
|
||||
securecookie.GenerateRandomKey(32),
|
||||
),
|
||||
}
|
||||
|
||||
// insert the user into the database
|
||||
|
@ -69,33 +83,23 @@ func GetLogin(c *gin.Context) {
|
|||
u.Email = tmpuser.Email
|
||||
u.Avatar = tmpuser.Avatar
|
||||
|
||||
// if self-registration is enabled for whitelisted organizations we need to
|
||||
// check the user's organization membership.
|
||||
if len(config.Orgs) != 0 {
|
||||
teams, terr := remote.Teams(c, u)
|
||||
if terr != nil || config.IsMember(teams) == false {
|
||||
logrus.Errorf("cannot verify team membership for %s.", u.Login)
|
||||
c.Redirect(303, "/login?error=access_denied")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := store.UpdateUser(c, u); err != nil {
|
||||
logrus.Errorf("cannot update %s. %s", u.Login, err)
|
||||
c.Redirect(303, "/login?error=internal_error")
|
||||
return
|
||||
}
|
||||
|
||||
if len(config.Orgs) != 0 {
|
||||
teams, terr := remote.Teams(c, u)
|
||||
if terr != nil {
|
||||
logrus.Errorf("cannot verify team membership for %s. %s.", tmpuser.Login, terr)
|
||||
c.Redirect(303, "/login?error=access_denied")
|
||||
return
|
||||
}
|
||||
var member bool
|
||||
for _, team := range teams {
|
||||
if config.Orgs[team.Login] {
|
||||
member = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !member {
|
||||
logrus.Errorf("cannot verify team membership for %s. %s.", tmpuser.Login, terr)
|
||||
c.Redirect(303, "/login?error=access_denied")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
exp := time.Now().Add(time.Hour * 72).Unix()
|
||||
token := token.New(token.SessToken, u.Login)
|
||||
tokenstr, err := token.SignExpires(u.Hash, exp)
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/securecookie"
|
||||
|
||||
"github.com/drone/drone/cache"
|
||||
"github.com/drone/drone/remote"
|
||||
"github.com/drone/drone/router/middleware/session"
|
||||
"github.com/drone/drone/shared/crypto"
|
||||
"github.com/drone/drone/shared/httputil"
|
||||
"github.com/drone/drone/shared/token"
|
||||
"github.com/drone/drone/store"
|
||||
|
@ -54,7 +55,9 @@ func PostRepo(c *gin.Context) {
|
|||
r.AllowPush = true
|
||||
r.AllowPull = true
|
||||
r.Timeout = 60 // 1 hour default build time
|
||||
r.Hash = crypto.Rand()
|
||||
r.Hash = base32.StdEncoding.EncodeToString(
|
||||
securecookie.GenerateRandomKey(32),
|
||||
)
|
||||
|
||||
// crates the jwt token used to verify the repository
|
||||
t := token.New(token.HookToken, r.FullName)
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/securecookie"
|
||||
|
||||
"github.com/drone/drone/cache"
|
||||
"github.com/drone/drone/router/middleware/session"
|
||||
"github.com/drone/drone/shared/crypto"
|
||||
"github.com/drone/drone/shared/token"
|
||||
"github.com/drone/drone/store"
|
||||
)
|
||||
|
@ -69,7 +70,9 @@ func PostToken(c *gin.Context) {
|
|||
|
||||
func DeleteToken(c *gin.Context) {
|
||||
user := session.User(c)
|
||||
user.Hash = crypto.Rand()
|
||||
user.Hash = base32.StdEncoding.EncodeToString(
|
||||
securecookie.GenerateRandomKey(32),
|
||||
)
|
||||
if err := store.UpdateUser(c, user); err != nil {
|
||||
c.String(500, "Error revoking tokens. %s", err)
|
||||
return
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/securecookie"
|
||||
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/shared/crypto"
|
||||
"github.com/drone/drone/store"
|
||||
)
|
||||
|
||||
|
@ -14,9 +15,9 @@ func GetUsers(c *gin.Context) {
|
|||
users, err := store.GetUserList(c)
|
||||
if err != nil {
|
||||
c.String(500, "Error getting user list. %s", err)
|
||||
} else {
|
||||
c.JSON(200, users)
|
||||
return
|
||||
}
|
||||
c.JSON(200, users)
|
||||
}
|
||||
|
||||
func GetUser(c *gin.Context) {
|
||||
|
@ -41,7 +42,6 @@ func PatchUser(c *gin.Context) {
|
|||
c.AbortWithStatus(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
user.Admin = in.Admin
|
||||
user.Active = in.Active
|
||||
|
||||
err = store.UpdateUser(c, user)
|
||||
|
@ -65,7 +65,9 @@ func PostUser(c *gin.Context) {
|
|||
Login: in.Login,
|
||||
Email: in.Email,
|
||||
Avatar: in.Avatar,
|
||||
Hash: crypto.Rand(),
|
||||
Hash: base32.StdEncoding.EncodeToString(
|
||||
securecookie.GenerateRandomKey(32),
|
||||
),
|
||||
}
|
||||
if err = store.CreateUser(c, user); err != nil {
|
||||
c.String(http.StatusInternalServerError, err.Error())
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"io"
|
||||
|
||||
"code.google.com/p/go.crypto/ssh"
|
||||
"github.com/square/go-jose"
|
||||
)
|
||||
|
||||
const (
|
||||
RSA_BITS = 2048 // Default number of bits in an RSA key
|
||||
RSA_BITS_MIN = 768 // Minimum number of bits in an RSA key
|
||||
)
|
||||
|
||||
// standard characters allowed in token string.
|
||||
var chars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
|
||||
|
||||
// default token length
|
||||
var length = 32
|
||||
|
||||
// Rand generates a 32-bit random string.
|
||||
func Rand() string {
|
||||
b := make([]byte, length)
|
||||
r := make([]byte, length+(length/4)) // storage for random bytes.
|
||||
clen := byte(len(chars))
|
||||
maxrb := byte(256 - (256 % len(chars)))
|
||||
i := 0
|
||||
for {
|
||||
io.ReadFull(rand.Reader, r)
|
||||
for _, c := range r {
|
||||
if c >= maxrb {
|
||||
// Skip this number to avoid modulo bias.
|
||||
continue
|
||||
}
|
||||
b[i] = chars[c%clen]
|
||||
i++
|
||||
if i == length {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// helper function to generate an RSA Private Key.
|
||||
func GeneratePrivateKey() (*rsa.PrivateKey, error) {
|
||||
return rsa.GenerateKey(rand.Reader, RSA_BITS)
|
||||
}
|
||||
|
||||
// helper function that marshalls an RSA Public Key to an SSH
|
||||
// .authorized_keys format
|
||||
func MarshalPublicKey(public *rsa.PublicKey) []byte {
|
||||
private, err := ssh.NewPublicKey(public)
|
||||
if err != nil {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
return ssh.MarshalAuthorizedKey(private)
|
||||
}
|
||||
|
||||
// helper function that marshalls an RSA Private Key to
|
||||
// a PEM encoded file.
|
||||
func MarshalPrivateKey(private *rsa.PrivateKey) []byte {
|
||||
marshaled := x509.MarshalPKCS1PrivateKey(private)
|
||||
encoded := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Headers: nil, Bytes: marshaled})
|
||||
return encoded
|
||||
}
|
||||
|
||||
// UnmarshalPrivateKey is a helper function that unmarshals a PEM
|
||||
// bytes to an RSA Private Key
|
||||
func UnmarshalPrivateKey(private []byte) *rsa.PrivateKey {
|
||||
decoded, _ := pem.Decode(private)
|
||||
parsed, err := x509.ParsePKCS1PrivateKey(decoded.Bytes)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
// Encrypt encrypts a secret string.
|
||||
func Encrypt(in, privKey string) (string, error) {
|
||||
rsaPrivKey, err := decodePrivateKey(privKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return encrypt(in, &rsaPrivKey.PublicKey)
|
||||
}
|
||||
|
||||
// decodePrivateKey is a helper function that unmarshals a PEM
|
||||
// bytes to an RSA Private Key
|
||||
func decodePrivateKey(privateKey string) (*rsa.PrivateKey, error) {
|
||||
derBlock, _ := pem.Decode([]byte(privateKey))
|
||||
return x509.ParsePKCS1PrivateKey(derBlock.Bytes)
|
||||
}
|
||||
|
||||
// encrypt encrypts a plaintext variable using JOSE with
|
||||
// RSA_OAEP and A128GCM algorithms.
|
||||
func encrypt(text string, pubKey *rsa.PublicKey) (string, error) {
|
||||
var encrypted string
|
||||
var plaintext = []byte(text)
|
||||
|
||||
// Creates a new encrypter using defaults
|
||||
encrypter, err := jose.NewEncrypter(jose.RSA_OAEP, jose.A128GCM, pubKey)
|
||||
if err != nil {
|
||||
return encrypted, err
|
||||
}
|
||||
// Encrypts the plaintext value and serializes
|
||||
// as a JOSE string.
|
||||
object, err := encrypter.Encrypt(plaintext)
|
||||
if err != nil {
|
||||
return encrypted, err
|
||||
}
|
||||
return object.CompactSerialize()
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
"github.com/square/go-jose"
|
||||
)
|
||||
|
||||
func TestKeys(t *testing.T) {
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Generate Key", func() {
|
||||
|
||||
g.It("Generates a private key", func() {
|
||||
_, err := GeneratePrivateKey()
|
||||
g.Assert(err == nil).IsTrue()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Encrypt(t *testing.T) {
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Secure", func() {
|
||||
|
||||
g.It("Should encrypt a string", func() {
|
||||
ciphertext, err := Encrypt("top_secret", fakePriv)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
|
||||
object, _ := jose.ParseEncrypted(ciphertext)
|
||||
privKey, _ := decodePrivateKey(fakePriv)
|
||||
plaintext, _ := object.Decrypt(privKey)
|
||||
g.Assert(string(plaintext)).Equal("top_secret")
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
var fakePriv = `
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEA71FaA+otDak2rXF/4h69Tz+OxS6NOWaOc/n7dinHXnlo3Toy
|
||||
ZzvwweJGQKIOfPNBMncz+8h6oLOByFvb95Z1UEM0d+KCFCCutOeN9NNMw4fkUtSZ
|
||||
7sm6T35wQUkDOiO1YAGy27hQfT7iryhPwA8KmgZmt7toNNf+WymPR8DMwAAYeqHA
|
||||
5DIEWWsg+RLohOJ0itIk9q6Us9WYhng0sZ9+U+C87FospjKRMyAinSvKx0Uan4ap
|
||||
YGbLjDQHimWtimfT4XWCGTO1cWno378Vm/newUN6WVaeZ2CSHcWgD2fWcjFixX2A
|
||||
SvcvfuCo7yZPUPWeiYKrc5d1CC3ncocu43LhSQIDAQABAoIBAQDIbYKM+sfmxAwF
|
||||
8KOg1gvIXjuNCrK+GxU9LmSajtzpU5cuiHoEGaBGUOJzaQXnQbcds9W2ji2dfxk3
|
||||
my87SShRIyfDK9GzV7fZzIAIRhrpO1tOv713zj0aLJOJKcPpIlTZ5jJMcC4A5vTk
|
||||
q0c3W6GOY8QNJohckXT2FnVoK6GPPiaZnavkwH33cJk0j1vMsbADdKF7Jdfq9FBF
|
||||
Lx+Za7wo79MQIr68KEqsqMpmrawIf1T3TqOCNbkPCL2tu5EfoyGIItrH33SBOV/B
|
||||
HbIfe4nJYZMWXhe3kZ/xCFqiRx6/wlc5pGCwCicgHJJe/l8Y9OticDCCyJDQtD8I
|
||||
6927/j2NAoGBAPNRRY8r5ES5f8ftEktcLwh2zw08PNkcolTeqsEMbWAQspV/v+Ay
|
||||
4niEXIN3ix2yTnMgrtxRGO7zdPnMaTN8E88FsSDKQ97lm7m3jo7lZtDMz16UxGmd
|
||||
AOOuXwUtpngz7OrQ25NXhvFYLTgLoPsv3PbFbF1pwbhZqPTttTdg5so3AoGBAPvK
|
||||
ta/n7DMZd/HptrkdkxxHaGN19ZjBVIqyeORhIDznEYjv9Z90JvzRxCmUriD4fyJC
|
||||
/XSTytORa34UgmOk1XFtxWusXhnYqCTIHG/MKCy9D4ifzFzii9y/M+EnQIMb658l
|
||||
+edLyrGFla+t5NS1XAqDYjfqpUFbMvU1kVoDJ/B/AoGBANBQe3o5PMSuAD19tdT5
|
||||
Rnc7qMcPFJVZE44P2SdQaW/+u7aM2gyr5AMEZ2RS+7LgDpQ4nhyX/f3OSA75t/PR
|
||||
PfBXUi/dm8AA2pNlGNM0ihMn1j6GpaY6OiG0DzwSulxdMHBVgjgijrCgKo66Pgfw
|
||||
EYDgw4cyXR1k/ec8gJK6Dr1/AoGBANvmSY77Kdnm4E4yIxbAsX39DznuBzQFhGQt
|
||||
Qk+SU6lc1H+Xshg0ROh/+qWl5/17iOzPPLPXb0getJZEKywDBTYu/D/xJa3E/fRB
|
||||
oDQzRNLtuudDSCPG5wc/JXv53+mhNMKlU/+gvcEUPYpUgIkUavHzlI/pKbJOh86H
|
||||
ng3Su8rZAn9w/zkoJu+n7sHta/Hp6zPTbvjZ1EijZp0+RygBgiv9UjDZ6D9EGcjR
|
||||
ZiFwuc8I0g7+GRkgG2NbfqX5Cewb/nbJQpHPO31bqJrcLzU0KurYAwQVx6WGW0He
|
||||
ERIlTeOMxVo6M0OpI+rH5bOLdLLEVhNtM/4HUFi1Qy6CCMbN2t3H
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`
|
Loading…
Reference in a new issue