diff --git a/extension/credentialshelper/aws/aws.go b/extension/credentialshelper/aws/aws.go index cc5f2e42..20fc353e 100644 --- a/extension/credentialshelper/aws/aws.go +++ b/extension/credentialshelper/aws/aws.go @@ -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 } } diff --git a/extension/credentialshelper/aws/cache.go b/extension/credentialshelper/aws/cache.go new file mode 100644 index 00000000..ce181dc7 --- /dev/null +++ b/extension/credentialshelper/aws/cache.go @@ -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 +} diff --git a/extension/credentialshelper/aws/cache_test.go b/extension/credentialshelper/aws/cache_test.go new file mode 100644 index 00000000..cae3b175 --- /dev/null +++ b/extension/credentialshelper/aws/cache_test.go @@ -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") + } + +}