github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/creds/cached_secrets.go (about)

     1  package creds
     2  
     3  import (
     4  	"time"
     5  
     6  	"github.com/patrickmn/go-cache"
     7  )
     8  
     9  type SecretCacheConfig struct {
    10  	Enabled          bool          `long:"secret-cache-enabled" description:"Enable in-memory cache for secrets"`
    11  	Duration         time.Duration `long:"secret-cache-duration" default:"1m" description:"If the cache is enabled, secret values will be cached for not longer than this duration (it can be less, if underlying secret lease time is smaller)"`
    12  	DurationNotFound time.Duration `long:"secret-cache-duration-notfound" default:"10s" description:"If the cache is enabled, secret not found responses will be cached for this duration"`
    13  	PurgeInterval    time.Duration `long:"secret-cache-purge-interval" default:"10m" description:"If the cache is enabled, expired items will be removed on this interval"`
    14  }
    15  
    16  type CachedSecrets struct {
    17  	secrets     Secrets
    18  	cacheConfig SecretCacheConfig
    19  	cache       *cache.Cache
    20  }
    21  
    22  type CacheEntry struct {
    23  	value      interface{}
    24  	expiration *time.Time
    25  	found      bool
    26  }
    27  
    28  func NewCachedSecrets(secrets Secrets, cacheConfig SecretCacheConfig) *CachedSecrets {
    29  	// Create a cache with:
    30  	// - default expiration time for entries set to 'cacheConfig.Duration'
    31  	// - purges expired items regularly, on every `cacheConfig.PurgeInterval` after creation
    32  	return &CachedSecrets{
    33  		secrets:     secrets,
    34  		cacheConfig: cacheConfig,
    35  		cache:       cache.New(cacheConfig.Duration, cacheConfig.PurgeInterval),
    36  	}
    37  }
    38  
    39  func (cs *CachedSecrets) Get(secretPath string) (interface{}, *time.Time, bool, error) {
    40  	// if there is a corresponding entry in the cache, return it
    41  	entry, found := cs.cache.Get(secretPath)
    42  	if found {
    43  		result := entry.(CacheEntry)
    44  		return result.value, result.expiration, result.found, nil
    45  	}
    46  
    47  	// otherwise, let's make a request to the underlying secret manager
    48  	value, expiration, found, err := cs.secrets.Get(secretPath)
    49  
    50  	// we don't want to cache errors, let the errors be retried the next time around
    51  	if err != nil {
    52  		return nil, nil, false, err
    53  	}
    54  
    55  	// here we want to cache secret value, expiration, and found flag too
    56  	// meaning that "secret not found" responses will be cached too!
    57  	entry = CacheEntry{value: value, expiration: expiration, found: found}
    58  
    59  	if found {
    60  		// take default cache ttl
    61  		duration := cs.cacheConfig.Duration
    62  		if expiration != nil {
    63  			// if secret lease time expires sooner, make duration smaller than default duration
    64  			itemDuration := expiration.Sub(time.Now())
    65  			if itemDuration < duration {
    66  				duration = itemDuration
    67  			}
    68  		}
    69  		cs.cache.Set(secretPath, entry, duration)
    70  	} else {
    71  		cs.cache.Set(secretPath, entry, cs.cacheConfig.DurationNotFound)
    72  	}
    73  
    74  	return value, expiration, found, nil
    75  }
    76  
    77  func (cs *CachedSecrets) NewSecretLookupPaths(teamName string, pipelineName string, allowRootPath bool) []SecretLookupPath {
    78  	return cs.secrets.NewSecretLookupPaths(teamName, pipelineName, allowRootPath)
    79  }