Code refactoring for bpa operator
[icn.git] / cmd / bpa-operator / vendor / k8s.io / client-go / restmapper / discovery.go
1 /*
2 Copyright 2016 The Kubernetes Authors.
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8     http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16
17 package restmapper
18
19 import (
20         "fmt"
21         "strings"
22         "sync"
23
24         "k8s.io/apimachinery/pkg/api/meta"
25         metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26         "k8s.io/apimachinery/pkg/runtime/schema"
27         "k8s.io/client-go/discovery"
28
29         "k8s.io/klog"
30 )
31
32 // APIGroupResources is an API group with a mapping of versions to
33 // resources.
34 type APIGroupResources struct {
35         Group metav1.APIGroup
36         // A mapping of version string to a slice of APIResources for
37         // that version.
38         VersionedResources map[string][]metav1.APIResource
39 }
40
41 // NewDiscoveryRESTMapper returns a PriorityRESTMapper based on the discovered
42 // groups and resources passed in.
43 func NewDiscoveryRESTMapper(groupResources []*APIGroupResources) meta.RESTMapper {
44         unionMapper := meta.MultiRESTMapper{}
45
46         var groupPriority []string
47         // /v1 is special.  It should always come first
48         resourcePriority := []schema.GroupVersionResource{{Group: "", Version: "v1", Resource: meta.AnyResource}}
49         kindPriority := []schema.GroupVersionKind{{Group: "", Version: "v1", Kind: meta.AnyKind}}
50
51         for _, group := range groupResources {
52                 groupPriority = append(groupPriority, group.Group.Name)
53
54                 // Make sure the preferred version comes first
55                 if len(group.Group.PreferredVersion.Version) != 0 {
56                         preferred := group.Group.PreferredVersion.Version
57                         if _, ok := group.VersionedResources[preferred]; ok {
58                                 resourcePriority = append(resourcePriority, schema.GroupVersionResource{
59                                         Group:    group.Group.Name,
60                                         Version:  group.Group.PreferredVersion.Version,
61                                         Resource: meta.AnyResource,
62                                 })
63
64                                 kindPriority = append(kindPriority, schema.GroupVersionKind{
65                                         Group:   group.Group.Name,
66                                         Version: group.Group.PreferredVersion.Version,
67                                         Kind:    meta.AnyKind,
68                                 })
69                         }
70                 }
71
72                 for _, discoveryVersion := range group.Group.Versions {
73                         resources, ok := group.VersionedResources[discoveryVersion.Version]
74                         if !ok {
75                                 continue
76                         }
77
78                         // Add non-preferred versions after the preferred version, in case there are resources that only exist in those versions
79                         if discoveryVersion.Version != group.Group.PreferredVersion.Version {
80                                 resourcePriority = append(resourcePriority, schema.GroupVersionResource{
81                                         Group:    group.Group.Name,
82                                         Version:  discoveryVersion.Version,
83                                         Resource: meta.AnyResource,
84                                 })
85
86                                 kindPriority = append(kindPriority, schema.GroupVersionKind{
87                                         Group:   group.Group.Name,
88                                         Version: discoveryVersion.Version,
89                                         Kind:    meta.AnyKind,
90                                 })
91                         }
92
93                         gv := schema.GroupVersion{Group: group.Group.Name, Version: discoveryVersion.Version}
94                         versionMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{gv})
95
96                         for _, resource := range resources {
97                                 scope := meta.RESTScopeNamespace
98                                 if !resource.Namespaced {
99                                         scope = meta.RESTScopeRoot
100                                 }
101
102                                 // if we have a slash, then this is a subresource and we shouldn't create mappings for those.
103                                 if strings.Contains(resource.Name, "/") {
104                                         continue
105                                 }
106
107                                 plural := gv.WithResource(resource.Name)
108                                 singular := gv.WithResource(resource.SingularName)
109                                 // this is for legacy resources and servers which don't list singular forms.  For those we must still guess.
110                                 if len(resource.SingularName) == 0 {
111                                         _, singular = meta.UnsafeGuessKindToResource(gv.WithKind(resource.Kind))
112                                 }
113
114                                 versionMapper.AddSpecific(gv.WithKind(strings.ToLower(resource.Kind)), plural, singular, scope)
115                                 versionMapper.AddSpecific(gv.WithKind(resource.Kind), plural, singular, scope)
116                                 // TODO this is producing unsafe guesses that don't actually work, but it matches previous behavior
117                                 versionMapper.Add(gv.WithKind(resource.Kind+"List"), scope)
118                         }
119                         // TODO why is this type not in discovery (at least for "v1")
120                         versionMapper.Add(gv.WithKind("List"), meta.RESTScopeRoot)
121                         unionMapper = append(unionMapper, versionMapper)
122                 }
123         }
124
125         for _, group := range groupPriority {
126                 resourcePriority = append(resourcePriority, schema.GroupVersionResource{
127                         Group:    group,
128                         Version:  meta.AnyVersion,
129                         Resource: meta.AnyResource,
130                 })
131                 kindPriority = append(kindPriority, schema.GroupVersionKind{
132                         Group:   group,
133                         Version: meta.AnyVersion,
134                         Kind:    meta.AnyKind,
135                 })
136         }
137
138         return meta.PriorityRESTMapper{
139                 Delegate:         unionMapper,
140                 ResourcePriority: resourcePriority,
141                 KindPriority:     kindPriority,
142         }
143 }
144
145 // GetAPIGroupResources uses the provided discovery client to gather
146 // discovery information and populate a slice of APIGroupResources.
147 func GetAPIGroupResources(cl discovery.DiscoveryInterface) ([]*APIGroupResources, error) {
148         apiGroups, err := cl.ServerGroups()
149         if err != nil {
150                 if apiGroups == nil || len(apiGroups.Groups) == 0 {
151                         return nil, err
152                 }
153                 // TODO track the errors and update callers to handle partial errors.
154         }
155         var result []*APIGroupResources
156         for _, group := range apiGroups.Groups {
157                 groupResources := &APIGroupResources{
158                         Group:              group,
159                         VersionedResources: make(map[string][]metav1.APIResource),
160                 }
161                 for _, version := range group.Versions {
162                         resources, err := cl.ServerResourcesForGroupVersion(version.GroupVersion)
163                         if err != nil {
164                                 // continue as best we can
165                                 // TODO track the errors and update callers to handle partial errors.
166                                 if resources == nil || len(resources.APIResources) == 0 {
167                                         continue
168                                 }
169                         }
170                         groupResources.VersionedResources[version.Version] = resources.APIResources
171                 }
172                 result = append(result, groupResources)
173         }
174         return result, nil
175 }
176
177 // DeferredDiscoveryRESTMapper is a RESTMapper that will defer
178 // initialization of the RESTMapper until the first mapping is
179 // requested.
180 type DeferredDiscoveryRESTMapper struct {
181         initMu   sync.Mutex
182         delegate meta.RESTMapper
183         cl       discovery.CachedDiscoveryInterface
184 }
185
186 // NewDeferredDiscoveryRESTMapper returns a
187 // DeferredDiscoveryRESTMapper that will lazily query the provided
188 // client for discovery information to do REST mappings.
189 func NewDeferredDiscoveryRESTMapper(cl discovery.CachedDiscoveryInterface) *DeferredDiscoveryRESTMapper {
190         return &DeferredDiscoveryRESTMapper{
191                 cl: cl,
192         }
193 }
194
195 func (d *DeferredDiscoveryRESTMapper) getDelegate() (meta.RESTMapper, error) {
196         d.initMu.Lock()
197         defer d.initMu.Unlock()
198
199         if d.delegate != nil {
200                 return d.delegate, nil
201         }
202
203         groupResources, err := GetAPIGroupResources(d.cl)
204         if err != nil {
205                 return nil, err
206         }
207
208         d.delegate = NewDiscoveryRESTMapper(groupResources)
209         return d.delegate, err
210 }
211
212 // Reset resets the internally cached Discovery information and will
213 // cause the next mapping request to re-discover.
214 func (d *DeferredDiscoveryRESTMapper) Reset() {
215         klog.V(5).Info("Invalidating discovery information")
216
217         d.initMu.Lock()
218         defer d.initMu.Unlock()
219
220         d.cl.Invalidate()
221         d.delegate = nil
222 }
223
224 // KindFor takes a partial resource and returns back the single match.
225 // It returns an error if there are multiple matches.
226 func (d *DeferredDiscoveryRESTMapper) KindFor(resource schema.GroupVersionResource) (gvk schema.GroupVersionKind, err error) {
227         del, err := d.getDelegate()
228         if err != nil {
229                 return schema.GroupVersionKind{}, err
230         }
231         gvk, err = del.KindFor(resource)
232         if err != nil && !d.cl.Fresh() {
233                 d.Reset()
234                 gvk, err = d.KindFor(resource)
235         }
236         return
237 }
238
239 // KindsFor takes a partial resource and returns back the list of
240 // potential kinds in priority order.
241 func (d *DeferredDiscoveryRESTMapper) KindsFor(resource schema.GroupVersionResource) (gvks []schema.GroupVersionKind, err error) {
242         del, err := d.getDelegate()
243         if err != nil {
244                 return nil, err
245         }
246         gvks, err = del.KindsFor(resource)
247         if len(gvks) == 0 && !d.cl.Fresh() {
248                 d.Reset()
249                 gvks, err = d.KindsFor(resource)
250         }
251         return
252 }
253
254 // ResourceFor takes a partial resource and returns back the single
255 // match. It returns an error if there are multiple matches.
256 func (d *DeferredDiscoveryRESTMapper) ResourceFor(input schema.GroupVersionResource) (gvr schema.GroupVersionResource, err error) {
257         del, err := d.getDelegate()
258         if err != nil {
259                 return schema.GroupVersionResource{}, err
260         }
261         gvr, err = del.ResourceFor(input)
262         if err != nil && !d.cl.Fresh() {
263                 d.Reset()
264                 gvr, err = d.ResourceFor(input)
265         }
266         return
267 }
268
269 // ResourcesFor takes a partial resource and returns back the list of
270 // potential resource in priority order.
271 func (d *DeferredDiscoveryRESTMapper) ResourcesFor(input schema.GroupVersionResource) (gvrs []schema.GroupVersionResource, err error) {
272         del, err := d.getDelegate()
273         if err != nil {
274                 return nil, err
275         }
276         gvrs, err = del.ResourcesFor(input)
277         if len(gvrs) == 0 && !d.cl.Fresh() {
278                 d.Reset()
279                 gvrs, err = d.ResourcesFor(input)
280         }
281         return
282 }
283
284 // RESTMapping identifies a preferred resource mapping for the
285 // provided group kind.
286 func (d *DeferredDiscoveryRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (m *meta.RESTMapping, err error) {
287         del, err := d.getDelegate()
288         if err != nil {
289                 return nil, err
290         }
291         m, err = del.RESTMapping(gk, versions...)
292         if err != nil && !d.cl.Fresh() {
293                 d.Reset()
294                 m, err = d.RESTMapping(gk, versions...)
295         }
296         return
297 }
298
299 // RESTMappings returns the RESTMappings for the provided group kind
300 // in a rough internal preferred order. If no kind is found, it will
301 // return a NoResourceMatchError.
302 func (d *DeferredDiscoveryRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) (ms []*meta.RESTMapping, err error) {
303         del, err := d.getDelegate()
304         if err != nil {
305                 return nil, err
306         }
307         ms, err = del.RESTMappings(gk, versions...)
308         if len(ms) == 0 && !d.cl.Fresh() {
309                 d.Reset()
310                 ms, err = d.RESTMappings(gk, versions...)
311         }
312         return
313 }
314
315 // ResourceSingularizer converts a resource name from plural to
316 // singular (e.g., from pods to pod).
317 func (d *DeferredDiscoveryRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
318         del, err := d.getDelegate()
319         if err != nil {
320                 return resource, err
321         }
322         singular, err = del.ResourceSingularizer(resource)
323         if err != nil && !d.cl.Fresh() {
324                 d.Reset()
325                 singular, err = d.ResourceSingularizer(resource)
326         }
327         return
328 }
329
330 func (d *DeferredDiscoveryRESTMapper) String() string {
331         del, err := d.getDelegate()
332         if err != nil {
333                 return fmt.Sprintf("DeferredDiscoveryRESTMapper{%v}", err)
334         }
335         return fmt.Sprintf("DeferredDiscoveryRESTMapper{\n\t%v\n}", del)
336 }
337
338 // Make sure it satisfies the interface
339 var _ meta.RESTMapper = &DeferredDiscoveryRESTMapper{}