refactoring github package to increase test coverage
This commit is contained in:
parent
78cfd3a0db
commit
f930545410
20 changed files with 872 additions and 873 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/)
|
* [CLI Reference](http://readme.drone.io/devs/cli/)
|
||||||
* [API Reference](http://readme.drone.io/devs/api/builds)
|
* [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
|
### 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.
|
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
|
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.
|
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 {
|
for {
|
||||||
if err := r.run(); err != nil {
|
if err := r.run(); err != nil {
|
||||||
dur := c.Duration("backoff")
|
dur := c.Duration("backoff")
|
||||||
logrus.Warnf("Attempting to reconnect in %v", dur)
|
logrus.Warnf("reconnect in %v. %s", dur, err.Error())
|
||||||
time.Sleep(dur)
|
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 = `
|
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 (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/base32"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -13,104 +14,111 @@ import (
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
"github.com/drone/drone/remote"
|
"github.com/drone/drone/remote"
|
||||||
"github.com/drone/drone/shared/httputil"
|
"github.com/drone/drone/shared/httputil"
|
||||||
"github.com/drone/drone/shared/oauth2"
|
"github.com/gorilla/securecookie"
|
||||||
|
|
||||||
"github.com/google/go-github/github"
|
"github.com/google/go-github/github"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DefaultURL = "https://github.com" // Default GitHub URL
|
defaultURL = "https://github.com" // Default GitHub URL
|
||||||
DefaultAPI = "https://api.github.com" // Default GitHub API 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
|
URL string
|
||||||
API string
|
API string
|
||||||
Client string
|
Client string
|
||||||
Secret string
|
Secret string
|
||||||
Scope string
|
Scope string
|
||||||
MergeRef string
|
Machine string
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
PrivateMode bool
|
PrivateMode bool
|
||||||
SkipVerify bool
|
SkipVerify bool
|
||||||
}
|
MergeRef 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login authenticates the session and returns the remote user details.
|
// 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{
|
code := req.FormValue("code")
|
||||||
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")
|
|
||||||
if len(code) == 0 {
|
if len(code) == 0 {
|
||||||
var random = GetRandom()
|
rand := base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32))
|
||||||
http.Redirect(res, req, config.AuthCodeURL(random), http.StatusSeeOther)
|
http.Redirect(res, req, config.AuthCodeURL(rand), http.StatusSeeOther)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var trans = &oauth2.Transport{
|
// TODO(bradrydzewski) what is the best way to provide a SkipVerify flag
|
||||||
Config: config,
|
// when exchanging the token?
|
||||||
}
|
|
||||||
if g.SkipVerify {
|
token, err := config.Exchange(oauth2.NoContext, code)
|
||||||
trans.Transport = &http.Transport{
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var token, err = trans.Exchange(code)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Error exchanging token. %s", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var client = NewClient(g.API, token.AccessToken, g.SkipVerify)
|
client := c.newClientToken(token.AccessToken)
|
||||||
var useremail, errr = GetUserEmail(client)
|
useremail, err := GetUserEmail(client)
|
||||||
if errr != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Error retrieving user or verified email. %s", errr)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
user := model.User{}
|
return &model.User{
|
||||||
user.Login = *useremail.Login
|
Login: *useremail.Login,
|
||||||
user.Email = *useremail.Email
|
Email: *useremail.Email,
|
||||||
user.Token = token.AccessToken
|
Token: token.AccessToken,
|
||||||
user.Avatar = *useremail.AvatarURL
|
Avatar: *useremail.AvatarURL,
|
||||||
return &user, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth authenticates the session and returns the remote user
|
// Auth returns the GitHub user login for the given access token.
|
||||||
// login for the given token and secret
|
func (c *client) Auth(token, secret string) (string, error) {
|
||||||
func (g *Github) Auth(token, secret string) (string, error) {
|
client := c.newClientToken(token)
|
||||||
client := NewClient(g.API, token, g.SkipVerify)
|
|
||||||
user, _, err := client.Users.Get("")
|
user, _, err := client.Users.Get("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -118,117 +126,150 @@ func (g *Github) Auth(token, secret string) (string, error) {
|
||||||
return *user.Login, nil
|
return *user.Login, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Github) Teams(u *model.User) ([]*model.Team, error) {
|
// Teams returns a list of all team membership for the GitHub account.
|
||||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
func (c *client) Teams(u *model.User) ([]*model.Team, error) {
|
||||||
orgs, err := GetOrgs(client)
|
client := c.newClientToken(u.Token)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
opts := new(github.ListOptions)
|
||||||
}
|
opts.Page = 1
|
||||||
|
|
||||||
var teams []*model.Team
|
var teams []*model.Team
|
||||||
for _, org := range orgs {
|
for opts.Page > 0 {
|
||||||
teams = append(teams, &model.Team{
|
list, resp, err := client.Organizations.List("", opts)
|
||||||
Login: *org.Login,
|
if err != nil {
|
||||||
Avatar: *org.AvatarURL,
|
return nil, err
|
||||||
})
|
}
|
||||||
|
teams = append(teams, convertTeamList(list)...)
|
||||||
|
opts.Page = resp.NextPage
|
||||||
}
|
}
|
||||||
return teams, nil
|
return teams, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repo fetches the named repository from the remote system.
|
// Repo returns the named GitHub repository.
|
||||||
func (g *Github) Repo(u *model.User, owner, name string) (*model.Repo, error) {
|
func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) {
|
||||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
client := c.newClientToken(u.Token)
|
||||||
r, err := GetRepo(client, owner, name)
|
repo, _, err := client.Repositories.Get(owner, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return convertRepo(repo, c.PrivateMode), nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repos fetches a list of repos from the remote system.
|
// Repos returns a list of all repositories for GitHub account, including
|
||||||
func (g *Github) Repos(u *model.User) ([]*model.RepoLite, error) {
|
// organization repositories.
|
||||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return convertPerm(repo), nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perm fetches the named repository permissions from
|
// File fetches the file from the Bitbucket repository and returns its contents.
|
||||||
// the remote system for the specified user.
|
func (c *client) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
|
||||||
func (g *Github) Perm(u *model.User, owner, name string) (*model.Perm, error) {
|
client := c.newClientToken(u.Token)
|
||||||
|
|
||||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
opts := new(github.RepositoryContentGetOptions)
|
||||||
repo, err := GetRepo(client, owner, name)
|
opts.Ref = b.Commit
|
||||||
|
data, _, _, err := client.Repositories.GetContents(r.Owner, r.Name, f, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
m := &model.Perm{}
|
return data.Decode()
|
||||||
m.Admin = (*repo.Permissions)["admin"]
|
|
||||||
m.Push = (*repo.Permissions)["push"]
|
|
||||||
m.Pull = (*repo.Permissions)["pull"]
|
|
||||||
return m, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// File fetches a file from the remote repository and returns in string format.
|
// Netrc returns a netrc file capable of authenticating GitHub requests and
|
||||||
func (g *Github) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
|
// cloning GitHub repositories. The netrc will use the global machine account
|
||||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
// when configured.
|
||||||
cfg, err := GetFile(client, r.Owner, r.Name, f, b.Commit)
|
func (c *client) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
||||||
return cfg, err
|
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.
|
// Status sends the commit status to the remote system.
|
||||||
// An example would be the GitHub pull request status.
|
// 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 {
|
func (c *client) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
||||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
client := c.newClientToken(u.Token)
|
||||||
if b.Event == "deployment" {
|
switch b.Event {
|
||||||
|
case "deployment":
|
||||||
return deploymentStatus(client, r, b, link)
|
return deploymentStatus(client, r, b, link)
|
||||||
} else {
|
default:
|
||||||
return repoStatus(client, r, b, link)
|
return repoStatus(client, r, b, link)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func repoStatus(client *github.Client, r *model.Repo, b *model.Build, link string) error {
|
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{
|
data := github.RepoStatus{
|
||||||
Context: github.String("continuous-integration/drone"),
|
Context: github.String("continuous-integration/drone"),
|
||||||
State: github.String(status),
|
State: github.String(convertStatus(b.Status)),
|
||||||
Description: github.String(desc),
|
Description: github.String(convertDesc(b.Status)),
|
||||||
TargetURL: github.String(link),
|
TargetURL: github.String(link),
|
||||||
}
|
}
|
||||||
_, _, err := client.Repositories.CreateStatus(r.Owner, r.Name, b.Commit, &data)
|
_, _, 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 {
|
func deploymentStatus(client *github.Client, r *model.Repo, b *model.Build, link string) error {
|
||||||
matches := reDeploy.FindStringSubmatch(b.Link)
|
matches := reDeploy.FindStringSubmatch(b.Link)
|
||||||
// if the deployment was not triggered from github, don't send a deployment status
|
|
||||||
if len(matches) != 2 {
|
if len(matches) != 2 {
|
||||||
return nil
|
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])
|
id, _ := strconv.Atoi(matches[1])
|
||||||
status := getStatus(b.Status)
|
|
||||||
desc := getDesc(b.Status)
|
|
||||||
data := github.DeploymentStatusRequest{
|
data := github.DeploymentStatusRequest{
|
||||||
State: github.String(status),
|
State: github.String(convertStatus(b.Status)),
|
||||||
Description: github.String(desc),
|
Description: github.String(convertDesc(b.Status)),
|
||||||
TargetURL: github.String(link),
|
TargetURL: github.String(link),
|
||||||
}
|
}
|
||||||
_, _, err := client.Repositories.CreateDeploymentStatus(r.Owner, r.Name, id, &data)
|
_, _, err := client.Repositories.CreateDeploymentStatus(r.Owner, r.Name, id, &data)
|
||||||
return err
|
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
|
// Activate activates a repository by creating the post-commit hook and
|
||||||
// adding the SSH deploy key, if applicable.
|
// adding the SSH deploy key, if applicable.
|
||||||
func (g *Github) Activate(u *model.User, r *model.Repo, link string) error {
|
func (c *client) Activate(u *model.User, r *model.Repo, link string) error {
|
||||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
client := c.newClientToken(u.Token)
|
||||||
_, err := CreateUpdateHook(client, r.Owner, r.Name, link)
|
_, err := CreateUpdateHook(client, r.Owner, r.Name, link)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deactivate removes a repository by removing all the post-commit hooks
|
// Deactivate removes a repository by removing all the post-commit hooks
|
||||||
// which are equal to link and removing the SSH deploy key.
|
// which are equal to link and removing the SSH deploy key.
|
||||||
func (g *Github) Deactivate(u *model.User, r *model.Repo, link string) error {
|
func (c *client) Deactivate(u *model.User, r *model.Repo, link string) error {
|
||||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
client := c.newClientToken(u.Token)
|
||||||
return DeleteHook(client, r.Owner, r.Name, link)
|
return DeleteHook(client, r.Owner, r.Name, link)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook parses the post-commit hook from the Request body
|
// Hook parses the post-commit hook from the Request body
|
||||||
// and returns the required data in a standard format.
|
// 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") {
|
switch r.Header.Get("X-Github-Event") {
|
||||||
case "pull_request":
|
case "pull_request":
|
||||||
return g.pullRequest(r)
|
return c.pullRequest(r)
|
||||||
case "push":
|
case "push":
|
||||||
return g.push(r)
|
return c.push(r)
|
||||||
case "deployment":
|
case "deployment":
|
||||||
return g.deployment(r)
|
return c.deployment(r)
|
||||||
default:
|
default:
|
||||||
return nil, nil, nil
|
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) {
|
func TestHook(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
github Github
|
github client
|
||||||
r *http.Request
|
r *http.Request
|
||||||
body *bytes.Buffer
|
body *bytes.Buffer
|
||||||
)
|
)
|
||||||
|
@ -20,7 +20,7 @@ func TestHook(t *testing.T) {
|
||||||
|
|
||||||
g.Describe("Hook", func() {
|
g.Describe("Hook", func() {
|
||||||
g.BeforeEach(func() {
|
g.BeforeEach(func() {
|
||||||
github = Github{}
|
github = client{}
|
||||||
body = bytes.NewBuffer([]byte{})
|
body = bytes.NewBuffer([]byte{})
|
||||||
r, _ = http.NewRequest("POST", "https://drone.com/hook", body)
|
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() {
|
g.It("Should set build author to the pull request author", func() {
|
||||||
hookJson, err := ioutil.ReadFile("fixtures/pull_request.json")
|
hookJSON, ioerr := ioutil.ReadFile("fixtures/pull_request.json")
|
||||||
if err != nil {
|
if ioerr != nil {
|
||||||
panic(err)
|
panic(ioerr)
|
||||||
}
|
}
|
||||||
body.Write(hookJson)
|
body.Write(hookJSON)
|
||||||
|
|
||||||
_, build, err := github.Hook(r)
|
_, build, err := github.Hook(r)
|
||||||
g.Assert(err).Equal(nil)
|
g.Assert(err).Equal(nil)
|
||||||
|
|
|
@ -1,40 +1,14 @@
|
||||||
package github
|
package github
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"encoding/base32"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/drone/drone/shared/oauth2"
|
|
||||||
"github.com/google/go-github/github"
|
"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
|
// GetUserEmail is a heper function that retrieves the currently
|
||||||
// authenticated user from GitHub + Email address.
|
// authenticated user from GitHub + Email address.
|
||||||
func GetUserEmail(client *github.Client) (*github.User, error) {
|
func GetUserEmail(client *github.Client) (*github.User, error) {
|
||||||
|
@ -58,7 +32,7 @@ func GetUserEmail(client *github.Client) (*github.User, error) {
|
||||||
// WARNING, HACK
|
// WARNING, HACK
|
||||||
// for out-of-date github enterprise editions the primary
|
// for out-of-date github enterprise editions the primary
|
||||||
// and verified fields won't exist.
|
// 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
|
user.Email = emails[0].Email
|
||||||
return user, nil
|
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")
|
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
|
// GetHook is a heper function that retrieves a hook by
|
||||||
// hostname. To do this, it will retrieve a list of all hooks
|
// hostname. To do this, it will retrieve a list of all hooks
|
||||||
// and iterate through the list.
|
// and iterate through the list.
|
||||||
|
@ -136,6 +60,7 @@ func GetHook(client *github.Client, owner, name, url string) (*github.Hook, erro
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteHook does exactly what you think it does.
|
||||||
func DeleteHook(client *github.Client, owner, name, url string) error {
|
func DeleteHook(client *github.Client, owner, name, url string) error {
|
||||||
hook, err := GetHook(client, owner, name, url)
|
hook, err := GetHook(client, owner, name, url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -179,24 +104,6 @@ func CreateUpdateHook(client *github.Client, owner, name, url string) (*github.H
|
||||||
return CreateHook(client, owner, name, url)
|
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
|
// 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
|
// first check for a `payload` parameter in a POST, but can fallback to a
|
||||||
// raw JSON body as well.
|
// 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
|
package github
|
||||||
|
|
||||||
type postHook struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
type pushHook struct {
|
type pushHook struct {
|
||||||
Ref string `json:"ref"`
|
Ref string `json:"ref"`
|
||||||
Deleted bool `json:"deleted"`
|
Deleted bool `json:"deleted"`
|
||||||
|
@ -54,7 +51,7 @@ type deployHook struct {
|
||||||
Ref string `json:"ref"`
|
Ref string `json:"ref"`
|
||||||
Task string `json:"task"`
|
Task string `json:"task"`
|
||||||
Env string `json:"environment"`
|
Env string `json:"environment"`
|
||||||
Url string `json:"url"`
|
URL string `json:"url"`
|
||||||
Desc string `json:"description"`
|
Desc string `json:"description"`
|
||||||
} `json:"deployment"`
|
} `json:"deployment"`
|
||||||
|
|
||||||
|
@ -78,9 +75,8 @@ type deployHook struct {
|
||||||
DefaultBranch string `json:"default_branch"`
|
DefaultBranch string `json:"default_branch"`
|
||||||
} `json:"repository"`
|
} `json:"repository"`
|
||||||
|
|
||||||
// these are legacy fields that have been moded
|
// these are legacy fields that have been added to the deployment section.
|
||||||
// to the deployment section. They are here for
|
// They are here for older versions of GitHub and will be removed.
|
||||||
// older versions of GitHub and will be removed
|
|
||||||
|
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Sha string `json:"sha"`
|
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.
|
// helper function to setup the GitHub remote from the CLI arguments.
|
||||||
func setupGithub(c *cli.Context) (remote.Remote, error) {
|
func setupGithub(c *cli.Context) (remote.Remote, error) {
|
||||||
return github.New(
|
return github.New(github.Opts{
|
||||||
c.String("github-server"),
|
URL: c.String("github-server"),
|
||||||
c.String("github-client"),
|
Client: c.String("github-client"),
|
||||||
c.String("github-sercret"),
|
Secret: c.String("github-sercret"),
|
||||||
c.StringSlice("github-scope"),
|
Scopes: c.StringSlice("github-scope"),
|
||||||
c.Bool("github-private-mode"),
|
Username: c.String("github-git-username"),
|
||||||
c.Bool("github-skip-verify"),
|
Password: c.String("github-git-password"),
|
||||||
c.BoolT("github-merge-ref"),
|
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.
|
// AuthorizeAgent authorizes requsts from build agents to access the queue.
|
||||||
func AuthorizeAgent(c *gin.Context) {
|
func AuthorizeAgent(c *gin.Context) {
|
||||||
secret := c.MustGet("agent").(string)
|
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) {
|
parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
|
||||||
return secret, nil
|
return secret, nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(403, err)
|
c.String(500, "invalid or empty token. %s", err)
|
||||||
} else if parsed.Kind != token.AgentToken {
|
} else if parsed.Kind != token.AgentToken {
|
||||||
c.AbortWithStatus(403)
|
c.String(403, "invalid token. please use an agent token")
|
||||||
} else {
|
} else {
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base32"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
"github.com/drone/drone/remote"
|
"github.com/drone/drone/remote"
|
||||||
"github.com/drone/drone/shared/crypto"
|
|
||||||
"github.com/drone/drone/shared/httputil"
|
"github.com/drone/drone/shared/httputil"
|
||||||
"github.com/drone/drone/shared/token"
|
"github.com/drone/drone/shared/token"
|
||||||
"github.com/drone/drone/store"
|
"github.com/drone/drone/store"
|
||||||
|
"github.com/gorilla/securecookie"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
@ -39,12 +40,23 @@ func GetLogin(c *gin.Context) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
// if self-registration is disabled we should return a not authorized error
|
// 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)
|
logrus.Errorf("cannot register %s. registration closed", tmpuser.Login)
|
||||||
c.Redirect(303, "/login?error=access_denied")
|
c.Redirect(303, "/login?error=access_denied")
|
||||||
return
|
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
|
// create the user account
|
||||||
u = &model.User{
|
u = &model.User{
|
||||||
Login: tmpuser.Login,
|
Login: tmpuser.Login,
|
||||||
|
@ -52,7 +64,9 @@ func GetLogin(c *gin.Context) {
|
||||||
Secret: tmpuser.Secret,
|
Secret: tmpuser.Secret,
|
||||||
Email: tmpuser.Email,
|
Email: tmpuser.Email,
|
||||||
Avatar: tmpuser.Avatar,
|
Avatar: tmpuser.Avatar,
|
||||||
Hash: crypto.Rand(),
|
Hash: base32.StdEncoding.EncodeToString(
|
||||||
|
securecookie.GenerateRandomKey(32),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert the user into the database
|
// insert the user into the database
|
||||||
|
@ -69,33 +83,23 @@ func GetLogin(c *gin.Context) {
|
||||||
u.Email = tmpuser.Email
|
u.Email = tmpuser.Email
|
||||||
u.Avatar = tmpuser.Avatar
|
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 {
|
if err := store.UpdateUser(c, u); err != nil {
|
||||||
logrus.Errorf("cannot update %s. %s", u.Login, err)
|
logrus.Errorf("cannot update %s. %s", u.Login, err)
|
||||||
c.Redirect(303, "/login?error=internal_error")
|
c.Redirect(303, "/login?error=internal_error")
|
||||||
return
|
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()
|
exp := time.Now().Add(time.Hour * 72).Unix()
|
||||||
token := token.New(token.SessToken, u.Login)
|
token := token.New(token.SessToken, u.Login)
|
||||||
tokenstr, err := token.SignExpires(u.Hash, exp)
|
tokenstr, err := token.SignExpires(u.Hash, exp)
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base32"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gorilla/securecookie"
|
||||||
|
|
||||||
"github.com/drone/drone/cache"
|
"github.com/drone/drone/cache"
|
||||||
"github.com/drone/drone/remote"
|
"github.com/drone/drone/remote"
|
||||||
"github.com/drone/drone/router/middleware/session"
|
"github.com/drone/drone/router/middleware/session"
|
||||||
"github.com/drone/drone/shared/crypto"
|
|
||||||
"github.com/drone/drone/shared/httputil"
|
"github.com/drone/drone/shared/httputil"
|
||||||
"github.com/drone/drone/shared/token"
|
"github.com/drone/drone/shared/token"
|
||||||
"github.com/drone/drone/store"
|
"github.com/drone/drone/store"
|
||||||
|
@ -54,7 +55,9 @@ func PostRepo(c *gin.Context) {
|
||||||
r.AllowPush = true
|
r.AllowPush = true
|
||||||
r.AllowPull = true
|
r.AllowPull = true
|
||||||
r.Timeout = 60 // 1 hour default build time
|
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
|
// crates the jwt token used to verify the repository
|
||||||
t := token.New(token.HookToken, r.FullName)
|
t := token.New(token.HookToken, r.FullName)
|
||||||
|
|
|
@ -5,9 +5,9 @@ import (
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"github.com/drone/drone-exp/shared/crypto"
|
||||||
"github.com/drone/drone/cache"
|
"github.com/drone/drone/cache"
|
||||||
"github.com/drone/drone/router/middleware/session"
|
"github.com/drone/drone/router/middleware/session"
|
||||||
"github.com/drone/drone/shared/crypto"
|
|
||||||
"github.com/drone/drone/shared/token"
|
"github.com/drone/drone/shared/token"
|
||||||
"github.com/drone/drone/store"
|
"github.com/drone/drone/store"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base32"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gorilla/securecookie"
|
||||||
|
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
"github.com/drone/drone/shared/crypto"
|
|
||||||
"github.com/drone/drone/store"
|
"github.com/drone/drone/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,9 +15,9 @@ func GetUsers(c *gin.Context) {
|
||||||
users, err := store.GetUserList(c)
|
users, err := store.GetUserList(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.String(500, "Error getting user list. %s", err)
|
c.String(500, "Error getting user list. %s", err)
|
||||||
} else {
|
return
|
||||||
c.JSON(200, users)
|
|
||||||
}
|
}
|
||||||
|
c.JSON(200, users)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUser(c *gin.Context) {
|
func GetUser(c *gin.Context) {
|
||||||
|
@ -41,7 +42,6 @@ func PatchUser(c *gin.Context) {
|
||||||
c.AbortWithStatus(http.StatusNotFound)
|
c.AbortWithStatus(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user.Admin = in.Admin
|
|
||||||
user.Active = in.Active
|
user.Active = in.Active
|
||||||
|
|
||||||
err = store.UpdateUser(c, user)
|
err = store.UpdateUser(c, user)
|
||||||
|
@ -65,7 +65,9 @@ func PostUser(c *gin.Context) {
|
||||||
Login: in.Login,
|
Login: in.Login,
|
||||||
Email: in.Email,
|
Email: in.Email,
|
||||||
Avatar: in.Avatar,
|
Avatar: in.Avatar,
|
||||||
Hash: crypto.Rand(),
|
Hash: base32.StdEncoding.EncodeToString(
|
||||||
|
securecookie.GenerateRandomKey(32),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
if err = store.CreateUser(c, user); err != nil {
|
if err = store.CreateUser(c, user); err != nil {
|
||||||
c.String(http.StatusInternalServerError, err.Error())
|
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