improve and simplify repository caching

This commit is contained in:
Brad Rydzewski 2016-03-04 21:15:50 -08:00
parent 6769eed65d
commit 3dd0260b69
15 changed files with 444 additions and 364 deletions

10
cache/cache.go vendored
View file

@ -1,5 +1,7 @@
package cache package cache
//go:generate mockery -name Cache -output mock -case=underscore
import ( import (
"time" "time"
@ -23,7 +25,7 @@ func Set(c context.Context, key string, value interface{}) error {
// Default creates an in-memory cache with the default // Default creates an in-memory cache with the default
// 30 minute expiration period. // 30 minute expiration period.
func Default() Cache { func Default() Cache {
return cache.NewMemoryWithTTL(time.Minute * 30) return NewTTL(time.Minute * 30)
} }
// NewTTL returns an in-memory cache with the specified // NewTTL returns an in-memory cache with the specified
@ -31,9 +33,3 @@ func Default() Cache {
func NewTTL(t time.Duration) Cache { func NewTTL(t time.Duration) Cache {
return cache.NewMemoryWithTTL(t) return cache.NewMemoryWithTTL(t)
} }
// NewTTL returns an in-memory cache with the specified
// ttl expiration period.
func NewLRU(size int) Cache {
return cache.NewLRU(size)
}

79
cache/helper.go vendored
View file

@ -4,72 +4,51 @@ import (
"fmt" "fmt"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/remote"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
// GetRepos returns the user permissions to the named repository // GetPerm returns the user permissions repositories from the cache
// from the cache associated with the current context. // associated with the current repository.
func GetPerms(c context.Context, user *model.User, owner, name string) *model.Perm { func GetPerms(c context.Context, user *model.User, owner, name string) (*model.Perm, error) {
key := fmt.Sprintf("perms:%s:%s/%s", key := fmt.Sprintf("perms:%s:%s/%s",
user.Login, user.Login,
owner, owner,
name, name,
) )
val, err := FromContext(c).Get(key) // if we fetch from the cache we can return immediately
if err != nil { val, err := Get(c, key)
return nil if err == nil {
return val.(*model.Perm), nil
} }
return val.(*model.Perm) // else we try to grab from the remote system and
} // populate our cache.
perm, err := remote.Perm(c, user, owner, name)
// SetRepos adds the listof user permissions to the named repsotiory if err != nil {
// to the cache assocaited with the current context. return nil, err
func SetPerms(c context.Context, user *model.User, perm *model.Perm, owner, name string) { }
key := fmt.Sprintf("perms:%s:%s/%s", Set(c, key, perm)
user.Login, return perm, nil
owner,
name,
)
FromContext(c).Set(key, perm)
} }
// GetRepos returns the list of user repositories from the cache // GetRepos returns the list of user repositories from the cache
// associated with the current context. // associated with the current context.
func GetRepos(c context.Context, user *model.User) []*model.RepoLite { func GetRepos(c context.Context, user *model.User) ([]*model.RepoLite, error) {
key := fmt.Sprintf("repos:%s", key := fmt.Sprintf("repos:%s",
user.Login, user.Login,
) )
val, err := FromContext(c).Get(key) // if we fetch from the cache we can return immediately
if err != nil { val, err := Get(c, key)
return nil if err == nil {
return val.([]*model.RepoLite), nil
}
// else we try to grab from the remote system and
// populate our cache.
repos, err := remote.Repos(c, user)
if err != nil {
return nil, err
} }
return val.([]*model.RepoLite)
}
// SetRepos adds the listof user repositories to the cache assocaited Set(c, key, repos)
// with the current context. return repos, nil
func SetRepos(c context.Context, user *model.User, repos []*model.RepoLite) {
key := fmt.Sprintf("repos:%s",
user.Login,
)
FromContext(c).Set(key, repos)
} }
// GetSetRepos is a helper function that attempts to get the
// repository list from the cache first. If no data is in the
// cache or it is expired, it will remotely fetch the list of
// repositories and populate the cache.
// func GetSetRepos(c context.Context, user *model.User) ([]*model.RepoLite, error) {
// cache := FromContext(c).Repos()
// repos := FromContext(c).Repos().Get(user)
// if repos != nil {
// return repos, nil
// }
// var err error
// repos, err = remote.FromContext(c).Repos(user)
// if err != nil {
// return nil, err
// }
// cache.Set(user, repos)
// return repos, nil
// }

74
cache/helper_test.go vendored
View file

@ -1,9 +1,13 @@
package cache package cache
import ( import (
"errors"
"fmt"
"testing" "testing"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/remote/mock"
"github.com/franela/goblin" "github.com/franela/goblin"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -11,43 +15,83 @@ import (
func TestHelper(t *testing.T) { func TestHelper(t *testing.T) {
g := goblin.Goblin(t) g := goblin.Goblin(t)
g.Describe("Cache helpers", func() { g.Describe("Cache helpers", func() {
var c *gin.Context var c *gin.Context
var r *mock.Remote
g.BeforeEach(func() { g.BeforeEach(func() {
c = new(gin.Context) c = new(gin.Context)
ToContext(c, Default()) ToContext(c, Default())
r = new(mock.Remote)
remote.ToContext(c, r)
}) })
g.It("Should set and get permissions", func() { g.It("Should get permissions from remote", func() {
SetPerms(c, fakeUser, fakePerm, "octocat", "Spoon-Knife") r.On("Perm", fakeUser, fakeRepo.Owner, fakeRepo.Name).Return(fakePerm, nil).Once()
p, err := GetPerms(c, fakeUser, fakeRepo.Owner, fakeRepo.Name)
g.Assert(p).Equal(fakePerm)
g.Assert(err).Equal(nil)
v := GetPerms(c, fakeUser, "octocat", "Spoon-Knife")
g.Assert(v).Equal(fakePerm)
}) })
g.It("Should return nil if permissions if not found", func() { g.It("Should get permissions from cache", func() {
v := GetPerms(c, fakeUser, "octocat", "Spoon-Knife") key := fmt.Sprintf("perms:%s:%s/%s",
g.Assert(v == nil).IsTrue() fakeUser.Login,
fakeRepo.Owner,
fakeRepo.Name,
)
Set(c, key, fakePerm)
r.On("Perm", fakeUser, fakeRepo.Owner, fakeRepo.Name).Return(nil, fakeErr).Once()
p, err := GetPerms(c, fakeUser, fakeRepo.Owner, fakeRepo.Name)
g.Assert(p).Equal(fakePerm)
g.Assert(err).Equal(nil)
}) })
g.It("Should set and get repositories", func() { g.It("Should get permissions error", func() {
SetRepos(c, fakeUser, fakeRepos) r.On("Perm", fakeUser, fakeRepo.Owner, fakeRepo.Name).Return(nil, fakeErr).Once()
p, err := GetPerms(c, fakeUser, fakeRepo.Owner, fakeRepo.Name)
v := GetRepos(c, fakeUser) g.Assert(p == nil).IsTrue()
g.Assert(v).Equal(fakeRepos) g.Assert(err).Equal(fakeErr)
}) })
g.It("Should return nil if repositories not found", func() { g.It("Should set and get repos", func() {
v := GetRepos(c, fakeUser)
g.Assert(v == nil).IsTrue() r.On("Repos", fakeUser).Return(fakeRepos, nil).Once()
p, err := GetRepos(c, fakeUser)
g.Assert(p).Equal(fakeRepos)
g.Assert(err).Equal(nil)
})
g.It("Should get repos", func() {
key := fmt.Sprintf("repos:%s",
fakeUser.Login,
)
Set(c, key, fakeRepos)
r.On("Repos", fakeUser).Return(nil, fakeErr).Once()
p, err := GetRepos(c, fakeUser)
g.Assert(p).Equal(fakeRepos)
g.Assert(err).Equal(nil)
})
g.It("Should get repos error", func() {
r.On("Repos", fakeUser).Return(nil, fakeErr).Once()
p, err := GetRepos(c, fakeUser)
g.Assert(p == nil).IsTrue()
g.Assert(err).Equal(fakeErr)
}) })
}) })
} }
var ( var (
fakeErr = errors.New("Not Found")
fakeUser = &model.User{Login: "octocat"} fakeUser = &model.User{Login: "octocat"}
fakePerm = &model.Perm{true, true, true} fakePerm = &model.Perm{true, true, true}
fakeRepo = &model.RepoLite{Owner: "octocat", Name: "Hello-World"}
fakeRepos = []*model.RepoLite{ fakeRepos = []*model.RepoLite{
{Owner: "octocat", Name: "Hello-World"}, {Owner: "octocat", Name: "Hello-World"},
{Owner: "octocat", Name: "hello-world"}, {Owner: "octocat", Name: "hello-world"},

View file

@ -5,11 +5,10 @@ import (
"strconv" "strconv"
"time" "time"
log "github.com/Sirupsen/logrus"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/drone/drone/cache"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/router/middleware/session" "github.com/drone/drone/router/middleware/session"
"github.com/drone/drone/shared/httputil" "github.com/drone/drone/shared/httputil"
"github.com/drone/drone/shared/token" "github.com/drone/drone/shared/token"
@ -17,38 +16,19 @@ import (
) )
func ShowIndex(c *gin.Context) { func ShowIndex(c *gin.Context) {
remote := remote.FromContext(c)
user := session.User(c) user := session.User(c)
if user == nil { if user == nil {
c.Redirect(http.StatusSeeOther, "/login") c.Redirect(http.StatusSeeOther, "/login")
return return
} }
var err error
var repos []*model.RepoLite
// get the repository list from the cache // get the repository list from the cache
reposv, ok := c.Get("repos") repos, err := cache.GetRepos(c, user)
if ok { if err != nil {
repos = reposv.([]*model.RepoLite) c.String(400, err.Error())
} else { return
repos, err = remote.Repos(user)
if err != nil {
log.Errorf("Failure to get remote repositories for %s. %s.",
user.Login, err)
} else {
c.Set("repos", repos)
}
} }
// for each repository in the remote system we get
// the intersection of those repostiories in Drone
// repos_, err := store.GetRepoListOf(c, repos)
// if err != nil {
// log.Errorf("Failure to get repository list for %s. %s.",
// user.Login, err)
// }
c.HTML(200, "repos.html", gin.H{ c.HTML(200, "repos.html", gin.H{
"User": user, "User": user,
"Repos": repos, "Repos": repos,

View file

@ -9,6 +9,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"github.com/drone/drone/cache"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/remote" "github.com/drone/drone/remote"
"github.com/drone/drone/router/middleware/session" "github.com/drone/drone/router/middleware/session"
@ -34,7 +35,7 @@ func PostRepo(c *gin.Context) {
c.String(404, err.Error()) c.String(404, err.Error())
return return
} }
m, err := remote.Perm(user, owner, name) m, err := cache.GetPerms(c, user, owner, name)
if err != nil { if err != nil {
c.String(404, err.Error()) c.String(404, err.Error())
return return

View file

@ -5,8 +5,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/drone/drone/model" "github.com/drone/drone/cache"
"github.com/drone/drone/remote"
"github.com/drone/drone/router/middleware/session" "github.com/drone/drone/router/middleware/session"
"github.com/drone/drone/shared/token" "github.com/drone/drone/shared/token"
"github.com/drone/drone/store" "github.com/drone/drone/store"
@ -18,20 +17,12 @@ func GetSelf(c *gin.Context) {
func GetFeed(c *gin.Context) { func GetFeed(c *gin.Context) {
user := session.User(c) user := session.User(c)
remote := remote.FromContext(c)
var repos []*model.RepoLite
// get the repository list from the cache // get the repository list from the cache
reposv, ok := c.Get("repos") repos, err := cache.GetRepos(c, user)
if ok { if err != nil {
repos = reposv.([]*model.RepoLite) c.String(400, err.Error())
} else { return
var err error
repos, err = remote.Repos(user)
if err != nil {
c.String(400, err.Error())
return
}
} }
feed, err := store.GetUserFeed(c, repos) feed, err := store.GetUserFeed(c, repos)
@ -44,20 +35,11 @@ func GetFeed(c *gin.Context) {
func GetRepos(c *gin.Context) { func GetRepos(c *gin.Context) {
user := session.User(c) user := session.User(c)
remote := remote.FromContext(c)
var repos []*model.RepoLite
// get the repository list from the cache repos, err := cache.GetRepos(c, user)
reposv, ok := c.Get("repos") if err != nil {
if ok { c.AbortWithStatus(http.StatusInternalServerError)
repos = reposv.([]*model.RepoLite) return
} else {
var err error
repos, err = remote.Repos(user)
if err != nil {
c.AbortWithStatus(http.StatusInternalServerError)
return
}
} }
// for each repository in the remote system we get // for each repository in the remote system we get
@ -68,27 +50,18 @@ func GetRepos(c *gin.Context) {
return return
} }
c.Set("repos", repos)
c.IndentedJSON(http.StatusOK, repos_) c.IndentedJSON(http.StatusOK, repos_)
} }
func GetRemoteRepos(c *gin.Context) { func GetRemoteRepos(c *gin.Context) {
user := session.User(c) user := session.User(c)
remote := remote.FromContext(c)
reposv, ok := c.Get("repos") repos, err := cache.GetRepos(c, user)
if ok {
c.IndentedJSON(http.StatusOK, reposv)
return
}
repos, err := remote.Repos(user)
if err != nil { if err != nil {
c.AbortWithStatus(http.StatusInternalServerError) c.AbortWithStatus(http.StatusInternalServerError)
return return
} }
c.Set("repos", repos)
c.IndentedJSON(http.StatusOK, repos) c.IndentedJSON(http.StatusOK, repos)
} }

View file

@ -19,9 +19,10 @@ import (
) )
const ( const (
DefaultURL = "https://github.com" DefaultURL = "https://github.com"
DefaultAPI = "https://api.github.com" DefaultAPI = "https://api.github.com"
DefaultScope = "repo,repo:status,user:email" DefaultScope = "repo,repo:status,user:email"
DefaultMergeRef = "merge"
) )
type Github struct { type Github struct {
@ -29,6 +30,7 @@ type Github struct {
API string API string
Client string Client string
Secret string Secret string
MergeRef string
Orgs []string Orgs []string
Open bool Open bool
PrivateMode bool PrivateMode bool
@ -59,6 +61,7 @@ func Load(env envconfig.Env) *Github {
github.SkipVerify, _ = strconv.ParseBool(params.Get("skip_verify")) github.SkipVerify, _ = strconv.ParseBool(params.Get("skip_verify"))
github.Open, _ = strconv.ParseBool(params.Get("open")) github.Open, _ = strconv.ParseBool(params.Get("open"))
github.GitSSH, _ = strconv.ParseBool(params.Get("ssh")) github.GitSSH, _ = strconv.ParseBool(params.Get("ssh"))
github.MergeRef = params.Get("merge_ref")
if github.URL == DefaultURL { if github.URL == DefaultURL {
github.API = DefaultAPI github.API = DefaultAPI
@ -66,6 +69,10 @@ func Load(env envconfig.Env) *Github {
github.API = github.URL + "/api/v3/" github.API = github.URL + "/api/v3/"
} }
if github.MergeRef == "" {
github.MergeRef = DefaultMergeRef
}
return &github return &github
} }
@ -402,7 +409,7 @@ func (g *Github) pullRequest(r *http.Request) (*model.Repo, *model.Build, error)
build := &model.Build{} build := &model.Build{}
build.Event = model.EventPull build.Event = model.EventPull
build.Commit = *hook.PullRequest.Head.SHA build.Commit = *hook.PullRequest.Head.SHA
build.Ref = fmt.Sprintf("refs/pull/%d/merge", *hook.PullRequest.Number) build.Ref = fmt.Sprintf("refs/pull/%d/%s", *hook.PullRequest.Number, g.MergeRef)
build.Link = *hook.PullRequest.HTMLURL build.Link = *hook.PullRequest.HTMLURL
build.Branch = *hook.PullRequest.Head.Ref build.Branch = *hook.PullRequest.Head.Ref
build.Message = *hook.PullRequest.Title build.Message = *hook.PullRequest.Title

238
remote/mock/remote.go Normal file
View file

@ -0,0 +1,238 @@
package mock
import "github.com/stretchr/testify/mock"
import "net/http"
import "github.com/drone/drone/model"
type Remote struct {
mock.Mock
}
func (_m *Remote) Login(w http.ResponseWriter, r *http.Request) (*model.User, bool, error) {
ret := _m.Called(w, r)
var r0 *model.User
if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) *model.User); ok {
r0 = rf(w, r)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.User)
}
}
var r1 bool
if rf, ok := ret.Get(1).(func(http.ResponseWriter, *http.Request) bool); ok {
r1 = rf(w, r)
} else {
r1 = ret.Get(1).(bool)
}
var r2 error
if rf, ok := ret.Get(2).(func(http.ResponseWriter, *http.Request) error); ok {
r2 = rf(w, r)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
func (_m *Remote) Auth(token string, secret string) (string, error) {
ret := _m.Called(token, secret)
var r0 string
if rf, ok := ret.Get(0).(func(string, string) string); ok {
r0 = rf(token, secret)
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func(string, string) error); ok {
r1 = rf(token, secret)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
func (_m *Remote) Repo(u *model.User, owner string, repo string) (*model.Repo, error) {
ret := _m.Called(u, owner, repo)
var r0 *model.Repo
if rf, ok := ret.Get(0).(func(*model.User, string, string) *model.Repo); ok {
r0 = rf(u, owner, repo)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Repo)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*model.User, string, string) error); ok {
r1 = rf(u, owner, repo)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
func (_m *Remote) Repos(u *model.User) ([]*model.RepoLite, error) {
ret := _m.Called(u)
var r0 []*model.RepoLite
if rf, ok := ret.Get(0).(func(*model.User) []*model.RepoLite); ok {
r0 = rf(u)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.RepoLite)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*model.User) error); ok {
r1 = rf(u)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
func (_m *Remote) Perm(u *model.User, owner string, repo string) (*model.Perm, error) {
ret := _m.Called(u, owner, repo)
var r0 *model.Perm
if rf, ok := ret.Get(0).(func(*model.User, string, string) *model.Perm); ok {
r0 = rf(u, owner, repo)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Perm)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*model.User, string, string) error); ok {
r1 = rf(u, owner, repo)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
func (_m *Remote) Script(u *model.User, r *model.Repo, b *model.Build) ([]byte, []byte, error) {
ret := _m.Called(u, r, b)
var r0 []byte
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo, *model.Build) []byte); ok {
r0 = rf(u, r, b)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]byte)
}
}
var r1 []byte
if rf, ok := ret.Get(1).(func(*model.User, *model.Repo, *model.Build) []byte); ok {
r1 = rf(u, r, b)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).([]byte)
}
}
var r2 error
if rf, ok := ret.Get(2).(func(*model.User, *model.Repo, *model.Build) error); ok {
r2 = rf(u, r, b)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
func (_m *Remote) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
ret := _m.Called(u, r, b, link)
var r0 error
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo, *model.Build, string) error); ok {
r0 = rf(u, r, b, link)
} else {
r0 = ret.Error(0)
}
return r0
}
func (_m *Remote) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
ret := _m.Called(u, r)
var r0 *model.Netrc
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo) *model.Netrc); ok {
r0 = rf(u, r)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Netrc)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*model.User, *model.Repo) error); ok {
r1 = rf(u, r)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
func (_m *Remote) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
ret := _m.Called(u, r, k, link)
var r0 error
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo, *model.Key, string) error); ok {
r0 = rf(u, r, k, link)
} else {
r0 = ret.Error(0)
}
return r0
}
func (_m *Remote) Deactivate(u *model.User, r *model.Repo, link string) error {
ret := _m.Called(u, r, link)
var r0 error
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo, string) error); ok {
r0 = rf(u, r, link)
} else {
r0 = ret.Error(0)
}
return r0
}
func (_m *Remote) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
ret := _m.Called(r)
var r0 *model.Repo
if rf, ok := ret.Get(0).(func(*http.Request) *model.Repo); ok {
r0 = rf(r)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Repo)
}
}
var r1 *model.Build
if rf, ok := ret.Get(1).(func(*http.Request) *model.Build); ok {
r1 = rf(r)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.Build)
}
}
var r2 error
if rf, ok := ret.Get(2).(func(*http.Request) error); ok {
r2 = rf(r)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}

View file

@ -1,5 +1,7 @@
package remote package remote
//go:generate mockery -name Remote -output mock -case=underscore
import ( import (
"net/http" "net/http"
@ -10,7 +12,8 @@ import (
"github.com/drone/drone/remote/gogs" "github.com/drone/drone/remote/gogs"
"github.com/drone/drone/shared/envconfig" "github.com/drone/drone/shared/envconfig"
log "github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"golang.org/x/net/context"
) )
func Load(env envconfig.Env) Remote { func Load(env envconfig.Env) Remote {
@ -27,7 +30,7 @@ func Load(env envconfig.Env) Remote {
return gogs.Load(env) return gogs.Load(env)
default: default:
log.Fatalf("unknown remote driver %s", driver) logrus.Fatalf("unknown remote driver %s", driver)
} }
return nil return nil
@ -83,3 +86,79 @@ type Refresher interface {
// token was not refreshed, and error if it failed to refersh. // token was not refreshed, and error if it failed to refersh.
Refresh(*model.User) (bool, error) Refresh(*model.User) (bool, error)
} }
// Login authenticates the session and returns the
// remote user details.
func Login(c context.Context, w http.ResponseWriter, r *http.Request) (*model.User, bool, error) {
return FromContext(c).Login(w, r)
}
// Auth authenticates the session and returns the remote user
// login for the given token and secret
func Auth(c context.Context, token, secret string) (string, error) {
return FromContext(c).Auth(token, secret)
}
// Repo fetches the named repository from the remote system.
func Repo(c context.Context, u *model.User, owner, repo string) (*model.Repo, error) {
return FromContext(c).Repo(u, owner, repo)
}
// Repos fetches a list of repos from the remote system.
func Repos(c context.Context, u *model.User) ([]*model.RepoLite, error) {
return FromContext(c).Repos(u)
}
// Perm fetches the named repository permissions from
// the remote system for the specified user.
func Perm(c context.Context, u *model.User, owner, repo string) (*model.Perm, error) {
return FromContext(c).Perm(u, owner, repo)
}
// Script fetches the build script (.drone.yml) from the remote
// repository and returns in string format.
func Script(c context.Context, u *model.User, r *model.Repo, b *model.Build) ([]byte, []byte, error) {
return FromContext(c).Script(u, r, b)
}
// Status sends the commit status to the remote system.
// An example would be the GitHub pull request status.
func Status(c context.Context, u *model.User, r *model.Repo, b *model.Build, link string) error {
return FromContext(c).Status(u, r, b, link)
}
// Netrc returns a .netrc file that can be used to clone
// private repositories from a remote system.
func Netrc(c context.Context, u *model.User, r *model.Repo) (*model.Netrc, error) {
return FromContext(c).Netrc(u, r)
}
// Activate activates a repository by creating the post-commit hook and
// adding the SSH deploy key, if applicable.
func Activate(c context.Context, u *model.User, r *model.Repo, k *model.Key, link string) error {
return FromContext(c).Activate(u, r, k, link)
}
// Deactivate removes a repository by removing all the post-commit hooks
// which are equal to link and removing the SSH deploy key.
func Deactivate(c context.Context, u *model.User, r *model.Repo, link string) error {
return FromContext(c).Deactivate(u, r, link)
}
// Hook parses the post-commit hook from the Request body
// and returns the required data in a standard format.
func Hook(c context.Context, r *http.Request) (*model.Repo, *model.Build, error) {
return FromContext(c).Hook(r)
}
// Refresh refreshes an oauth token and expiration for the given
// user. It returns true if the token was refreshed, false if the
// token was not refreshed, and error if it failed to refersh.
func Refresh(c context.Context, u *model.User) (bool, error) {
remote := FromContext(c)
refresher, ok := remote.(Refresher)
if !ok {
return false, nil
}
return refresher.Refresh(u)
}

View file

@ -1,54 +0,0 @@
package cache
import (
"github.com/drone/drone/cache"
"github.com/drone/drone/model"
"github.com/gin-gonic/gin"
)
const permKey = "perm"
// Perms is a middleware function that attempts to cache the
// user's remote repository permissions (ie in GitHub) to minimize
// remote calls that might be expensive, slow or rate-limited.
func Perms(c *gin.Context) {
var (
owner = c.Param("owner")
name = c.Param("name")
user, _ = c.Get("user")
)
if user == nil {
c.Next()
return
}
// if the item already exists in the cache
// we can continue the middleware chain and
// exit afterwards.
v := cache.GetPerms(c,
user.(*model.User),
owner,
name,
)
if v != nil {
c.Set("perm", v)
c.Next()
return
}
// otherwise, if the item isn't cached we execute
// the middleware chain and then cache the permissions
// after the request is processed.
c.Next()
perm, ok := c.Get("perm")
if ok {
cache.SetPerms(c,
user.(*model.User),
perm.(*model.Perm),
owner,
name,
)
}
}

View file

@ -1,61 +0,0 @@
package cache
import (
"testing"
"github.com/drone/drone/cache"
"github.com/drone/drone/model"
"github.com/franela/goblin"
"github.com/gin-gonic/gin"
)
func TestPermCache(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Perm Cache", func() {
var c *gin.Context
g.BeforeEach(func() {
c = new(gin.Context)
cache.ToContext(c, cache.Default())
})
g.It("should skip when no user session", func() {
c.Params = gin.Params{
gin.Param{Key: "owner", Value: "octocat"},
gin.Param{Key: "name", Value: "hello-world"},
}
Perms(c)
_, ok := c.Get("perm")
g.Assert(ok).IsFalse()
})
g.It("should get perms from cache", func() {
c.Params = gin.Params{
gin.Param{Key: "owner", Value: "octocat"},
gin.Param{Key: "name", Value: "hello-world"},
}
c.Set("user", fakeUser)
cache.SetPerms(c, fakeUser, fakePerm, "octocat", "hello-world")
Perms(c)
perm, ok := c.Get("perm")
g.Assert(ok).IsTrue()
g.Assert(perm).Equal(fakePerm)
})
})
}
var fakePerm = &model.Perm{
Pull: true,
Push: true,
Admin: true,
}
var fakeUser = &model.User{
Login: "octocat",
}

View file

@ -1,42 +0,0 @@
package cache
import (
"github.com/drone/drone/cache"
"github.com/drone/drone/model"
"github.com/gin-gonic/gin"
)
// Repos is a middleware function that attempts to cache the
// user's list of remote repositories (ie in GitHub) to minimize
// remote calls that might be expensive, slow or rate-limited.
func Repos(c *gin.Context) {
var user, _ = c.Get("user")
if user == nil {
c.Next()
return
}
// if the item already exists in the cache
// we can continue the middleware chain and
// exit afterwards.
v := cache.GetRepos(c, user.(*model.User))
if v != nil {
c.Set("repos", v)
c.Next()
return
}
// otherwise, if the item isn't cached we execute
// the middleware chain and then cache the permissions
// after the request is processed.
c.Next()
repos, ok := c.Get("repos")
if ok {
cache.SetRepos(c,
user.(*model.User),
repos.([]*model.RepoLite),
)
}
}

View file

@ -1,46 +0,0 @@
package cache
import (
"testing"
"github.com/drone/drone/cache"
"github.com/drone/drone/model"
"github.com/franela/goblin"
"github.com/gin-gonic/gin"
)
func TestReposCache(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Repo List Cache", func() {
var c *gin.Context
g.BeforeEach(func() {
c = new(gin.Context)
cache.ToContext(c, cache.Default())
})
g.It("should skip when no user session", func() {
Perms(c)
_, ok := c.Get("perm")
g.Assert(ok).IsFalse()
})
g.It("should get repos from cache", func() {
c.Set("user", fakeUser)
cache.SetRepos(c, fakeUser, fakeRepos)
Repos(c)
repos, ok := c.Get("repos")
g.Assert(ok).IsTrue()
g.Assert(repos).Equal(fakeRepos)
})
})
}
var fakeRepos = []*model.RepoLite{
{Owner: "octocat", Name: "hello-world"},
}

View file

@ -4,6 +4,7 @@ import (
"net/http" "net/http"
"os" "os"
"github.com/drone/drone/cache"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/remote" "github.com/drone/drone/remote"
"github.com/drone/drone/shared/token" "github.com/drone/drone/shared/token"
@ -112,19 +113,6 @@ func SetPerm() gin.HandlerFunc {
repo := Repo(c) repo := Repo(c)
perm := &model.Perm{} perm := &model.Perm{}
if user != nil {
// attempt to get the permissions from a local cache
// just to avoid excess API calls to GitHub
val, ok := c.Get("perm")
if ok {
c.Next()
log.Debugf("%s using cached %+v permission to %s",
user.Login, val, repo.FullName)
return
}
}
switch { switch {
// if the user is not authenticated, and the // if the user is not authenticated, and the
// repository is private, the user has NO permission // repository is private, the user has NO permission
@ -150,7 +138,7 @@ func SetPerm() gin.HandlerFunc {
// check the remote system to get the users permissiosn. // check the remote system to get the users permissiosn.
default: default:
var err error var err error
perm, err = remote.FromContext(c).Perm(user, repo.Owner, repo.Name) perm, err = cache.GetPerms(c, user, repo.Owner, repo.Name)
if err != nil { if err != nil {
perm.Pull = false perm.Pull = false
perm.Push = false perm.Push = false

View file

@ -7,7 +7,6 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/drone/drone/controller" "github.com/drone/drone/controller"
"github.com/drone/drone/router/middleware/cache"
"github.com/drone/drone/router/middleware/header" "github.com/drone/drone/router/middleware/header"
"github.com/drone/drone/router/middleware/location" "github.com/drone/drone/router/middleware/location"
"github.com/drone/drone/router/middleware/session" "github.com/drone/drone/router/middleware/session"
@ -27,10 +26,9 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
e.Use(header.Secure) e.Use(header.Secure)
e.Use(middleware...) e.Use(middleware...)
e.Use(session.SetUser()) e.Use(session.SetUser())
e.Use(cache.Perms)
e.Use(token.Refresh) e.Use(token.Refresh)
e.GET("/", cache.Repos, controller.ShowIndex) e.GET("/", controller.ShowIndex)
e.GET("/login", controller.ShowLogin) e.GET("/login", controller.ShowLogin)
e.GET("/login/form", controller.ShowLoginForm) e.GET("/login/form", controller.ShowLoginForm)
e.GET("/logout", controller.GetLogout) e.GET("/logout", controller.GetLogout)
@ -64,8 +62,8 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
user.Use(session.MustUser()) user.Use(session.MustUser())
user.GET("", controller.GetSelf) user.GET("", controller.GetSelf)
user.GET("/feed", controller.GetFeed) user.GET("/feed", controller.GetFeed)
user.GET("/repos", cache.Repos, controller.GetRepos) user.GET("/repos", controller.GetRepos)
user.GET("/repos/remote", cache.Repos, controller.GetRemoteRepos) user.GET("/repos/remote", controller.GetRemoteRepos)
user.POST("/token", controller.PostToken) user.POST("/token", controller.PostToken)
} }