mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-26 11:51:02 +00:00
refactored plugin/remote and adapted @bugBugagazavr Gitlab code
This commit is contained in:
parent
71dfaa40fb
commit
38379992bf
31 changed files with 1525 additions and 993 deletions
2
Makefile
2
Makefile
|
@ -29,7 +29,7 @@ clean:
|
||||||
rm -f debian/drone/usr/local/bin/droned
|
rm -f debian/drone/usr/local/bin/droned
|
||||||
rm -f debian/drone.deb
|
rm -f debian/drone.deb
|
||||||
rm -f server/server
|
rm -f server/server
|
||||||
rm -f client/client
|
rm -f cmd/cmd
|
||||||
|
|
||||||
lessc:
|
lessc:
|
||||||
lessc --clean-css server/app/styles/drone.less server/app/styles/drone.css
|
lessc --clean-css server/app/styles/drone.less server/app/styles/drone.css
|
||||||
|
|
|
@ -6,78 +6,50 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/drone/drone/plugin/remote"
|
|
||||||
"github.com/drone/drone/shared/httputil"
|
"github.com/drone/drone/shared/httputil"
|
||||||
|
"github.com/drone/drone/shared/model"
|
||||||
"github.com/drone/go-bitbucket/bitbucket"
|
"github.com/drone/go-bitbucket/bitbucket"
|
||||||
"github.com/drone/go-bitbucket/oauth1"
|
"github.com/drone/go-bitbucket/oauth1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultAPI = "https://api.bitbucket.org/1.0"
|
||||||
|
DefaultURL = "https://bitbucket.org"
|
||||||
|
)
|
||||||
|
|
||||||
type Bitbucket struct {
|
type Bitbucket struct {
|
||||||
URL string `json:"url"` // https://bitbucket.org
|
URL string
|
||||||
API string `json:"api"` // https://api.bitbucket.org
|
API string
|
||||||
Client string `json:"client"`
|
Client string
|
||||||
Secret string `json:"secret"`
|
Secret string
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetName returns the name of this remote system.
|
func New(url, api, client, secret string) *Bitbucket {
|
||||||
func (b *Bitbucket) GetName() string {
|
return &Bitbucket{
|
||||||
return "bitbucket.org"
|
URL: url,
|
||||||
}
|
API: api,
|
||||||
|
Client: client,
|
||||||
// GetHost returns the url.Host of this remote system.
|
Secret: secret,
|
||||||
func (b *Bitbucket) GetHost() (host string) {
|
|
||||||
u, err := url.Parse(b.URL)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
return u.Host
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHook parses the post-commit hook from the Request body
|
func NewDefault(client, secret string) *Bitbucket {
|
||||||
// and returns the required data in a standard format.
|
return New(DefaultURL, DefaultAPI, client, secret)
|
||||||
func (b *Bitbucket) GetHook(r *http.Request) (*remote.Hook, error) {
|
|
||||||
// get the payload from the request
|
|
||||||
payload := r.FormValue("payload")
|
|
||||||
|
|
||||||
// parse the post-commit hook
|
|
||||||
hook, err := bitbucket.ParseHook([]byte(payload))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify the payload has the minimum amount of required data.
|
|
||||||
if hook.Repo == nil || hook.Commits == nil || len(hook.Commits) == 0 {
|
|
||||||
return nil, fmt.Errorf("Invalid Bitbucket post-commit Hook. Missing Repo or Commit data.")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &remote.Hook{
|
|
||||||
Owner: hook.Repo.Owner,
|
|
||||||
Repo: hook.Repo.Name,
|
|
||||||
Sha: hook.Commits[len(hook.Commits)-1].Hash,
|
|
||||||
Branch: hook.Commits[len(hook.Commits)-1].Branch,
|
|
||||||
Author: hook.Commits[len(hook.Commits)-1].Author,
|
|
||||||
Timestamp: time.Now().UTC().String(),
|
|
||||||
Message: hook.Commits[len(hook.Commits)-1].Message,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLogin handles authentication to third party, remote services
|
// Authorize handles Bitbucket API Authorization
|
||||||
// and returns the required user data in a standard format.
|
func (r *Bitbucket) Authorize(res http.ResponseWriter, req *http.Request) (*model.Login, error) {
|
||||||
func (b *Bitbucket) GetLogin(w http.ResponseWriter, r *http.Request) (*remote.Login, error) {
|
|
||||||
|
|
||||||
// bitbucket oauth1 consumer
|
|
||||||
consumer := oauth1.Consumer{
|
consumer := oauth1.Consumer{
|
||||||
RequestTokenURL: "https://bitbucket.org/api/1.0/oauth/request_token/",
|
RequestTokenURL: "https://bitbucket.org/api/1.0/oauth/request_token/",
|
||||||
AuthorizationURL: "https://bitbucket.org/!api/1.0/oauth/authenticate",
|
AuthorizationURL: "https://bitbucket.org/!api/1.0/oauth/authenticate",
|
||||||
AccessTokenURL: "https://bitbucket.org/api/1.0/oauth/access_token/",
|
AccessTokenURL: "https://bitbucket.org/api/1.0/oauth/access_token/",
|
||||||
CallbackURL: httputil.GetScheme(r) + "://" + httputil.GetHost(r) + "/login/bitbucket.org",
|
CallbackURL: httputil.GetScheme(req) + "://" + httputil.GetHost(req) + "/login/bitbucket.org",
|
||||||
ConsumerKey: b.Client,
|
ConsumerKey: r.Client,
|
||||||
ConsumerSecret: b.Secret,
|
ConsumerSecret: r.Secret,
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the oauth verifier
|
// get the oauth verifier
|
||||||
verifier := r.FormValue("oauth_verifier")
|
verifier := req.FormValue("oauth_verifier")
|
||||||
if len(verifier) == 0 {
|
if len(verifier) == 0 {
|
||||||
// Generate a Request Token
|
// Generate a Request Token
|
||||||
requestToken, err := consumer.RequestToken()
|
requestToken, err := consumer.RequestToken()
|
||||||
|
@ -86,19 +58,19 @@ func (b *Bitbucket) GetLogin(w http.ResponseWriter, r *http.Request) (*remote.Lo
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the request token as a signed cookie
|
// add the request token as a signed cookie
|
||||||
httputil.SetCookie(w, r, "bitbucket_token", requestToken.Encode())
|
httputil.SetCookie(res, req, "bitbucket_token", requestToken.Encode())
|
||||||
|
|
||||||
url, _ := consumer.AuthorizeRedirect(requestToken)
|
url, _ := consumer.AuthorizeRedirect(requestToken)
|
||||||
http.Redirect(w, r, url, http.StatusSeeOther)
|
http.Redirect(res, req, url, http.StatusSeeOther)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove bitbucket token data once before redirecting
|
// remove bitbucket token data once before redirecting
|
||||||
// back to the application.
|
// back to the application.
|
||||||
defer httputil.DelCookie(w, r, "bitbucket_token")
|
defer httputil.DelCookie(res, req, "bitbucket_token")
|
||||||
|
|
||||||
// get the tokens from the request
|
// get the tokens from the request
|
||||||
requestTokenStr := httputil.GetCookie(r, "bitbucket_token")
|
requestTokenStr := httputil.GetCookie(req, "bitbucket_token")
|
||||||
requestToken, err := oauth1.ParseRequestTokenStr(requestTokenStr)
|
requestToken, err := oauth1.ParseRequestTokenStr(requestTokenStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -112,8 +84,8 @@ func (b *Bitbucket) GetLogin(w http.ResponseWriter, r *http.Request) (*remote.Lo
|
||||||
|
|
||||||
// create the Bitbucket client
|
// create the Bitbucket client
|
||||||
client := bitbucket.New(
|
client := bitbucket.New(
|
||||||
b.Client,
|
r.Client,
|
||||||
b.Secret,
|
r.Secret,
|
||||||
accessToken.Token(),
|
accessToken.Token(),
|
||||||
accessToken.Secret(),
|
accessToken.Secret(),
|
||||||
)
|
)
|
||||||
|
@ -125,7 +97,7 @@ func (b *Bitbucket) GetLogin(w http.ResponseWriter, r *http.Request) (*remote.Lo
|
||||||
}
|
}
|
||||||
|
|
||||||
// put the user data in the common format
|
// put the user data in the common format
|
||||||
login := remote.Login{
|
login := model.Login{
|
||||||
Login: user.User.Username,
|
Login: user.User.Username,
|
||||||
Access: accessToken.Token(),
|
Access: accessToken.Token(),
|
||||||
Secret: accessToken.Secret(),
|
Secret: accessToken.Secret(),
|
||||||
|
@ -140,13 +112,147 @@ func (b *Bitbucket) GetLogin(w http.ResponseWriter, r *http.Request) (*remote.Lo
|
||||||
return &login, nil
|
return &login, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetClient returns a new Bitbucket remote client.
|
// GetKind returns the internal identifier of this remote Bitbucket instane.
|
||||||
func (b *Bitbucket) GetClient(access, secret string) remote.Client {
|
func (r *Bitbucket) GetKind() string {
|
||||||
return &Client{b, access, secret}
|
return model.RemoteBitbucket
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsMatch returns true if the hostname matches the
|
// GetHost returns the hostname of this remote Bitbucket instance.
|
||||||
// hostname of this remote client.
|
func (r *Bitbucket) GetHost() string {
|
||||||
func (b *Bitbucket) IsMatch(hostname string) bool {
|
uri, _ := url.Parse(r.URL)
|
||||||
return hostname == "bitbucket.org"
|
return uri.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRepos fetches all repositories that the specified
|
||||||
|
// user has access to in the remote system.
|
||||||
|
func (r *Bitbucket) GetRepos(user *model.User) ([]*model.Repo, error) {
|
||||||
|
var repos []*model.Repo
|
||||||
|
var client = bitbucket.New(
|
||||||
|
r.Client,
|
||||||
|
r.Secret,
|
||||||
|
user.Access,
|
||||||
|
user.Secret,
|
||||||
|
)
|
||||||
|
var list, err = client.Repos.List()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var remote = r.GetKind()
|
||||||
|
var hostname = r.GetHost()
|
||||||
|
|
||||||
|
for _, item := range list {
|
||||||
|
// for now we only support git repos
|
||||||
|
if item.Scm != "git" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// these are the urls required to clone the repository
|
||||||
|
// TODO use the bitbucketurl.Host and bitbucketurl.Scheme instead of hardcoding
|
||||||
|
// so that we can support Stash.
|
||||||
|
var clone = fmt.Sprintf("https://bitbucket.org/%s/%s.git", item.Owner, item.Name)
|
||||||
|
var ssh = fmt.Sprintf("git@bitbucket.org:%s/%s.git", item.Owner, item.Name)
|
||||||
|
|
||||||
|
var repo = model.Repo{
|
||||||
|
UserID: user.ID,
|
||||||
|
Remote: remote,
|
||||||
|
Host: hostname,
|
||||||
|
Owner: item.Owner,
|
||||||
|
Name: item.Name,
|
||||||
|
Private: item.Private,
|
||||||
|
CloneURL: clone,
|
||||||
|
GitURL: clone,
|
||||||
|
SSHURL: ssh,
|
||||||
|
Role: &model.Perm{
|
||||||
|
Admin: true,
|
||||||
|
Write: true,
|
||||||
|
Read: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if repo.Private {
|
||||||
|
repo.CloneURL = repo.SSHURL
|
||||||
|
}
|
||||||
|
|
||||||
|
repos = append(repos, &repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return repos, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetScript fetches the build script (.drone.yml) from the remote
|
||||||
|
// repository and returns in string format.
|
||||||
|
func (r *Bitbucket) GetScript(user *model.User, repo *model.Repo, hook *model.Hook) ([]byte, error) {
|
||||||
|
var client = bitbucket.New(
|
||||||
|
r.Client,
|
||||||
|
r.Secret,
|
||||||
|
user.Access,
|
||||||
|
user.Secret,
|
||||||
|
)
|
||||||
|
|
||||||
|
// get the yaml from the database
|
||||||
|
var raw, err = client.Sources.Find(repo.Owner, repo.Name, hook.Sha, ".drone.yml")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(raw.Data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate activates a repository by adding a Post-commit hook and
|
||||||
|
// a Public Deploy key, if applicable.
|
||||||
|
func (r *Bitbucket) Activate(user *model.User, repo *model.Repo, link string) error {
|
||||||
|
var client = bitbucket.New(
|
||||||
|
r.Client,
|
||||||
|
r.Secret,
|
||||||
|
user.Access,
|
||||||
|
user.Secret,
|
||||||
|
)
|
||||||
|
|
||||||
|
// parse the hostname from the hook, and use this
|
||||||
|
// to name the ssh key
|
||||||
|
var hookurl, err = url.Parse(link)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the repository is private we'll need
|
||||||
|
// to upload a github key to the repository
|
||||||
|
if repo.Private {
|
||||||
|
// name the key
|
||||||
|
var keyname = "drone@" + hookurl.Host
|
||||||
|
var _, err = client.RepoKeys.CreateUpdate(repo.Owner, repo.Name, repo.PublicKey, keyname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the hook
|
||||||
|
_, err = client.Brokers.CreateUpdate(repo.Owner, repo.Name, link, bitbucket.BrokerTypePost)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseHook parses the post-commit hook from the Request body
|
||||||
|
// and returns the required data in a standard format.
|
||||||
|
func (r *Bitbucket) ParseHook(req *http.Request) (*model.Hook, error) {
|
||||||
|
var payload = req.FormValue("payload")
|
||||||
|
var hook, err = bitbucket.ParseHook([]byte(payload))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify the payload has the minimum amount of required data.
|
||||||
|
if hook.Repo == nil || hook.Commits == nil || len(hook.Commits) == 0 {
|
||||||
|
return nil, fmt.Errorf("Invalid Bitbucket post-commit Hook. Missing Repo or Commit data.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Hook{
|
||||||
|
Owner: hook.Repo.Owner,
|
||||||
|
Repo: hook.Repo.Name,
|
||||||
|
Sha: hook.Commits[len(hook.Commits)-1].Hash,
|
||||||
|
Branch: hook.Commits[len(hook.Commits)-1].Branch,
|
||||||
|
Author: hook.Commits[len(hook.Commits)-1].Author,
|
||||||
|
Timestamp: time.Now().UTC().String(),
|
||||||
|
Message: hook.Commits[len(hook.Commits)-1].Message,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,146 +0,0 @@
|
||||||
package bitbucket
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/drone/drone/plugin/remote"
|
|
||||||
"github.com/drone/go-bitbucket/bitbucket"
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
config *Bitbucket
|
|
||||||
access string // user access token
|
|
||||||
secret string // user access token secret
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUser fetches the user by ID (login name).
|
|
||||||
func (c *Client) GetUser(login string) (*remote.User, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRepos fetches all repositories that the specified
|
|
||||||
// user has access to in the remote system.
|
|
||||||
func (c *Client) GetRepos(owner string) ([]*remote.Repo, error) {
|
|
||||||
// create the Bitbucket client
|
|
||||||
client := bitbucket.New(
|
|
||||||
c.config.Client,
|
|
||||||
c.config.Secret,
|
|
||||||
c.access,
|
|
||||||
c.secret,
|
|
||||||
)
|
|
||||||
|
|
||||||
// parse the hostname from the bitbucket url
|
|
||||||
bitbucketurl, err := url.Parse(c.config.URL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
repos, err := client.Repos.List()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// store results in common format
|
|
||||||
result := []*remote.Repo{}
|
|
||||||
|
|
||||||
// loop throught the list and convert to the standard repo format
|
|
||||||
for _, repo := range repos {
|
|
||||||
// for now we only support git repos
|
|
||||||
if repo.Scm != "git" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// these are the urls required to clone the repository
|
|
||||||
// TODO use the bitbucketurl.Host and bitbucketurl.Scheme instead of hardcoding
|
|
||||||
// so that we can support Stash.
|
|
||||||
clone := fmt.Sprintf("https://bitbucket.org/%s/%s.git", repo.Owner, repo.Name)
|
|
||||||
ssh := fmt.Sprintf("git@bitbucket.org:%s/%s.git", repo.Owner, repo.Name)
|
|
||||||
|
|
||||||
result = append(result, &remote.Repo{
|
|
||||||
Host: bitbucketurl.Host,
|
|
||||||
Owner: repo.Owner,
|
|
||||||
Name: repo.Name,
|
|
||||||
Kind: repo.Scm,
|
|
||||||
Private: repo.Private,
|
|
||||||
Clone: clone,
|
|
||||||
SSH: ssh,
|
|
||||||
// Bitbucket doesn't return permissions with repository
|
|
||||||
// lists, so we're going to grant full access.
|
|
||||||
//
|
|
||||||
// TODO we need to verify this API call only returns
|
|
||||||
// repositories that we can access (ie not repos we just follow).
|
|
||||||
// otherwise this would cause a security flaw.
|
|
||||||
Push: true,
|
|
||||||
Pull: true,
|
|
||||||
Admin: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetScript fetches the build script (.drone.yml) from the remote
|
|
||||||
// repository and returns in string format.
|
|
||||||
func (c *Client) GetScript(hook *remote.Hook) (out string, err error) {
|
|
||||||
// create the Bitbucket client
|
|
||||||
client := bitbucket.New(
|
|
||||||
c.config.Client,
|
|
||||||
c.config.Secret,
|
|
||||||
c.access,
|
|
||||||
c.secret,
|
|
||||||
)
|
|
||||||
|
|
||||||
// get the yaml from the database
|
|
||||||
raw, err := client.Sources.Find(hook.Owner, hook.Repo, hook.Sha, ".drone.yml")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetStatus
|
|
||||||
func (c *Client) SetStatus(owner, name, sha, status string) error {
|
|
||||||
// not implemented for Bitbucket
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetActive
|
|
||||||
func (c *Client) SetActive(owner, name, hook, key string) error {
|
|
||||||
// create the Bitbucket client
|
|
||||||
client := bitbucket.New(
|
|
||||||
c.config.Client,
|
|
||||||
c.config.Secret,
|
|
||||||
c.access,
|
|
||||||
c.secret,
|
|
||||||
)
|
|
||||||
|
|
||||||
// parse the hostname from the hook, and use this
|
|
||||||
// to name the ssh key
|
|
||||||
hookurl, err := url.Parse(hook)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetch the repository so that we can see if it
|
|
||||||
// is public or private.
|
|
||||||
repo, err := client.Repos.Find(owner, name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the repository is private we'll need
|
|
||||||
// to upload a github key to the repository
|
|
||||||
if repo.Private {
|
|
||||||
// name the key
|
|
||||||
keyname := "drone@" + hookurl.Host
|
|
||||||
_, err := client.RepoKeys.CreateUpdate(owner, name, key, keyname)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add the hook
|
|
||||||
_, err = client.Brokers.CreateUpdate(owner, name, hook, bitbucket.BrokerTypePost)
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
package bitbucket
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/drone/drone/plugin/remote"
|
|
||||||
"github.com/drone/drone/shared/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
remote.Register(model.RemoteBitbucket, plugin)
|
|
||||||
}
|
|
||||||
|
|
||||||
func plugin(remote *model.Remote) remote.Remote {
|
|
||||||
return &Bitbucket{
|
|
||||||
URL: remote.URL,
|
|
||||||
API: remote.API,
|
|
||||||
Client: remote.Client,
|
|
||||||
Secret: remote.Secret,
|
|
||||||
Enabled: remote.Open,
|
|
||||||
}
|
|
||||||
}
|
|
16
plugin/remote/bitbucket/register.go
Normal file
16
plugin/remote/bitbucket/register.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package bitbucket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/drone/drone/plugin/remote"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var cli = os.Getenv("BITBUCKET_CLIENT")
|
||||||
|
var sec = os.Getenv("BITBUCKET_SECRET")
|
||||||
|
if len(cli) == 0 || len(sec) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
remote.Register(NewDefault(cli, sec))
|
||||||
|
}
|
|
@ -1,166 +0,0 @@
|
||||||
package github
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/drone/drone/plugin/remote"
|
|
||||||
"github.com/drone/go-github/github"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
config *Github
|
|
||||||
access string // user access token
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUser fetches the user by ID (login name).
|
|
||||||
func (c *Client) GetUser(login string) (*remote.User, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRepos fetches all repositories that the specified
|
|
||||||
// user has access to in the remote system.
|
|
||||||
func (c *Client) GetRepos(owner string) ([]*remote.Repo, error) {
|
|
||||||
// create the github client
|
|
||||||
client := github.New(c.access)
|
|
||||||
client.ApiUrl = c.config.API
|
|
||||||
|
|
||||||
// retrieve a list of all github repositories
|
|
||||||
repos, err := client.Repos.ListAll()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// store results in common format
|
|
||||||
result := []*remote.Repo{}
|
|
||||||
|
|
||||||
// parse the hostname from the github url
|
|
||||||
githuburl, err := url.Parse(c.config.URL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop throught the list and convert to the standard repo format
|
|
||||||
for _, repo := range repos {
|
|
||||||
// if the repository is private we should use the ssh
|
|
||||||
// url to clone, else we should use the git url
|
|
||||||
if repo.Private {
|
|
||||||
repo.CloneUrl = repo.SshUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
result = append(result, &remote.Repo{
|
|
||||||
ID: repo.ID,
|
|
||||||
Host: githuburl.Host,
|
|
||||||
Owner: repo.Owner.Login,
|
|
||||||
Name: repo.Name,
|
|
||||||
Kind: "git",
|
|
||||||
Clone: repo.CloneUrl,
|
|
||||||
Git: repo.GitUrl,
|
|
||||||
SSH: repo.SshUrl,
|
|
||||||
Private: repo.Private,
|
|
||||||
Push: repo.Permissions.Push,
|
|
||||||
Pull: repo.Permissions.Pull,
|
|
||||||
Admin: repo.Permissions.Admin,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetScript fetches the build script (.drone.yml) from the remote
|
|
||||||
// repository using the GitHub API and returns the raw file in string format.
|
|
||||||
func (c *Client) GetScript(hook *remote.Hook) (out string, err error) {
|
|
||||||
// create the github client
|
|
||||||
client := github.New(c.access)
|
|
||||||
client.ApiUrl = c.config.API
|
|
||||||
|
|
||||||
// retrieve the .drone.yml file from GitHub
|
|
||||||
content, err := client.Contents.FindRef(hook.Owner, hook.Repo, ".drone.yml", hook.Sha)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// decode the content
|
|
||||||
raw, err := content.DecodeContent()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(raw), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetStatus
|
|
||||||
func (c *Client) SetStatus(owner, name, sha, status string) error {
|
|
||||||
// create the github client
|
|
||||||
client := github.New(c.access)
|
|
||||||
client.ApiUrl = c.config.API
|
|
||||||
|
|
||||||
// convert from drone status to github status
|
|
||||||
var message string
|
|
||||||
switch status {
|
|
||||||
case "Success":
|
|
||||||
status = "success"
|
|
||||||
message = "The build succeeded on drone.io"
|
|
||||||
case "Failure":
|
|
||||||
status = "failure"
|
|
||||||
message = "The build failed on drone.io"
|
|
||||||
case "Started", "Pending":
|
|
||||||
status = "pending"
|
|
||||||
message = "The build is pending on drone.io"
|
|
||||||
default:
|
|
||||||
status = "error"
|
|
||||||
message = "The build errored on drone.io"
|
|
||||||
}
|
|
||||||
|
|
||||||
// format the build URL
|
|
||||||
// TODO we really need the branch here
|
|
||||||
// TODO we really need the drone.io hostname as well
|
|
||||||
url := fmt.Sprintf("http://beta.drone.io/%s/%s/%s/%s", owner, name, "master", sha)
|
|
||||||
|
|
||||||
// update the status
|
|
||||||
return client.Repos.CreateStatus(owner, name, status, url, message, sha)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetActive will configure a post-commit and pull-request hook
|
|
||||||
// with the remote GitHub repository using the GitHub API.
|
|
||||||
//
|
|
||||||
// It will also, optionally, add a public RSA key. This is primarily
|
|
||||||
// used for private repositories, which typically must use the Git+SSH
|
|
||||||
// protocol to clone the repository.
|
|
||||||
func (c *Client) SetActive(owner, name, hook, key string) error {
|
|
||||||
// create the github client
|
|
||||||
client := github.New(c.access)
|
|
||||||
client.ApiUrl = c.config.API
|
|
||||||
|
|
||||||
// parse the hostname from the hook, and use this
|
|
||||||
// to name the ssh key
|
|
||||||
hookurl, err := url.Parse(hook)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetch the repository so that we can see if it
|
|
||||||
// is public or private.
|
|
||||||
repo, err := client.Repos.Find(owner, name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the repository is private we'll need
|
|
||||||
// to upload a github key to the repository
|
|
||||||
if repo.Private {
|
|
||||||
// name the key
|
|
||||||
keyname := "drone@" + hookurl.Host
|
|
||||||
_, err := client.RepoKeys.CreateUpdate(owner, name, key, keyname)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add the hook
|
|
||||||
if _, err := client.Hooks.CreateUpdate(owner, name, hook); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
package github
|
package github
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base32"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -9,74 +8,225 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/drone/drone/plugin/remote"
|
"code.google.com/p/goauth2/oauth"
|
||||||
"github.com/drone/drone/shared/httputil"
|
"github.com/drone/drone/shared/httputil"
|
||||||
|
"github.com/drone/drone/shared/model"
|
||||||
"github.com/drone/go-github/github"
|
"github.com/drone/go-github/github"
|
||||||
"github.com/drone/go-github/oauth2"
|
|
||||||
"github.com/gorilla/securecookie"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const (
|
||||||
scope = "repo,repo:status,user:email"
|
DefaultAPI = "https://api.github.com/"
|
||||||
|
DefaultURL = "https://github.com"
|
||||||
|
DefaultScope = "repo,repo:status,user:email"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Github struct {
|
type GitHub struct {
|
||||||
URL string `json:"url"` // https://github.com
|
URL string
|
||||||
API string `json:"api"` // https://api.github.com
|
API string
|
||||||
Client string `json:"client"`
|
Client string
|
||||||
Secret string `json:"secret"`
|
Secret string
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetName returns the name of this remote system.
|
func New(url, api, client, secret string) *GitHub {
|
||||||
func (g *Github) GetName() string {
|
var github = GitHub{
|
||||||
switch g.URL {
|
URL: url,
|
||||||
case "https://github.com":
|
API: api,
|
||||||
return "github.com"
|
Client: client,
|
||||||
default:
|
Secret: secret,
|
||||||
return "enterprise.github.com"
|
|
||||||
}
|
}
|
||||||
|
// the API must have a trailing slash
|
||||||
|
if !strings.HasSuffix(github.API, "/") {
|
||||||
|
github.API += "/"
|
||||||
|
}
|
||||||
|
// the URL must NOT have a trailing slash
|
||||||
|
if strings.HasSuffix(github.URL, "/") {
|
||||||
|
github.URL = github.URL[:len(github.URL)-1]
|
||||||
|
}
|
||||||
|
return &github
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHost returns the url.Host of this remote system.
|
func NewDefault(client, secret string) *GitHub {
|
||||||
func (g *Github) GetHost() (host string) {
|
return New(DefaultURL, DefaultAPI, client, secret)
|
||||||
u, err := url.Parse(g.URL)
|
}
|
||||||
|
|
||||||
|
// Authorize handles GitHub API Authorization.
|
||||||
|
func (r *GitHub) Authorize(res http.ResponseWriter, req *http.Request) (*model.Login, error) {
|
||||||
|
var config = &oauth.Config{
|
||||||
|
ClientId: r.Client,
|
||||||
|
ClientSecret: r.Secret,
|
||||||
|
Scope: DefaultScope,
|
||||||
|
AuthURL: fmt.Sprintf("%s/login/oauth/authorize", r.URL),
|
||||||
|
TokenURL: fmt.Sprintf("%s/login/oauth/access_token", r.URL),
|
||||||
|
RedirectURL: fmt.Sprintf("%s/login/%s", httputil.GetURL(req), r.GetKind()),
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the OAuth code
|
||||||
|
var code = req.FormValue("code")
|
||||||
|
var state = req.FormValue("state")
|
||||||
|
if len(code) == 0 {
|
||||||
|
var random = GetRandom()
|
||||||
|
httputil.SetCookie(res, req, "github_state", random)
|
||||||
|
http.Redirect(res, req, config.AuthCodeURL(random), http.StatusSeeOther)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cookieState := httputil.GetCookie(req, "github_state")
|
||||||
|
httputil.DelCookie(res, req, "github_state")
|
||||||
|
if cookieState != state {
|
||||||
|
return nil, fmt.Errorf("Error matching state in OAuth2 redirect")
|
||||||
|
}
|
||||||
|
|
||||||
|
var trans = &oauth.Transport{Config: config}
|
||||||
|
var token, err = trans.Exchange(code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
return u.Host
|
|
||||||
|
var client = NewClient(r.API, token.AccessToken)
|
||||||
|
var useremail, errr = GetUserEmail(client)
|
||||||
|
if errr != nil {
|
||||||
|
return nil, errr
|
||||||
|
}
|
||||||
|
|
||||||
|
var login = new(model.Login)
|
||||||
|
login.ID = int64(*useremail.ID)
|
||||||
|
login.Access = token.AccessToken
|
||||||
|
login.Login = *useremail.Login
|
||||||
|
login.Name = *useremail.Name
|
||||||
|
login.Email = *useremail.Email
|
||||||
|
return login, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHook parses the post-commit hook from the Request body
|
// GetKind returns the internal identifier of this remote GitHub instane.
|
||||||
|
func (r *GitHub) GetKind() string {
|
||||||
|
if r.IsEnterprise() {
|
||||||
|
return model.RemoteGithubEnterprise
|
||||||
|
} else {
|
||||||
|
return model.RemoteGithub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHost returns the hostname of this remote GitHub instance.
|
||||||
|
func (r *GitHub) GetHost() string {
|
||||||
|
uri, _ := url.Parse(r.URL)
|
||||||
|
return uri.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEnterprise returns true if the remote system is an
|
||||||
|
// instance of GitHub Enterprise Edition.
|
||||||
|
func (r *GitHub) IsEnterprise() bool {
|
||||||
|
return r.URL != DefaultURL
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRepos fetches all repositories that the specified
|
||||||
|
// user has access to in the remote system.
|
||||||
|
func (r *GitHub) GetRepos(user *model.User) ([]*model.Repo, error) {
|
||||||
|
var repos []*model.Repo
|
||||||
|
var client = NewClient(r.API, user.Access)
|
||||||
|
var list, err = GetAllRepos(client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var remote = r.GetKind()
|
||||||
|
var hostname = r.GetHost()
|
||||||
|
var enterprise = r.IsEnterprise()
|
||||||
|
|
||||||
|
for _, item := range list {
|
||||||
|
var repo = model.Repo{
|
||||||
|
UserID: user.ID,
|
||||||
|
Remote: remote,
|
||||||
|
Host: hostname,
|
||||||
|
Owner: *item.Owner.Login,
|
||||||
|
Name: *item.Name,
|
||||||
|
Private: *item.Private,
|
||||||
|
URL: *item.URL,
|
||||||
|
CloneURL: *item.GitURL,
|
||||||
|
GitURL: *item.GitURL,
|
||||||
|
SSHURL: *item.SSHURL,
|
||||||
|
Role: &model.Perm{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if enterprise || repo.Private {
|
||||||
|
repo.CloneURL = *item.SSHURL
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no permissions we should skip the repository
|
||||||
|
// entirely, since this should never happen
|
||||||
|
if item.Permissions == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
repo.Role.Admin = (*item.Permissions)["admin"]
|
||||||
|
repo.Role.Write = (*item.Permissions)["push"]
|
||||||
|
repo.Role.Read = (*item.Permissions)["pull"]
|
||||||
|
repos = append(repos, &repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return repos, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetScript fetches the build script (.drone.yml) from the remote
|
||||||
|
// repository and returns in string format.
|
||||||
|
func (r *GitHub) GetScript(user *model.User, repo *model.Repo, hook *model.Hook) ([]byte, error) {
|
||||||
|
var client = NewClient(r.API, user.Access)
|
||||||
|
return GetFile(client, repo.Owner, repo.Name, ".drone.yml", hook.Sha)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate activates a repository by adding a Post-commit hook and
|
||||||
|
// a Public Deploy key, if applicable.
|
||||||
|
func (r *GitHub) Activate(user *model.User, repo *model.Repo, link string) error {
|
||||||
|
var client = NewClient(r.API, user.Access)
|
||||||
|
var title, err = GetKeyTitle(link)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the CloneURL is using the SSHURL then we know that
|
||||||
|
// we need to add an SSH key to GitHub.
|
||||||
|
if repo.SSHURL == repo.CloneURL {
|
||||||
|
_, err = CreateUpdateKey(client, repo.Owner, repo.Name, title, repo.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = CreateUpdateHook(client, repo.Owner, repo.Name, link)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseHook 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) GetHook(r *http.Request) (*remote.Hook, error) {
|
func (r *GitHub) ParseHook(req *http.Request) (*model.Hook, error) {
|
||||||
// handle github ping
|
// handle github ping
|
||||||
if r.Header.Get("X-Github-Event") == "ping" {
|
if req.Header.Get("X-Github-Event") == "ping" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle github pull request hook differently
|
// handle github pull request hook differently
|
||||||
if r.Header.Get("X-Github-Event") == "pull_request" {
|
if req.Header.Get("X-Github-Event") == "pull_request" {
|
||||||
return g.GetPullRequestHook(r)
|
return r.ParsePullRequestHook(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the payload of the message
|
// get the payload of the message
|
||||||
payload := r.FormValue("payload")
|
var payload = req.FormValue("payload")
|
||||||
|
|
||||||
// parse the github Hook payload
|
// parse the github Hook payload
|
||||||
data, err := github.ParseHook([]byte(payload))
|
var data, err = github.ParseHook([]byte(payload))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure this is being triggered because of a commit
|
// make sure this is being triggered because of a commit
|
||||||
// and not something like a tag deletion or whatever
|
// and not something like a tag deletion or whatever
|
||||||
if data.IsTag() || data.IsGithubPages() ||
|
if data.IsTag() ||
|
||||||
data.IsHead() == false || data.IsDeleted() {
|
data.IsGithubPages() ||
|
||||||
|
data.IsHead() == false ||
|
||||||
|
data.IsDeleted() {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
hook := remote.Hook{}
|
var hook = new(model.Hook)
|
||||||
hook.Repo = data.Repo.Name
|
hook.Repo = data.Repo.Name
|
||||||
hook.Owner = data.Repo.Owner.Login
|
hook.Owner = data.Repo.Owner.Login
|
||||||
hook.Sha = data.Head.Id
|
hook.Sha = data.Head.Id
|
||||||
|
@ -99,15 +249,17 @@ func (g *Github) GetHook(r *http.Request) (*remote.Hook, error) {
|
||||||
hook.Author = data.Commits[0].Author.Email
|
hook.Author = data.Commits[0].Author.Email
|
||||||
}
|
}
|
||||||
|
|
||||||
return &hook, nil
|
return hook, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Github) GetPullRequestHook(r *http.Request) (*remote.Hook, error) {
|
// ParsePullRequestHook parses the pull request hook from the Request body
|
||||||
payload := r.FormValue("payload")
|
// and returns the required data in a standard format.
|
||||||
|
func (r *GitHub) ParsePullRequestHook(req *http.Request) (*model.Hook, error) {
|
||||||
|
var payload = req.FormValue("payload")
|
||||||
|
|
||||||
// parse the payload to retrieve the pull-request
|
// parse the payload to retrieve the pull-request
|
||||||
// hook meta-data.
|
// hook meta-data.
|
||||||
data, err := github.ParsePullRequestHook([]byte(payload))
|
var data, err = github.ParsePullRequestHook([]byte(payload))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -119,7 +271,7 @@ func (g *Github) GetPullRequestHook(r *http.Request) (*remote.Hook, error) {
|
||||||
|
|
||||||
// TODO we should also store the pull request branch (ie from x to y)
|
// TODO we should also store the pull request branch (ie from x to y)
|
||||||
// we can find it here: data.PullRequest.Head.Ref
|
// we can find it here: data.PullRequest.Head.Ref
|
||||||
hook := remote.Hook{
|
var hook = model.Hook{
|
||||||
Owner: data.Repo.Owner.Login,
|
Owner: data.Repo.Owner.Login,
|
||||||
Repo: data.Repo.Name,
|
Repo: data.Repo.Name,
|
||||||
Sha: data.PullRequest.Head.Sha,
|
Sha: data.PullRequest.Head.Sha,
|
||||||
|
@ -137,78 +289,3 @@ func (g *Github) GetPullRequestHook(r *http.Request) (*remote.Hook, error) {
|
||||||
|
|
||||||
return &hook, nil
|
return &hook, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLogin handles authentication to third party, remote services
|
|
||||||
// and returns the required user data in a standard format.
|
|
||||||
func (g *Github) GetLogin(w http.ResponseWriter, r *http.Request) (*remote.Login, error) {
|
|
||||||
// create the oauth2 client
|
|
||||||
oauth := oauth2.Client{
|
|
||||||
RedirectURL: fmt.Sprintf("%s://%s/login/%s", httputil.GetScheme(r), httputil.GetHost(r), g.GetName()),
|
|
||||||
AccessTokenURL: fmt.Sprintf("%s/login/oauth/access_token", g.URL),
|
|
||||||
AuthorizationURL: fmt.Sprintf("%s/login/oauth/authorize", g.URL),
|
|
||||||
ClientId: g.Client,
|
|
||||||
ClientSecret: g.Secret,
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the OAuth code
|
|
||||||
code := r.FormValue("code")
|
|
||||||
state := r.FormValue("state")
|
|
||||||
if len(code) == 0 {
|
|
||||||
var random = base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32))
|
|
||||||
httputil.SetCookie(w, r, "github_state", string(random))
|
|
||||||
|
|
||||||
// redirect the user to login
|
|
||||||
redirect := oauth.AuthorizeRedirect(scope, random)
|
|
||||||
http.Redirect(w, r, redirect, http.StatusSeeOther)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cookieState := httputil.GetCookie(r, "github_state")
|
|
||||||
httputil.DelCookie(w, r, "github_state")
|
|
||||||
if cookieState != state {
|
|
||||||
return nil, fmt.Errorf("Error matching state in OAuth2 redirect")
|
|
||||||
}
|
|
||||||
|
|
||||||
// exchange code for an auth token
|
|
||||||
token, err := oauth.GrantToken(code)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Error granting GitHub authorization token. %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the client
|
|
||||||
client := github.New(token.AccessToken)
|
|
||||||
client.ApiUrl = g.API
|
|
||||||
|
|
||||||
// get the user information
|
|
||||||
user, err := client.Users.Current()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Error retrieving currently authenticated GitHub user. %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// put the user data in the common format
|
|
||||||
login := remote.Login{
|
|
||||||
ID: user.ID,
|
|
||||||
Login: user.Login,
|
|
||||||
Access: token.AccessToken,
|
|
||||||
Name: user.Name,
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the users primary email address
|
|
||||||
email, err := client.Emails.FindPrimary()
|
|
||||||
if err == nil {
|
|
||||||
login.Email = email.Email
|
|
||||||
}
|
|
||||||
|
|
||||||
return &login, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetClient returns a new Github remote client.
|
|
||||||
func (g *Github) GetClient(access, secret string) remote.Client {
|
|
||||||
return &Client{g, access}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsMatch returns true if the hostname matches the
|
|
||||||
// hostname of this remote client.
|
|
||||||
func (g *Github) IsMatch(hostname string) bool {
|
|
||||||
return strings.HasSuffix(hostname, g.URL)
|
|
||||||
}
|
|
||||||
|
|
94
plugin/remote/github/github_test.go
Normal file
94
plugin/remote/github/github_test.go
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package github
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/plugin/remote/github/testdata"
|
||||||
|
"github.com/drone/drone/shared/model"
|
||||||
|
"github.com/franela/goblin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Github(t *testing.T) {
|
||||||
|
// setup a dummy github server
|
||||||
|
var server = testdata.NewServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
var github = GitHub{
|
||||||
|
URL: server.URL,
|
||||||
|
API: server.URL,
|
||||||
|
}
|
||||||
|
var user = model.User{
|
||||||
|
Access: "e3b0c44298fc1c149afbf4c8996fb",
|
||||||
|
}
|
||||||
|
var repo = model.Repo{
|
||||||
|
Owner: "octocat",
|
||||||
|
Name: "Hello-World",
|
||||||
|
}
|
||||||
|
var hook = model.Hook{
|
||||||
|
Sha: "6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||||
|
}
|
||||||
|
|
||||||
|
g := goblin.Goblin(t)
|
||||||
|
g.Describe("GitHub Plugin", func() {
|
||||||
|
|
||||||
|
g.It("Should identify github vs github enterprise", func() {
|
||||||
|
var ghc = &GitHub{URL: "https://github.com"}
|
||||||
|
var ghe = &GitHub{URL: "https://github.drone.io"}
|
||||||
|
g.Assert(ghc.IsEnterprise()).IsFalse()
|
||||||
|
g.Assert(ghe.IsEnterprise()).IsTrue()
|
||||||
|
g.Assert(ghc.GetKind()).Equal(model.RemoteGithub)
|
||||||
|
g.Assert(ghe.GetKind()).Equal(model.RemoteGithubEnterprise)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should parse the hostname", func() {
|
||||||
|
var ghc = &GitHub{URL: "https://github.com"}
|
||||||
|
var ghe = &GitHub{URL: "https://github.drone.io:80"}
|
||||||
|
g.Assert(ghc.GetHost()).Equal("github.com")
|
||||||
|
g.Assert(ghe.GetHost()).Equal("github.drone.io:80")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should get the repo list", func() {
|
||||||
|
var repos, err = github.GetRepos(&user)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(len(repos)).Equal(4)
|
||||||
|
g.Assert(repos[0].Name).Equal("Hello-World")
|
||||||
|
g.Assert(repos[0].Owner).Equal("octocat")
|
||||||
|
g.Assert(repos[0].Host).Equal(github.GetHost())
|
||||||
|
g.Assert(repos[0].Remote).Equal(github.GetKind())
|
||||||
|
g.Assert(repos[0].Private).Equal(true)
|
||||||
|
g.Assert(repos[0].CloneURL).Equal("git@github.com:octocat/Hello-World.git")
|
||||||
|
g.Assert(repos[0].SSHURL).Equal("git@github.com:octocat/Hello-World.git")
|
||||||
|
g.Assert(repos[0].GitURL).Equal("git://github.com/octocat/Hello-World.git")
|
||||||
|
g.Assert(repos[0].Role.Admin).Equal(true)
|
||||||
|
g.Assert(repos[0].Role.Read).Equal(true)
|
||||||
|
g.Assert(repos[0].Role.Write).Equal(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should get the build script", func() {
|
||||||
|
var script, err = github.GetScript(&user, &repo, &hook)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(string(script)).Equal("image: go")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should activate a public repo", func() {
|
||||||
|
repo.Private = false
|
||||||
|
repo.CloneURL = "git://github.com/octocat/Hello-World.git"
|
||||||
|
repo.SSHURL = "git@github.com:octocat/Hello-World.git"
|
||||||
|
var err = github.Activate(&user, &repo, "http://example.com")
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should activate a private repo", func() {
|
||||||
|
repo.Name = "Hola-Mundo"
|
||||||
|
repo.Private = true
|
||||||
|
repo.CloneURL = "git@github.com:octocat/Hola-Mundo.git"
|
||||||
|
repo.SSHURL = "git@github.com:octocat/Hola-Mundo.git"
|
||||||
|
var err = github.Activate(&user, &repo, "http://example.com")
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should parse a commit hook")
|
||||||
|
|
||||||
|
g.It("Should parse a pull request hook")
|
||||||
|
})
|
||||||
|
}
|
238
plugin/remote/github/helper.go
Normal file
238
plugin/remote/github/helper.go
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
package github
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base32"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"code.google.com/p/goauth2/oauth"
|
||||||
|
"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) *github.Client {
|
||||||
|
t := &oauth.Transport{
|
||||||
|
Token: &oauth.Token{AccessToken: token},
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
user, _, err := client.Users.Get("")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
emails, _, err := client.Users.ListEmails(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, email := range emails {
|
||||||
|
if *email.Primary && *email.Verified {
|
||||||
|
user.Email = email.Email
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("No verified Email address for GitHub account")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllRepos is a helper function that returns an aggregated list
|
||||||
|
// of all user and organization repositories.
|
||||||
|
func GetAllRepos(client *github.Client) ([]github.Repository, error) {
|
||||||
|
orgs, err := GetOrgs(client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
repos, err := GetUserRepos(client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, org := range orgs {
|
||||||
|
list, err := GetOrgRepos(client, *org.Login)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
repos = append(repos, list...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return repos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOrgRepos is a helper function that returns a list of
|
||||||
|
// all org repositories. Paginated results are aggregated into
|
||||||
|
// a single list.
|
||||||
|
func GetOrgRepos(client *github.Client, org string) ([]github.Repository, error) {
|
||||||
|
var repos []github.Repository
|
||||||
|
var opts = github.RepositoryListByOrgOptions{}
|
||||||
|
opts.PerPage = 100
|
||||||
|
opts.Page = 1
|
||||||
|
|
||||||
|
// loop through user repository list
|
||||||
|
for opts.Page > 0 {
|
||||||
|
list, resp, err := client.Repositories.ListByOrg(org, &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 org repositories.
|
||||||
|
func GetOrgs(client *github.Client) ([]github.Organization, error) {
|
||||||
|
orgs, _, err := client.Organizations.List("", nil)
|
||||||
|
return orgs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func GetHook(client *github.Client, owner, name, url string) (*github.Hook, error) {
|
||||||
|
hooks, _, err := client.Repositories.ListHooks(owner, name, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, hook := range hooks {
|
||||||
|
if hook.Config["url"] == url {
|
||||||
|
return &hook, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateHook is a heper function that creates a post-commit hook
|
||||||
|
// for the specified repository.
|
||||||
|
func CreateHook(client *github.Client, owner, name, url string) (*github.Hook, error) {
|
||||||
|
var hook = new(github.Hook)
|
||||||
|
hook.Name = github.String("web")
|
||||||
|
hook.Events = []string{"push", "pull_request"}
|
||||||
|
hook.Config = map[string]interface{}{}
|
||||||
|
hook.Config["url"] = url
|
||||||
|
hook.Config["content_type"] = "json"
|
||||||
|
created, _, err := client.Repositories.CreateHook(owner, name, hook)
|
||||||
|
return created, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUpdateHook is a heper function that creates a post-commit hook
|
||||||
|
// for the specified repository if it does not already exist, otherwise
|
||||||
|
// it updates the existing hook
|
||||||
|
func CreateUpdateHook(client *github.Client, owner, name, url string) (*github.Hook, error) {
|
||||||
|
var hook, _ = GetHook(client, owner, name, url)
|
||||||
|
if hook != nil {
|
||||||
|
hook.Name = github.String("web")
|
||||||
|
hook.Events = []string{"push", "pull_request"}
|
||||||
|
hook.Config = map[string]interface{}{}
|
||||||
|
hook.Config["url"] = url
|
||||||
|
hook.Config["content_type"] = "json"
|
||||||
|
var updated, _, err = client.Repositories.EditHook(owner, name, *hook.ID, hook)
|
||||||
|
return updated, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateHook(client, owner, name, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKey is a heper function that retrieves a public Key by
|
||||||
|
// title. To do this, it will retrieve a list of all keys
|
||||||
|
// and iterate through the list.
|
||||||
|
func GetKey(client *github.Client, owner, name, title string) (*github.Key, error) {
|
||||||
|
keys, _, err := client.Repositories.ListKeys(owner, name, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, key := range keys {
|
||||||
|
if *key.Title == title {
|
||||||
|
return &key, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKeyTitle is a helper function that generates a title for the
|
||||||
|
// RSA public key based on the username and domain name.
|
||||||
|
func GetKeyTitle(rawurl string) (string, error) {
|
||||||
|
var uri, err = url.Parse(rawurl)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("drone@%s", uri.Host), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateKey is a heper function that creates a deploy key
|
||||||
|
// for the specified repository.
|
||||||
|
func CreateKey(client *github.Client, owner, name, title, key string) (*github.Key, error) {
|
||||||
|
var k = new(github.Key)
|
||||||
|
k.Title = github.String(title)
|
||||||
|
k.Key = github.String(key)
|
||||||
|
created, _, err := client.Repositories.CreateKey(owner, name, k)
|
||||||
|
return created, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUpdateKey is a heper function that creates a deployment key
|
||||||
|
// for the specified repository if it does not already exist, otherwise
|
||||||
|
// it updates the existing key
|
||||||
|
func CreateUpdateKey(client *github.Client, owner, name, title, key string) (*github.Key, error) {
|
||||||
|
var k, _ = GetKey(client, owner, name, title)
|
||||||
|
if k != nil {
|
||||||
|
k.Title = github.String(title)
|
||||||
|
k.Key = github.String(key)
|
||||||
|
var updated, _, err = client.Repositories.EditKey(owner, name, *k.ID, k)
|
||||||
|
return updated, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateKey(client, owner, name, title, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
}
|
34
plugin/remote/github/helper_test.go
Normal file
34
plugin/remote/github/helper_test.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package github
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/plugin/remote/github/testdata"
|
||||||
|
"github.com/franela/goblin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Helper(t *testing.T) {
|
||||||
|
// setup a dummy github server
|
||||||
|
var server = testdata.NewServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
g := goblin.Goblin(t)
|
||||||
|
g.Describe("GitHub Helper Functions", func() {
|
||||||
|
|
||||||
|
g.It("Should Get a User")
|
||||||
|
g.It("Should Get a User Primary Email")
|
||||||
|
g.It("Should Get a User + Primary Email")
|
||||||
|
g.It("Should Get a list of Orgs")
|
||||||
|
g.It("Should Get a list of User Repos")
|
||||||
|
g.It("Should Get a list of Org Repos")
|
||||||
|
g.It("Should Get a list of All Repos")
|
||||||
|
g.It("Should Get a Repo Key")
|
||||||
|
g.It("Should Get a Repo Hook")
|
||||||
|
g.It("Should Create a Repo Key")
|
||||||
|
g.It("Should Create a Repo Hook")
|
||||||
|
g.It("Should Create or Update a Repo Key")
|
||||||
|
g.It("Should Create or Update a Repo Hook")
|
||||||
|
g.It("Should Get a Repo File")
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,21 +0,0 @@
|
||||||
package github
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/drone/drone/plugin/remote"
|
|
||||||
"github.com/drone/drone/shared/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
remote.Register(model.RemoteGithub, plugin)
|
|
||||||
remote.Register(model.RemoteGithubEnterprise, plugin)
|
|
||||||
}
|
|
||||||
|
|
||||||
func plugin(remote *model.Remote) remote.Remote {
|
|
||||||
return &Github{
|
|
||||||
URL: remote.URL,
|
|
||||||
API: remote.API,
|
|
||||||
Client: remote.Client,
|
|
||||||
Secret: remote.Secret,
|
|
||||||
Enabled: remote.Open,
|
|
||||||
}
|
|
||||||
}
|
|
41
plugin/remote/github/register.go
Normal file
41
plugin/remote/github/register.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package github
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/drone/drone/plugin/remote"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
init_github()
|
||||||
|
init_github_enterprise()
|
||||||
|
}
|
||||||
|
|
||||||
|
// registers the GitHub (github.com) plugin
|
||||||
|
func init_github() {
|
||||||
|
var cli = os.Getenv("GITHUB_CLIENT")
|
||||||
|
var sec = os.Getenv("GITHUB_SECRET")
|
||||||
|
if len(cli) == 0 ||
|
||||||
|
len(sec) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var github = NewDefault(cli, sec)
|
||||||
|
remote.Register(github)
|
||||||
|
}
|
||||||
|
|
||||||
|
// registers the GitHub Enterprise plugin
|
||||||
|
func init_github_enterprise() {
|
||||||
|
var url = os.Getenv("GITHUB_ENTERPRISE_URL")
|
||||||
|
var api = os.Getenv("GITHUB_ENTERPRISE_API")
|
||||||
|
var cli = os.Getenv("GITHUB_ENTERPRISE_CLIENT")
|
||||||
|
var sec = os.Getenv("GITHUB_ENTERPRISE_SECRET")
|
||||||
|
|
||||||
|
if len(url) == 0 ||
|
||||||
|
len(api) == 0 ||
|
||||||
|
len(cli) == 0 ||
|
||||||
|
len(sec) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var github = New(url, api, cli, sec)
|
||||||
|
remote.Register(github)
|
||||||
|
}
|
148
plugin/remote/github/testdata/testdata.go
vendored
Normal file
148
plugin/remote/github/testdata/testdata.go
vendored
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
package testdata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// setup a mock server for testing purposes.
|
||||||
|
func NewServer() *httptest.Server {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
server := httptest.NewServer(mux)
|
||||||
|
|
||||||
|
// handle requests and serve mock data
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
// evaluate the path to serve a dummy data file
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/user/repos":
|
||||||
|
w.Write(userReposPayload)
|
||||||
|
return
|
||||||
|
case "/user/orgs":
|
||||||
|
w.Write(userOrgsPayload)
|
||||||
|
return
|
||||||
|
case "/orgs/github/repos":
|
||||||
|
w.Write(userReposPayload)
|
||||||
|
return
|
||||||
|
case "/repos/octocat/Hello-World/contents/.drone.yml":
|
||||||
|
w.Write(droneYamlPayload)
|
||||||
|
return
|
||||||
|
case "/repos/octocat/Hello-World/hooks":
|
||||||
|
switch r.Method {
|
||||||
|
case "POST":
|
||||||
|
w.Write(createHookPayload)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "/repos/octocat/Hola-Mundo/hooks":
|
||||||
|
switch r.Method {
|
||||||
|
case "POST":
|
||||||
|
w.Write(createHookPayload)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "/repos/octocat/Hola-Mundo/keys":
|
||||||
|
switch r.Method {
|
||||||
|
case "POST":
|
||||||
|
w.Write(createKeyPayload)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// else return a 404
|
||||||
|
http.NotFound(w, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
// return the server to the client which
|
||||||
|
// will need to know the base URL path
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
// sample repository list
|
||||||
|
var userReposPayload = []byte(`
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"owner": {
|
||||||
|
"login": "octocat",
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"id": 1296269,
|
||||||
|
"name": "Hello-World",
|
||||||
|
"full_name": "octocat/Hello-World",
|
||||||
|
"private": true,
|
||||||
|
"url": "https://api.github.com/repos/octocat/Hello-World",
|
||||||
|
"html_url": "https://github.com/octocat/Hello-World",
|
||||||
|
"clone_url": "https://github.com/octocat/Hello-World.git",
|
||||||
|
"git_url": "git://github.com/octocat/Hello-World.git",
|
||||||
|
"ssh_url": "git@github.com:octocat/Hello-World.git",
|
||||||
|
"permissions": {
|
||||||
|
"admin": true,
|
||||||
|
"push": true,
|
||||||
|
"pull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"owner": {
|
||||||
|
"login": "octocat",
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"id": 9626921,
|
||||||
|
"name": "Hola-Mundo",
|
||||||
|
"full_name": "octocat/Hola-Mundo",
|
||||||
|
"private": false,
|
||||||
|
"url": "https://api.github.com/repos/octocat/Hola-Mundo",
|
||||||
|
"html_url": "https://github.com/octocat/Hola-Mundo",
|
||||||
|
"clone_url": "https://github.com/octocat/Hola-Mundo.git",
|
||||||
|
"git_url": "git://github.com/octocat/Hola-Mundo.git",
|
||||||
|
"ssh_url": "git@github.com:octocat/Hola-Mundo.git",
|
||||||
|
"permissions": {
|
||||||
|
"admin": false,
|
||||||
|
"push": false,
|
||||||
|
"pull": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
var emptySetPayload = []byte(`[]`)
|
||||||
|
var emptyObjPayload = []byte(`{}`)
|
||||||
|
|
||||||
|
// sample org list response
|
||||||
|
var userOrgsPayload = []byte(`
|
||||||
|
[
|
||||||
|
{ "login": "github", "id": 1 }
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
// sample content response for .drone.yml request
|
||||||
|
var droneYamlPayload = []byte(`
|
||||||
|
{
|
||||||
|
"type": "file",
|
||||||
|
"encoding": "base64",
|
||||||
|
"name": ".drone.yml",
|
||||||
|
"path": ".drone.yml",
|
||||||
|
"content": "aW1hZ2U6IGdv"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
// sample create hook response
|
||||||
|
var createHookPayload = []byte(`
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "web",
|
||||||
|
"events": [ "push", "pull_request" ],
|
||||||
|
"active": true,
|
||||||
|
"config": {
|
||||||
|
"url": "http://example.com",
|
||||||
|
"content_type": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
// sample create hook response
|
||||||
|
var createKeyPayload = []byte(`
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"key": "ssh-rsa AAA...",
|
||||||
|
"url": "https://api.github.com/user/keys/1",
|
||||||
|
"title": "octocat@octomac"
|
||||||
|
}
|
||||||
|
`)
|
|
@ -1,37 +0,0 @@
|
||||||
package gitlab
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/drone/drone/plugin/remote"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
config *Gitlab
|
|
||||||
access string // user access token
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUser fetches the user by ID (login name).
|
|
||||||
func (c *Client) GetUser(login string) (*remote.User, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRepos fetches all repositories that the specified
|
|
||||||
// user has access to in the remote system.
|
|
||||||
func (c *Client) GetRepos(owner string) ([]*remote.Repo, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetScript fetches the build script (.drone.yml) from the remote
|
|
||||||
// repository and returns in string format.
|
|
||||||
func (c *Client) GetScript(*remote.Hook) (string, error) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetStatus
|
|
||||||
func (c *Client) SetStatus(owner, repo, sha, status string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetActive
|
|
||||||
func (c *Client) SetActive(owner, repo, hook, key string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,51 +1,174 @@
|
||||||
package gitlab
|
package gitlab
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/drone/drone/plugin/remote"
|
"github.com/Bugagazavr/go-gitlab-client"
|
||||||
|
"github.com/drone/drone/shared/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Gitlab struct {
|
type Gitlab struct {
|
||||||
URL string `json:"url"` // https://github.com
|
url string
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetName returns the name of this remote system.
|
func New(url string) *Gitlab {
|
||||||
func (g *Gitlab) GetName() string {
|
return &Gitlab{url: url}
|
||||||
return "gitlab.com"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHost returns the url.Host of this remote system.
|
// Authorize handles authentication with thrid party remote systems,
|
||||||
func (g *Gitlab) GetHost() (host string) {
|
// such as github or bitbucket, and returns user data.
|
||||||
u, err := url.Parse(g.URL)
|
func (r *Gitlab) Authorize(res http.ResponseWriter, req *http.Request) (*model.Login, error) {
|
||||||
|
var username = req.FormValue("username")
|
||||||
|
var password = req.FormValue("password")
|
||||||
|
|
||||||
|
var client = NewClient(r.url, "")
|
||||||
|
var session, err = client.GetSession(username, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
return u.Host
|
|
||||||
|
var login = new(model.Login)
|
||||||
|
login.ID = int64(session.Id)
|
||||||
|
login.Access = session.PrivateToken
|
||||||
|
login.Login = session.UserName
|
||||||
|
login.Name = session.Name
|
||||||
|
login.Email = session.Email
|
||||||
|
return login, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHook parses the post-commit hook from the Request body
|
// GetKind returns the identifier of this remote GitHub instane.
|
||||||
|
func (r *Gitlab) GetKind() string {
|
||||||
|
return model.RemoteGitlab
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHost returns the hostname of this remote GitHub instance.
|
||||||
|
func (r *Gitlab) GetHost() string {
|
||||||
|
uri, _ := url.Parse(r.url)
|
||||||
|
return uri.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRepos fetches all repositories that the specified
|
||||||
|
// user has access to in the remote system.
|
||||||
|
func (r *Gitlab) GetRepos(user *model.User) ([]*model.Repo, error) {
|
||||||
|
|
||||||
|
var repos []*model.Repo
|
||||||
|
var client = NewClient(r.url, user.Access)
|
||||||
|
var list, err = client.AllProjects()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var remote = r.GetKind()
|
||||||
|
var hostname = r.GetHost()
|
||||||
|
|
||||||
|
for _, item := range list {
|
||||||
|
var repo = model.Repo{
|
||||||
|
UserID: user.ID,
|
||||||
|
Remote: remote,
|
||||||
|
Host: hostname,
|
||||||
|
Owner: item.Namespace.Path,
|
||||||
|
Name: item.Path,
|
||||||
|
Private: !item.Public,
|
||||||
|
CloneURL: item.HttpRepoUrl,
|
||||||
|
GitURL: item.HttpRepoUrl,
|
||||||
|
SSHURL: item.SshRepoUrl,
|
||||||
|
Role: &model.Perm{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if repo.Private {
|
||||||
|
repo.CloneURL = repo.SSHURL
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no permissions we should skip the repository
|
||||||
|
// entirely, since this should never happen
|
||||||
|
if item.Permissions == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
repo.Role.Admin = IsAdmin(item)
|
||||||
|
repo.Role.Write = IsWrite(item)
|
||||||
|
repo.Role.Read = IsRead(item)
|
||||||
|
repos = append(repos, &repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return repos, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetScript fetches the build script (.drone.yml) from the remote
|
||||||
|
// repository and returns in string format.
|
||||||
|
func (r *Gitlab) GetScript(user *model.User, repo *model.Repo, hook *model.Hook) ([]byte, error) {
|
||||||
|
var client = NewClient(r.url, user.Access)
|
||||||
|
var path = ns(repo.Owner, repo.Name)
|
||||||
|
return client.RepoRawFile(path, hook.Sha, ".drone.yml")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate activates a repository by adding a Post-commit hook and
|
||||||
|
// a Public Deploy key, if applicable.
|
||||||
|
func (r *Gitlab) Activate(user *model.User, repo *model.Repo, link string) error {
|
||||||
|
var client = NewClient(r.url, user.Access)
|
||||||
|
var path = ns(repo.Owner, repo.Name)
|
||||||
|
var title, err = GetKeyTitle(link)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the repository is private we'll need
|
||||||
|
// to upload a github key to the repository
|
||||||
|
if repo.Private {
|
||||||
|
var err = client.AddProjectDeployKey(path, title, repo.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// append the repo owner / name to the hook url since gitlab
|
||||||
|
// doesn't send this detail in the post-commit hook
|
||||||
|
link += "?owner=" + repo.Owner + "&name=" + repo.Name
|
||||||
|
|
||||||
|
// add the hook
|
||||||
|
return client.AddProjectHook(path, link, true, false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseHook 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 *Gitlab) GetHook(*http.Request) (*remote.Hook, error) {
|
func (r *Gitlab) ParseHook(req *http.Request) (*model.Hook, error) {
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLogin handles authentication to third party, remote services
|
defer req.Body.Close()
|
||||||
// and returns the required user data in a standard format.
|
var payload, _ = ioutil.ReadAll(req.Body)
|
||||||
func (g *Gitlab) GetLogin(http.ResponseWriter, *http.Request) (*remote.Login, error) {
|
var parsed, err = gogitlab.ParseHook(payload)
|
||||||
return nil, nil
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// GetClient returns a new Gitlab remote client.
|
if parsed.ObjectKind == "merge_request" {
|
||||||
func (g *Gitlab) GetClient(access, secret string) remote.Client {
|
// TODO (bradrydzewski) figure out how to handle merge requests
|
||||||
return &Client{g, access}
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsMatch returns true if the hostname matches the
|
if len(parsed.After) == 0 {
|
||||||
// hostname of this remote client.
|
return nil, nil
|
||||||
func (g *Gitlab) IsMatch(hostname string) bool {
|
}
|
||||||
return strings.HasSuffix(hostname, g.URL)
|
|
||||||
|
var hook = new(model.Hook)
|
||||||
|
hook.Owner = req.FormValue("owner")
|
||||||
|
hook.Repo = req.FormValue("name")
|
||||||
|
hook.Sha = parsed.After
|
||||||
|
hook.Branch = parsed.Branch()
|
||||||
|
|
||||||
|
var head = parsed.Head()
|
||||||
|
hook.Message = head.Message
|
||||||
|
hook.Timestamp = head.Timestamp
|
||||||
|
|
||||||
|
// extracts the commit author (ideally email)
|
||||||
|
// from the post-commit hook
|
||||||
|
switch {
|
||||||
|
case head.Author != nil:
|
||||||
|
hook.Author = head.Author.Email
|
||||||
|
case head.Author == nil:
|
||||||
|
hook.Author = parsed.UserName
|
||||||
|
}
|
||||||
|
|
||||||
|
return hook, nil
|
||||||
}
|
}
|
||||||
|
|
86
plugin/remote/gitlab/gitlab_test.go
Normal file
86
plugin/remote/gitlab/gitlab_test.go
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
package gitlab
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/plugin/remote/gitlab/testdata"
|
||||||
|
"github.com/drone/drone/shared/model"
|
||||||
|
"github.com/franela/goblin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Github(t *testing.T) {
|
||||||
|
// setup a dummy github server
|
||||||
|
var server = testdata.NewServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
var gitlab = New(server.URL)
|
||||||
|
var user = model.User{
|
||||||
|
Access: "e3b0c44298fc1c149afbf4c8996fb",
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
var repo = model.Repo{
|
||||||
|
Owner: "gitlab",
|
||||||
|
Name: "Hello-World",
|
||||||
|
}
|
||||||
|
var commit = model.Commit{
|
||||||
|
Sha: "6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
g := goblin.Goblin(t)
|
||||||
|
g.Describe("Gitlab Plugin", func() {
|
||||||
|
|
||||||
|
g.It("Should authorize user", func() {
|
||||||
|
var req, _ = http.NewRequest("GET", "/login/gitlab", nil)
|
||||||
|
var login, err = gitlab.Authorize(nil, req)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(login.Email).Equal("john@example.com")
|
||||||
|
g.Assert(login.Name).Equal("John Smith")
|
||||||
|
g.Assert(login.Login).Equal("john_smith")
|
||||||
|
g.Assert(login.Access).Equal("dd34asd13as")
|
||||||
|
g.Assert(login.ID).Equal(int64(1))
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should get the repo list", func() {
|
||||||
|
var repos, err = gitlab.GetRepos(&user)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(len(repos)).Equal(2)
|
||||||
|
g.Assert(repos[0].Name).Equal("diaspora-client")
|
||||||
|
g.Assert(repos[0].Owner).Equal("diaspora")
|
||||||
|
g.Assert(repos[0].Host).Equal(gitlab.GetHost())
|
||||||
|
g.Assert(repos[0].Remote).Equal(gitlab.GetKind())
|
||||||
|
g.Assert(repos[0].Private).Equal(true)
|
||||||
|
g.Assert(repos[0].Role.Admin).Equal(true)
|
||||||
|
g.Assert(repos[0].Role.Read).Equal(true)
|
||||||
|
g.Assert(repos[0].Role.Write).Equal(true)
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
g.It("Should get the build script", func() {
|
||||||
|
var script, err = github.GetScript(&user, &repo, &commit)
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
g.Assert(string(script)).Equal("image: go")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should activate a public repo", func() {
|
||||||
|
repo.Private = false
|
||||||
|
repo.CloneURL = "git://github.com/octocat/Hello-World.git"
|
||||||
|
repo.SSHURL = "git@github.com:octocat/Hello-World.git"
|
||||||
|
var err = github.Activate(&user, &repo, "http://example.com")
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should activate a private repo", func() {
|
||||||
|
repo.Name = "Hola-Mundo"
|
||||||
|
repo.Private = true
|
||||||
|
repo.CloneURL = "git@github.com:octocat/Hola-Mundo.git"
|
||||||
|
repo.SSHURL = "git@github.com:octocat/Hola-Mundo.git"
|
||||||
|
var err = github.Activate(&user, &repo, "http://example.com")
|
||||||
|
g.Assert(err == nil).IsTrue()
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
g.It("Should parse a commit hook")
|
||||||
|
|
||||||
|
g.It("Should ignore a pull request hook")
|
||||||
|
})
|
||||||
|
}
|
78
plugin/remote/gitlab/helper.go
Normal file
78
plugin/remote/gitlab/helper.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package gitlab
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/Bugagazavr/go-gitlab-client"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewClient is a helper function that returns a new GitHub
|
||||||
|
// client using the provided OAuth token.
|
||||||
|
func NewClient(uri, token string) *gogitlab.Gitlab {
|
||||||
|
return gogitlab.NewGitlab(uri, "/api/v3", token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRead is a helper function that returns true if the
|
||||||
|
// user has Read-only access to the repository.
|
||||||
|
func IsRead(proj *gogitlab.Project) bool {
|
||||||
|
var user = proj.Permissions.ProjectAccess
|
||||||
|
var group = proj.Permissions.GroupAccess
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case proj.Public:
|
||||||
|
return true
|
||||||
|
case user != nil && user.AccessLevel >= 20:
|
||||||
|
return true
|
||||||
|
case group != nil && group.AccessLevel >= 20:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsWrite is a helper function that returns true if the
|
||||||
|
// user has Read-Write access to the repository.
|
||||||
|
func IsWrite(proj *gogitlab.Project) bool {
|
||||||
|
var user = proj.Permissions.ProjectAccess
|
||||||
|
var group = proj.Permissions.GroupAccess
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case user != nil && user.AccessLevel >= 30:
|
||||||
|
return true
|
||||||
|
case group != nil && group.AccessLevel >= 30:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAdmin is a helper function that returns true if the
|
||||||
|
// user has Admin access to the repository.
|
||||||
|
func IsAdmin(proj *gogitlab.Project) bool {
|
||||||
|
var user = proj.Permissions.ProjectAccess
|
||||||
|
var group = proj.Permissions.GroupAccess
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case user != nil && user.AccessLevel >= 40:
|
||||||
|
return true
|
||||||
|
case group != nil && group.AccessLevel >= 40:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKeyTitle is a helper function that generates a title for the
|
||||||
|
// RSA public key based on the username and domain name.
|
||||||
|
func GetKeyTitle(rawurl string) (string, error) {
|
||||||
|
var uri, err = url.Parse(rawurl)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("drone@%s", uri.Host), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ns(owner, name string) string {
|
||||||
|
return fmt.Sprintf("%s%%2F%s", owner, name)
|
||||||
|
}
|
1
plugin/remote/gitlab/helper_test.go
Normal file
1
plugin/remote/gitlab/helper_test.go
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package gitlab
|
|
@ -1,17 +0,0 @@
|
||||||
package gitlab
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/drone/drone/plugin/remote"
|
|
||||||
"github.com/drone/drone/shared/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
remote.Register(model.RemoteGitlab, plugin)
|
|
||||||
}
|
|
||||||
|
|
||||||
func plugin(remote *model.Remote) remote.Remote {
|
|
||||||
return &Gitlab{
|
|
||||||
URL: remote.URL,
|
|
||||||
Enabled: remote.Open,
|
|
||||||
}
|
|
||||||
}
|
|
16
plugin/remote/gitlab/register.go
Normal file
16
plugin/remote/gitlab/register.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package gitlab
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/drone/drone/plugin/remote"
|
||||||
|
)
|
||||||
|
|
||||||
|
// registers the Gitlab plugin
|
||||||
|
func init() {
|
||||||
|
var url = os.Getenv("GITLAB_URL")
|
||||||
|
if len(url) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
remote.Register(New(url))
|
||||||
|
}
|
151
plugin/remote/gitlab/testdata/testdata.go
vendored
Normal file
151
plugin/remote/gitlab/testdata/testdata.go
vendored
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
package testdata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// setup a mock server for testing purposes.
|
||||||
|
func NewServer() *httptest.Server {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
server := httptest.NewServer(mux)
|
||||||
|
|
||||||
|
// handle requests and serve mock data
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
//println(r.URL.Path + " " + r.Method)
|
||||||
|
// evaluate the path to serve a dummy data file
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/api/v3/projects":
|
||||||
|
w.Write(projectsPayload)
|
||||||
|
return
|
||||||
|
case "/api/v3/session":
|
||||||
|
w.Write(sessionPayload)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// else return a 404
|
||||||
|
http.NotFound(w, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
// return the server to the client which
|
||||||
|
// will need to know the base URL path
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
// sample repository list
|
||||||
|
var projectsPayload = []byte(`
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"description": null,
|
||||||
|
"default_branch": "master",
|
||||||
|
"public": false,
|
||||||
|
"visibility_level": 0,
|
||||||
|
"ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
|
||||||
|
"http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
|
||||||
|
"web_url": "http://example.com/diaspora/diaspora-client",
|
||||||
|
"owner": {
|
||||||
|
"id": 3,
|
||||||
|
"name": "Diaspora",
|
||||||
|
"created_at": "2013-09-30T13: 46: 02Z"
|
||||||
|
},
|
||||||
|
"name": "Diaspora Client",
|
||||||
|
"name_with_namespace": "Diaspora / Diaspora Client",
|
||||||
|
"path": "diaspora-client",
|
||||||
|
"path_with_namespace": "diaspora/diaspora-client",
|
||||||
|
"issues_enabled": true,
|
||||||
|
"merge_requests_enabled": true,
|
||||||
|
"wiki_enabled": true,
|
||||||
|
"snippets_enabled": false,
|
||||||
|
"created_at": "2013-09-30T13: 46: 02Z",
|
||||||
|
"last_activity_at": "2013-09-30T13: 46: 02Z",
|
||||||
|
"namespace": {
|
||||||
|
"created_at": "2013-09-30T13: 46: 02Z",
|
||||||
|
"description": "",
|
||||||
|
"id": 3,
|
||||||
|
"name": "Diaspora",
|
||||||
|
"owner_id": 1,
|
||||||
|
"path": "diaspora",
|
||||||
|
"updated_at": "2013-09-30T13: 46: 02Z"
|
||||||
|
},
|
||||||
|
"archived": false,
|
||||||
|
"permissions": {
|
||||||
|
"project_access": {
|
||||||
|
"access_level": 10,
|
||||||
|
"notification_level": 3
|
||||||
|
},
|
||||||
|
"group_access": {
|
||||||
|
"access_level": 50,
|
||||||
|
"notification_level": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"description": null,
|
||||||
|
"default_branch": "master",
|
||||||
|
"public": false,
|
||||||
|
"visibility_level": 0,
|
||||||
|
"ssh_url_to_repo": "git@example.com:brightbox/puppet.git",
|
||||||
|
"http_url_to_repo": "http://example.com/brightbox/puppet.git",
|
||||||
|
"web_url": "http://example.com/brightbox/puppet",
|
||||||
|
"owner": {
|
||||||
|
"id": 4,
|
||||||
|
"name": "Brightbox",
|
||||||
|
"created_at": "2013-09-30T13:46:02Z"
|
||||||
|
},
|
||||||
|
"name": "Puppet",
|
||||||
|
"name_with_namespace": "Brightbox / Puppet",
|
||||||
|
"path": "puppet",
|
||||||
|
"path_with_namespace": "brightbox/puppet",
|
||||||
|
"issues_enabled": true,
|
||||||
|
"merge_requests_enabled": true,
|
||||||
|
"wiki_enabled": true,
|
||||||
|
"snippets_enabled": false,
|
||||||
|
"created_at": "2013-09-30T13:46:02Z",
|
||||||
|
"last_activity_at": "2013-09-30T13:46:02Z",
|
||||||
|
"namespace": {
|
||||||
|
"created_at": "2013-09-30T13:46:02Z",
|
||||||
|
"description": "",
|
||||||
|
"id": 4,
|
||||||
|
"name": "Brightbox",
|
||||||
|
"owner_id": 1,
|
||||||
|
"path": "brightbox",
|
||||||
|
"updated_at": "2013-09-30T13:46:02Z"
|
||||||
|
},
|
||||||
|
"archived": false,
|
||||||
|
"permissions": {
|
||||||
|
"project_access": {
|
||||||
|
"access_level": 10,
|
||||||
|
"notification_level": 3
|
||||||
|
},
|
||||||
|
"group_access": {
|
||||||
|
"access_level": 50,
|
||||||
|
"notification_level": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
// sample org list response
|
||||||
|
var sessionPayload = []byte(`
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"username": "john_smith",
|
||||||
|
"email": "john@example.com",
|
||||||
|
"name": "John Smith",
|
||||||
|
"private_token": "dd34asd13as"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
// sample content response for .drone.yml request
|
||||||
|
var droneYamlPayload = []byte(`
|
||||||
|
{
|
||||||
|
"type": "file",
|
||||||
|
"encoding": "base64",
|
||||||
|
"name": ".drone.yml",
|
||||||
|
"path": ".drone.yml",
|
||||||
|
"content": "aW1hZ2U6IGdv"
|
||||||
|
}
|
||||||
|
`)
|
|
@ -6,113 +6,58 @@ import (
|
||||||
"github.com/drone/drone/shared/model"
|
"github.com/drone/drone/shared/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Defines a model for integrating (or pluggin in) remote version
|
|
||||||
// control systems, such as GitHub and Bitbucket.
|
|
||||||
type Plugin func(*model.Remote) Remote
|
|
||||||
|
|
||||||
var plugins = map[string]Plugin{}
|
|
||||||
|
|
||||||
// Register registers a new plugin.
|
|
||||||
func Register(name string, plugin Plugin) {
|
|
||||||
plugins[name] = plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lookup retrieves the plugin for the remote.
|
|
||||||
func Lookup(name string) (Plugin, bool) {
|
|
||||||
plugin, ok := plugins[name]
|
|
||||||
return plugin, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
type Remote interface {
|
type Remote interface {
|
||||||
// GetName returns the name of this remote system.
|
// Authorize handles authentication with thrid party remote systems,
|
||||||
GetName() string
|
// such as github or bitbucket, and returns user data.
|
||||||
|
Authorize(w http.ResponseWriter, r *http.Request) (*model.Login, error)
|
||||||
|
|
||||||
// GetHost returns the URL hostname of this remote system.
|
// GetKind returns the kind of plugin
|
||||||
GetHost() (host string)
|
GetKind() string
|
||||||
|
|
||||||
// GetHook parses the post-commit hook from the Request body
|
// GetHost returns the hostname of the remote service.
|
||||||
// and returns the required data in a standard format.
|
GetHost() string
|
||||||
GetHook(*http.Request) (*Hook, error)
|
|
||||||
|
|
||||||
// GetLogin handles authentication to third party, remote services
|
|
||||||
// and returns the required user data in a standard format.
|
|
||||||
GetLogin(http.ResponseWriter, *http.Request) (*Login, error)
|
|
||||||
|
|
||||||
// NewClient returns a new Bitbucket remote client.
|
|
||||||
GetClient(access, secret string) Client
|
|
||||||
|
|
||||||
// Match returns true if the hostname matches the
|
|
||||||
// hostname of this remote client.
|
|
||||||
IsMatch(hostname string) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type Client interface {
|
|
||||||
// GetUser fetches the user by ID (login name).
|
|
||||||
GetUser(login string) (*User, error)
|
|
||||||
|
|
||||||
// GetRepos fetches all repositories that the specified
|
// GetRepos fetches all repositories that the specified
|
||||||
// user has access to in the remote system.
|
// user has access to in the remote system.
|
||||||
GetRepos(owner string) ([]*Repo, error)
|
GetRepos(user *model.User) ([]*model.Repo, error)
|
||||||
|
|
||||||
// GetScript fetches the build script (.drone.yml) from the remote
|
// GetScript fetches the build script (.drone.yml) from the remote
|
||||||
// repository and returns in string format.
|
// repository and returns in string format.
|
||||||
GetScript(*Hook) (string, error)
|
GetScript(user *model.User, repo *model.Repo, hook *model.Hook) ([]byte, error)
|
||||||
|
|
||||||
// SetStatus
|
// Activate activates a repository by creating the post-commit hook and
|
||||||
SetStatus(owner, repo, sha, status string) error
|
// adding the SSH deploy key, if applicable.
|
||||||
|
Activate(user *model.User, repo *model.Repo, link string) error
|
||||||
|
|
||||||
// SetActive
|
// ParseHook parses the post-commit hook from the Request body
|
||||||
SetActive(owner, repo, hook, key string) error
|
// and returns the required data in a standard format.
|
||||||
|
ParseHook(r *http.Request) (*model.Hook, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook represents a subset of commit meta-data provided
|
// List of registered plugins.
|
||||||
// by post-commit and pull request hooks.
|
var remotes []Remote
|
||||||
type Hook struct {
|
|
||||||
Owner string
|
// Register registers a plugin by name.
|
||||||
Repo string
|
//
|
||||||
Sha string
|
// All plugins must be registered when the application
|
||||||
Branch string
|
// initializes. This should not be invoked while the application
|
||||||
PullRequest string
|
// is running, and is not thread safe.
|
||||||
Author string
|
func Register(remote Remote) {
|
||||||
Gravatar string
|
remotes = append(remotes, remote)
|
||||||
Timestamp string
|
|
||||||
Message string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login represents a standard subset of user meta-data
|
// List Registered remote plugins
|
||||||
// provided by OAuth login services.
|
func Registered() []Remote {
|
||||||
type Login struct {
|
return remotes
|
||||||
ID int64
|
|
||||||
Login string
|
|
||||||
Access string
|
|
||||||
Secret string
|
|
||||||
Name string
|
|
||||||
Email string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// User represents a standard subset of user meta-data
|
// Lookup gets a plugin by name.
|
||||||
// returned by REST API user endpoints (ie github user api).
|
func Lookup(name string) Remote {
|
||||||
type User struct {
|
for _, remote := range remotes {
|
||||||
ID int64
|
if remote.GetKind() == name ||
|
||||||
Login string
|
remote.GetHost() == name {
|
||||||
Name string
|
return remote
|
||||||
Gravatar string
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
// Repo represents a standard subset of repository meta-data
|
|
||||||
// returned by REST API endpoints (ie github repo api).
|
|
||||||
type Repo struct {
|
|
||||||
ID int64
|
|
||||||
Host string
|
|
||||||
Owner string
|
|
||||||
Name string
|
|
||||||
Kind string
|
|
||||||
Clone string
|
|
||||||
Git string
|
|
||||||
SSH string
|
|
||||||
URL string
|
|
||||||
Private bool
|
|
||||||
Pull bool
|
|
||||||
Push bool
|
|
||||||
Admin bool
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
package stash
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/drone/drone/plugin/remote"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
config *Stash
|
|
||||||
access string // user access token
|
|
||||||
secret string // user access token secret
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUser fetches the user by ID (login name).
|
|
||||||
func (c *Client) GetUser(login string) (*remote.User, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRepos fetches all repositories that the specified
|
|
||||||
// user has access to in the remote system.
|
|
||||||
func (c *Client) GetRepos(owner string) ([]*remote.Repo, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetScript fetches the build script (.drone.yml) from the remote
|
|
||||||
// repository and returns in string format.
|
|
||||||
func (c *Client) GetScript(*remote.Hook) (string, error) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetStatus
|
|
||||||
func (c *Client) SetStatus(owner, repo, sha, status string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetActive
|
|
||||||
func (c *Client) SetActive(owner, repo, hook, key string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
package stash
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/drone/drone/plugin/remote"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Stash struct {
|
|
||||||
URL string `json:"url"` // https://bitbucket.org
|
|
||||||
API string `json:"api"` // https://api.bitbucket.org
|
|
||||||
Client string `json:"client"`
|
|
||||||
Secret string `json:"secret"`
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of this remote system.
|
|
||||||
func (s *Stash) GetName() string {
|
|
||||||
return "stash.atlassian.com"
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHost returns the url.Host of this remote system.
|
|
||||||
func (s *Stash) GetHost() (host string) {
|
|
||||||
u, err := url.Parse(s.URL)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return u.Host
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHook parses the post-commit hook from the Request body
|
|
||||||
// and returns the required data in a standard format.
|
|
||||||
func (s *Stash) GetHook(*http.Request) (*remote.Hook, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLogin handles authentication to third party, remote services
|
|
||||||
// and returns the required user data in a standard format.
|
|
||||||
func (s *Stash) GetLogin(http.ResponseWriter, *http.Request) (*remote.Login, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetClient returns a new Stash remote client.
|
|
||||||
func (s *Stash) GetClient(access, secret string) remote.Client {
|
|
||||||
return &Client{s, access, secret}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsMatch returns true if the hostname matches the
|
|
||||||
// hostname of this remote client.
|
|
||||||
func (s *Stash) IsMatch(hostname string) bool {
|
|
||||||
return strings.HasSuffix(hostname, s.URL)
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -28,24 +27,14 @@ func NewHookHandler(users database.UserManager, repos database.RepoManager, comm
|
||||||
// PostHook receives a post-commit hook from GitHub, Bitbucket, etc
|
// PostHook receives a post-commit hook from GitHub, Bitbucket, etc
|
||||||
// GET /hook/:host
|
// GET /hook/:host
|
||||||
func (h *HookHandler) PostHook(w http.ResponseWriter, r *http.Request) error {
|
func (h *HookHandler) PostHook(w http.ResponseWriter, r *http.Request) error {
|
||||||
host := r.FormValue(":host")
|
var host = r.FormValue(":host")
|
||||||
log.Println("received post-commit hook.")
|
var remote = remote.Lookup(host)
|
||||||
|
if remote == nil {
|
||||||
remoteServer, err := h.remotes.FindType(host)
|
|
||||||
if err != nil {
|
|
||||||
return notFound{err}
|
|
||||||
}
|
|
||||||
|
|
||||||
remotePlugin, ok := remote.Lookup(remoteServer.Type)
|
|
||||||
if !ok {
|
|
||||||
return notFound{}
|
return notFound{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the remote system's client.
|
|
||||||
plugin := remotePlugin(remoteServer)
|
|
||||||
|
|
||||||
// parse the hook payload
|
// parse the hook payload
|
||||||
hook, err := plugin.GetHook(r)
|
hook, err := remote.ParseHook(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return badRequest{err}
|
return badRequest{err}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +48,7 @@ func (h *HookHandler) PostHook(w http.ResponseWriter, r *http.Request) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch the repository from the database
|
// fetch the repository from the database
|
||||||
repo, err := h.repos.FindName(plugin.GetHost(), hook.Owner, hook.Repo)
|
repo, err := h.repos.FindName(remote.GetHost(), hook.Owner, hook.Repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return notFound{}
|
return notFound{}
|
||||||
}
|
}
|
||||||
|
@ -78,8 +67,7 @@ func (h *HookHandler) PostHook(w http.ResponseWriter, r *http.Request) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// featch the .drone.yml file from the database
|
// featch the .drone.yml file from the database
|
||||||
client := plugin.GetClient(user.Access, user.Secret)
|
yml, err := remote.GetScript(user, repo, hook)
|
||||||
yml, err := client.GetScript(hook)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return badRequest{err}
|
return badRequest{err}
|
||||||
}
|
}
|
||||||
|
@ -87,7 +75,7 @@ func (h *HookHandler) PostHook(w http.ResponseWriter, r *http.Request) error {
|
||||||
// verify the commit hooks branch matches the list of approved
|
// verify the commit hooks branch matches the list of approved
|
||||||
// branches (unless it is a pull request). Note that we don't really
|
// branches (unless it is a pull request). Note that we don't really
|
||||||
// care if parsing the yaml fails here.
|
// care if parsing the yaml fails here.
|
||||||
s, _ := script.ParseBuild(yml, map[string]string{})
|
s, _ := script.ParseBuild(string(yml), map[string]string{})
|
||||||
if len(hook.PullRequest) == 0 && !s.MatchBranch(hook.Branch) {
|
if len(hook.PullRequest) == 0 && !s.MatchBranch(hook.Branch) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
return nil
|
return nil
|
||||||
|
@ -101,7 +89,7 @@ func (h *HookHandler) PostHook(w http.ResponseWriter, r *http.Request) error {
|
||||||
PullRequest: hook.PullRequest,
|
PullRequest: hook.PullRequest,
|
||||||
Timestamp: hook.Timestamp,
|
Timestamp: hook.Timestamp,
|
||||||
Message: hook.Message,
|
Message: hook.Message,
|
||||||
Config: yml}
|
Config: string(yml)}
|
||||||
c.SetAuthor(hook.Author)
|
c.SetAuthor(hook.Author)
|
||||||
// inser the commit into the database
|
// inser the commit into the database
|
||||||
if err := h.commits.Insert(&c); err != nil {
|
if err := h.commits.Insert(&c); err != nil {
|
||||||
|
|
|
@ -28,24 +28,15 @@ func NewLoginHandler(users database.UserManager, repos database.RepoManager, per
|
||||||
// GetLogin gets the login to the 3rd party remote system.
|
// GetLogin gets the login to the 3rd party remote system.
|
||||||
// GET /login/:host
|
// GET /login/:host
|
||||||
func (h *LoginHandler) GetLogin(w http.ResponseWriter, r *http.Request) error {
|
func (h *LoginHandler) GetLogin(w http.ResponseWriter, r *http.Request) error {
|
||||||
host := r.FormValue(":host")
|
var host = r.FormValue(":host")
|
||||||
redirect := "/"
|
var redirect = "/"
|
||||||
|
var remote = remote.Lookup(host)
|
||||||
remoteServer, err := h.remotes.FindType(host)
|
if remote == nil {
|
||||||
if err != nil {
|
|
||||||
return notFound{err}
|
|
||||||
}
|
|
||||||
|
|
||||||
remotePlugin, ok := remote.Lookup(remoteServer.Type)
|
|
||||||
if !ok {
|
|
||||||
return notFound{}
|
return notFound{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the remote system's client.
|
|
||||||
plugin := remotePlugin(remoteServer)
|
|
||||||
|
|
||||||
// authenticate the user
|
// authenticate the user
|
||||||
login, err := plugin.GetLogin(w, r)
|
login, err := remote.Authorize(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return badRequest{err}
|
return badRequest{err}
|
||||||
} else if login == nil {
|
} else if login == nil {
|
||||||
|
@ -60,12 +51,12 @@ func (h *LoginHandler) GetLogin(w http.ResponseWriter, r *http.Request) error {
|
||||||
// if self-registration is disabled we should
|
// if self-registration is disabled we should
|
||||||
// return a notAuthorized error. the only exception
|
// return a notAuthorized error. the only exception
|
||||||
// is if no users exist yet in the system we'll proceed.
|
// is if no users exist yet in the system we'll proceed.
|
||||||
if remoteServer.Open == false && h.users.Exist() {
|
if h.users.Exist() {
|
||||||
return notAuthorized{}
|
return notAuthorized{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the user account
|
// create the user account
|
||||||
u = model.NewUser(plugin.GetName(), login.Login, login.Email)
|
u = model.NewUser(remote.GetKind(), login.Login, login.Email)
|
||||||
u.Name = login.Name
|
u.Name = login.Name
|
||||||
u.SetEmail(login.Email)
|
u.SetEmail(login.Email)
|
||||||
|
|
||||||
|
@ -110,41 +101,32 @@ func (h *LoginHandler) GetLogin(w http.ResponseWriter, r *http.Request) error {
|
||||||
// sync inside a goroutine. This should eventually be moved to
|
// sync inside a goroutine. This should eventually be moved to
|
||||||
// its own package / sync utility.
|
// its own package / sync utility.
|
||||||
go func() {
|
go func() {
|
||||||
// list all repositories
|
repos, err := remote.GetRepos(u)
|
||||||
client := plugin.GetClient(u.Access, u.Secret)
|
|
||||||
repos, err := client.GetRepos("")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error syncing user account, listing repositories", u.Login, err)
|
log.Println("Error syncing user account, listing repositories", u.Login, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert all repositories
|
// insert all repositories
|
||||||
for _, remoteRepo := range repos {
|
for _, repo := range repos {
|
||||||
repo, _ := model.NewRepo(plugin.GetName(), remoteRepo.Owner, remoteRepo.Name)
|
var role = repo.Role
|
||||||
repo.Private = remoteRepo.Private
|
|
||||||
repo.Host = remoteRepo.Host
|
|
||||||
repo.CloneURL = remoteRepo.Clone
|
|
||||||
repo.GitURL = remoteRepo.Git
|
|
||||||
repo.SSHURL = remoteRepo.SSH
|
|
||||||
repo.URL = remoteRepo.URL
|
|
||||||
|
|
||||||
if err := h.repos.Insert(repo); err != nil {
|
if err := h.repos.Insert(repo); err != nil {
|
||||||
// typically we see a failure because the repository already exists
|
// typically we see a failure because the repository already exists
|
||||||
// in which case, we can retrieve the existing record to get the ID.
|
// in which case, we can retrieve the existing record to get the ID.
|
||||||
repo, err = h.repos.FindName(repo.Host, repo.Owner, repo.Name)
|
repo, err = h.repos.FindName(repo.Host, repo.Owner, repo.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error adding repo.", u.Login, remoteRepo.Name, err)
|
log.Println("Error adding repo.", u.Login, repo.Name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add user permissions
|
// add user permissions
|
||||||
if err := h.perms.Grant(u, repo, remoteRepo.Pull, remoteRepo.Push, remoteRepo.Admin); err != nil {
|
if err := h.perms.Grant(u, repo, role.Read, role.Write, role.Admin); err != nil {
|
||||||
log.Println("Error adding permissions.", u.Login, remoteRepo.Name, err)
|
log.Println("Error adding permissions.", u.Login, repo.Name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Successfully syced repo.", u.Login+"/"+remoteRepo.Name)
|
log.Println("Successfully syced repo.", u.Login+"/"+repo.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
u.Synced = time.Now().UTC().Unix()
|
u.Synced = time.Now().UTC().Unix()
|
||||||
|
|
|
@ -3,11 +3,10 @@ package handler
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
|
|
||||||
|
"github.com/drone/drone/plugin/remote"
|
||||||
"github.com/drone/drone/server/database"
|
"github.com/drone/drone/server/database"
|
||||||
"github.com/drone/drone/server/session"
|
"github.com/drone/drone/server/session"
|
||||||
"github.com/drone/drone/shared/model"
|
|
||||||
"github.com/gorilla/pat"
|
"github.com/gorilla/pat"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,130 +20,20 @@ func NewRemoteHandler(users database.UserManager, remotes database.RemoteManager
|
||||||
return &RemoteHandler{users, remotes, sess}
|
return &RemoteHandler{users, remotes, sess}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRemotes gets all remotes.
|
|
||||||
// GET /api/remotes
|
|
||||||
func (h *RemoteHandler) GetRemotes(w http.ResponseWriter, r *http.Request) error {
|
|
||||||
// get the user form the session
|
|
||||||
user := h.sess.User(r)
|
|
||||||
switch {
|
|
||||||
case user == nil:
|
|
||||||
return notAuthorized{}
|
|
||||||
case user.Admin == false:
|
|
||||||
return forbidden{}
|
|
||||||
}
|
|
||||||
// get all remotes
|
|
||||||
remotes, err := h.remotes.List()
|
|
||||||
if err != nil {
|
|
||||||
return internalServerError{err}
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.NewEncoder(w).Encode(remotes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRemoteLogins gets all remote logins.
|
// GetRemoteLogins gets all remote logins.
|
||||||
// GET /api/remotes/logins
|
// GET /api/remotes/logins
|
||||||
func (h *RemoteHandler) GetRemoteLogins(w http.ResponseWriter, r *http.Request) error {
|
func (h *RemoteHandler) GetRemoteLogins(w http.ResponseWriter, r *http.Request) error {
|
||||||
remotes, err := h.remotes.List()
|
var list = remote.Registered()
|
||||||
if err != nil {
|
|
||||||
return internalServerError{err}
|
|
||||||
}
|
|
||||||
var logins []interface{}
|
var logins []interface{}
|
||||||
for _, remote := range remotes {
|
for _, item := range list {
|
||||||
logins = append(logins, struct {
|
logins = append(logins, struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
}{remote.Type, remote.Host})
|
}{item.GetKind(), item.GetHost()})
|
||||||
}
|
}
|
||||||
return json.NewEncoder(w).Encode(&logins)
|
return json.NewEncoder(w).Encode(&logins)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostRemote creates a new remote.
|
|
||||||
// POST /api/remotes
|
|
||||||
func (h *RemoteHandler) PostRemote(w http.ResponseWriter, r *http.Request) error {
|
|
||||||
// get the user form the session
|
|
||||||
user := h.sess.User(r)
|
|
||||||
if user == nil || !user.Admin {
|
|
||||||
// if no users exist, this request is part of
|
|
||||||
// the system installation process and can proceed.
|
|
||||||
// else we should reject.
|
|
||||||
if h.users.Exist() {
|
|
||||||
return notAuthorized{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// unmarshal the remote from the payload
|
|
||||||
defer r.Body.Close()
|
|
||||||
in := model.Remote{}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
|
||||||
return badRequest{err}
|
|
||||||
}
|
|
||||||
uri, err := url.Parse(in.URL)
|
|
||||||
if err != nil {
|
|
||||||
return badRequest{err}
|
|
||||||
}
|
|
||||||
in.Host = uri.Host
|
|
||||||
|
|
||||||
// there is an edge case where, during installation, a user could attempt
|
|
||||||
// to add the same result multiple times. In this case we will delete
|
|
||||||
// the old remote prior to adding the new one.
|
|
||||||
if remote, err := h.remotes.FindHost(in.Host); err == nil && h.users.Exist() {
|
|
||||||
h.remotes.Delete(remote)
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert the remote in the database
|
|
||||||
if err := h.remotes.Insert(&in); err != nil {
|
|
||||||
return internalServerError{err}
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.NewEncoder(w).Encode(&in)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutRemote updates an existing remote.
|
|
||||||
// PUT /api/remotes
|
|
||||||
func (h *RemoteHandler) PutRemote(w http.ResponseWriter, r *http.Request) error {
|
|
||||||
// get the user form the session
|
|
||||||
user := h.sess.User(r)
|
|
||||||
switch {
|
|
||||||
case user == nil:
|
|
||||||
return notAuthorized{}
|
|
||||||
case user.Admin == false:
|
|
||||||
return forbidden{}
|
|
||||||
}
|
|
||||||
// unmarshal the remote from the payload
|
|
||||||
defer r.Body.Close()
|
|
||||||
in := model.Remote{}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
|
||||||
return badRequest{err}
|
|
||||||
}
|
|
||||||
uri, err := url.Parse(in.URL)
|
|
||||||
if err != nil {
|
|
||||||
return badRequest{err}
|
|
||||||
}
|
|
||||||
in.Host = uri.Host
|
|
||||||
|
|
||||||
// retrieve the remote and return an error if not exists
|
|
||||||
remote, err := h.remotes.FindHost(in.Host)
|
|
||||||
if err != nil {
|
|
||||||
return notFound{err}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update the remote details
|
|
||||||
remote.API = in.API
|
|
||||||
remote.URL = in.URL
|
|
||||||
remote.Host = in.Host
|
|
||||||
remote.Client = in.Client
|
|
||||||
remote.Secret = in.Secret
|
|
||||||
|
|
||||||
// insert the remote in the database
|
|
||||||
if err := h.remotes.Update(remote); err != nil {
|
|
||||||
return internalServerError{err}
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.NewEncoder(w).Encode(remote)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *RemoteHandler) Register(r *pat.Router) {
|
func (h *RemoteHandler) Register(r *pat.Router) {
|
||||||
r.Get("/v1/logins", errorHandler(h.GetRemoteLogins))
|
r.Get("/v1/logins", errorHandler(h.GetRemoteLogins))
|
||||||
r.Get("/v1/remotes", errorHandler(h.GetRemotes))
|
|
||||||
r.Post("/v1/remotes", errorHandler(h.PostRemote))
|
|
||||||
r.Put("/v1/remotes", errorHandler(h.PutRemote))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,20 +46,17 @@ func (h *RepoHandler) GetRepo(w http.ResponseWriter, r *http.Request) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// user must have read access to the repository.
|
// user must have read access to the repository.
|
||||||
role := h.perms.Find(user, repo)
|
repo.Role = h.perms.Find(user, repo)
|
||||||
switch {
|
switch {
|
||||||
case role.Read == false && user == nil:
|
case repo.Role.Read == false && user == nil:
|
||||||
return notAuthorized{}
|
return notAuthorized{}
|
||||||
case role.Read == false && user != nil:
|
case repo.Role.Read == false && user != nil:
|
||||||
return notFound{}
|
return notFound{}
|
||||||
}
|
}
|
||||||
// if the user is not requesting admin data we can
|
// if the user is not requesting admin data we can
|
||||||
// return exactly what we have.
|
// return exactly what we have.
|
||||||
if len(admin) == 0 {
|
if len(admin) == 0 {
|
||||||
return json.NewEncoder(w).Encode(struct {
|
return json.NewEncoder(w).Encode(repo)
|
||||||
*model.Repo
|
|
||||||
Role *model.Perm `json:"role"`
|
|
||||||
}{repo, role})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ammend the response to include data that otherwise
|
// ammend the response to include data that otherwise
|
||||||
|
@ -71,10 +68,9 @@ func (h *RepoHandler) GetRepo(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
|
||||||
return json.NewEncoder(w).Encode(struct {
|
return json.NewEncoder(w).Encode(struct {
|
||||||
*model.Repo
|
*model.Repo
|
||||||
Role *model.Perm `json:"role"`
|
PublicKey string `json:"public_key"`
|
||||||
PublicKey string `json:"public_key"`
|
Params string `json:"params"`
|
||||||
Params string `json:"params"`
|
}{repo, repo.PublicKey, repo.Params})
|
||||||
}{repo, role, repo.PublicKey, repo.Params})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostRepo activates the named repository.
|
// PostRepo activates the named repository.
|
||||||
|
@ -118,26 +114,16 @@ func (h *RepoHandler) PostRepo(w http.ResponseWriter, r *http.Request) error {
|
||||||
repo.PublicKey = sshutil.MarshalPublicKey(&key.PublicKey)
|
repo.PublicKey = sshutil.MarshalPublicKey(&key.PublicKey)
|
||||||
repo.PrivateKey = sshutil.MarshalPrivateKey(key)
|
repo.PrivateKey = sshutil.MarshalPrivateKey(key)
|
||||||
|
|
||||||
// get the remote and client
|
var remote = remote.Lookup(host)
|
||||||
remoteServer, err := h.remotes.FindType(repo.Remote)
|
if remote == nil {
|
||||||
if err != nil {
|
|
||||||
return notFound{err}
|
|
||||||
}
|
|
||||||
|
|
||||||
remotePlugin, ok := remote.Lookup(remoteServer.Type)
|
|
||||||
if !ok {
|
|
||||||
return notFound{}
|
return notFound{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the remote system's client.
|
|
||||||
plugin := remotePlugin(remoteServer)
|
|
||||||
|
|
||||||
// post commit hook url
|
// post commit hook url
|
||||||
hook := fmt.Sprintf("%s://%s/v1/hook/%s", httputil.GetScheme(r), httputil.GetHost(r), plugin.GetName())
|
hook := fmt.Sprintf("%s://%s/v1/hook/%s", httputil.GetScheme(r), httputil.GetHost(r), remote.GetKind())
|
||||||
|
|
||||||
// activate the repository in the remote system
|
// activate the repository in the remote system
|
||||||
client := plugin.GetClient(user.Access, user.Secret)
|
if err := remote.Activate(user, repo, hook); err != nil {
|
||||||
if err := client.SetActive(owner, name, hook, repo.PublicKey); err != nil {
|
|
||||||
return badRequest{err}
|
return badRequest{err}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
15
shared/model/hook.go
Normal file
15
shared/model/hook.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
// Hook represents a subset of commit meta-data provided
|
||||||
|
// by post-commit and pull request hooks.
|
||||||
|
type Hook struct {
|
||||||
|
Owner string
|
||||||
|
Repo string
|
||||||
|
Sha string
|
||||||
|
Branch string
|
||||||
|
PullRequest string
|
||||||
|
Author string
|
||||||
|
Gravatar string
|
||||||
|
Timestamp string
|
||||||
|
Message string
|
||||||
|
}
|
12
shared/model/login.go
Normal file
12
shared/model/login.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
// Login represents a standard subset of user meta-data
|
||||||
|
// provided by OAuth login services.
|
||||||
|
type Login struct {
|
||||||
|
ID int64
|
||||||
|
Login string
|
||||||
|
Access string
|
||||||
|
Secret string
|
||||||
|
Name string
|
||||||
|
Email string
|
||||||
|
}
|
|
@ -39,6 +39,11 @@ type Repo struct {
|
||||||
Timeout int64 `meddler:"repo_timeout" json:"timeout"`
|
Timeout int64 `meddler:"repo_timeout" json:"timeout"`
|
||||||
Created int64 `meddler:"repo_created" json:"created_at"`
|
Created int64 `meddler:"repo_created" json:"created_at"`
|
||||||
Updated int64 `meddler:"repo_updated" json:"updated_at"`
|
Updated int64 `meddler:"repo_updated" json:"updated_at"`
|
||||||
|
|
||||||
|
// Role defines the user's role relative to this repository.
|
||||||
|
// Note that this data is stored separately in the datastore,
|
||||||
|
// and must be joined to populate.
|
||||||
|
Role *Perm `meddler:"-" json:"role,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRepo(remote, owner, name string) (*Repo, error) {
|
func NewRepo(remote, owner, name string) (*Repo, error) {
|
||||||
|
|
Loading…
Reference in a new issue