github.com/timstclair/heapster@v0.20.0-alpha1/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  	"strings"
    23  
    24  	"k8s.io/kubernetes/pkg/api/unversioned"
    25  )
    26  
    27  // Implements RESTScope interface
    28  type restScope struct {
    29  	name             RESTScopeName
    30  	paramName        string
    31  	argumentName     string
    32  	paramDescription string
    33  }
    34  
    35  func (r *restScope) Name() RESTScopeName {
    36  	return r.name
    37  }
    38  func (r *restScope) ParamName() string {
    39  	return r.paramName
    40  }
    41  func (r *restScope) ArgumentName() string {
    42  	return r.argumentName
    43  }
    44  func (r *restScope) ParamDescription() string {
    45  	return r.paramDescription
    46  }
    47  
    48  var RESTScopeNamespace = &restScope{
    49  	name:             RESTScopeNameNamespace,
    50  	paramName:        "namespaces",
    51  	argumentName:     "namespace",
    52  	paramDescription: "object name and auth scope, such as for teams and projects",
    53  }
    54  
    55  var RESTScopeRoot = &restScope{
    56  	name: RESTScopeNameRoot,
    57  }
    58  
    59  // DefaultRESTMapper exposes mappings between the types defined in a
    60  // runtime.Scheme. It assumes that all types defined the provided scheme
    61  // can be mapped with the provided MetadataAccessor and Codec interfaces.
    62  //
    63  // The resource name of a Kind is defined as the lowercase,
    64  // English-plural version of the Kind string.
    65  // When converting from resource to Kind, the singular version of the
    66  // resource name is also accepted for convenience.
    67  //
    68  // TODO: Only accept plural for some operations for increased control?
    69  // (`get pod bar` vs `get pods bar`)
    70  // TODO these maps should be keyed based on GroupVersionKinds
    71  type DefaultRESTMapper struct {
    72  	defaultGroupVersions []unversioned.GroupVersion
    73  
    74  	resourceToKind       map[string]unversioned.GroupVersionKind
    75  	kindToPluralResource map[unversioned.GroupVersionKind]string
    76  	kindToScope          map[unversioned.GroupVersionKind]RESTScope
    77  	singularToPlural     map[string]string
    78  	pluralToSingular     map[string]string
    79  
    80  	interfacesFunc VersionInterfacesFunc
    81  }
    82  
    83  // VersionInterfacesFunc returns the appropriate codec, typer, and metadata accessor for a
    84  // given api version, or an error if no such api version exists.
    85  type VersionInterfacesFunc func(apiVersion string) (*VersionInterfaces, error)
    86  
    87  // NewDefaultRESTMapper initializes a mapping between Kind and APIVersion
    88  // to a resource name and back based on the objects in a runtime.Scheme
    89  // and the Kubernetes API conventions. Takes a group name, a priority list of the versions
    90  // to search when an object has no default version (set empty to return an error),
    91  // and a function that retrieves the correct codec and metadata for a given version.
    92  func NewDefaultRESTMapper(defaultGroupVersions []unversioned.GroupVersion, f VersionInterfacesFunc) *DefaultRESTMapper {
    93  	resourceToKind := make(map[string]unversioned.GroupVersionKind)
    94  	kindToPluralResource := make(map[unversioned.GroupVersionKind]string)
    95  	kindToScope := make(map[unversioned.GroupVersionKind]RESTScope)
    96  	singularToPlural := make(map[string]string)
    97  	pluralToSingular := make(map[string]string)
    98  	// TODO: verify name mappings work correctly when versions differ
    99  
   100  	return &DefaultRESTMapper{
   101  		resourceToKind:       resourceToKind,
   102  		kindToPluralResource: kindToPluralResource,
   103  		kindToScope:          kindToScope,
   104  		defaultGroupVersions: defaultGroupVersions,
   105  		singularToPlural:     singularToPlural,
   106  		pluralToSingular:     pluralToSingular,
   107  		interfacesFunc:       f,
   108  	}
   109  }
   110  
   111  func (m *DefaultRESTMapper) Add(gvk unversioned.GroupVersionKind, scope RESTScope, mixedCase bool) {
   112  	plural, singular := KindToResource(gvk.Kind, mixedCase)
   113  	m.singularToPlural[singular] = plural
   114  	m.pluralToSingular[plural] = singular
   115  	_, ok1 := m.resourceToKind[plural]
   116  	_, ok2 := m.resourceToKind[strings.ToLower(plural)]
   117  	if !ok1 && !ok2 {
   118  		m.resourceToKind[plural] = gvk
   119  		m.resourceToKind[singular] = gvk
   120  		if strings.ToLower(plural) != plural {
   121  			m.resourceToKind[strings.ToLower(plural)] = gvk
   122  			m.resourceToKind[strings.ToLower(singular)] = gvk
   123  		}
   124  	}
   125  	m.kindToPluralResource[gvk] = plural
   126  	m.kindToScope[gvk] = scope
   127  }
   128  
   129  // KindToResource converts Kind to a resource name.
   130  func KindToResource(kind string, mixedCase bool) (plural, singular string) {
   131  	if len(kind) == 0 {
   132  		return
   133  	}
   134  	if mixedCase {
   135  		// Legacy support for mixed case names
   136  		singular = strings.ToLower(kind[:1]) + kind[1:]
   137  	} else {
   138  		singular = strings.ToLower(kind)
   139  	}
   140  	if strings.HasSuffix(singular, "endpoints") {
   141  		plural = singular
   142  	} else {
   143  		switch string(singular[len(singular)-1]) {
   144  		case "s":
   145  			plural = singular + "es"
   146  		case "y":
   147  			plural = strings.TrimSuffix(singular, "y") + "ies"
   148  		default:
   149  			plural = singular + "s"
   150  		}
   151  	}
   152  	return
   153  }
   154  
   155  // ResourceSingularizer implements RESTMapper
   156  // It converts a resource name from plural to singular (e.g., from pods to pod)
   157  func (m *DefaultRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
   158  	singular, ok := m.pluralToSingular[resource]
   159  	if !ok {
   160  		return resource, fmt.Errorf("no singular of resource %q has been defined", resource)
   161  	}
   162  	return singular, nil
   163  }
   164  
   165  // VersionAndKindForResource implements RESTMapper
   166  func (m *DefaultRESTMapper) VersionAndKindForResource(resource string) (gvString, kind string, err error) {
   167  	gvk, ok := m.resourceToKind[strings.ToLower(resource)]
   168  	if !ok {
   169  		return "", "", fmt.Errorf("in version and kind for resource, no resource %q has been defined", resource)
   170  	}
   171  	return gvk.GroupVersion().String(), gvk.Kind, nil
   172  }
   173  
   174  func (m *DefaultRESTMapper) GroupForResource(resource string) (string, error) {
   175  	gvk, exists := m.resourceToKind[strings.ToLower(resource)]
   176  	if !exists {
   177  		return "", fmt.Errorf("in group for resource, no resource %q has been defined", resource)
   178  	}
   179  
   180  	return gvk.Group, nil
   181  }
   182  
   183  // RESTMapping returns a struct representing the resource path and conversion interfaces a
   184  // RESTClient should use to operate on the provided kind in order of versions. If a version search
   185  // order is not provided, the search order provided to DefaultRESTMapper will be used to resolve which
   186  // APIVersion should be used to access the named kind.
   187  // TODO version here in this RESTMapper means just APIVersion, but the RESTMapper API is intended to handle multiple groups
   188  // So this API is broken.  The RESTMapper test made it clear that versions here were API versions, but the code tries to use
   189  // them with group/version tuples.
   190  // TODO this should probably become RESTMapping(GroupKind, versions ...string)
   191  func (m *DefaultRESTMapper) RESTMapping(kind string, versions ...string) (*RESTMapping, error) {
   192  	// Pick an appropriate version
   193  	var groupVersion *unversioned.GroupVersion
   194  	hadVersion := false
   195  	for _, v := range versions {
   196  		if len(v) == 0 {
   197  			continue
   198  		}
   199  		currGroupVersion, err := unversioned.ParseGroupVersion(v)
   200  		if err != nil {
   201  			return nil, err
   202  		}
   203  
   204  		currGVK := currGroupVersion.WithKind(kind)
   205  		hadVersion = true
   206  		if _, ok := m.kindToPluralResource[currGVK]; ok {
   207  			groupVersion = &currGroupVersion
   208  			break
   209  		}
   210  	}
   211  	// Use the default preferred versions
   212  	if !hadVersion && (groupVersion == nil) {
   213  		for _, currGroupVersion := range m.defaultGroupVersions {
   214  			currGVK := currGroupVersion.WithKind(kind)
   215  			if _, ok := m.kindToPluralResource[currGVK]; ok {
   216  				groupVersion = &currGroupVersion
   217  				break
   218  			}
   219  		}
   220  	}
   221  	if groupVersion == nil {
   222  		return nil, fmt.Errorf("no kind named %q is registered in versions %q", kind, versions)
   223  	}
   224  
   225  	gvk := groupVersion.WithKind(kind)
   226  
   227  	// Ensure we have a REST mapping
   228  	resource, ok := m.kindToPluralResource[gvk]
   229  	if !ok {
   230  		found := []unversioned.GroupVersion{}
   231  		for _, gv := range m.defaultGroupVersions {
   232  			if _, ok := m.kindToPluralResource[gvk]; ok {
   233  				found = append(found, gv)
   234  			}
   235  		}
   236  		if len(found) > 0 {
   237  			return nil, fmt.Errorf("object with kind %q exists in versions %v, not %v", kind, found, *groupVersion)
   238  		}
   239  		return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported object", groupVersion, kind)
   240  	}
   241  
   242  	// Ensure we have a REST scope
   243  	scope, ok := m.kindToScope[gvk]
   244  	if !ok {
   245  		return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported scope", gvk.GroupVersion().String(), gvk.Kind)
   246  	}
   247  
   248  	interfaces, err := m.interfacesFunc(gvk.GroupVersion().String())
   249  	if err != nil {
   250  		return nil, fmt.Errorf("the provided version %q has no relevant versions", gvk.GroupVersion().String())
   251  	}
   252  
   253  	retVal := &RESTMapping{
   254  		Resource:         resource,
   255  		GroupVersionKind: gvk,
   256  		Scope:            scope,
   257  
   258  		Codec:            interfaces.Codec,
   259  		ObjectConvertor:  interfaces.ObjectConvertor,
   260  		MetadataAccessor: interfaces.MetadataAccessor,
   261  	}
   262  
   263  	return retVal, nil
   264  }
   265  
   266  // aliasToResource is used for mapping aliases to resources
   267  var aliasToResource = map[string][]string{}
   268  
   269  // AddResourceAlias maps aliases to resources
   270  func (m *DefaultRESTMapper) AddResourceAlias(alias string, resources ...string) {
   271  	if len(resources) == 0 {
   272  		return
   273  	}
   274  	aliasToResource[alias] = resources
   275  }
   276  
   277  // AliasesForResource returns whether a resource has an alias or not
   278  func (m *DefaultRESTMapper) AliasesForResource(alias string) ([]string, bool) {
   279  	if res, ok := aliasToResource[alias]; ok {
   280  		return res, true
   281  	}
   282  	return nil, false
   283  }
   284  
   285  // ResourceIsValid takes a string (kind) and checks if it's a valid resource
   286  func (m *DefaultRESTMapper) ResourceIsValid(resource string) bool {
   287  	_, _, err := m.VersionAndKindForResource(resource)
   288  	return err == nil
   289  }
   290  
   291  // MultiRESTMapper is a wrapper for multiple RESTMappers.
   292  type MultiRESTMapper []RESTMapper
   293  
   294  // ResourceSingularizer converts a REST resource name from plural to singular (e.g., from pods to pod)
   295  // This implementation supports multiple REST schemas and return the first match.
   296  func (m MultiRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
   297  	for _, t := range m {
   298  		singular, err = t.ResourceSingularizer(resource)
   299  		if err == nil {
   300  			return
   301  		}
   302  	}
   303  	return
   304  }
   305  
   306  // VersionAndKindForResource provides the Version and Kind  mappings for the
   307  // REST resources. This implementation supports multiple REST schemas and return
   308  // the first match.
   309  func (m MultiRESTMapper) VersionAndKindForResource(resource string) (defaultVersion, kind string, err error) {
   310  	for _, t := range m {
   311  		defaultVersion, kind, err = t.VersionAndKindForResource(resource)
   312  		if err == nil {
   313  			return
   314  		}
   315  	}
   316  	return
   317  }
   318  
   319  // GroupForResource provides the Group mappings for the REST resources. This
   320  // implementation supports multiple REST schemas and returns the first match.
   321  func (m MultiRESTMapper) GroupForResource(resource string) (group string, err error) {
   322  	for _, t := range m {
   323  		group, err = t.GroupForResource(resource)
   324  		if err == nil {
   325  			return
   326  		}
   327  	}
   328  	return
   329  }
   330  
   331  // RESTMapping provides the REST mapping for the resource based on the resource
   332  // kind and version. This implementation supports multiple REST schemas and
   333  // return the first match.
   334  func (m MultiRESTMapper) RESTMapping(kind string, versions ...string) (mapping *RESTMapping, err error) {
   335  	for _, t := range m {
   336  		mapping, err = t.RESTMapping(kind, versions...)
   337  		if err == nil {
   338  			return
   339  		}
   340  	}
   341  	return
   342  }
   343  
   344  // AliasesForResource finds the first alias response for the provided mappers.
   345  func (m MultiRESTMapper) AliasesForResource(alias string) (aliases []string, ok bool) {
   346  	for _, t := range m {
   347  		if aliases, ok = t.AliasesForResource(alias); ok {
   348  			return
   349  		}
   350  	}
   351  	return nil, false
   352  }
   353  
   354  // ResourceIsValid takes a string (either group/kind or kind) and checks if it's a valid resource
   355  func (m MultiRESTMapper) ResourceIsValid(resource string) bool {
   356  	for _, t := range m {
   357  		if t.ResourceIsValid(resource) {
   358  			return true
   359  		}
   360  	}
   361  	return false
   362  }