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 }