github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/api/meta/restmapper.go (about)

     1  /*
     2  Copyright 2014 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  // TODO: move everything in this file to pkg/api/rest
    18  package meta
    19  
    20  import (
    21  	"fmt"
    22  	"sort"
    23  	"strings"
    24  
    25  	"github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/runtime"
    26  	"github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/runtime/schema"
    27  )
    28  
    29  // Implements RESTScope interface
    30  type restScope struct {
    31  	name RESTScopeName
    32  }
    33  
    34  func (r *restScope) Name() RESTScopeName {
    35  	return r.name
    36  }
    37  
    38  var RESTScopeNamespace = &restScope{
    39  	name: RESTScopeNameNamespace,
    40  }
    41  
    42  var RESTScopeRoot = &restScope{
    43  	name: RESTScopeNameRoot,
    44  }
    45  
    46  // DefaultRESTMapper exposes mappings between the types defined in a
    47  // runtime.Scheme. It assumes that all types defined the provided scheme
    48  // can be mapped with the provided MetadataAccessor and Codec interfaces.
    49  //
    50  // The resource name of a Kind is defined as the lowercase,
    51  // English-plural version of the Kind string.
    52  // When converting from resource to Kind, the singular version of the
    53  // resource name is also accepted for convenience.
    54  //
    55  // TODO: Only accept plural for some operations for increased control?
    56  // (`get pod bar` vs `get pods bar`)
    57  type DefaultRESTMapper struct {
    58  	defaultGroupVersions []schema.GroupVersion
    59  
    60  	resourceToKind       map[schema.GroupVersionResource]schema.GroupVersionKind
    61  	kindToPluralResource map[schema.GroupVersionKind]schema.GroupVersionResource
    62  	kindToScope          map[schema.GroupVersionKind]RESTScope
    63  	singularToPlural     map[schema.GroupVersionResource]schema.GroupVersionResource
    64  	pluralToSingular     map[schema.GroupVersionResource]schema.GroupVersionResource
    65  }
    66  
    67  func (m *DefaultRESTMapper) String() string {
    68  	if m == nil {
    69  		return "<nil>"
    70  	}
    71  	return fmt.Sprintf("DefaultRESTMapper{kindToPluralResource=%v}", m.kindToPluralResource)
    72  }
    73  
    74  var _ RESTMapper = &DefaultRESTMapper{}
    75  
    76  // NewDefaultRESTMapper initializes a mapping between Kind and APIVersion
    77  // to a resource name and back based on the objects in a runtime.Scheme
    78  // and the Kubernetes API conventions. Takes a group name, a priority list of the versions
    79  // to search when an object has no default version (set empty to return an error),
    80  // and a function that retrieves the correct metadata for a given version.
    81  func NewDefaultRESTMapper(defaultGroupVersions []schema.GroupVersion) *DefaultRESTMapper {
    82  	resourceToKind := make(map[schema.GroupVersionResource]schema.GroupVersionKind)
    83  	kindToPluralResource := make(map[schema.GroupVersionKind]schema.GroupVersionResource)
    84  	kindToScope := make(map[schema.GroupVersionKind]RESTScope)
    85  	singularToPlural := make(map[schema.GroupVersionResource]schema.GroupVersionResource)
    86  	pluralToSingular := make(map[schema.GroupVersionResource]schema.GroupVersionResource)
    87  	// TODO: verify name mappings work correctly when versions differ
    88  
    89  	return &DefaultRESTMapper{
    90  		resourceToKind:       resourceToKind,
    91  		kindToPluralResource: kindToPluralResource,
    92  		kindToScope:          kindToScope,
    93  		defaultGroupVersions: defaultGroupVersions,
    94  		singularToPlural:     singularToPlural,
    95  		pluralToSingular:     pluralToSingular,
    96  	}
    97  }
    98  
    99  func (m *DefaultRESTMapper) Add(kind schema.GroupVersionKind, scope RESTScope) {
   100  	plural, singular := UnsafeGuessKindToResource(kind)
   101  	m.AddSpecific(kind, plural, singular, scope)
   102  }
   103  
   104  func (m *DefaultRESTMapper) AddSpecific(kind schema.GroupVersionKind, plural, singular schema.GroupVersionResource, scope RESTScope) {
   105  	m.singularToPlural[singular] = plural
   106  	m.pluralToSingular[plural] = singular
   107  
   108  	m.resourceToKind[singular] = kind
   109  	m.resourceToKind[plural] = kind
   110  
   111  	m.kindToPluralResource[kind] = plural
   112  	m.kindToScope[kind] = scope
   113  }
   114  
   115  // unpluralizedSuffixes is a list of resource suffixes that are the same plural and singular
   116  // This is only is only necessary because some bits of code are lazy and don't actually use the RESTMapper like they should.
   117  // TODO eliminate this so that different callers can correctly map to resources.  This probably means updating all
   118  // callers to use the RESTMapper they mean.
   119  var unpluralizedSuffixes = []string{
   120  	"endpoints",
   121  }
   122  
   123  // UnsafeGuessKindToResource converts Kind to a resource name.
   124  // Broken. This method only "sort of" works when used outside of this package.  It assumes that Kinds and Resources match
   125  // and they aren't guaranteed to do so.
   126  func UnsafeGuessKindToResource(kind schema.GroupVersionKind) ( /*plural*/ schema.GroupVersionResource /*singular*/, schema.GroupVersionResource) {
   127  	kindName := kind.Kind
   128  	if len(kindName) == 0 {
   129  		return schema.GroupVersionResource{}, schema.GroupVersionResource{}
   130  	}
   131  	singularName := strings.ToLower(kindName)
   132  	singular := kind.GroupVersion().WithResource(singularName)
   133  
   134  	for _, skip := range unpluralizedSuffixes {
   135  		if strings.HasSuffix(singularName, skip) {
   136  			return singular, singular
   137  		}
   138  	}
   139  
   140  	switch string(singularName[len(singularName)-1]) {
   141  	case "s":
   142  		return kind.GroupVersion().WithResource(singularName + "es"), singular
   143  	case "y":
   144  		return kind.GroupVersion().WithResource(strings.TrimSuffix(singularName, "y") + "ies"), singular
   145  	}
   146  
   147  	return kind.GroupVersion().WithResource(singularName + "s"), singular
   148  }
   149  
   150  // ResourceSingularizer implements RESTMapper
   151  // It converts a resource name from plural to singular (e.g., from pods to pod)
   152  func (m *DefaultRESTMapper) ResourceSingularizer(resourceType string) (string, error) {
   153  	partialResource := schema.GroupVersionResource{Resource: resourceType}
   154  	resources, err := m.ResourcesFor(partialResource)
   155  	if err != nil {
   156  		return resourceType, err
   157  	}
   158  
   159  	singular := schema.GroupVersionResource{}
   160  	for _, curr := range resources {
   161  		currSingular, ok := m.pluralToSingular[curr]
   162  		if !ok {
   163  			continue
   164  		}
   165  		if singular.Empty() {
   166  			singular = currSingular
   167  			continue
   168  		}
   169  
   170  		if currSingular.Resource != singular.Resource {
   171  			return resourceType, fmt.Errorf("multiple possible singular resources (%v) found for %v", resources, resourceType)
   172  		}
   173  	}
   174  
   175  	if singular.Empty() {
   176  		return resourceType, fmt.Errorf("no singular of resource %v has been defined", resourceType)
   177  	}
   178  
   179  	return singular.Resource, nil
   180  }
   181  
   182  // coerceResourceForMatching makes the resource lower case and converts internal versions to unspecified (legacy behavior)
   183  func coerceResourceForMatching(resource schema.GroupVersionResource) schema.GroupVersionResource {
   184  	resource.Resource = strings.ToLower(resource.Resource)
   185  	if resource.Version == runtime.APIVersionInternal {
   186  		resource.Version = ""
   187  	}
   188  
   189  	return resource
   190  }
   191  
   192  func (m *DefaultRESTMapper) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) {
   193  	resource := coerceResourceForMatching(input)
   194  
   195  	hasResource := len(resource.Resource) > 0
   196  	hasGroup := len(resource.Group) > 0
   197  	hasVersion := len(resource.Version) > 0
   198  
   199  	if !hasResource {
   200  		return nil, fmt.Errorf("a resource must be present, got: %v", resource)
   201  	}
   202  
   203  	ret := []schema.GroupVersionResource{}
   204  	switch {
   205  	case hasGroup && hasVersion:
   206  		// fully qualified.  Find the exact match
   207  		for plural, singular := range m.pluralToSingular {
   208  			if singular == resource {
   209  				ret = append(ret, plural)
   210  				break
   211  			}
   212  			if plural == resource {
   213  				ret = append(ret, plural)
   214  				break
   215  			}
   216  		}
   217  
   218  	case hasGroup:
   219  		// given a group, prefer an exact match.  If you don't find one, resort to a prefix match on group
   220  		foundExactMatch := false
   221  		requestedGroupResource := resource.GroupResource()
   222  		for plural, singular := range m.pluralToSingular {
   223  			if singular.GroupResource() == requestedGroupResource {
   224  				foundExactMatch = true
   225  				ret = append(ret, plural)
   226  			}
   227  			if plural.GroupResource() == requestedGroupResource {
   228  				foundExactMatch = true
   229  				ret = append(ret, plural)
   230  			}
   231  		}
   232  
   233  		// if you didn't find an exact match, match on group prefixing. This allows storageclass.storage to match
   234  		// storageclass.storage.k8s.io
   235  		if !foundExactMatch {
   236  			for plural, singular := range m.pluralToSingular {
   237  				if !strings.HasPrefix(plural.Group, requestedGroupResource.Group) {
   238  					continue
   239  				}
   240  				if singular.Resource == requestedGroupResource.Resource {
   241  					ret = append(ret, plural)
   242  				}
   243  				if plural.Resource == requestedGroupResource.Resource {
   244  					ret = append(ret, plural)
   245  				}
   246  			}
   247  
   248  		}
   249  
   250  	case hasVersion:
   251  		for plural, singular := range m.pluralToSingular {
   252  			if singular.Version == resource.Version && singular.Resource == resource.Resource {
   253  				ret = append(ret, plural)
   254  			}
   255  			if plural.Version == resource.Version && plural.Resource == resource.Resource {
   256  				ret = append(ret, plural)
   257  			}
   258  		}
   259  
   260  	default:
   261  		for plural, singular := range m.pluralToSingular {
   262  			if singular.Resource == resource.Resource {
   263  				ret = append(ret, plural)
   264  			}
   265  			if plural.Resource == resource.Resource {
   266  				ret = append(ret, plural)
   267  			}
   268  		}
   269  	}
   270  
   271  	if len(ret) == 0 {
   272  		return nil, &NoResourceMatchError{PartialResource: resource}
   273  	}
   274  
   275  	sort.Sort(resourceByPreferredGroupVersion{ret, m.defaultGroupVersions})
   276  	return ret, nil
   277  }
   278  
   279  func (m *DefaultRESTMapper) ResourceFor(resource schema.GroupVersionResource) (schema.GroupVersionResource, error) {
   280  	resources, err := m.ResourcesFor(resource)
   281  	if err != nil {
   282  		return schema.GroupVersionResource{}, err
   283  	}
   284  	if len(resources) == 1 {
   285  		return resources[0], nil
   286  	}
   287  
   288  	return schema.GroupVersionResource{}, &AmbiguousResourceError{PartialResource: resource, MatchingResources: resources}
   289  }
   290  
   291  func (m *DefaultRESTMapper) KindsFor(input schema.GroupVersionResource) ([]schema.GroupVersionKind, error) {
   292  	resource := coerceResourceForMatching(input)
   293  
   294  	hasResource := len(resource.Resource) > 0
   295  	hasGroup := len(resource.Group) > 0
   296  	hasVersion := len(resource.Version) > 0
   297  
   298  	if !hasResource {
   299  		return nil, fmt.Errorf("a resource must be present, got: %v", resource)
   300  	}
   301  
   302  	ret := []schema.GroupVersionKind{}
   303  	switch {
   304  	// fully qualified.  Find the exact match
   305  	case hasGroup && hasVersion:
   306  		kind, exists := m.resourceToKind[resource]
   307  		if exists {
   308  			ret = append(ret, kind)
   309  		}
   310  
   311  	case hasGroup:
   312  		foundExactMatch := false
   313  		requestedGroupResource := resource.GroupResource()
   314  		for currResource, currKind := range m.resourceToKind {
   315  			if currResource.GroupResource() == requestedGroupResource {
   316  				foundExactMatch = true
   317  				ret = append(ret, currKind)
   318  			}
   319  		}
   320  
   321  		// if you didn't find an exact match, match on group prefixing. This allows storageclass.storage to match
   322  		// storageclass.storage.k8s.io
   323  		if !foundExactMatch {
   324  			for currResource, currKind := range m.resourceToKind {
   325  				if !strings.HasPrefix(currResource.Group, requestedGroupResource.Group) {
   326  					continue
   327  				}
   328  				if currResource.Resource == requestedGroupResource.Resource {
   329  					ret = append(ret, currKind)
   330  				}
   331  			}
   332  
   333  		}
   334  
   335  	case hasVersion:
   336  		for currResource, currKind := range m.resourceToKind {
   337  			if currResource.Version == resource.Version && currResource.Resource == resource.Resource {
   338  				ret = append(ret, currKind)
   339  			}
   340  		}
   341  
   342  	default:
   343  		for currResource, currKind := range m.resourceToKind {
   344  			if currResource.Resource == resource.Resource {
   345  				ret = append(ret, currKind)
   346  			}
   347  		}
   348  	}
   349  
   350  	if len(ret) == 0 {
   351  		return nil, &NoResourceMatchError{PartialResource: input}
   352  	}
   353  
   354  	sort.Sort(kindByPreferredGroupVersion{ret, m.defaultGroupVersions})
   355  	return ret, nil
   356  }
   357  
   358  func (m *DefaultRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
   359  	kinds, err := m.KindsFor(resource)
   360  	if err != nil {
   361  		return schema.GroupVersionKind{}, err
   362  	}
   363  	if len(kinds) == 1 {
   364  		return kinds[0], nil
   365  	}
   366  
   367  	return schema.GroupVersionKind{}, &AmbiguousResourceError{PartialResource: resource, MatchingKinds: kinds}
   368  }
   369  
   370  type kindByPreferredGroupVersion struct {
   371  	list      []schema.GroupVersionKind
   372  	sortOrder []schema.GroupVersion
   373  }
   374  
   375  func (o kindByPreferredGroupVersion) Len() int      { return len(o.list) }
   376  func (o kindByPreferredGroupVersion) Swap(i, j int) { o.list[i], o.list[j] = o.list[j], o.list[i] }
   377  func (o kindByPreferredGroupVersion) Less(i, j int) bool {
   378  	lhs := o.list[i]
   379  	rhs := o.list[j]
   380  	if lhs == rhs {
   381  		return false
   382  	}
   383  
   384  	if lhs.GroupVersion() == rhs.GroupVersion() {
   385  		return lhs.Kind < rhs.Kind
   386  	}
   387  
   388  	// otherwise, the difference is in the GroupVersion, so we need to sort with respect to the preferred order
   389  	lhsIndex := -1
   390  	rhsIndex := -1
   391  
   392  	for i := range o.sortOrder {
   393  		if o.sortOrder[i] == lhs.GroupVersion() {
   394  			lhsIndex = i
   395  		}
   396  		if o.sortOrder[i] == rhs.GroupVersion() {
   397  			rhsIndex = i
   398  		}
   399  	}
   400  
   401  	if rhsIndex == -1 {
   402  		return true
   403  	}
   404  
   405  	return lhsIndex < rhsIndex
   406  }
   407  
   408  type resourceByPreferredGroupVersion struct {
   409  	list      []schema.GroupVersionResource
   410  	sortOrder []schema.GroupVersion
   411  }
   412  
   413  func (o resourceByPreferredGroupVersion) Len() int      { return len(o.list) }
   414  func (o resourceByPreferredGroupVersion) Swap(i, j int) { o.list[i], o.list[j] = o.list[j], o.list[i] }
   415  func (o resourceByPreferredGroupVersion) Less(i, j int) bool {
   416  	lhs := o.list[i]
   417  	rhs := o.list[j]
   418  	if lhs == rhs {
   419  		return false
   420  	}
   421  
   422  	if lhs.GroupVersion() == rhs.GroupVersion() {
   423  		return lhs.Resource < rhs.Resource
   424  	}
   425  
   426  	// otherwise, the difference is in the GroupVersion, so we need to sort with respect to the preferred order
   427  	lhsIndex := -1
   428  	rhsIndex := -1
   429  
   430  	for i := range o.sortOrder {
   431  		if o.sortOrder[i] == lhs.GroupVersion() {
   432  			lhsIndex = i
   433  		}
   434  		if o.sortOrder[i] == rhs.GroupVersion() {
   435  			rhsIndex = i
   436  		}
   437  	}
   438  
   439  	if rhsIndex == -1 {
   440  		return true
   441  	}
   442  
   443  	return lhsIndex < rhsIndex
   444  }
   445  
   446  // RESTMapping returns a struct representing the resource path and conversion interfaces a
   447  // RESTClient should use to operate on the provided group/kind in order of versions. If a version search
   448  // order is not provided, the search order provided to DefaultRESTMapper will be used to resolve which
   449  // version should be used to access the named group/kind.
   450  func (m *DefaultRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*RESTMapping, error) {
   451  	mappings, err := m.RESTMappings(gk, versions...)
   452  	if err != nil {
   453  		return nil, err
   454  	}
   455  	if len(mappings) == 0 {
   456  		return nil, &NoKindMatchError{GroupKind: gk, SearchedVersions: versions}
   457  	}
   458  	// since we rely on RESTMappings method
   459  	// take the first match and return to the caller
   460  	// as this was the existing behavior.
   461  	return mappings[0], nil
   462  }
   463  
   464  // RESTMappings returns the RESTMappings for the provided group kind. If a version search order
   465  // is not provided, the search order provided to DefaultRESTMapper will be used.
   466  func (m *DefaultRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) {
   467  	mappings := make([]*RESTMapping, 0)
   468  	potentialGVK := make([]schema.GroupVersionKind, 0)
   469  	hadVersion := false
   470  
   471  	// Pick an appropriate version
   472  	for _, version := range versions {
   473  		if len(version) == 0 || version == runtime.APIVersionInternal {
   474  			continue
   475  		}
   476  		currGVK := gk.WithVersion(version)
   477  		hadVersion = true
   478  		if _, ok := m.kindToPluralResource[currGVK]; ok {
   479  			potentialGVK = append(potentialGVK, currGVK)
   480  			break
   481  		}
   482  	}
   483  	// Use the default preferred versions
   484  	if !hadVersion && len(potentialGVK) == 0 {
   485  		for _, gv := range m.defaultGroupVersions {
   486  			if gv.Group != gk.Group {
   487  				continue
   488  			}
   489  			potentialGVK = append(potentialGVK, gk.WithVersion(gv.Version))
   490  		}
   491  	}
   492  
   493  	if len(potentialGVK) == 0 {
   494  		return nil, &NoKindMatchError{GroupKind: gk, SearchedVersions: versions}
   495  	}
   496  
   497  	for _, gvk := range potentialGVK {
   498  		//Ensure we have a REST mapping
   499  		res, ok := m.kindToPluralResource[gvk]
   500  		if !ok {
   501  			continue
   502  		}
   503  
   504  		// Ensure we have a REST scope
   505  		scope, ok := m.kindToScope[gvk]
   506  		if !ok {
   507  			return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported scope", gvk.GroupVersion(), gvk.Kind)
   508  		}
   509  
   510  		mappings = append(mappings, &RESTMapping{
   511  			Resource:         res,
   512  			GroupVersionKind: gvk,
   513  			Scope:            scope,
   514  		})
   515  	}
   516  
   517  	if len(mappings) == 0 {
   518  		return nil, &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Group: gk.Group, Resource: gk.Kind}}
   519  	}
   520  	return mappings, nil
   521  }
   522  
   523  // MaybeResetRESTMapper calls Reset() on the mapper if it is a ResettableRESTMapper.
   524  func MaybeResetRESTMapper(mapper RESTMapper) {
   525  	m, ok := mapper.(ResettableRESTMapper)
   526  	if ok {
   527  		m.Reset()
   528  	}
   529  }