github.com/hernad/nomad@v1.6.112/helper/users/cache.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package users
     5  
     6  import (
     7  	"os/user"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/hernad/nomad/lib/lang"
    12  	"oss.indeed.com/go/libtime"
    13  )
    14  
    15  const (
    16  	cacheTTL   = 1 * time.Hour
    17  	failureTTL = 1 * time.Minute
    18  )
    19  
    20  type entry[T any] lang.Pair[T, time.Time]
    21  
    22  func (e *entry[T]) expired(now time.Time, ttl time.Duration) bool {
    23  	return now.After(e.Second.Add(ttl))
    24  }
    25  
    26  type (
    27  	userCache        map[string]*entry[*user.User]
    28  	userFailureCache map[string]*entry[error]
    29  )
    30  
    31  type lookupUserFunc func(string) (*user.User, error)
    32  
    33  type cache struct {
    34  	clock      libtime.Clock
    35  	lookupUser lookupUserFunc
    36  
    37  	lock         sync.Mutex
    38  	users        userCache
    39  	userFailures userFailureCache
    40  }
    41  
    42  func newCache() *cache {
    43  	return &cache{
    44  		clock:        libtime.SystemClock(),
    45  		lookupUser:   internalLookupUser,
    46  		users:        make(userCache),
    47  		userFailures: make(userFailureCache),
    48  	}
    49  }
    50  
    51  func (c *cache) GetUser(username string) (*user.User, error) {
    52  	c.lock.Lock()
    53  	defer c.lock.Unlock()
    54  
    55  	// record this moment as "now" for further cache operations
    56  	now := c.clock.Now()
    57  
    58  	// first check if the user is in the cache and the entry we have
    59  	// is not yet expired
    60  	usr, exists := c.users[username]
    61  	if exists && !usr.expired(now, cacheTTL) {
    62  		return usr.First, nil
    63  	}
    64  
    65  	// next check if there was a recent failure already, so we
    66  	// avoid spamming the OS with dead user lookups
    67  	failure, exists2 := c.userFailures[username]
    68  	if exists2 {
    69  		if !failure.expired(now, failureTTL) {
    70  			return nil, failure.First
    71  		}
    72  		// may as well cleanup expired case
    73  		delete(c.userFailures, username)
    74  	}
    75  
    76  	// need to perform an OS lookup
    77  	u, err := c.lookupUser(username)
    78  
    79  	// lookup was a failure, populate the failure cache
    80  	if err != nil {
    81  		c.userFailures[username] = &entry[error]{err, now}
    82  		return nil, err
    83  	}
    84  
    85  	// lookup was a success, populate the user cache
    86  	c.users[username] = &entry[*user.User]{u, now}
    87  	return u, nil
    88  }