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 }