some finishing touches on the bitbucket implementation for 0.4

This commit is contained in:
Brad Rydzewski 2015-10-04 17:40:27 -07:00
parent 1c87bd9c3b
commit 528fbb0f2c
9 changed files with 421 additions and 142 deletions

View file

@ -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)

View file

@ -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)
} }

View file

@ -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"`

View file

@ -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
} }

View file

@ -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()
}

View 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,
}
}

View file

@ -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 {

View 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()
}

View file

@ -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)