-
Notifications
You must be signed in to change notification settings - Fork 8
/
verifier.go
179 lines (154 loc) · 6.1 KB
/
verifier.go
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package verifier
import (
"context"
"sync"
"github.com/rotisserie/eris"
"github.com/solo-io/go-utils/contextutils"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/rest"
)
// ServerVerifyOption is used to specify one of several options on
// whether and how to verify the resource's availabiltiy on the API server
type ServerVerifyOption int
const (
// skip verifying whether a resource is for a kind before reading it from the API server
ServerVerifyOption_SkipVerify ServerVerifyOption = iota
// return an error if the resource does not exist for a kind before reading it from the API server
ServerVerifyOption_ErrorIfNotPresent
// log a warning (and continue) if the resource does not exist for a kind before reading it from the API server
// the reconcile loop will not be started if the server resource is not supported.
ServerVerifyOption_WarnIfNotPresent
// write a debug log (and continue) if the resource does not exist for a kind before reading it from the API server
// the reconcile loop will not be started if the server resource is not supported.
ServerVerifyOption_LogDebugIfNotPresent
// ignore error (and continue) if the resource does not exist for a kind before reading it from the API server.
// the reconcile loop will not be started if the server resource is not supported.
ServerVerifyOption_IgnoreIfNotPresent
)
// ServerResourceVerifier verifies whether a given cluster server supports a given resource.
type ServerResourceVerifier interface {
// VerifyServerResource verifies whether the API server for the given rest.Config supports the resource with the given GVK.
// For the "local/management" cluster, set cluster to ""
// Note that once a resource has been verified, the result will be cached for subsequent calls.
// Returns true if the resource is registered, false otherwise.
// an error will be returned if the ErrorIfNotPresent option is selected for the given GVK
VerifyServerResource(cluster string, cfg *rest.Config, gvk schema.GroupVersionKind) (bool, error)
}
type verifier struct {
// used for logging
ctx context.Context
// set of per-resource-type verify options; default is Skip.
options map[schema.GroupVersionKind]ServerVerifyOption
optionsLock sync.RWMutex
// set when a resource is successfully verified for the first time.
// future calls to VerifyServerResource will return quickly if the
// resources have already been verified for the cluster.
cachedVerificationResponses *cachedVerificationResponses
}
type cachedVerificationResponses struct {
verifiedClusterResources map[string]map[schema.GroupVersionKind]bool
lock sync.RWMutex
}
func newCachedVerificationResponses() *cachedVerificationResponses {
return &cachedVerificationResponses{verifiedClusterResources: map[string]map[schema.GroupVersionKind]bool{}}
}
// returns <is response cached>, <is resource supported>
func (c *cachedVerificationResponses) getCachedResponse(cluster string, gvk schema.GroupVersionKind) (bool, bool) {
c.lock.RLock()
defer c.lock.RUnlock()
verifiedResources, ok := c.verifiedClusterResources[cluster]
if !ok {
return false, false
}
resourceRegistered, ok := verifiedResources[gvk]
if !ok {
return false, false
}
return true, resourceRegistered
}
// returns <is response cached>, <is resource supported>
func (c *cachedVerificationResponses) setCachedResponse(cluster string, gvk schema.GroupVersionKind, registered bool) {
c.lock.Lock()
defer c.lock.Unlock()
verifiedResources, ok := c.verifiedClusterResources[cluster]
if !ok {
verifiedResources = map[schema.GroupVersionKind]bool{}
}
verifiedResources[gvk] = registered
}
func NewVerifier(
ctx context.Context,
options map[schema.GroupVersionKind]ServerVerifyOption,
) *verifier {
if options == nil {
options = map[schema.GroupVersionKind]ServerVerifyOption{}
}
return &verifier{
ctx: ctx,
options: options,
cachedVerificationResponses: newCachedVerificationResponses(),
}
}
// Verify whether the API server for the given rest.Config supports the resource with the given GVK.
func (v *verifier) VerifyServerResource(cluster string, cfg *rest.Config, gvk schema.GroupVersionKind) (bool, error) {
responseCached, resourceVerified := v.cachedVerificationResponses.getCachedResponse(cluster, gvk)
if responseCached {
return resourceVerified, nil
}
// get option for this gvk
// defaults to skip
v.optionsLock.RLock()
verifyOption := v.options[gvk]
v.optionsLock.RUnlock()
if verifyOption == ServerVerifyOption_SkipVerify {
return true, nil
}
var resourceRegistered bool
if verifyFailed, err := verifyServerResource(cfg, gvk); err != nil {
if verifyFailed {
return false, eris.Wrap(err, "resource verify failed")
}
switch verifyOption {
case ServerVerifyOption_ErrorIfNotPresent:
return false, err
case ServerVerifyOption_WarnIfNotPresent:
contextutils.LoggerFrom(v.ctx).Warnf("%v not registered (fetch err: %v)", gvk, err)
resourceRegistered = false
case ServerVerifyOption_LogDebugIfNotPresent:
contextutils.LoggerFrom(v.ctx).Debugf("%v not registered (fetch err: %v)", gvk, err)
resourceRegistered = false
case ServerVerifyOption_IgnoreIfNotPresent:
resourceRegistered = false
}
} else {
resourceRegistered = true
}
// update cache
v.cachedVerificationResponses.setCachedResponse(cluster, gvk, resourceRegistered)
return resourceRegistered, nil
}
// The boolean return return argument indicates whether a resulting error was caused by
// a failure to run the verification verify.
func verifyServerResource(
cfg *rest.Config,
gvk schema.GroupVersionKind,
) (bool, error) {
disc, err := discovery.NewDiscoveryClientForConfig(cfg)
if err != nil {
return true, err
}
rss, err := disc.ServerResourcesForGroupVersion(gvk.Group + "/" + gvk.Version)
if err != nil {
verifyFailed := !apierrors.IsNotFound(err)
return verifyFailed, err
}
for _, resource := range rss.APIResources {
if resource.Kind == gvk.Kind {
// success
return false, nil
}
}
return false, eris.Errorf("resource %v not supported by API Server", gvk.String())
}