github.com/aclisp/heapster@v0.19.2-0.20160613100040-51756f899a96/Godeps/_workspace/src/k8s.io/kubernetes/pkg/api/meta/restmapper.go (about)

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