aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/google.golang.org/grpc/credentials/tls/certprovider/store.go
blob: 90f98b3c9e71306b1865a44bd894ab20ceb3ccb9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/*
 *
 * Copyright 2020 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package certprovider

import (
	"fmt"
	"sync"
)

// provStore is the global singleton certificate provider store.
var provStore = &store{
	providers: make(map[storeKey]*wrappedProvider),
}

// storeKey acts as the key to the map of providers maintained by the store. A
// combination of provider name and configuration is used to uniquely identify
// every provider instance in the store. Go maps need to be indexed by
// comparable types, so the provider configuration is converted from
// `interface{}` to string using the ParseConfig method while creating this key.
type storeKey struct {
	// name of the certificate provider.
	name string
	// configuration of the certificate provider in string form.
	config string
	// opts contains the certificate name and other keyMaterial options.
	opts BuildOptions
}

// wrappedProvider wraps a provider instance with a reference count.
type wrappedProvider struct {
	Provider
	refCount int

	// A reference to the key and store are also kept here to override the
	// Close method on the provider.
	storeKey storeKey
	store    *store
}

// store is a collection of provider instances, safe for concurrent access.
type store struct {
	mu        sync.Mutex
	providers map[storeKey]*wrappedProvider
}

// Close overrides the Close method of the embedded provider. It releases the
// reference held by the caller on the underlying provider and if the
// provider's reference count reaches zero, it is removed from the store, and
// its Close method is also invoked.
func (wp *wrappedProvider) Close() {
	ps := wp.store
	ps.mu.Lock()
	defer ps.mu.Unlock()

	wp.refCount--
	if wp.refCount == 0 {
		wp.Provider.Close()
		delete(ps.providers, wp.storeKey)
	}
}

// BuildableConfig wraps parsed provider configuration and functionality to
// instantiate provider instances.
type BuildableConfig struct {
	name    string
	config  []byte
	starter func(BuildOptions) Provider
	pStore  *store
}

// NewBuildableConfig creates a new BuildableConfig with the given arguments.
// Provider implementations are expected to invoke this function after parsing
// the given configuration as part of their ParseConfig() method.
// Equivalent configurations are expected to invoke this function with the same
// config argument.
func NewBuildableConfig(name string, config []byte, starter func(BuildOptions) Provider) *BuildableConfig {
	return &BuildableConfig{
		name:    name,
		config:  config,
		starter: starter,
		pStore:  provStore,
	}
}

// Build kicks off a provider instance with the wrapped configuration. Multiple
// invocations of this method with the same opts will result in provider
// instances being reused.
func (bc *BuildableConfig) Build(opts BuildOptions) (Provider, error) {
	provStore.mu.Lock()
	defer provStore.mu.Unlock()

	sk := storeKey{
		name:   bc.name,
		config: string(bc.config),
		opts:   opts,
	}
	if wp, ok := provStore.providers[sk]; ok {
		wp.refCount++
		return wp, nil
	}

	provider := bc.starter(opts)
	if provider == nil {
		return nil, fmt.Errorf("provider(%q, %q).Build(%v) failed", sk.name, sk.config, opts)
	}
	wp := &wrappedProvider{
		Provider: provider,
		refCount: 1,
		storeKey: sk,
		store:    provStore,
	}
	provStore.providers[sk] = wp
	return wp, nil
}

// String returns the provider name and config as a colon separated string.
func (bc *BuildableConfig) String() string {
	return fmt.Sprintf("%s:%s", bc.name, string(bc.config))
}

// ParseConfig is a convenience function to create a BuildableConfig given a
// provider name and configuration. Returns an error if there is no registered
// builder for the given name or if the config parsing fails.
func ParseConfig(name string, config interface{}) (*BuildableConfig, error) {
	parser := getBuilder(name)
	if parser == nil {
		return nil, fmt.Errorf("no certificate provider builder found for %q", name)
	}
	return parser.ParseConfig(config)
}

// GetProvider is a convenience function to create a provider given the name,
// config and build options.
func GetProvider(name string, config interface{}, opts BuildOptions) (Provider, error) {
	bc, err := ParseConfig(name, config)
	if err != nil {
		return nil, err
	}
	return bc.Build(opts)
}