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"
|
||||
"github.com/drone/drone/engine"
|
||||
"github.com/drone/drone/remote"
|
||||
"github.com/drone/drone/shared/httputil"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
|
@ -124,7 +125,7 @@ func DeleteBuild(c *gin.Context) {
|
|||
|
||||
func PostBuild(c *gin.Context) {
|
||||
|
||||
remote := context.Remote(c)
|
||||
remote_ := context.Remote(c)
|
||||
repo := session.Repo(c)
|
||||
db := context.Database(c)
|
||||
|
||||
|
@ -148,8 +149,18 @@ func PostBuild(c *gin.Context) {
|
|||
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
|
||||
raw, sec, err := remote.Script(user, repo, build)
|
||||
raw, sec, err := remote_.Script(user, repo, build)
|
||||
if err != nil {
|
||||
log.Errorf("failure to get .drone.yml for %s. %s", repo.FullName, err)
|
||||
c.AbortWithError(404, err)
|
||||
|
@ -157,7 +168,7 @@ func PostBuild(c *gin.Context) {
|
|||
}
|
||||
|
||||
key, _ := model.GetKey(db, repo)
|
||||
netrc, err := remote.Netrc(user, repo)
|
||||
netrc, err := remote_.Netrc(user, repo)
|
||||
if err != nil {
|
||||
log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err)
|
||||
c.AbortWithError(500, err)
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/drone/drone/engine"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/remote"
|
||||
"github.com/drone/drone/router/middleware/context"
|
||||
"github.com/drone/drone/shared/httputil"
|
||||
"github.com/drone/drone/shared/token"
|
||||
|
@ -18,10 +19,10 @@ import (
|
|||
)
|
||||
|
||||
func PostHook(c *gin.Context) {
|
||||
remote := context.Remote(c)
|
||||
remote_ := context.Remote(c)
|
||||
db := context.Database(c)
|
||||
|
||||
tmprepo, build, err := remote.Hook(c.Request)
|
||||
tmprepo, build, err := remote_.Hook(c.Request)
|
||||
if err != nil {
|
||||
log.Errorf("failure to parse hook. %s", err)
|
||||
c.AbortWithError(400, err)
|
||||
|
@ -93,8 +94,18 @@ func PostHook(c *gin.Context) {
|
|||
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
|
||||
raw, sec, err := remote.Script(user, repo, build)
|
||||
raw, sec, err := remote_.Script(user, repo, build)
|
||||
if err != nil {
|
||||
log.Errorf("failure to get .drone.yml for %s. %s", repo.FullName, err)
|
||||
c.AbortWithError(404, err)
|
||||
|
@ -111,7 +122,7 @@ func PostHook(c *gin.Context) {
|
|||
axes = append(axes, matrix.Axis{})
|
||||
}
|
||||
|
||||
netrc, err := remote.Netrc(user, repo)
|
||||
netrc, err := remote_.Netrc(user, repo)
|
||||
if err != nil {
|
||||
log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err)
|
||||
c.AbortWithError(500, err)
|
||||
|
@ -170,7 +181,7 @@ func PostHook(c *gin.Context) {
|
|||
c.JSON(200, build)
|
||||
|
||||
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 {
|
||||
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"`
|
||||
Token string `json:"-" meddler:"user_token"`
|
||||
Secret string `json:"-" meddler:"user_secret"`
|
||||
Expiry int64 `json:"-" meddler:"user_expiry"`
|
||||
Email string `json:"email" meddler:"user_email"`
|
||||
Avatar string `json:"avatar_url" meddler:"user_avatar"`
|
||||
Active bool `json:"active," meddler:"user_active"`
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package bitbucket
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
@ -80,6 +82,7 @@ func (bb *Bitbucket) Login(res http.ResponseWriter, req *http.Request) (*model.U
|
|||
user.Login = curr.Login
|
||||
user.Token = token.AccessToken
|
||||
user.Secret = token.RefreshToken
|
||||
user.Expiry = token.Expiry.UTC().Unix()
|
||||
user.Avatar = curr.Links.Avatar.Href
|
||||
|
||||
// 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
|
||||
// user is a member.
|
||||
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 {
|
||||
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
|
||||
user.Token = token.AccessToken
|
||||
user.Secret = token.RefreshToken
|
||||
user.Expiry = token.Expiry.UTC().Unix()
|
||||
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) {
|
||||
token := oauth2.Token{AccessToken: u.Token, RefreshToken: u.Secret}
|
||||
client := NewClientToken(bb.Client, bb.Secret, &token)
|
||||
|
||||
// var accounts = []string{u.Login}
|
||||
var repos []*model.RepoLite
|
||||
|
||||
// for {
|
||||
// resp, err := client.ListTeams(&ListOpts{Page: page})
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// gets a list of all accounts to query, including the
|
||||
// user's account and all team accounts.
|
||||
logins := []string{u.Login}
|
||||
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 {
|
||||
// accounts = append(accounts, team.Login)
|
||||
// }
|
||||
|
||||
// if resp.Page == resp.Pages {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
var page = 1
|
||||
for {
|
||||
resp, err := client.ListRepos(u.Login, &ListOpts{Page: page, PageLen: 100})
|
||||
// for each account, get the list of repos
|
||||
for _, login := range logins {
|
||||
repos_, err := client.ListReposAll(login)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
return nil, err
|
||||
return repos, err
|
||||
}
|
||||
|
||||
for _, repo := range resp.Values {
|
||||
repos = append(repos, convertRepoLite(&repo))
|
||||
for _, repo := range repos_ {
|
||||
repos = append(repos, convertRepoLite(repo))
|
||||
}
|
||||
|
||||
if len(resp.Next) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
page = resp.Page + 1
|
||||
break
|
||||
}
|
||||
|
||||
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
|
||||
// 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 {
|
||||
perms.Push = 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
|
||||
// private repositories from a remote system.
|
||||
func (bb *Bitbucket) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
||||
netrc := &model.Netrc{}
|
||||
netrc.Login = "x-token-auth"
|
||||
netrc.Password = u.Token
|
||||
netrc.Machine = "bitbucket.org"
|
||||
return netrc, nil
|
||||
return &model.Netrc{
|
||||
Machine: "bitbucket.org",
|
||||
Login: "x-token-auth",
|
||||
Password: u.Token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Activate activates a repository by creating the post-commit hook and
|
||||
// adding the SSH deploy key, if applicable.
|
||||
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"
|
||||
return nil
|
||||
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 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
|
||||
// which are equal to link and removing the SSH deploy key.
|
||||
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
|
||||
}
|
||||
|
||||
// Hook parses the post-commit hook from the Request body
|
||||
// and returns the required data in a standard format.
|
||||
func (bb *Bitbucket) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
func convertRepo(from *Repo) *model.Repo {
|
||||
repo := &model.Repo{}
|
||||
repo.Owner = from.Owner.Login
|
||||
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
|
||||
}
|
||||
// only a subset of hooks are processed by drone
|
||||
if r.Header.Get("X-Event-Key") != "repo:push" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
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 {
|
||||
repo := &model.RepoLite{}
|
||||
repo.Owner = from.Owner.Login
|
||||
repo.Name = from.Name
|
||||
repo.FullName = from.FullName
|
||||
repo.Avatar = from.Owner.Links.Avatar.Href
|
||||
return repo
|
||||
hook := PushHook{}
|
||||
err := json.Unmarshal(payload, &hook)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// 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"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/bitbucket"
|
||||
|
@ -20,7 +19,18 @@ const (
|
|||
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 {
|
||||
*http.Client
|
||||
|
@ -40,68 +50,87 @@ func NewClientToken(client, secret string, token *oauth2.Token) *Client {
|
|||
}
|
||||
|
||||
func (c *Client) FindCurrent() (*Account, error) {
|
||||
var out = new(Account)
|
||||
var uri = fmt.Sprintf("%s/2.0/user/", api)
|
||||
var err = c.do(uri, get, nil, out)
|
||||
out := new(Account)
|
||||
uri := fmt.Sprintf(pathUser, base)
|
||||
err := c.do(uri, get, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (c *Client) ListEmail() (*EmailResp, error) {
|
||||
var out = new(EmailResp)
|
||||
var uri = fmt.Sprintf("%s/2.0/user/emails", api)
|
||||
var err = c.do(uri, get, nil, out)
|
||||
out := new(EmailResp)
|
||||
uri := fmt.Sprintf(pathEmails, base)
|
||||
err := c.do(uri, get, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (c *Client) ListTeams(opts *ListOpts) (*AccountResp, error) {
|
||||
var out = new(AccountResp)
|
||||
var uri = fmt.Sprintf("%s/2.0/teams/?role=member&%s", api, encodeListOpts(opts))
|
||||
var err = c.do(uri, get, nil, out)
|
||||
func (c *Client) ListTeams(opts *ListTeamOpts) (*AccountResp, error) {
|
||||
out := new(AccountResp)
|
||||
uri := fmt.Sprintf(pathTeams, base, opts.Encode())
|
||||
err := c.do(uri, get, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (c *Client) FindRepo(owner, name string) (*Repo, error) {
|
||||
var out = new(Repo)
|
||||
var uri = fmt.Sprintf("%s/2.0/repositories/%s/%s", api, owner, name)
|
||||
var err = c.do(uri, get, nil, out)
|
||||
out := new(Repo)
|
||||
uri := fmt.Sprintf(pathRepo, base, owner, name)
|
||||
err := c.do(uri, get, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (c *Client) ListRepos(account string, opts *ListOpts) (*RepoResp, error) {
|
||||
var out = new(RepoResp)
|
||||
var uri = fmt.Sprintf("%s/2.0/repositories/%s?%s", api, account, encodeListOpts(opts))
|
||||
var err = c.do(uri, get, nil, out)
|
||||
out := new(RepoResp)
|
||||
uri := fmt.Sprintf(pathRepos, base, account, opts.Encode())
|
||||
err := c.do(uri, get, nil, out)
|
||||
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) {
|
||||
var out = new(Hook)
|
||||
var uri = fmt.Sprintf("%s/2.0/repositories/%s/%s/hooks/%s", api, owner, name, id)
|
||||
var err = c.do(uri, get, nil, out)
|
||||
out := new(Hook)
|
||||
uri := fmt.Sprintf(pathHook, base, owner, name, id)
|
||||
err := c.do(uri, get, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (c *Client) ListHooks(owner, name string, opts *ListOpts) (*HookResp, error) {
|
||||
var out = new(HookResp)
|
||||
var uri = fmt.Sprintf("%s/2.0/repositories/%s/%s/hooks?%s", api, owner, name, encodeListOpts(opts))
|
||||
var err = c.do(uri, get, nil, out)
|
||||
out := new(HookResp)
|
||||
uri := fmt.Sprintf(pathHooks, base, owner, name, opts.Encode())
|
||||
err := c.do(uri, get, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (c *Client) CreateHook(owner, name, hook *Hook) error {
|
||||
var uri = fmt.Sprintf("%s/2.0/repositories/%s/%s/hooks", api, owner, name)
|
||||
func (c *Client) CreateHook(owner, name string, hook *Hook) error {
|
||||
uri := fmt.Sprintf(pathHooks, base, owner, name, "")
|
||||
return c.do(uri, post, hook, nil)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (c *Client) FindSource(owner, name, revision, path string) (*Source, error) {
|
||||
var out = new(Source)
|
||||
var uri = fmt.Sprintf("%s/1.0/repositories/%s/%s/src/%s/%s", api, owner, name, revision, path)
|
||||
var err = c.do(uri, get, nil, out)
|
||||
out := new(Source)
|
||||
uri := fmt.Sprintf(pathSource, base, owner, name, revision, path)
|
||||
err := c.do(uri, get, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
|
@ -128,6 +157,9 @@ func (c *Client) do(rawurl, method string, in, out interface{}) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if in != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
resp, err := c.Do(req)
|
||||
if err != nil {
|
||||
|
@ -141,6 +173,11 @@ func (c *Client) do(rawurl, method string, in, out interface{}) error {
|
|||
err := Error{}
|
||||
json.NewDecoder(resp.Body).Decode(&err)
|
||||
err.Status = resp.StatusCode
|
||||
|
||||
instr, _ := json.Marshal(in)
|
||||
println(err.Body.Message)
|
||||
println(string(instr))
|
||||
println(uri.String())
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -152,17 +189,3 @@ func (c *Client) do(rawurl, method string, in, out interface{}) error {
|
|||
|
||||
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
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Account struct {
|
||||
Login string `json:"username"`
|
||||
Name string `json:"display_name"`
|
||||
|
@ -8,11 +14,11 @@ type Account struct {
|
|||
}
|
||||
|
||||
type AccountResp struct {
|
||||
Page int `json:"page"`
|
||||
Pages int `json:"pagelen"`
|
||||
Size int `json:"size"`
|
||||
Next string `json:"next"`
|
||||
Values []Account `json:"values"`
|
||||
Page int `json:"page"`
|
||||
Pages int `json:"pagelen"`
|
||||
Size int `json:"size"`
|
||||
Next string `json:"next"`
|
||||
Values []*Account `json:"values"`
|
||||
}
|
||||
|
||||
type Email struct {
|
||||
|
@ -22,11 +28,11 @@ type Email struct {
|
|||
}
|
||||
|
||||
type EmailResp struct {
|
||||
Page int `json:"page"`
|
||||
Pages int `json:"pagelen"`
|
||||
Size int `json:"size"`
|
||||
Next string `json:"next"`
|
||||
Values []Email `json:"values"`
|
||||
Page int `json:"page"`
|
||||
Pages int `json:"pagelen"`
|
||||
Size int `json:"size"`
|
||||
Next string `json:"next"`
|
||||
Values []*Email `json:"values"`
|
||||
}
|
||||
|
||||
type Hook struct {
|
||||
|
@ -38,11 +44,11 @@ type Hook struct {
|
|||
}
|
||||
|
||||
type HookResp struct {
|
||||
Page int `json:"page"`
|
||||
Pages int `json:"pagelen"`
|
||||
Size int `json:"size"`
|
||||
Next string `json:"next"`
|
||||
Values []Hook `json:"values"`
|
||||
Page int `json:"page"`
|
||||
Pages int `json:"pagelen"`
|
||||
Size int `json:"size"`
|
||||
Next string `json:"next"`
|
||||
Values []*Hook `json:"values"`
|
||||
}
|
||||
|
||||
type Links struct {
|
||||
|
@ -72,11 +78,11 @@ type Repo struct {
|
|||
}
|
||||
|
||||
type RepoResp struct {
|
||||
Page int `json:"page"`
|
||||
Pages int `json:"pagelen"`
|
||||
Size int `json:"size"`
|
||||
Next string `json:"next"`
|
||||
Values []Repo `json:"values"`
|
||||
Page int `json:"page"`
|
||||
Pages int `json:"pagelen"`
|
||||
Size int `json:"size"`
|
||||
Next string `json:"next"`
|
||||
Values []*Repo `json:"values"`
|
||||
}
|
||||
|
||||
type Source struct {
|
||||
|
@ -86,11 +92,66 @@ type Source struct {
|
|||
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 {
|
||||
Page 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 {
|
||||
Status int
|
||||
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/router/middleware/header"
|
||||
"github.com/drone/drone/router/middleware/refresh"
|
||||
"github.com/drone/drone/router/middleware/session"
|
||||
"github.com/drone/drone/static"
|
||||
"github.com/drone/drone/template"
|
||||
|
@ -21,6 +22,7 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
|
|||
e.Use(header.SetHeaders())
|
||||
e.Use(middleware...)
|
||||
e.Use(session.SetUser())
|
||||
e.Use(refresh.Refresh)
|
||||
|
||||
e.GET("/", controller.ShowIndex)
|
||||
e.GET("/login", controller.ShowLogin)
|
||||
|
|
Loading…
Reference in a new issue