1package creds
2
3import (
4	"sync"
5	"time"
6
7	"encoding/json"
8
9	"code.cloudfoundry.org/clock"
10	"code.cloudfoundry.org/lager"
11)
12
13//go:generate counterfeiter . VarSourcePool
14
15type VarSourcePool interface {
16	FindOrCreate(lager.Logger, map[string]interface{}, ManagerFactory) (Secrets, error)
17	Size() int
18	Close()
19}
20
21type inPoolManager struct {
22	manager     Manager
23	secrets     Secrets
24	lastUseTime time.Time
25	clock       clock.Clock
26}
27
28func (m *inPoolManager) close(logger lager.Logger) {
29	m.manager.Close(logger)
30}
31
32func (m *inPoolManager) getSecrets() Secrets {
33	m.lastUseTime = m.clock.Now()
34	return m.secrets
35}
36
37type varSourcePool struct {
38	pool                 map[string]*inPoolManager
39	lock                 sync.Mutex
40	credentialManagement CredentialManagementConfig
41	ttl                  time.Duration
42	clock                clock.Clock
43
44	closeOnce sync.Once
45	closed    chan struct{}
46}
47
48func NewVarSourcePool(
49	logger lager.Logger,
50	credentialManagement CredentialManagementConfig,
51	ttl time.Duration,
52	collectInterval time.Duration,
53	clock clock.Clock,
54) VarSourcePool {
55	pool := &varSourcePool{
56		pool: map[string]*inPoolManager{},
57		lock: sync.Mutex{},
58
59		credentialManagement: credentialManagement,
60		ttl:                  ttl,
61		clock:                clock,
62
63		closeOnce: sync.Once{},
64		closed:    make(chan struct{}),
65	}
66
67	go pool.collectLoop(
68		logger.Session("collect"),
69		collectInterval,
70	)
71
72	return pool
73}
74
75func (pool *varSourcePool) Size() int {
76	pool.lock.Lock()
77	defer pool.lock.Unlock()
78	return len(pool.pool)
79}
80
81func (pool *varSourcePool) FindOrCreate(logger lager.Logger, config map[string]interface{}, factory ManagerFactory) (Secrets, error) {
82	b, err := json.Marshal(config)
83	if err != nil {
84		return nil, err
85	}
86
87	key := string(b)
88
89	pool.lock.Lock()
90	defer pool.lock.Unlock()
91
92	if _, ok := pool.pool[key]; !ok {
93		manager, err := factory.NewInstance(config)
94		if err != nil {
95			return nil, err
96		}
97		err = manager.Init(logger)
98		if err != nil {
99			return nil, err
100		}
101		secretsFactory, err := manager.NewSecretsFactory(logger)
102		if err != nil {
103			return nil, err
104		}
105
106		pool.pool[key] = &inPoolManager{
107			clock:   pool.clock,
108			manager: manager,
109			secrets: pool.credentialManagement.NewSecrets(secretsFactory),
110		}
111	} else {
112		logger.Debug("found-existing-credential-manager")
113	}
114
115	return pool.pool[key].getSecrets(), nil
116}
117
118func (pool *varSourcePool) Close() {
119	pool.closeOnce.Do(func() {
120		close(pool.closed)
121	})
122}
123
124func (pool *varSourcePool) collectLoop(logger lager.Logger, interval time.Duration) {
125	ticker := pool.clock.NewTicker(interval)
126	defer ticker.Stop()
127
128	for {
129		select {
130		case <-pool.closed:
131			pool.collect(logger.Session("close"), true)
132			return
133		case <-ticker.C():
134			pool.collect(logger.Session("tick"), false)
135		}
136	}
137}
138
139func (pool *varSourcePool) collect(logger lager.Logger, all bool) error {
140	pool.lock.Lock()
141	defer pool.lock.Unlock()
142
143	logger.Debug("before", lager.Data{"size": len(pool.pool)})
144
145	toDeleteKeys := []string{}
146	for key, manager := range pool.pool {
147		if all || manager.lastUseTime.Add(pool.ttl).Before(pool.clock.Now()) {
148			toDeleteKeys = append(toDeleteKeys, key)
149			manager.close(logger)
150		}
151	}
152
153	for _, key := range toDeleteKeys {
154		delete(pool.pool, key)
155	}
156
157	logger.Debug("after", lager.Data{"size": len(pool.pool)})
158
159	return nil
160}
161