func (s *cachingStore) Groups() ([]*accounts.Group, error) {
	s.RLock()
	defer s.RUnlock()
	ret := make([]*accounts.Group, len(s.groupsByName)+1)
	i := 0
	for _, g := range s.groupsByName {
		ret[i] = g
		i++
	}
	ret[i] = s.sudoersGroup()
	return ret, nil
}
Groups satisfies AccountProvider.
package store
import (
	"sync"
	"time"
	"github.com/GoogleCloudPlatform/compute-user-accounts/accounts"
	"github.com/GoogleCloudPlatform/compute-user-accounts/apiclient"
	"github.com/GoogleCloudPlatform/compute-user-accounts/logger"
	cua "google.golang.org/api/clouduseraccounts/vm_beta"
)
const (
	sudoersGroupName = "gce-sudoers"
	sudoersGroupGID  = 4001
)
var (
	timeNow   = time.Now
	timeAfter = time.After
	refreshCallback = func(*Config) {}
)
type Config struct {
	AccountRefreshFrequency time.Duration
	AccountRefreshCooldown time.Duration
	KeyRefreshFrequency time.Duration
	KeyRefreshCooldown time.Duration
}
type cachedUser struct {
	user           *accounts.User
	keys           []string
	sudoer         bool
	keyRefreshTime time.Time
}
type cachingStore struct {
	sync.RWMutex
	apiClient     apiclient.APIClient
	config        *Config
	updateWaiters chan chan struct{}
	usersByName   map[string]*cachedUser
	usersByUID    map[uint32]*cachedUser
	groupsByName  map[string]*accounts.Group
	groupsByGID   map[uint32]*accounts.Group
}
func New(apiClient apiclient.APIClient, config *Config) accounts.AccountProvider {
	store := &cachingStore{
		apiClient:     apiClient,
		config:        config,
		updateWaiters: make(chan chan struct{}),
	}
	ch := make(chan struct{})
	go updateTask(store)
	store.updateWaiters <- ch
	<-ch
	return store
}
func nowOutsideTimespan(start time.Time, duration time.Duration) bool {
	now := timeNow()
	end := start.Add(duration)
	return now.Before(start) || now.After(end)
}
func updateTask(s *cachingStore) {
	var lastRefresh time.Time
	for {
		var ch chan struct{}
		select {
		case ch = <-s.updateWaiters:
		case <-timeAfter(s.config.AccountRefreshFrequency):
		}
		if nowOutsideTimespan(lastRefresh, s.config.AccountRefreshCooldown) {
			logger.Info("Refreshing users and groups.")
			updateAccounts(s)
			lastRefresh = timeNow()
		}
		go updateKeys(s)
		if ch != nil {
			close(ch)
		}
	}
}
func updateAccounts(s *cachingStore) {
	users, groups, err := s.apiClient.UsersAndGroups()
	if err != nil {
		logger.Errorf("Failed refresh: %v.", err)
		return
	}
	s.Lock()
	defer s.Unlock()
	oldUsers := s.usersByName
	s.usersByName = make(map[string]*cachedUser)
	s.usersByUID = make(map[uint32]*cachedUser)
	s.groupsByName = make(map[string]*accounts.Group)
	s.groupsByGID = make(map[uint32]*accounts.Group)
	for _, u := range users {
		user := &accounts.User{
			Name:          u.Username,
			UID:           uint32(u.Uid),
			GID:           uint32(u.Gid),
			Gecos:         u.Gecos,
			HomeDirectory: u.HomeDirectory,
			Shell:         u.Shell,
		}
		cu := &cachedUser{user: user}
		if old, ok := oldUsers[user.Name]; ok {
			cu.keyRefreshTime = old.keyRefreshTime
			cu.keys = old.keys
			cu.sudoer = old.sudoer
		}
		s.usersByName[user.Name] = cu
		s.usersByUID[user.UID] = cu
	}
	for _, g := range groups {
		group := &accounts.Group{
			Name:    g.GroupName,
			GID:     uint32(g.Gid),
			Members: g.Members,
		}
		s.groupsByName[group.Name] = group
		s.groupsByGID[group.GID] = group
	}
	logger.Info("Refreshing users and groups succeeded.")
}
func updateKeys(s *cachingStore) {
	type update struct {
		name string
		view *cua.AuthorizedKeysView
		err  error
		time time.Time
	}
	ch := make(chan update)
	workers := 0
	for _, name := range keysRequiringRefresh(s) {
		go func(n string) {
			view, err := s.apiClient.AuthorizedKeys(n)
			ch <- update{n, view, err, timeNow()}
		}(name)
		workers += 1
	}
	refreshedKeys := make([]update, workers)
	for workers != 0 {
		up := <-ch
		workers -= 1
		if up.err != nil {
			logger.Errorf("Failed key refresh for %v: %v.", up.name, up.err)
			continue
		}
		logger.Infof("Refreshed keys for %v.", up.name)
		refreshedKeys = append(refreshedKeys, up)
	}
	s.Lock()
	defer s.Unlock()
	for _, rk := range refreshedKeys {
		if cu, ok := s.usersByName[rk.name]; ok {
			cu.keys = rk.view.Keys
			cu.sudoer = rk.view.Sudoer
			cu.keyRefreshTime = rk.time
		}
	}
	refreshCallback(s.config)
}
func keysRequiringRefresh(s *cachingStore) []string {
	var result []string
	s.RLock()
	defer s.RUnlock()
	for name, cu := range s.usersByName {
		if nowOutsideTimespan(cu.keyRefreshTime, s.config.KeyRefreshFrequency) {
			result = append(result, name)
		}
	}
	return result
}
func (s *cachingStore) userByNameImpl(name string) (*cachedUser, bool) {
	s.RLock()
	defer s.RUnlock()
	cu, ok := s.usersByName[name]
	return cu, ok
}
func (s *cachingStore) UserByName(name string) (*accounts.User, error) {
	cu, ok := s.userByNameImpl(name)
	if ok {
		return cu.user, nil
	}
	ch := make(chan struct{})
	logger.Infof("Triggering refresh due to missing user %v.", name)
	s.updateWaiters <- ch
	<-ch
	cu, ok = s.userByNameImpl(name)
	if ok {
		return cu.user, nil
	}
	return nil, accounts.UsernameNotFound(name)
}
func (s *cachingStore) UserByUID(uid uint32) (*accounts.User, error) {
	s.RLock()
	defer s.RUnlock()
	cu, ok := s.usersByUID[uid]
	if ok {
		return cu.user, nil
	}
	return nil, accounts.UIDNotFound(uid)
}
func (s *cachingStore) Users() ([]*accounts.User, error) {
	s.RLock()
	defer s.RUnlock()
	ret := make([]*accounts.User, len(s.usersByName))
	i := 0
	for _, cu := range s.usersByName {
		ret[i] = cu.user
		i++
	}
	return ret, nil
}
func (s *cachingStore) GroupByName(name string) (*accounts.Group, error) {
	s.RLock()
	defer s.RUnlock()
	if name == sudoersGroupName {
		return s.sudoersGroup(), nil
	}
	g, ok := s.groupsByName[name]
	if ok {
		return g, nil
	}
	logger.Info("Triggering refresh due to missing group.")
	go func() { s.updateWaiters <- nil }()
	return nil, accounts.GroupNameNotFound(name)
}
func (s *cachingStore) GroupByGID(gid uint32) (*accounts.Group, error) {
	s.RLock()
	defer s.RUnlock()
	if gid == sudoersGroupGID {
		return s.sudoersGroup(), nil
	}
	g, ok := s.groupsByGID[gid]
	if ok {
		return g, nil
	}
	return nil, accounts.GIDNotFound(gid)
}