...

Source file src/k8s.io/client-go/restmapper/discovery.go

Documentation: k8s.io/client-go/restmapper

     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/v2"
    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  	gs, rs, err := cl.ServerGroupsAndResources()
   149  	if rs == nil || gs == nil {
   150  		return nil, err
   151  		// TODO track the errors and update callers to handle partial errors.
   152  	}
   153  	rsm := map[string]*metav1.APIResourceList{}
   154  	for _, r := range rs {
   155  		rsm[r.GroupVersion] = r
   156  	}
   157  
   158  	var result []*APIGroupResources
   159  	for _, group := range gs {
   160  		groupResources := &APIGroupResources{
   161  			Group:              *group,
   162  			VersionedResources: make(map[string][]metav1.APIResource),
   163  		}
   164  		for _, version := range group.Versions {
   165  			resources, ok := rsm[version.GroupVersion]
   166  			if !ok {
   167  				continue
   168  			}
   169  			groupResources.VersionedResources[version.Version] = resources.APIResources
   170  		}
   171  		result = append(result, groupResources)
   172  	}
   173  	return result, nil
   174  }
   175  
   176  // DeferredDiscoveryRESTMapper is a RESTMapper that will defer
   177  // initialization of the RESTMapper until the first mapping is
   178  // requested.
   179  type DeferredDiscoveryRESTMapper struct {
   180  	initMu   sync.Mutex
   181  	delegate meta.RESTMapper
   182  	cl       discovery.CachedDiscoveryInterface
   183  }
   184  
   185  // NewDeferredDiscoveryRESTMapper returns a
   186  // DeferredDiscoveryRESTMapper that will lazily query the provided
   187  // client for discovery information to do REST mappings.
   188  func NewDeferredDiscoveryRESTMapper(cl discovery.CachedDiscoveryInterface) *DeferredDiscoveryRESTMapper {
   189  	return &DeferredDiscoveryRESTMapper{
   190  		cl: cl,
   191  	}
   192  }
   193  
   194  func (d *DeferredDiscoveryRESTMapper) getDelegate() (meta.RESTMapper, error) {
   195  	d.initMu.Lock()
   196  	defer d.initMu.Unlock()
   197  
   198  	if d.delegate != nil {
   199  		return d.delegate, nil
   200  	}
   201  
   202  	groupResources, err := GetAPIGroupResources(d.cl)
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  
   207  	d.delegate = NewDiscoveryRESTMapper(groupResources)
   208  	return d.delegate, nil
   209  }
   210  
   211  // Reset resets the internally cached Discovery information and will
   212  // cause the next mapping request to re-discover.
   213  func (d *DeferredDiscoveryRESTMapper) Reset() {
   214  	klog.V(5).Info("Invalidating discovery information")
   215  
   216  	d.initMu.Lock()
   217  	defer d.initMu.Unlock()
   218  
   219  	d.cl.Invalidate()
   220  	d.delegate = nil
   221  }
   222  
   223  // KindFor takes a partial resource and returns back the single match.
   224  // It returns an error if there are multiple matches.
   225  func (d *DeferredDiscoveryRESTMapper) KindFor(resource schema.GroupVersionResource) (gvk schema.GroupVersionKind, err error) {
   226  	del, err := d.getDelegate()
   227  	if err != nil {
   228  		return schema.GroupVersionKind{}, err
   229  	}
   230  	gvk, err = del.KindFor(resource)
   231  	if err != nil && !d.cl.Fresh() {
   232  		d.Reset()
   233  		gvk, err = d.KindFor(resource)
   234  	}
   235  	return
   236  }
   237  
   238  // KindsFor takes a partial resource and returns back the list of
   239  // potential kinds in priority order.
   240  func (d *DeferredDiscoveryRESTMapper) KindsFor(resource schema.GroupVersionResource) (gvks []schema.GroupVersionKind, err error) {
   241  	del, err := d.getDelegate()
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  	gvks, err = del.KindsFor(resource)
   246  	if len(gvks) == 0 && !d.cl.Fresh() {
   247  		d.Reset()
   248  		gvks, err = d.KindsFor(resource)
   249  	}
   250  	return
   251  }
   252  
   253  // ResourceFor takes a partial resource and returns back the single
   254  // match. It returns an error if there are multiple matches.
   255  func (d *DeferredDiscoveryRESTMapper) ResourceFor(input schema.GroupVersionResource) (gvr schema.GroupVersionResource, err error) {
   256  	del, err := d.getDelegate()
   257  	if err != nil {
   258  		return schema.GroupVersionResource{}, err
   259  	}
   260  	gvr, err = del.ResourceFor(input)
   261  	if err != nil && !d.cl.Fresh() {
   262  		d.Reset()
   263  		gvr, err = d.ResourceFor(input)
   264  	}
   265  	return
   266  }
   267  
   268  // ResourcesFor takes a partial resource and returns back the list of
   269  // potential resource in priority order.
   270  func (d *DeferredDiscoveryRESTMapper) ResourcesFor(input schema.GroupVersionResource) (gvrs []schema.GroupVersionResource, err error) {
   271  	del, err := d.getDelegate()
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  	gvrs, err = del.ResourcesFor(input)
   276  	if len(gvrs) == 0 && !d.cl.Fresh() {
   277  		d.Reset()
   278  		gvrs, err = d.ResourcesFor(input)
   279  	}
   280  	return
   281  }
   282  
   283  // RESTMapping identifies a preferred resource mapping for the
   284  // provided group kind.
   285  func (d *DeferredDiscoveryRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (m *meta.RESTMapping, err error) {
   286  	del, err := d.getDelegate()
   287  	if err != nil {
   288  		return nil, err
   289  	}
   290  	m, err = del.RESTMapping(gk, versions...)
   291  	if err != nil && !d.cl.Fresh() {
   292  		d.Reset()
   293  		m, err = d.RESTMapping(gk, versions...)
   294  	}
   295  	return
   296  }
   297  
   298  // RESTMappings returns the RESTMappings for the provided group kind
   299  // in a rough internal preferred order. If no kind is found, it will
   300  // return a NoResourceMatchError.
   301  func (d *DeferredDiscoveryRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) (ms []*meta.RESTMapping, err error) {
   302  	del, err := d.getDelegate()
   303  	if err != nil {
   304  		return nil, err
   305  	}
   306  	ms, err = del.RESTMappings(gk, versions...)
   307  	if len(ms) == 0 && !d.cl.Fresh() {
   308  		d.Reset()
   309  		ms, err = d.RESTMappings(gk, versions...)
   310  	}
   311  	return
   312  }
   313  
   314  // ResourceSingularizer converts a resource name from plural to
   315  // singular (e.g., from pods to pod).
   316  func (d *DeferredDiscoveryRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
   317  	del, err := d.getDelegate()
   318  	if err != nil {
   319  		return resource, err
   320  	}
   321  	singular, err = del.ResourceSingularizer(resource)
   322  	if err != nil && !d.cl.Fresh() {
   323  		d.Reset()
   324  		singular, err = d.ResourceSingularizer(resource)
   325  	}
   326  	return
   327  }
   328  
   329  func (d *DeferredDiscoveryRESTMapper) String() string {
   330  	del, err := d.getDelegate()
   331  	if err != nil {
   332  		return fmt.Sprintf("DeferredDiscoveryRESTMapper{%v}", err)
   333  	}
   334  	return fmt.Sprintf("DeferredDiscoveryRESTMapper{\n\t%v\n}", del)
   335  }
   336  
   337  // Make sure it satisfies the interface
   338  var _ meta.ResettableRESTMapper = &DeferredDiscoveryRESTMapper{}
   339  

View as plain text