some finishing touches on the bitbucket implementation for 0.4
This commit is contained in:
parent
1c87bd9c3b
commit
528fbb0f2c
9 changed files with 421 additions and 142 deletions
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/drone/drone/engine"
|
"github.com/drone/drone/engine"
|
||||||
|
"github.com/drone/drone/remote"
|
||||||
"github.com/drone/drone/shared/httputil"
|
"github.com/drone/drone/shared/httputil"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
@ -124,7 +125,7 @@ func DeleteBuild(c *gin.Context) {
|
||||||
|
|
||||||
func PostBuild(c *gin.Context) {
|
func PostBuild(c *gin.Context) {
|
||||||
|
|
||||||
remote := context.Remote(c)
|
remote_ := context.Remote(c)
|
||||||
repo := session.Repo(c)
|
repo := session.Repo(c)
|
||||||
db := context.Database(c)
|
db := context.Database(c)
|
||||||
|
|
||||||
|
@ -148,8 +149,18 @@ func PostBuild(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the remote has a refresh token, the current access token
|
||||||
|
// may be stale. Therefore, we should refresh prior to dispatching
|
||||||
|
// the job.
|
||||||
|
if refresher, ok := remote_.(remote.Refresher); ok {
|
||||||
|
ok, _ := refresher.Refresh(user)
|
||||||
|
if ok {
|
||||||
|
model.UpdateUser(db, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// fetch the .drone.yml file from the database
|
// fetch the .drone.yml file from the database
|
||||||
raw, sec, err := remote.Script(user, repo, build)
|
raw, sec, err := remote_.Script(user, repo, build)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failure to get .drone.yml for %s. %s", repo.FullName, err)
|
log.Errorf("failure to get .drone.yml for %s. %s", repo.FullName, err)
|
||||||
c.AbortWithError(404, err)
|
c.AbortWithError(404, err)
|
||||||
|
@ -157,7 +168,7 @@ func PostBuild(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
key, _ := model.GetKey(db, repo)
|
key, _ := model.GetKey(db, repo)
|
||||||
netrc, err := remote.Netrc(user, repo)
|
netrc, err := remote_.Netrc(user, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err)
|
log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err)
|
||||||
c.AbortWithError(500, err)
|
c.AbortWithError(500, err)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/drone/drone/engine"
|
"github.com/drone/drone/engine"
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/remote"
|
||||||
"github.com/drone/drone/router/middleware/context"
|
"github.com/drone/drone/router/middleware/context"
|
||||||
"github.com/drone/drone/shared/httputil"
|
"github.com/drone/drone/shared/httputil"
|
||||||
"github.com/drone/drone/shared/token"
|
"github.com/drone/drone/shared/token"
|
||||||
|
@ -18,10 +19,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func PostHook(c *gin.Context) {
|
func PostHook(c *gin.Context) {
|
||||||
remote := context.Remote(c)
|
remote_ := context.Remote(c)
|
||||||
db := context.Database(c)
|
db := context.Database(c)
|
||||||
|
|
||||||
tmprepo, build, err := remote.Hook(c.Request)
|
tmprepo, build, err := remote_.Hook(c.Request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failure to parse hook. %s", err)
|
log.Errorf("failure to parse hook. %s", err)
|
||||||
c.AbortWithError(400, err)
|
c.AbortWithError(400, err)
|
||||||
|
@ -93,8 +94,18 @@ func PostHook(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the remote has a refresh token, the current access token
|
||||||
|
// may be stale. Therefore, we should refresh prior to dispatching
|
||||||
|
// the job.
|
||||||
|
if refresher, ok := remote_.(remote.Refresher); ok {
|
||||||
|
ok, _ := refresher.Refresh(user)
|
||||||
|
if ok {
|
||||||
|
model.UpdateUser(db, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// fetch the .drone.yml file from the database
|
// fetch the .drone.yml file from the database
|
||||||
raw, sec, err := remote.Script(user, repo, build)
|
raw, sec, err := remote_.Script(user, repo, build)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failure to get .drone.yml for %s. %s", repo.FullName, err)
|
log.Errorf("failure to get .drone.yml for %s. %s", repo.FullName, err)
|
||||||
c.AbortWithError(404, err)
|
c.AbortWithError(404, err)
|
||||||
|
@ -111,7 +122,7 @@ func PostHook(c *gin.Context) {
|
||||||
axes = append(axes, matrix.Axis{})
|
axes = append(axes, matrix.Axis{})
|
||||||
}
|
}
|
||||||
|
|
||||||
netrc, err := remote.Netrc(user, repo)
|
netrc, err := remote_.Netrc(user, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err)
|
log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err)
|
||||||
c.AbortWithError(500, err)
|
c.AbortWithError(500, err)
|
||||||
|
@ -170,7 +181,7 @@ func PostHook(c *gin.Context) {
|
||||||
c.JSON(200, build)
|
c.JSON(200, build)
|
||||||
|
|
||||||
url := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
|
url := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
|
||||||
err = remote.Status(user, repo, build, url)
|
err = remote_.Status(user, repo, build, url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("error setting commit status for %s/%d", repo.FullName, build.Number)
|
log.Errorf("error setting commit status for %s/%d", repo.FullName, build.Number)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ type User struct {
|
||||||
Login string `json:"login" meddler:"user_login"`
|
Login string `json:"login" meddler:"user_login"`
|
||||||
Token string `json:"-" meddler:"user_token"`
|
Token string `json:"-" meddler:"user_token"`
|
||||||
Secret string `json:"-" meddler:"user_secret"`
|
Secret string `json:"-" meddler:"user_secret"`
|
||||||
|
Expiry int64 `json:"-" meddler:"user_expiry"`
|
||||||
Email string `json:"email" meddler:"user_email"`
|
Email string `json:"email" meddler:"user_email"`
|
||||||
Avatar string `json:"avatar_url" meddler:"user_avatar"`
|
Avatar string `json:"avatar_url" meddler:"user_avatar"`
|
||||||
Active bool `json:"active," meddler:"user_active"`
|
Active bool `json:"active," meddler:"user_active"`
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package bitbucket
|
package bitbucket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -80,6 +82,7 @@ func (bb *Bitbucket) Login(res http.ResponseWriter, req *http.Request) (*model.U
|
||||||
user.Login = curr.Login
|
user.Login = curr.Login
|
||||||
user.Token = token.AccessToken
|
user.Token = token.AccessToken
|
||||||
user.Secret = token.RefreshToken
|
user.Secret = token.RefreshToken
|
||||||
|
user.Expiry = token.Expiry.UTC().Unix()
|
||||||
user.Avatar = curr.Links.Avatar.Href
|
user.Avatar = curr.Links.Avatar.Href
|
||||||
|
|
||||||
// gets the primary, confirmed email from bitbucket
|
// gets the primary, confirmed email from bitbucket
|
||||||
|
@ -98,7 +101,7 @@ func (bb *Bitbucket) Login(res http.ResponseWriter, req *http.Request) (*model.U
|
||||||
// of organizations, get the orgs and verify the
|
// of organizations, get the orgs and verify the
|
||||||
// user is a member.
|
// user is a member.
|
||||||
if len(bb.Orgs) != 0 {
|
if len(bb.Orgs) != 0 {
|
||||||
resp, err := client.ListTeams(&ListOpts{Page: 1, PageLen: 100})
|
resp, err := client.ListTeams(&ListTeamOpts{Page: 1, PageLen: 100, Role: "member"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
@ -160,6 +163,7 @@ func (bb *Bitbucket) Refresh(user *model.User) (bool, error) {
|
||||||
// update the user to include tne new access token
|
// update the user to include tne new access token
|
||||||
user.Token = token.AccessToken
|
user.Token = token.AccessToken
|
||||||
user.Secret = token.RefreshToken
|
user.Secret = token.RefreshToken
|
||||||
|
user.Expiry = token.Expiry.UTC().Unix()
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,43 +183,28 @@ func (bb *Bitbucket) Repo(u *model.User, owner, name string) (*model.Repo, error
|
||||||
func (bb *Bitbucket) Repos(u *model.User) ([]*model.RepoLite, error) {
|
func (bb *Bitbucket) Repos(u *model.User) ([]*model.RepoLite, error) {
|
||||||
token := oauth2.Token{AccessToken: u.Token, RefreshToken: u.Secret}
|
token := oauth2.Token{AccessToken: u.Token, RefreshToken: u.Secret}
|
||||||
client := NewClientToken(bb.Client, bb.Secret, &token)
|
client := NewClientToken(bb.Client, bb.Secret, &token)
|
||||||
|
|
||||||
// var accounts = []string{u.Login}
|
|
||||||
var repos []*model.RepoLite
|
var repos []*model.RepoLite
|
||||||
|
|
||||||
// for {
|
// gets a list of all accounts to query, including the
|
||||||
// resp, err := client.ListTeams(&ListOpts{Page: page})
|
// user's account and all team accounts.
|
||||||
// if err != nil {
|
logins := []string{u.Login}
|
||||||
// return nil, err
|
resp, err := client.ListTeams(&ListTeamOpts{PageLen: 100, Role: "member"})
|
||||||
// }
|
if err != nil {
|
||||||
|
return repos, err
|
||||||
|
}
|
||||||
|
for _, team := range resp.Values {
|
||||||
|
logins = append(logins, team.Login)
|
||||||
|
}
|
||||||
|
|
||||||
// for _, team := range resp.Values {
|
// for each account, get the list of repos
|
||||||
// accounts = append(accounts, team.Login)
|
for _, login := range logins {
|
||||||
// }
|
repos_, err := client.ListReposAll(login)
|
||||||
|
|
||||||
// if resp.Page == resp.Pages {
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
var page = 1
|
|
||||||
for {
|
|
||||||
resp, err := client.ListRepos(u.Login, &ListOpts{Page: page, PageLen: 100})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println(err.Error())
|
return repos, err
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
for _, repo := range repos_ {
|
||||||
for _, repo := range resp.Values {
|
repos = append(repos, convertRepoLite(repo))
|
||||||
repos = append(repos, convertRepoLite(&repo))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(resp.Next) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
page = resp.Page + 1
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return repos, nil
|
return repos, nil
|
||||||
|
@ -239,7 +228,7 @@ func (bb *Bitbucket) Perm(u *model.User, owner, name string) (*model.Perm, error
|
||||||
|
|
||||||
// if the user has access to the repository hooks we
|
// if the user has access to the repository hooks we
|
||||||
// can deduce that the user has push and admin access.
|
// can deduce that the user has push and admin access.
|
||||||
_, err = client.ListHooks(owner, name, nil)
|
_, err = client.ListHooks(owner, name, &ListOpts{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
perms.Push = true
|
perms.Push = true
|
||||||
perms.Admin = true
|
perms.Admin = true
|
||||||
|
@ -284,62 +273,132 @@ func (bb *Bitbucket) Status(u *model.User, r *model.Repo, b *model.Build, link s
|
||||||
// Netrc returns a .netrc file that can be used to clone
|
// Netrc returns a .netrc file that can be used to clone
|
||||||
// private repositories from a remote system.
|
// private repositories from a remote system.
|
||||||
func (bb *Bitbucket) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
func (bb *Bitbucket) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
||||||
netrc := &model.Netrc{}
|
return &model.Netrc{
|
||||||
netrc.Login = "x-token-auth"
|
Machine: "bitbucket.org",
|
||||||
netrc.Password = u.Token
|
Login: "x-token-auth",
|
||||||
netrc.Machine = "bitbucket.org"
|
Password: u.Token,
|
||||||
return netrc, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activate activates a repository by creating the post-commit hook and
|
// Activate activates a repository by creating the post-commit hook and
|
||||||
// adding the SSH deploy key, if applicable.
|
// adding the SSH deploy key, if applicable.
|
||||||
func (bb *Bitbucket) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
|
func (bb *Bitbucket) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
|
||||||
|
client := NewClientToken(
|
||||||
|
bb.Client,
|
||||||
|
bb.Secret,
|
||||||
|
&oauth2.Token{
|
||||||
|
AccessToken: u.Token,
|
||||||
|
RefreshToken: u.Secret,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// "repo:push"
|
linkurl, err := url.Parse(link)
|
||||||
return nil
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// see if the hook already exists. If yes be sure to
|
||||||
|
// delete so that multiple messages aren't sent.
|
||||||
|
hooks, _ := client.ListHooks(r.Owner, r.Name, &ListOpts{})
|
||||||
|
for _, hook := range hooks.Values {
|
||||||
|
hookurl, err := url.Parse(hook.Url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if hookurl.Host == linkurl.Host {
|
||||||
|
client.DeleteHook(r.Owner, r.Name, hook.Uuid)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.CreateHook(r.Owner, r.Name, &Hook{
|
||||||
|
Active: true,
|
||||||
|
Desc: linkurl.Host,
|
||||||
|
Events: []string{"repo:push"},
|
||||||
|
Url: link,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deactivate removes a repository by removing all the post-commit hooks
|
// Deactivate removes a repository by removing all the post-commit hooks
|
||||||
// which are equal to link and removing the SSH deploy key.
|
// which are equal to link and removing the SSH deploy key.
|
||||||
func (bb *Bitbucket) Deactivate(u *model.User, r *model.Repo, link string) error {
|
func (bb *Bitbucket) Deactivate(u *model.User, r *model.Repo, link string) error {
|
||||||
|
client := NewClientToken(
|
||||||
|
bb.Client,
|
||||||
|
bb.Secret,
|
||||||
|
&oauth2.Token{
|
||||||
|
AccessToken: u.Token,
|
||||||
|
RefreshToken: u.Secret,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
linkurl, err := url.Parse(link)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// see if the hook already exists. If yes be sure to
|
||||||
|
// delete so that multiple messages aren't sent.
|
||||||
|
hooks, _ := client.ListHooks(r.Owner, r.Name, &ListOpts{})
|
||||||
|
for _, hook := range hooks.Values {
|
||||||
|
hookurl, err := url.Parse(hook.Url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if hookurl.Host == linkurl.Host {
|
||||||
|
client.DeleteHook(r.Owner, r.Name, hook.Uuid)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook parses the post-commit hook from the Request body
|
// Hook parses the post-commit hook from the Request body
|
||||||
// and returns the required data in a standard format.
|
// and returns the required data in a standard format.
|
||||||
func (bb *Bitbucket) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
func (bb *Bitbucket) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertRepo(from *Repo) *model.Repo {
|
// only a subset of hooks are processed by drone
|
||||||
repo := &model.Repo{}
|
if r.Header.Get("X-Event-Key") != "repo:push" {
|
||||||
repo.Owner = from.Owner.Login
|
return nil, nil, nil
|
||||||
repo.Name = from.Name
|
|
||||||
repo.FullName = from.FullName
|
|
||||||
repo.Link = from.Links.Html.Href
|
|
||||||
repo.IsPrivate = from.IsPrivate
|
|
||||||
repo.Avatar = from.Owner.Links.Avatar.Href
|
|
||||||
repo.Branch = "master"
|
|
||||||
repo.Clone = fmt.Sprintf("https://bitbucket.org/%s.git", repo.FullName)
|
|
||||||
|
|
||||||
// above we manually constructed the repository clone url.
|
|
||||||
// below we will iterate through the list of clone links and
|
|
||||||
// attempt to instead use the clone url provided by bitbucket.
|
|
||||||
for _, link := range from.Links.Clone {
|
|
||||||
if link.Name == "https" {
|
|
||||||
repo.Clone = link.Href
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return repo
|
// extract the hook payload
|
||||||
}
|
payload := []byte(r.FormValue("payload"))
|
||||||
|
if len(payload) == 0 {
|
||||||
|
defer r.Body.Close()
|
||||||
|
payload, _ = ioutil.ReadAll(r.Body)
|
||||||
|
}
|
||||||
|
|
||||||
func convertRepoLite(from *Repo) *model.RepoLite {
|
hook := PushHook{}
|
||||||
repo := &model.RepoLite{}
|
err := json.Unmarshal(payload, &hook)
|
||||||
repo.Owner = from.Owner.Login
|
if err != nil {
|
||||||
repo.Name = from.Name
|
return nil, nil, err
|
||||||
repo.FullName = from.FullName
|
}
|
||||||
repo.Avatar = from.Owner.Links.Avatar.Href
|
|
||||||
return repo
|
// the hook can container one or many changes. Since I don't
|
||||||
|
// fully understand this yet, we will just pick the first
|
||||||
|
// change that has branch information.
|
||||||
|
for _, change := range hook.Push.Changes {
|
||||||
|
|
||||||
|
// must have branch and sha information
|
||||||
|
if change.New.Type != "branch" || change.New.Target.Hash == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the updated repsitory information and the
|
||||||
|
// build information.
|
||||||
|
return convertRepo(&hook.Repo), &model.Build{
|
||||||
|
Event: model.EventPush,
|
||||||
|
Commit: change.New.Target.Hash,
|
||||||
|
Ref: fmt.Sprintf("refs/heads/%s", change.New.Name),
|
||||||
|
Link: change.New.Target.Links.Html.Href,
|
||||||
|
Branch: change.New.Name,
|
||||||
|
Message: change.New.Target.Message,
|
||||||
|
Avatar: hook.Actor.Links.Avatar.Href,
|
||||||
|
Author: hook.Actor.Login,
|
||||||
|
Timestamp: change.New.Target.Date.UTC().Unix(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
"golang.org/x/oauth2/bitbucket"
|
"golang.org/x/oauth2/bitbucket"
|
||||||
|
@ -20,7 +19,18 @@ const (
|
||||||
del = "DELETE"
|
del = "DELETE"
|
||||||
)
|
)
|
||||||
|
|
||||||
const api = "https://api.bitbucket.org"
|
const (
|
||||||
|
base = "https://api.bitbucket.org"
|
||||||
|
|
||||||
|
pathUser = "%s/2.0/user/"
|
||||||
|
pathEmails = "%s/2.0/user/emails"
|
||||||
|
pathTeams = "%s/2.0/teams/?%s"
|
||||||
|
pathRepo = "%s/2.0/repositories/%s/%s"
|
||||||
|
pathRepos = "%s/2.0/repositories/%s?%s"
|
||||||
|
pathHook = "%s/2.0/repositories/%s/%s/hooks/%s"
|
||||||
|
pathHooks = "%s/2.0/repositories/%s/%s/hooks?%s"
|
||||||
|
pathSource = "%s/1.0/repositories/%s/%s/src/%s/%s"
|
||||||
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
*http.Client
|
*http.Client
|
||||||
|
@ -40,68 +50,87 @@ func NewClientToken(client, secret string, token *oauth2.Token) *Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) FindCurrent() (*Account, error) {
|
func (c *Client) FindCurrent() (*Account, error) {
|
||||||
var out = new(Account)
|
out := new(Account)
|
||||||
var uri = fmt.Sprintf("%s/2.0/user/", api)
|
uri := fmt.Sprintf(pathUser, base)
|
||||||
var err = c.do(uri, get, nil, out)
|
err := c.do(uri, get, nil, out)
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ListEmail() (*EmailResp, error) {
|
func (c *Client) ListEmail() (*EmailResp, error) {
|
||||||
var out = new(EmailResp)
|
out := new(EmailResp)
|
||||||
var uri = fmt.Sprintf("%s/2.0/user/emails", api)
|
uri := fmt.Sprintf(pathEmails, base)
|
||||||
var err = c.do(uri, get, nil, out)
|
err := c.do(uri, get, nil, out)
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ListTeams(opts *ListOpts) (*AccountResp, error) {
|
func (c *Client) ListTeams(opts *ListTeamOpts) (*AccountResp, error) {
|
||||||
var out = new(AccountResp)
|
out := new(AccountResp)
|
||||||
var uri = fmt.Sprintf("%s/2.0/teams/?role=member&%s", api, encodeListOpts(opts))
|
uri := fmt.Sprintf(pathTeams, base, opts.Encode())
|
||||||
var err = c.do(uri, get, nil, out)
|
err := c.do(uri, get, nil, out)
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) FindRepo(owner, name string) (*Repo, error) {
|
func (c *Client) FindRepo(owner, name string) (*Repo, error) {
|
||||||
var out = new(Repo)
|
out := new(Repo)
|
||||||
var uri = fmt.Sprintf("%s/2.0/repositories/%s/%s", api, owner, name)
|
uri := fmt.Sprintf(pathRepo, base, owner, name)
|
||||||
var err = c.do(uri, get, nil, out)
|
err := c.do(uri, get, nil, out)
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ListRepos(account string, opts *ListOpts) (*RepoResp, error) {
|
func (c *Client) ListRepos(account string, opts *ListOpts) (*RepoResp, error) {
|
||||||
var out = new(RepoResp)
|
out := new(RepoResp)
|
||||||
var uri = fmt.Sprintf("%s/2.0/repositories/%s?%s", api, account, encodeListOpts(opts))
|
uri := fmt.Sprintf(pathRepos, base, account, opts.Encode())
|
||||||
var err = c.do(uri, get, nil, out)
|
err := c.do(uri, get, nil, out)
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) ListReposAll(account string) ([]*Repo, error) {
|
||||||
|
var page = 1
|
||||||
|
var repos []*Repo
|
||||||
|
|
||||||
|
for {
|
||||||
|
resp, err := c.ListRepos(account, &ListOpts{Page: page, PageLen: 100})
|
||||||
|
if err != nil {
|
||||||
|
println(err.Error())
|
||||||
|
return repos, err
|
||||||
|
}
|
||||||
|
repos = append(repos, resp.Values...)
|
||||||
|
if len(resp.Next) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
page = resp.Page + 1
|
||||||
|
}
|
||||||
|
return repos, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) FindHook(owner, name, id string) (*Hook, error) {
|
func (c *Client) FindHook(owner, name, id string) (*Hook, error) {
|
||||||
var out = new(Hook)
|
out := new(Hook)
|
||||||
var uri = fmt.Sprintf("%s/2.0/repositories/%s/%s/hooks/%s", api, owner, name, id)
|
uri := fmt.Sprintf(pathHook, base, owner, name, id)
|
||||||
var err = c.do(uri, get, nil, out)
|
err := c.do(uri, get, nil, out)
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ListHooks(owner, name string, opts *ListOpts) (*HookResp, error) {
|
func (c *Client) ListHooks(owner, name string, opts *ListOpts) (*HookResp, error) {
|
||||||
var out = new(HookResp)
|
out := new(HookResp)
|
||||||
var uri = fmt.Sprintf("%s/2.0/repositories/%s/%s/hooks?%s", api, owner, name, encodeListOpts(opts))
|
uri := fmt.Sprintf(pathHooks, base, owner, name, opts.Encode())
|
||||||
var err = c.do(uri, get, nil, out)
|
err := c.do(uri, get, nil, out)
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) CreateHook(owner, name, hook *Hook) error {
|
func (c *Client) CreateHook(owner, name string, hook *Hook) error {
|
||||||
var uri = fmt.Sprintf("%s/2.0/repositories/%s/%s/hooks", api, owner, name)
|
uri := fmt.Sprintf(pathHooks, base, owner, name, "")
|
||||||
return c.do(uri, post, hook, nil)
|
return c.do(uri, post, hook, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) DeleteHook(owner, name, id string) error {
|
func (c *Client) DeleteHook(owner, name, id string) error {
|
||||||
var uri = fmt.Sprintf("%s/2.0/repositories/%s/%s/hooks/%s", api, owner, name, id)
|
uri := fmt.Sprintf(pathHook, base, owner, name, id)
|
||||||
return c.do(uri, del, nil, nil)
|
return c.do(uri, del, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) FindSource(owner, name, revision, path string) (*Source, error) {
|
func (c *Client) FindSource(owner, name, revision, path string) (*Source, error) {
|
||||||
var out = new(Source)
|
out := new(Source)
|
||||||
var uri = fmt.Sprintf("%s/1.0/repositories/%s/%s/src/%s/%s", api, owner, name, revision, path)
|
uri := fmt.Sprintf(pathSource, base, owner, name, revision, path)
|
||||||
var err = c.do(uri, get, nil, out)
|
err := c.do(uri, get, nil, out)
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,6 +157,9 @@ func (c *Client) do(rawurl, method string, in, out interface{}) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if in != nil {
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := c.Do(req)
|
resp, err := c.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -141,6 +173,11 @@ func (c *Client) do(rawurl, method string, in, out interface{}) error {
|
||||||
err := Error{}
|
err := Error{}
|
||||||
json.NewDecoder(resp.Body).Decode(&err)
|
json.NewDecoder(resp.Body).Decode(&err)
|
||||||
err.Status = resp.StatusCode
|
err.Status = resp.StatusCode
|
||||||
|
|
||||||
|
instr, _ := json.Marshal(in)
|
||||||
|
println(err.Body.Message)
|
||||||
|
println(string(instr))
|
||||||
|
println(uri.String())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,17 +189,3 @@ func (c *Client) do(rawurl, method string, in, out interface{}) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func encodeListOpts(opts *ListOpts) string {
|
|
||||||
var params = new(url.Values)
|
|
||||||
if opts == nil {
|
|
||||||
return params.Encode()
|
|
||||||
}
|
|
||||||
if opts.Page != 0 {
|
|
||||||
params.Set("page", strconv.Itoa(opts.Page))
|
|
||||||
}
|
|
||||||
if opts.PageLen != 0 {
|
|
||||||
params.Set("pagelen", strconv.Itoa(opts.PageLen))
|
|
||||||
}
|
|
||||||
return params.Encode()
|
|
||||||
}
|
|
||||||
|
|
56
remote/bitbucket/helper.go
Normal file
56
remote/bitbucket/helper.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package bitbucket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/drone/drone/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// convertRepo is a helper function used to convert a Bitbucket
|
||||||
|
// repository structure to the common Drone repository structure.
|
||||||
|
func convertRepo(from *Repo) *model.Repo {
|
||||||
|
repo := model.Repo{
|
||||||
|
Owner: from.Owner.Login,
|
||||||
|
Name: from.Name,
|
||||||
|
FullName: from.FullName,
|
||||||
|
Link: from.Links.Html.Href,
|
||||||
|
IsPrivate: from.IsPrivate,
|
||||||
|
Avatar: from.Owner.Links.Avatar.Href,
|
||||||
|
Branch: "master",
|
||||||
|
}
|
||||||
|
|
||||||
|
// in some cases, the owner of the repository is not
|
||||||
|
// provided, however, we do have the full name.
|
||||||
|
if len(repo.Owner) == 0 {
|
||||||
|
repo.Owner = strings.Split(repo.FullName, "/")[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// above we manually constructed the repository clone url.
|
||||||
|
// below we will iterate through the list of clone links and
|
||||||
|
// attempt to instead use the clone url provided by bitbucket.
|
||||||
|
for _, link := range from.Links.Clone {
|
||||||
|
if link.Name == "https" {
|
||||||
|
repo.Clone = link.Href
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no repository name is provided, we use the Html link.
|
||||||
|
// this excludes the .git suffix, but will still clone the repo.
|
||||||
|
if len(repo.Clone) == 0 {
|
||||||
|
repo.Clone = repo.Link
|
||||||
|
}
|
||||||
|
|
||||||
|
return &repo
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertRepoLite is a helper function used to convert a Bitbucket
|
||||||
|
// repository structure to the simplified Drone repository structure.
|
||||||
|
func convertRepoLite(from *Repo) *model.RepoLite {
|
||||||
|
return &model.RepoLite{
|
||||||
|
Owner: from.Owner.Login,
|
||||||
|
Name: from.Name,
|
||||||
|
FullName: from.FullName,
|
||||||
|
Avatar: from.Owner.Links.Avatar.Href,
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,11 @@
|
||||||
package bitbucket
|
package bitbucket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type Account struct {
|
type Account struct {
|
||||||
Login string `json:"username"`
|
Login string `json:"username"`
|
||||||
Name string `json:"display_name"`
|
Name string `json:"display_name"`
|
||||||
|
@ -8,11 +14,11 @@ type Account struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type AccountResp struct {
|
type AccountResp struct {
|
||||||
Page int `json:"page"`
|
Page int `json:"page"`
|
||||||
Pages int `json:"pagelen"`
|
Pages int `json:"pagelen"`
|
||||||
Size int `json:"size"`
|
Size int `json:"size"`
|
||||||
Next string `json:"next"`
|
Next string `json:"next"`
|
||||||
Values []Account `json:"values"`
|
Values []*Account `json:"values"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Email struct {
|
type Email struct {
|
||||||
|
@ -22,11 +28,11 @@ type Email struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmailResp struct {
|
type EmailResp struct {
|
||||||
Page int `json:"page"`
|
Page int `json:"page"`
|
||||||
Pages int `json:"pagelen"`
|
Pages int `json:"pagelen"`
|
||||||
Size int `json:"size"`
|
Size int `json:"size"`
|
||||||
Next string `json:"next"`
|
Next string `json:"next"`
|
||||||
Values []Email `json:"values"`
|
Values []*Email `json:"values"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Hook struct {
|
type Hook struct {
|
||||||
|
@ -38,11 +44,11 @@ type Hook struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type HookResp struct {
|
type HookResp struct {
|
||||||
Page int `json:"page"`
|
Page int `json:"page"`
|
||||||
Pages int `json:"pagelen"`
|
Pages int `json:"pagelen"`
|
||||||
Size int `json:"size"`
|
Size int `json:"size"`
|
||||||
Next string `json:"next"`
|
Next string `json:"next"`
|
||||||
Values []Hook `json:"values"`
|
Values []*Hook `json:"values"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Links struct {
|
type Links struct {
|
||||||
|
@ -72,11 +78,11 @@ type Repo struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type RepoResp struct {
|
type RepoResp struct {
|
||||||
Page int `json:"page"`
|
Page int `json:"page"`
|
||||||
Pages int `json:"pagelen"`
|
Pages int `json:"pagelen"`
|
||||||
Size int `json:"size"`
|
Size int `json:"size"`
|
||||||
Next string `json:"next"`
|
Next string `json:"next"`
|
||||||
Values []Repo `json:"values"`
|
Values []*Repo `json:"values"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Source struct {
|
type Source struct {
|
||||||
|
@ -86,11 +92,66 @@ type Source struct {
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PushHook struct {
|
||||||
|
Actor Account `json:"actor"`
|
||||||
|
Repo Repo `json:"repository"`
|
||||||
|
Push struct {
|
||||||
|
Changes []struct {
|
||||||
|
New struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Target struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Date time.Time `json:"date"`
|
||||||
|
Links Links `json:"links"`
|
||||||
|
Author struct {
|
||||||
|
Raw string `json:"raw"`
|
||||||
|
User Account `json:"user"`
|
||||||
|
} `json:"author"`
|
||||||
|
} `json:"target"`
|
||||||
|
} `json:"new"`
|
||||||
|
} `json:"changes"`
|
||||||
|
} `json:"push"`
|
||||||
|
}
|
||||||
|
|
||||||
type ListOpts struct {
|
type ListOpts struct {
|
||||||
Page int
|
Page int
|
||||||
PageLen int
|
PageLen int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *ListOpts) Encode() string {
|
||||||
|
params := new(url.Values)
|
||||||
|
if o.Page != 0 {
|
||||||
|
params.Set("page", strconv.Itoa(o.Page))
|
||||||
|
}
|
||||||
|
if o.PageLen != 0 {
|
||||||
|
params.Set("pagelen", strconv.Itoa(o.PageLen))
|
||||||
|
}
|
||||||
|
return params.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListTeamOpts struct {
|
||||||
|
Page int
|
||||||
|
PageLen int
|
||||||
|
Role string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ListTeamOpts) Encode() string {
|
||||||
|
params := new(url.Values)
|
||||||
|
if o.Page != 0 {
|
||||||
|
params.Set("page", strconv.Itoa(o.Page))
|
||||||
|
}
|
||||||
|
if o.PageLen != 0 {
|
||||||
|
params.Set("pagelen", strconv.Itoa(o.PageLen))
|
||||||
|
}
|
||||||
|
if len(o.Role) != 0 {
|
||||||
|
params.Set("role", o.Role)
|
||||||
|
}
|
||||||
|
return params.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
type Error struct {
|
type Error struct {
|
||||||
Status int
|
Status int
|
||||||
Body struct {
|
Body struct {
|
||||||
|
|
55
router/middleware/refresh/refresh.go
Normal file
55
router/middleware/refresh/refresh.go
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package refresh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/remote"
|
||||||
|
"github.com/drone/drone/router/middleware/context"
|
||||||
|
"github.com/drone/drone/router/middleware/session"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Refresh(c *gin.Context) {
|
||||||
|
user := session.User(c)
|
||||||
|
if user == nil || user.Expiry == 0 {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
db := context.Database(c)
|
||||||
|
remote_ := context.Remote(c)
|
||||||
|
|
||||||
|
// check if the remote includes the ability to
|
||||||
|
// refresh the user token.
|
||||||
|
refresher, ok := remote_.(remote.Refresher)
|
||||||
|
if !ok {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check to see if the user token is expired or
|
||||||
|
// will expire within the next 30 minutes (1800 seconds).
|
||||||
|
// If not, there is nothing we really need to do here.
|
||||||
|
if time.Now().UTC().Unix() > (user.Expiry - 1800) {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempts to refresh the access token. If the
|
||||||
|
// token is refreshed, we must also persist to the
|
||||||
|
// database.
|
||||||
|
ok, _ = refresher.Refresh(user)
|
||||||
|
if ok {
|
||||||
|
err := model.UpdateUser(db, user)
|
||||||
|
if err != nil {
|
||||||
|
// we only log the error at this time. not sure
|
||||||
|
// if we really want to fail the request, do we?
|
||||||
|
log.Errorf("cannot refresh access token for %s. %s", user.Login, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/drone/drone/controller"
|
"github.com/drone/drone/controller"
|
||||||
"github.com/drone/drone/router/middleware/header"
|
"github.com/drone/drone/router/middleware/header"
|
||||||
|
"github.com/drone/drone/router/middleware/refresh"
|
||||||
"github.com/drone/drone/router/middleware/session"
|
"github.com/drone/drone/router/middleware/session"
|
||||||
"github.com/drone/drone/static"
|
"github.com/drone/drone/static"
|
||||||
"github.com/drone/drone/template"
|
"github.com/drone/drone/template"
|
||||||
|
@ -21,6 +22,7 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
|
||||||
e.Use(header.SetHeaders())
|
e.Use(header.SetHeaders())
|
||||||
e.Use(middleware...)
|
e.Use(middleware...)
|
||||||
e.Use(session.SetUser())
|
e.Use(session.SetUser())
|
||||||
|
e.Use(refresh.Refresh)
|
||||||
|
|
||||||
e.GET("/", controller.ShowIndex)
|
e.GET("/", controller.ShowIndex)
|
||||||
e.GET("/login", controller.ShowLogin)
|
e.GET("/login", controller.ShowLogin)
|
||||||
|
|
Loading…
Reference in a new issue