cache for credentials

feature/ecr_token_cache
Karolis Rusenas 2018-04-29 19:47:33 +01:00
parent 08468c20e2
commit 72faa5566a
3 changed files with 152 additions and 2 deletions

View File

@ -6,6 +6,7 @@ import (
"net/url"
"os"
"strings"
"time"
// "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws"
@ -32,6 +33,7 @@ func init() {
type CredentialsHelper struct {
enabled bool
region string
cache *Cache
}
// New creates a new instance of aws credentials helper
@ -48,6 +50,7 @@ func New() *CredentialsHelper {
ch.enabled = true
log.Infof("extension.credentialshelper.aws: enabled")
ch.region = region
ch.cache = NewCache(2 * time.Hour)
}
// if os.Getenv("AWS_ACCESS_KEY_ID") != "" && os.Getenv("AWS_SECRET_ACCESS_KEY") != "" && os.Getenv("AWS_REGION") != "" {
@ -70,6 +73,11 @@ func (h *CredentialsHelper) GetCredentials(image *types.TrackedImage) (*types.Cr
return nil, credentialshelper.ErrUnsupportedRegistry
}
cached, err := h.cache.Get(registry)
if err == nil {
return cached, nil
}
svc := ecr.New(session.New(), &aws.Config{
Region: aws.String(h.region),
})
@ -116,10 +124,14 @@ func (h *CredentialsHelper) GetCredentials(image *types.TrackedImage) (*types.Cr
return nil, fmt.Errorf("failed to decode authentication token: %s, error: %s", *ad.AuthorizationToken, err)
}
return &types.Credentials{
creds := &types.Credentials{
Username: username,
Password: password,
}, nil
}
h.cache.Put(registry, creds)
return creds, nil
}
}

View File

@ -0,0 +1,79 @@
package aws
import (
"fmt"
"sync"
"time"
"github.com/keel-hq/keel/types"
)
type item struct {
credentials *types.Credentials
created time.Time
}
// Cache - internal cache for aws
type Cache struct {
creds map[string]*item
tick time.Duration
ttl time.Duration
mu *sync.RWMutex
}
// NewCache - new credentials cache
func NewCache(ttl time.Duration) (c *Cache) {
c = &Cache{
creds: make(map[string]*item),
mu: &sync.RWMutex{},
ttl: ttl,
tick: 30 * time.Second,
}
go c.expiryService()
return
}
func (c *Cache) expiryService() {
ticker := time.NewTicker(c.tick)
defer ticker.Stop()
for {
select {
case <-ticker.C:
c.expire()
}
}
}
func (c *Cache) expire() {
c.mu.Lock()
t := time.Now()
for k, v := range c.creds {
if t.Sub(v.created) > c.ttl {
delete(c.creds, k)
}
}
c.mu.Unlock()
}
// Put - saves new creds
func (c *Cache) Put(registry string, creds *types.Credentials) {
c.mu.Lock()
defer c.mu.Unlock()
c.creds[registry] = &item{credentials: creds, created: time.Now()}
}
// Get - retrieves creds
func (c *Cache) Get(registry string) (*types.Credentials, error) {
c.mu.RLock()
defer c.mu.RUnlock()
item, ok := c.creds[registry]
if !ok {
return nil, fmt.Errorf("not found")
}
cr := new(types.Credentials)
*cr = *item.credentials
return cr, nil
}

View File

@ -0,0 +1,59 @@
package aws
import (
"sync"
"time"
"github.com/keel-hq/keel/types"
"testing"
)
func TestPutCreds(t *testing.T) {
c := NewCache(time.Second * 5)
creds := &types.Credentials{
Username: "user-1",
Password: "pass-1",
}
c.Put("reg1", creds)
stored, err := c.Get("reg1")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if stored.Username != "user-1" {
t.Errorf("username mismatch: %s", stored.Username)
}
if stored.Password != "pass-1" {
t.Errorf("password mismatch: %s", stored.Password)
}
}
func TestExpiry(t *testing.T) {
c := &Cache{
creds: make(map[string]*item),
mu: &sync.RWMutex{},
ttl: time.Millisecond * 500,
tick: time.Millisecond * 100,
}
go c.expiryService()
creds := &types.Credentials{
Username: "user-1",
Password: "pass-1",
}
c.Put("reg1", creds)
time.Sleep(1100 * time.Millisecond)
_, err := c.Get("reg1")
if err == nil {
t.Fatalf("expected to get an error about missing record")
}
}