forked from harness/harness
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
cleaning up the middleware and adding caching with TTL
- Loading branch information
1 parent
7be9392
commit a7a1b1d
Showing
14 changed files
with
428 additions
and
71 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package cache | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/hashicorp/golang-lru" | ||
) | ||
|
||
// single instance of a thread-safe lru cache | ||
var cache *lru.Cache | ||
|
||
func init() { | ||
var err error | ||
cache, err = lru.New(2048) | ||
if err != nil { | ||
panic(err) | ||
} | ||
} | ||
|
||
// item is a simple wrapper around a cacheable object | ||
// that tracks the ttl for item expiration in the cache. | ||
type item struct { | ||
value interface{} | ||
ttl time.Time | ||
} | ||
|
||
// set adds the key value pair to the cache with the | ||
// specified ttl expiration. | ||
func set(key string, value interface{}, ttl int64) { | ||
ttlv := time.Now().Add(time.Duration(ttl) * time.Second) | ||
cache.Add(key, &item{value, ttlv}) | ||
} | ||
|
||
// get gets the value from the cache for the given key. | ||
// if the value does not exist, a nil value is returned. | ||
// if the value exists, but is expired, the value is returned | ||
// with a bool flag set to true. | ||
func get(key string) (interface{}, bool) { | ||
v, ok := cache.Get(key) | ||
if !ok { | ||
return nil, false | ||
} | ||
vv := v.(*item) | ||
expired := vv.ttl.Before(time.Now()) | ||
if expired { | ||
cache.Remove(key) | ||
} | ||
return vv.value, expired | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package cache | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/franela/goblin" | ||
) | ||
|
||
func TestCache(t *testing.T) { | ||
|
||
g := goblin.Goblin(t) | ||
g.Describe("Cache", func() { | ||
|
||
g.BeforeEach(func() { | ||
cache.Purge() | ||
}) | ||
|
||
g.It("should set and get item", func() { | ||
set("foo", "bar", 1000) | ||
val, expired := get("foo") | ||
g.Assert(val).Equal("bar") | ||
g.Assert(expired).Equal(false) | ||
}) | ||
|
||
g.It("should return nil when item not found", func() { | ||
val, expired := get("foo") | ||
g.Assert(val == nil).IsTrue() | ||
g.Assert(expired).Equal(false) | ||
}) | ||
|
||
g.It("should get expired item and purge", func() { | ||
set("foo", "bar", -900) | ||
val, expired := get("foo") | ||
g.Assert(val).Equal("bar") | ||
g.Assert(expired).Equal(true) | ||
val, _ = get("foo") | ||
g.Assert(val == nil).IsTrue() | ||
}) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package cache | ||
|
||
import ( | ||
"fmt" | ||
|
||
"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 rempository 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 | ||
} | ||
|
||
key := fmt.Sprintf("perm/%s/%s/%s", | ||
user.(*model.User).Login, | ||
owner, | ||
name, | ||
) | ||
|
||
// if the item already exists in the cache | ||
// we can continue the middleware chain and | ||
// exit afterwards. | ||
v, _ := get(key) | ||
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 { | ||
set(key, perm, 86400) // 24 hours | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package cache | ||
|
||
import ( | ||
"testing" | ||
|
||
"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() { | ||
|
||
g.BeforeEach(func() { | ||
cache.Purge() | ||
}) | ||
|
||
g.It("should skip when no user session", func() { | ||
c := &gin.Context{} | ||
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 := &gin.Context{} | ||
c.Params = gin.Params{ | ||
gin.Param{Key: "owner", Value: "octocat"}, | ||
gin.Param{Key: "name", Value: "hello-world"}, | ||
} | ||
c.Set("user", fakeUser) | ||
set("perm/octocat/octocat/hello-world", fakePerm, 999) | ||
|
||
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", | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package cache | ||
|
||
import ( | ||
"fmt" | ||
|
||
"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 | ||
} | ||
|
||
key := fmt.Sprintf("repos/%s", | ||
user.(*model.User).Login, | ||
) | ||
|
||
// if the item already exists in the cache | ||
// we can continue the middleware chain and | ||
// exit afterwards. | ||
v, _ := get(key) | ||
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 { | ||
set(key, repos, 86400) // 24 hours | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package cache | ||
|
||
import ( | ||
"testing" | ||
|
||
"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() { | ||
|
||
g.BeforeEach(func() { | ||
cache.Purge() | ||
}) | ||
|
||
g.It("should skip when no user session", func() { | ||
c := &gin.Context{} | ||
|
||
Perms(c) | ||
|
||
_, ok := c.Get("perm") | ||
g.Assert(ok).IsFalse() | ||
}) | ||
|
||
g.It("should get repos from cache", func() { | ||
c := &gin.Context{} | ||
c.Set("user", fakeUser) | ||
set("repos/octocat", fakeRepos, 999) | ||
|
||
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"}, | ||
} |
Oops, something went wrong.