2014-10-29 16:33:46 +00:00
|
|
|
/*
|
2015-05-01 16:19:44 +00:00
|
|
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
2014-10-29 16:33:46 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2015-03-22 21:43:00 +00:00
|
|
|
// TODO: move everything in this file to pkg/api/rest
|
2014-10-29 16:33:46 +00:00
|
|
|
package meta
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2015-01-29 22:46:54 +00:00
|
|
|
// Implements RESTScope interface
|
|
|
|
type restScope struct {
|
|
|
|
name RESTScopeName
|
|
|
|
paramName string
|
|
|
|
paramPath bool
|
|
|
|
paramDescription string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *restScope) Name() RESTScopeName {
|
|
|
|
return r.name
|
|
|
|
}
|
|
|
|
func (r *restScope) ParamName() string {
|
|
|
|
return r.paramName
|
|
|
|
}
|
|
|
|
func (r *restScope) ParamPath() bool {
|
|
|
|
return r.paramPath
|
|
|
|
}
|
|
|
|
func (r *restScope) ParamDescription() string {
|
|
|
|
return r.paramDescription
|
|
|
|
}
|
|
|
|
|
|
|
|
var RESTScopeNamespaceLegacy = &restScope{
|
|
|
|
name: RESTScopeNameNamespace,
|
|
|
|
paramName: "namespace",
|
|
|
|
paramPath: false,
|
|
|
|
paramDescription: "object name and auth scope, such as for teams and projects",
|
|
|
|
}
|
|
|
|
|
|
|
|
var RESTScopeNamespace = &restScope{
|
|
|
|
name: RESTScopeNameNamespace,
|
2015-01-19 21:50:00 +00:00
|
|
|
paramName: "namespaces",
|
2015-01-29 22:46:54 +00:00
|
|
|
paramPath: true,
|
|
|
|
paramDescription: "object name and auth scope, such as for teams and projects",
|
|
|
|
}
|
|
|
|
|
|
|
|
var RESTScopeRoot = &restScope{
|
|
|
|
name: RESTScopeNameRoot,
|
|
|
|
}
|
|
|
|
|
2014-10-29 16:33:46 +00:00
|
|
|
// typeMeta is used as a key for lookup in the mapping between REST path and
|
|
|
|
// API object.
|
|
|
|
type typeMeta struct {
|
|
|
|
APIVersion string
|
|
|
|
Kind string
|
|
|
|
}
|
|
|
|
|
2014-11-13 12:01:25 +00:00
|
|
|
// DefaultRESTMapper exposes mappings between the types defined in a
|
2014-10-29 16:33:46 +00:00
|
|
|
// 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 in v1beta3 and onwards,
|
|
|
|
// and as the camelCase version of the name in v1beta1 and v1beta2.
|
|
|
|
// 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 {
|
|
|
|
mapping map[string]typeMeta
|
|
|
|
reverse map[typeMeta]string
|
2015-01-29 16:35:06 +00:00
|
|
|
scopes map[typeMeta]RESTScope
|
2014-10-29 16:33:46 +00:00
|
|
|
versions []string
|
|
|
|
interfacesFunc VersionInterfacesFunc
|
|
|
|
}
|
|
|
|
|
2014-11-02 14:31:29 +00:00
|
|
|
// VersionInterfacesFunc returns the appropriate codec, typer, and metadata accessor for a
|
2014-10-29 16:33:46 +00:00
|
|
|
// given api version, or false if no such api version exists.
|
|
|
|
type VersionInterfacesFunc func(apiVersion string) (*VersionInterfaces, bool)
|
|
|
|
|
|
|
|
// 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 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 codec and metadata for a given version.
|
|
|
|
func NewDefaultRESTMapper(versions []string, f VersionInterfacesFunc) *DefaultRESTMapper {
|
|
|
|
mapping := make(map[string]typeMeta)
|
|
|
|
reverse := make(map[typeMeta]string)
|
2015-01-29 16:35:06 +00:00
|
|
|
scopes := make(map[typeMeta]RESTScope)
|
2014-10-29 16:33:46 +00:00
|
|
|
// TODO: verify name mappings work correctly when versions differ
|
|
|
|
|
|
|
|
return &DefaultRESTMapper{
|
2015-01-29 16:35:06 +00:00
|
|
|
mapping: mapping,
|
|
|
|
reverse: reverse,
|
|
|
|
scopes: scopes,
|
2014-10-29 16:33:46 +00:00
|
|
|
versions: versions,
|
|
|
|
interfacesFunc: f,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-29 16:35:06 +00:00
|
|
|
func (m *DefaultRESTMapper) Add(scope RESTScope, kind string, version string, mixedCase bool) {
|
|
|
|
plural, singular := kindToResource(kind, mixedCase)
|
|
|
|
meta := typeMeta{APIVersion: version, Kind: kind}
|
2015-04-29 18:17:55 +00:00
|
|
|
_, ok1 := m.mapping[plural]
|
|
|
|
_, ok2 := m.mapping[strings.ToLower(plural)]
|
|
|
|
if !ok1 && !ok2 {
|
2015-01-29 16:35:06 +00:00
|
|
|
m.mapping[plural] = meta
|
|
|
|
m.mapping[singular] = meta
|
|
|
|
if strings.ToLower(plural) != plural {
|
|
|
|
m.mapping[strings.ToLower(plural)] = meta
|
|
|
|
m.mapping[strings.ToLower(singular)] = meta
|
2014-10-29 16:33:46 +00:00
|
|
|
}
|
|
|
|
}
|
2015-01-29 16:35:06 +00:00
|
|
|
m.reverse[meta] = plural
|
|
|
|
m.scopes[meta] = scope
|
2014-10-29 16:33:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// kindToResource converts Kind to a resource name.
|
|
|
|
func kindToResource(kind string, mixedCase bool) (plural, singular string) {
|
2015-01-29 22:46:54 +00:00
|
|
|
if len(kind) == 0 {
|
|
|
|
return
|
|
|
|
}
|
2014-10-29 16:33:46 +00:00
|
|
|
if mixedCase {
|
|
|
|
// Legacy support for mixed case names
|
|
|
|
singular = strings.ToLower(kind[:1]) + kind[1:]
|
|
|
|
} else {
|
|
|
|
singular = strings.ToLower(kind)
|
|
|
|
}
|
2015-04-15 19:23:02 +00:00
|
|
|
if strings.HasSuffix(singular, "status") {
|
|
|
|
plural = strings.TrimSuffix(singular, "status") + "statuses"
|
|
|
|
} else {
|
|
|
|
switch string(singular[len(singular)-1]) {
|
|
|
|
case "s":
|
|
|
|
plural = singular
|
|
|
|
case "y":
|
|
|
|
plural = strings.TrimSuffix(singular, "y") + "ies"
|
|
|
|
default:
|
|
|
|
plural = singular + "s"
|
|
|
|
}
|
2014-10-29 16:33:46 +00:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// VersionAndKindForResource implements RESTMapper
|
|
|
|
func (m *DefaultRESTMapper) VersionAndKindForResource(resource string) (defaultVersion, kind string, err error) {
|
2015-04-29 18:17:55 +00:00
|
|
|
meta, ok := m.mapping[strings.ToLower(resource)]
|
2014-10-29 16:33:46 +00:00
|
|
|
if !ok {
|
|
|
|
return "", "", fmt.Errorf("no resource %q has been defined", resource)
|
|
|
|
}
|
|
|
|
return meta.APIVersion, meta.Kind, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// RESTMapping returns a struct representing the resource path and conversion interfaces a
|
2014-12-12 19:05:27 +00:00
|
|
|
// RESTClient should use to operate on the provided 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
|
2014-10-29 16:33:46 +00:00
|
|
|
// APIVersion should be used to access the named kind.
|
2014-12-12 19:05:27 +00:00
|
|
|
func (m *DefaultRESTMapper) RESTMapping(kind string, versions ...string) (*RESTMapping, error) {
|
|
|
|
// Pick an appropriate version
|
|
|
|
var version string
|
|
|
|
hadVersion := false
|
|
|
|
for _, v := range versions {
|
|
|
|
if len(v) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
hadVersion = true
|
|
|
|
if _, ok := m.reverse[typeMeta{APIVersion: v, Kind: kind}]; ok {
|
|
|
|
version = v
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Use the default preferred versions
|
|
|
|
if !hadVersion && len(version) == 0 {
|
2014-10-29 16:33:46 +00:00
|
|
|
for _, v := range m.versions {
|
|
|
|
if _, ok := m.reverse[typeMeta{APIVersion: v, Kind: kind}]; ok {
|
|
|
|
version = v
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2014-12-12 19:05:27 +00:00
|
|
|
}
|
|
|
|
if len(version) == 0 {
|
|
|
|
return nil, fmt.Errorf("no object named %q is registered", kind)
|
2014-10-29 16:33:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure we have a REST mapping
|
|
|
|
resource, ok := m.reverse[typeMeta{APIVersion: version, Kind: kind}]
|
|
|
|
if !ok {
|
|
|
|
found := []string{}
|
|
|
|
for _, v := range m.versions {
|
|
|
|
if _, ok := m.reverse[typeMeta{APIVersion: v, Kind: kind}]; ok {
|
|
|
|
found = append(found, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(found) > 0 {
|
|
|
|
return nil, fmt.Errorf("object with kind %q exists in versions %q, not %q", kind, strings.Join(found, ", "), version)
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported object", version, kind)
|
|
|
|
}
|
|
|
|
|
2015-01-29 16:35:06 +00:00
|
|
|
// Ensure we have a REST scope
|
|
|
|
scope, ok := m.scopes[typeMeta{APIVersion: version, Kind: kind}]
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported scope", version, kind)
|
|
|
|
}
|
|
|
|
|
2014-10-29 16:33:46 +00:00
|
|
|
interfaces, ok := m.interfacesFunc(version)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("the provided version %q has no relevant versions", version)
|
|
|
|
}
|
|
|
|
|
2015-04-15 19:23:02 +00:00
|
|
|
retVal := &RESTMapping{
|
2014-11-02 14:21:25 +00:00
|
|
|
Resource: resource,
|
|
|
|
APIVersion: version,
|
|
|
|
Kind: kind,
|
2015-01-29 16:35:06 +00:00
|
|
|
Scope: scope,
|
2014-11-02 14:21:25 +00:00
|
|
|
|
2014-10-29 16:33:46 +00:00
|
|
|
Codec: interfaces.Codec,
|
2014-11-02 14:31:29 +00:00
|
|
|
ObjectConvertor: interfaces.ObjectConvertor,
|
2014-10-29 16:33:46 +00:00
|
|
|
MetadataAccessor: interfaces.MetadataAccessor,
|
2015-04-15 19:23:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return retVal, nil
|
2014-10-29 16:33:46 +00:00
|
|
|
}
|
2015-04-08 15:05:41 +00:00
|
|
|
|
|
|
|
// aliasToResource is used for mapping aliases to resources
|
|
|
|
var aliasToResource = map[string][]string{}
|
|
|
|
|
|
|
|
// AddResourceAlias maps aliases to resources
|
|
|
|
func (m *DefaultRESTMapper) AddResourceAlias(alias string, resources ...string) {
|
|
|
|
if len(resources) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
aliasToResource[alias] = resources
|
|
|
|
}
|
|
|
|
|
|
|
|
// AliasesForResource returns whether a resource has an alias or not
|
|
|
|
func (m *DefaultRESTMapper) AliasesForResource(alias string) ([]string, bool) {
|
|
|
|
if res, ok := aliasToResource[alias]; ok {
|
|
|
|
return res, true
|
|
|
|
}
|
|
|
|
return nil, false
|
|
|
|
}
|
2015-04-16 23:21:13 +00:00
|
|
|
|
|
|
|
// MultiRESTMapper is a wrapper for multiple RESTMappers.
|
|
|
|
type MultiRESTMapper []RESTMapper
|
|
|
|
|
|
|
|
// VersionAndKindForResource provides the Version and Kind mappings for the
|
|
|
|
// REST resources. This implementation supports multiple REST schemas and return
|
|
|
|
// the first match.
|
|
|
|
func (m MultiRESTMapper) VersionAndKindForResource(resource string) (defaultVersion, kind string, err error) {
|
|
|
|
for _, t := range m {
|
|
|
|
defaultVersion, kind, err = t.VersionAndKindForResource(resource)
|
|
|
|
if err == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// RESTMapping provides the REST mapping for the resource based on the resource
|
|
|
|
// kind and version. This implementation supports multiple REST schemas and
|
|
|
|
// return the first match.
|
|
|
|
func (m MultiRESTMapper) RESTMapping(kind string, versions ...string) (mapping *RESTMapping, err error) {
|
|
|
|
for _, t := range m {
|
|
|
|
mapping, err = t.RESTMapping(kind, versions...)
|
|
|
|
if err == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// AliasesForResource finds the first alias response for the provided mappers.
|
|
|
|
func (m MultiRESTMapper) AliasesForResource(alias string) (aliases []string, ok bool) {
|
|
|
|
for _, t := range m {
|
|
|
|
if aliases, ok = t.AliasesForResource(alias); ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, false
|
|
|
|
}
|