123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564 |
- /*
- Copyright 2014 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- // TODO: move everything in this file to pkg/api/rest
- package meta
- import (
- "fmt"
- "sort"
- "strings"
- "k8s.io/kubernetes/pkg/api/unversioned"
- "k8s.io/kubernetes/pkg/runtime"
- )
- // Implements RESTScope interface
- type restScope struct {
- name RESTScopeName
- paramName string
- argumentName string
- paramDescription string
- }
- func (r *restScope) Name() RESTScopeName {
- return r.name
- }
- func (r *restScope) ParamName() string {
- return r.paramName
- }
- func (r *restScope) ArgumentName() string {
- return r.argumentName
- }
- func (r *restScope) ParamDescription() string {
- return r.paramDescription
- }
- var RESTScopeNamespace = &restScope{
- name: RESTScopeNameNamespace,
- paramName: "namespaces",
- argumentName: "namespace",
- paramDescription: "object name and auth scope, such as for teams and projects",
- }
- var RESTScopeRoot = &restScope{
- name: RESTScopeNameRoot,
- }
- // DefaultRESTMapper exposes mappings between the types defined in a
- // runtime.Scheme. It assumes that all types defined the provided scheme
- // can be mapped with the provided MetadataAccessor and Codec interfaces.
- //
- // The resource name of a Kind is defined as the lowercase,
- // English-plural version of the Kind string.
- // When converting from resource to Kind, the singular version of the
- // resource name is also accepted for convenience.
- //
- // TODO: Only accept plural for some operations for increased control?
- // (`get pod bar` vs `get pods bar`)
- type DefaultRESTMapper struct {
- defaultGroupVersions []unversioned.GroupVersion
- resourceToKind map[unversioned.GroupVersionResource]unversioned.GroupVersionKind
- kindToPluralResource map[unversioned.GroupVersionKind]unversioned.GroupVersionResource
- kindToScope map[unversioned.GroupVersionKind]RESTScope
- singularToPlural map[unversioned.GroupVersionResource]unversioned.GroupVersionResource
- pluralToSingular map[unversioned.GroupVersionResource]unversioned.GroupVersionResource
- interfacesFunc VersionInterfacesFunc
- // aliasToResource is used for mapping aliases to resources
- aliasToResource map[string][]string
- }
- func (m *DefaultRESTMapper) String() string {
- return fmt.Sprintf("DefaultRESTMapper{kindToPluralResource=%v}", m.kindToPluralResource)
- }
- var _ RESTMapper = &DefaultRESTMapper{}
- // VersionInterfacesFunc returns the appropriate typer, and metadata accessor for a
- // given api version, or an error if no such api version exists.
- type VersionInterfacesFunc func(version unversioned.GroupVersion) (*VersionInterfaces, error)
- // NewDefaultRESTMapper initializes a mapping between Kind and APIVersion
- // to a resource name and back based on the objects in a runtime.Scheme
- // and the Kubernetes API conventions. Takes a group name, a priority list of the versions
- // to search when an object has no default version (set empty to return an error),
- // and a function that retrieves the correct metadata for a given version.
- func NewDefaultRESTMapper(defaultGroupVersions []unversioned.GroupVersion, f VersionInterfacesFunc) *DefaultRESTMapper {
- resourceToKind := make(map[unversioned.GroupVersionResource]unversioned.GroupVersionKind)
- kindToPluralResource := make(map[unversioned.GroupVersionKind]unversioned.GroupVersionResource)
- kindToScope := make(map[unversioned.GroupVersionKind]RESTScope)
- singularToPlural := make(map[unversioned.GroupVersionResource]unversioned.GroupVersionResource)
- pluralToSingular := make(map[unversioned.GroupVersionResource]unversioned.GroupVersionResource)
- aliasToResource := make(map[string][]string)
- // TODO: verify name mappings work correctly when versions differ
- return &DefaultRESTMapper{
- resourceToKind: resourceToKind,
- kindToPluralResource: kindToPluralResource,
- kindToScope: kindToScope,
- defaultGroupVersions: defaultGroupVersions,
- singularToPlural: singularToPlural,
- pluralToSingular: pluralToSingular,
- aliasToResource: aliasToResource,
- interfacesFunc: f,
- }
- }
- func (m *DefaultRESTMapper) Add(kind unversioned.GroupVersionKind, scope RESTScope) {
- plural, singular := KindToResource(kind)
- m.singularToPlural[singular] = plural
- m.pluralToSingular[plural] = singular
- m.resourceToKind[singular] = kind
- m.resourceToKind[plural] = kind
- m.kindToPluralResource[kind] = plural
- m.kindToScope[kind] = scope
- }
- // unpluralizedSuffixes is a list of resource suffixes that are the same plural and singular
- // This is only is only necessary because some bits of code are lazy and don't actually use the RESTMapper like they should.
- // TODO eliminate this so that different callers can correctly map to resources. This probably means updating all
- // callers to use the RESTMapper they mean.
- var unpluralizedSuffixes = []string{
- "endpoints",
- }
- // KindToResource converts Kind to a resource name.
- // Broken. This method only "sort of" works when used outside of this package. It assumes that Kinds and Resources match
- // and they aren't guaranteed to do so.
- func KindToResource(kind unversioned.GroupVersionKind) ( /*plural*/ unversioned.GroupVersionResource /*singular*/, unversioned.GroupVersionResource) {
- kindName := kind.Kind
- if len(kindName) == 0 {
- return unversioned.GroupVersionResource{}, unversioned.GroupVersionResource{}
- }
- singularName := strings.ToLower(kindName)
- singular := kind.GroupVersion().WithResource(singularName)
- for _, skip := range unpluralizedSuffixes {
- if strings.HasSuffix(singularName, skip) {
- return singular, singular
- }
- }
- switch string(singularName[len(singularName)-1]) {
- case "s":
- return kind.GroupVersion().WithResource(singularName + "es"), singular
- case "y":
- return kind.GroupVersion().WithResource(strings.TrimSuffix(singularName, "y") + "ies"), singular
- }
- return kind.GroupVersion().WithResource(singularName + "s"), singular
- }
- // ResourceSingularizer implements RESTMapper
- // It converts a resource name from plural to singular (e.g., from pods to pod)
- func (m *DefaultRESTMapper) ResourceSingularizer(resourceType string) (string, error) {
- partialResource := unversioned.GroupVersionResource{Resource: resourceType}
- resources, err := m.ResourcesFor(partialResource)
- if err != nil {
- return resourceType, err
- }
- singular := unversioned.GroupVersionResource{}
- for _, curr := range resources {
- currSingular, ok := m.pluralToSingular[curr]
- if !ok {
- continue
- }
- if singular.Empty() {
- singular = currSingular
- continue
- }
- if currSingular.Resource != singular.Resource {
- return resourceType, fmt.Errorf("multiple possible singular resources (%v) found for %v", resources, resourceType)
- }
- }
- if singular.Empty() {
- return resourceType, fmt.Errorf("no singular of resource %v has been defined", resourceType)
- }
- return singular.Resource, nil
- }
- // coerceResourceForMatching makes the resource lower case and converts internal versions to unspecified (legacy behavior)
- func coerceResourceForMatching(resource unversioned.GroupVersionResource) unversioned.GroupVersionResource {
- resource.Resource = strings.ToLower(resource.Resource)
- if resource.Version == runtime.APIVersionInternal {
- resource.Version = ""
- }
- return resource
- }
- func (m *DefaultRESTMapper) ResourcesFor(input unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error) {
- resource := coerceResourceForMatching(input)
- hasResource := len(resource.Resource) > 0
- hasGroup := len(resource.Group) > 0
- hasVersion := len(resource.Version) > 0
- if !hasResource {
- return nil, fmt.Errorf("a resource must be present, got: %v", resource)
- }
- ret := []unversioned.GroupVersionResource{}
- switch {
- // fully qualified. Find the exact match
- case hasGroup && hasVersion:
- for plural, singular := range m.pluralToSingular {
- if singular == resource {
- ret = append(ret, plural)
- break
- }
- if plural == resource {
- ret = append(ret, plural)
- break
- }
- }
- case hasGroup:
- requestedGroupResource := resource.GroupResource()
- for plural, singular := range m.pluralToSingular {
- if singular.GroupResource() == requestedGroupResource {
- ret = append(ret, plural)
- }
- if plural.GroupResource() == requestedGroupResource {
- ret = append(ret, plural)
- }
- }
- case hasVersion:
- for plural, singular := range m.pluralToSingular {
- if singular.Version == resource.Version && singular.Resource == resource.Resource {
- ret = append(ret, plural)
- }
- if plural.Version == resource.Version && plural.Resource == resource.Resource {
- ret = append(ret, plural)
- }
- }
- default:
- for plural, singular := range m.pluralToSingular {
- if singular.Resource == resource.Resource {
- ret = append(ret, plural)
- }
- if plural.Resource == resource.Resource {
- ret = append(ret, plural)
- }
- }
- }
- if len(ret) == 0 {
- return nil, &NoResourceMatchError{PartialResource: resource}
- }
- sort.Sort(resourceByPreferredGroupVersion{ret, m.defaultGroupVersions})
- return ret, nil
- }
- func (m *DefaultRESTMapper) ResourceFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionResource, error) {
- resources, err := m.ResourcesFor(resource)
- if err != nil {
- return unversioned.GroupVersionResource{}, err
- }
- if len(resources) == 1 {
- return resources[0], nil
- }
- return unversioned.GroupVersionResource{}, &AmbiguousResourceError{PartialResource: resource, MatchingResources: resources}
- }
- func (m *DefaultRESTMapper) KindsFor(input unversioned.GroupVersionResource) ([]unversioned.GroupVersionKind, error) {
- resource := coerceResourceForMatching(input)
- hasResource := len(resource.Resource) > 0
- hasGroup := len(resource.Group) > 0
- hasVersion := len(resource.Version) > 0
- if !hasResource {
- return nil, fmt.Errorf("a resource must be present, got: %v", resource)
- }
- ret := []unversioned.GroupVersionKind{}
- switch {
- // fully qualified. Find the exact match
- case hasGroup && hasVersion:
- kind, exists := m.resourceToKind[resource]
- if exists {
- ret = append(ret, kind)
- }
- case hasGroup:
- requestedGroupResource := resource.GroupResource()
- for currResource, currKind := range m.resourceToKind {
- if currResource.GroupResource() == requestedGroupResource {
- ret = append(ret, currKind)
- }
- }
- case hasVersion:
- for currResource, currKind := range m.resourceToKind {
- if currResource.Version == resource.Version && currResource.Resource == resource.Resource {
- ret = append(ret, currKind)
- }
- }
- default:
- for currResource, currKind := range m.resourceToKind {
- if currResource.Resource == resource.Resource {
- ret = append(ret, currKind)
- }
- }
- }
- if len(ret) == 0 {
- return nil, &NoResourceMatchError{PartialResource: input}
- }
- sort.Sort(kindByPreferredGroupVersion{ret, m.defaultGroupVersions})
- return ret, nil
- }
- func (m *DefaultRESTMapper) KindFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionKind, error) {
- kinds, err := m.KindsFor(resource)
- if err != nil {
- return unversioned.GroupVersionKind{}, err
- }
- if len(kinds) == 1 {
- return kinds[0], nil
- }
- return unversioned.GroupVersionKind{}, &AmbiguousResourceError{PartialResource: resource, MatchingKinds: kinds}
- }
- type kindByPreferredGroupVersion struct {
- list []unversioned.GroupVersionKind
- sortOrder []unversioned.GroupVersion
- }
- func (o kindByPreferredGroupVersion) Len() int { return len(o.list) }
- func (o kindByPreferredGroupVersion) Swap(i, j int) { o.list[i], o.list[j] = o.list[j], o.list[i] }
- func (o kindByPreferredGroupVersion) Less(i, j int) bool {
- lhs := o.list[i]
- rhs := o.list[j]
- if lhs == rhs {
- return false
- }
- if lhs.GroupVersion() == rhs.GroupVersion() {
- return lhs.Kind < rhs.Kind
- }
- // otherwise, the difference is in the GroupVersion, so we need to sort with respect to the preferred order
- lhsIndex := -1
- rhsIndex := -1
- for i := range o.sortOrder {
- if o.sortOrder[i] == lhs.GroupVersion() {
- lhsIndex = i
- }
- if o.sortOrder[i] == rhs.GroupVersion() {
- rhsIndex = i
- }
- }
- if rhsIndex == -1 {
- return true
- }
- return lhsIndex < rhsIndex
- }
- type resourceByPreferredGroupVersion struct {
- list []unversioned.GroupVersionResource
- sortOrder []unversioned.GroupVersion
- }
- func (o resourceByPreferredGroupVersion) Len() int { return len(o.list) }
- func (o resourceByPreferredGroupVersion) Swap(i, j int) { o.list[i], o.list[j] = o.list[j], o.list[i] }
- func (o resourceByPreferredGroupVersion) Less(i, j int) bool {
- lhs := o.list[i]
- rhs := o.list[j]
- if lhs == rhs {
- return false
- }
- if lhs.GroupVersion() == rhs.GroupVersion() {
- return lhs.Resource < rhs.Resource
- }
- // otherwise, the difference is in the GroupVersion, so we need to sort with respect to the preferred order
- lhsIndex := -1
- rhsIndex := -1
- for i := range o.sortOrder {
- if o.sortOrder[i] == lhs.GroupVersion() {
- lhsIndex = i
- }
- if o.sortOrder[i] == rhs.GroupVersion() {
- rhsIndex = i
- }
- }
- if rhsIndex == -1 {
- return true
- }
- return lhsIndex < rhsIndex
- }
- // RESTMapping returns a struct representing the resource path and conversion interfaces a
- // RESTClient should use to operate on the provided group/kind in order of versions. If a version search
- // order is not provided, the search order provided to DefaultRESTMapper will be used to resolve which
- // version should be used to access the named group/kind.
- // TODO: consider refactoring to use RESTMappings in a way that preserves version ordering and preference
- func (m *DefaultRESTMapper) RESTMapping(gk unversioned.GroupKind, versions ...string) (*RESTMapping, error) {
- // Pick an appropriate version
- var gvk *unversioned.GroupVersionKind
- hadVersion := false
- for _, version := range versions {
- if len(version) == 0 || version == runtime.APIVersionInternal {
- continue
- }
- currGVK := gk.WithVersion(version)
- hadVersion = true
- if _, ok := m.kindToPluralResource[currGVK]; ok {
- gvk = &currGVK
- break
- }
- }
- // Use the default preferred versions
- if !hadVersion && (gvk == nil) {
- for _, gv := range m.defaultGroupVersions {
- if gv.Group != gk.Group {
- continue
- }
- currGVK := gk.WithVersion(gv.Version)
- if _, ok := m.kindToPluralResource[currGVK]; ok {
- gvk = &currGVK
- break
- }
- }
- }
- if gvk == nil {
- return nil, &NoKindMatchError{PartialKind: gk.WithVersion("")}
- }
- // Ensure we have a REST mapping
- resource, ok := m.kindToPluralResource[*gvk]
- if !ok {
- found := []unversioned.GroupVersion{}
- for _, gv := range m.defaultGroupVersions {
- if _, ok := m.kindToPluralResource[*gvk]; ok {
- found = append(found, gv)
- }
- }
- if len(found) > 0 {
- return nil, fmt.Errorf("object with kind %q exists in versions %v, not %v", gvk.Kind, found, gvk.GroupVersion().String())
- }
- return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported object", gvk.GroupVersion().String(), gvk.Kind)
- }
- // Ensure we have a REST scope
- scope, ok := m.kindToScope[*gvk]
- if !ok {
- return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported scope", gvk.GroupVersion().String(), gvk.Kind)
- }
- interfaces, err := m.interfacesFunc(gvk.GroupVersion())
- if err != nil {
- return nil, fmt.Errorf("the provided version %q has no relevant versions", gvk.GroupVersion().String())
- }
- retVal := &RESTMapping{
- Resource: resource.Resource,
- GroupVersionKind: *gvk,
- Scope: scope,
- ObjectConvertor: interfaces.ObjectConvertor,
- MetadataAccessor: interfaces.MetadataAccessor,
- }
- return retVal, nil
- }
- // RESTMappings returns the RESTMappings for the provided group kind in a rough internal preferred order. If no
- // kind is found it will return a NoResourceMatchError.
- func (m *DefaultRESTMapper) RESTMappings(gk unversioned.GroupKind) ([]*RESTMapping, error) {
- // Use the default preferred versions
- var mappings []*RESTMapping
- for _, gv := range m.defaultGroupVersions {
- if gv.Group != gk.Group {
- continue
- }
- gvk := gk.WithVersion(gv.Version)
- gvr, ok := m.kindToPluralResource[gvk]
- if !ok {
- continue
- }
- // Ensure we have a REST scope
- scope, ok := m.kindToScope[gvk]
- if !ok {
- return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported scope", gvk.GroupVersion(), gvk.Kind)
- }
- interfaces, err := m.interfacesFunc(gvk.GroupVersion())
- if err != nil {
- return nil, fmt.Errorf("the provided version %q has no relevant versions", gvk.GroupVersion().String())
- }
- mappings = append(mappings, &RESTMapping{
- Resource: gvr.Resource,
- GroupVersionKind: gvk,
- Scope: scope,
- ObjectConvertor: interfaces.ObjectConvertor,
- MetadataAccessor: interfaces.MetadataAccessor,
- })
- }
- if len(mappings) == 0 {
- return nil, &NoResourceMatchError{PartialResource: unversioned.GroupVersionResource{Group: gk.Group, Resource: gk.Kind}}
- }
- return mappings, nil
- }
- // AddResourceAlias maps aliases to resources
- func (m *DefaultRESTMapper) AddResourceAlias(alias string, resources ...string) {
- if len(resources) == 0 {
- return
- }
- m.aliasToResource[alias] = resources
- }
- // AliasesForResource returns whether a resource has an alias or not
- func (m *DefaultRESTMapper) AliasesForResource(alias string) ([]string, bool) {
- if res, ok := m.aliasToResource[alias]; ok {
- return res, true
- }
- return nil, false
- }
|