github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/api/meta/restmapper.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 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 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/runtime" 26 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/runtime/schema" 27 ) 28 29 // Implements RESTScope interface 30 type restScope struct { 31 name RESTScopeName 32 } 33 34 func (r *restScope) Name() RESTScopeName { 35 return r.name 36 } 37 38 var RESTScopeNamespace = &restScope{ 39 name: RESTScopeNameNamespace, 40 } 41 42 var RESTScopeRoot = &restScope{ 43 name: RESTScopeNameRoot, 44 } 45 46 // DefaultRESTMapper exposes mappings between the types defined in a 47 // runtime.Scheme. It assumes that all types defined the provided scheme 48 // can be mapped with the provided MetadataAccessor and Codec interfaces. 49 // 50 // The resource name of a Kind is defined as the lowercase, 51 // English-plural version of the Kind string. 52 // When converting from resource to Kind, the singular version of the 53 // resource name is also accepted for convenience. 54 // 55 // TODO: Only accept plural for some operations for increased control? 56 // (`get pod bar` vs `get pods bar`) 57 type DefaultRESTMapper struct { 58 defaultGroupVersions []schema.GroupVersion 59 60 resourceToKind map[schema.GroupVersionResource]schema.GroupVersionKind 61 kindToPluralResource map[schema.GroupVersionKind]schema.GroupVersionResource 62 kindToScope map[schema.GroupVersionKind]RESTScope 63 singularToPlural map[schema.GroupVersionResource]schema.GroupVersionResource 64 pluralToSingular map[schema.GroupVersionResource]schema.GroupVersionResource 65 } 66 67 func (m *DefaultRESTMapper) String() string { 68 if m == nil { 69 return "<nil>" 70 } 71 return fmt.Sprintf("DefaultRESTMapper{kindToPluralResource=%v}", m.kindToPluralResource) 72 } 73 74 var _ RESTMapper = &DefaultRESTMapper{} 75 76 // NewDefaultRESTMapper initializes a mapping between Kind and APIVersion 77 // to a resource name and back based on the objects in a runtime.Scheme 78 // and the Kubernetes API conventions. Takes a group name, a priority list of the versions 79 // to search when an object has no default version (set empty to return an error), 80 // and a function that retrieves the correct metadata for a given version. 81 func NewDefaultRESTMapper(defaultGroupVersions []schema.GroupVersion) *DefaultRESTMapper { 82 resourceToKind := make(map[schema.GroupVersionResource]schema.GroupVersionKind) 83 kindToPluralResource := make(map[schema.GroupVersionKind]schema.GroupVersionResource) 84 kindToScope := make(map[schema.GroupVersionKind]RESTScope) 85 singularToPlural := make(map[schema.GroupVersionResource]schema.GroupVersionResource) 86 pluralToSingular := make(map[schema.GroupVersionResource]schema.GroupVersionResource) 87 // TODO: verify name mappings work correctly when versions differ 88 89 return &DefaultRESTMapper{ 90 resourceToKind: resourceToKind, 91 kindToPluralResource: kindToPluralResource, 92 kindToScope: kindToScope, 93 defaultGroupVersions: defaultGroupVersions, 94 singularToPlural: singularToPlural, 95 pluralToSingular: pluralToSingular, 96 } 97 } 98 99 func (m *DefaultRESTMapper) Add(kind schema.GroupVersionKind, scope RESTScope) { 100 plural, singular := UnsafeGuessKindToResource(kind) 101 m.AddSpecific(kind, plural, singular, scope) 102 } 103 104 func (m *DefaultRESTMapper) AddSpecific(kind schema.GroupVersionKind, plural, singular schema.GroupVersionResource, scope RESTScope) { 105 m.singularToPlural[singular] = plural 106 m.pluralToSingular[plural] = singular 107 108 m.resourceToKind[singular] = kind 109 m.resourceToKind[plural] = kind 110 111 m.kindToPluralResource[kind] = plural 112 m.kindToScope[kind] = scope 113 } 114 115 // unpluralizedSuffixes is a list of resource suffixes that are the same plural and singular 116 // This is only is only necessary because some bits of code are lazy and don't actually use the RESTMapper like they should. 117 // TODO eliminate this so that different callers can correctly map to resources. This probably means updating all 118 // callers to use the RESTMapper they mean. 119 var unpluralizedSuffixes = []string{ 120 "endpoints", 121 } 122 123 // UnsafeGuessKindToResource converts Kind to a resource name. 124 // Broken. This method only "sort of" works when used outside of this package. It assumes that Kinds and Resources match 125 // and they aren't guaranteed to do so. 126 func UnsafeGuessKindToResource(kind schema.GroupVersionKind) ( /*plural*/ schema.GroupVersionResource /*singular*/, schema.GroupVersionResource) { 127 kindName := kind.Kind 128 if len(kindName) == 0 { 129 return schema.GroupVersionResource{}, schema.GroupVersionResource{} 130 } 131 singularName := strings.ToLower(kindName) 132 singular := kind.GroupVersion().WithResource(singularName) 133 134 for _, skip := range unpluralizedSuffixes { 135 if strings.HasSuffix(singularName, skip) { 136 return singular, singular 137 } 138 } 139 140 switch string(singularName[len(singularName)-1]) { 141 case "s": 142 return kind.GroupVersion().WithResource(singularName + "es"), singular 143 case "y": 144 return kind.GroupVersion().WithResource(strings.TrimSuffix(singularName, "y") + "ies"), singular 145 } 146 147 return kind.GroupVersion().WithResource(singularName + "s"), singular 148 } 149 150 // ResourceSingularizer implements RESTMapper 151 // It converts a resource name from plural to singular (e.g., from pods to pod) 152 func (m *DefaultRESTMapper) ResourceSingularizer(resourceType string) (string, error) { 153 partialResource := schema.GroupVersionResource{Resource: resourceType} 154 resources, err := m.ResourcesFor(partialResource) 155 if err != nil { 156 return resourceType, err 157 } 158 159 singular := schema.GroupVersionResource{} 160 for _, curr := range resources { 161 currSingular, ok := m.pluralToSingular[curr] 162 if !ok { 163 continue 164 } 165 if singular.Empty() { 166 singular = currSingular 167 continue 168 } 169 170 if currSingular.Resource != singular.Resource { 171 return resourceType, fmt.Errorf("multiple possible singular resources (%v) found for %v", resources, resourceType) 172 } 173 } 174 175 if singular.Empty() { 176 return resourceType, fmt.Errorf("no singular of resource %v has been defined", resourceType) 177 } 178 179 return singular.Resource, nil 180 } 181 182 // coerceResourceForMatching makes the resource lower case and converts internal versions to unspecified (legacy behavior) 183 func coerceResourceForMatching(resource schema.GroupVersionResource) schema.GroupVersionResource { 184 resource.Resource = strings.ToLower(resource.Resource) 185 if resource.Version == runtime.APIVersionInternal { 186 resource.Version = "" 187 } 188 189 return resource 190 } 191 192 func (m *DefaultRESTMapper) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) { 193 resource := coerceResourceForMatching(input) 194 195 hasResource := len(resource.Resource) > 0 196 hasGroup := len(resource.Group) > 0 197 hasVersion := len(resource.Version) > 0 198 199 if !hasResource { 200 return nil, fmt.Errorf("a resource must be present, got: %v", resource) 201 } 202 203 ret := []schema.GroupVersionResource{} 204 switch { 205 case hasGroup && hasVersion: 206 // fully qualified. Find the exact match 207 for plural, singular := range m.pluralToSingular { 208 if singular == resource { 209 ret = append(ret, plural) 210 break 211 } 212 if plural == resource { 213 ret = append(ret, plural) 214 break 215 } 216 } 217 218 case hasGroup: 219 // given a group, prefer an exact match. If you don't find one, resort to a prefix match on group 220 foundExactMatch := false 221 requestedGroupResource := resource.GroupResource() 222 for plural, singular := range m.pluralToSingular { 223 if singular.GroupResource() == requestedGroupResource { 224 foundExactMatch = true 225 ret = append(ret, plural) 226 } 227 if plural.GroupResource() == requestedGroupResource { 228 foundExactMatch = true 229 ret = append(ret, plural) 230 } 231 } 232 233 // if you didn't find an exact match, match on group prefixing. This allows storageclass.storage to match 234 // storageclass.storage.k8s.io 235 if !foundExactMatch { 236 for plural, singular := range m.pluralToSingular { 237 if !strings.HasPrefix(plural.Group, requestedGroupResource.Group) { 238 continue 239 } 240 if singular.Resource == requestedGroupResource.Resource { 241 ret = append(ret, plural) 242 } 243 if plural.Resource == requestedGroupResource.Resource { 244 ret = append(ret, plural) 245 } 246 } 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 schema.GroupVersionResource) (schema.GroupVersionResource, error) { 280 resources, err := m.ResourcesFor(resource) 281 if err != nil { 282 return schema.GroupVersionResource{}, err 283 } 284 if len(resources) == 1 { 285 return resources[0], nil 286 } 287 288 return schema.GroupVersionResource{}, &AmbiguousResourceError{PartialResource: resource, MatchingResources: resources} 289 } 290 291 func (m *DefaultRESTMapper) KindsFor(input schema.GroupVersionResource) ([]schema.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 := []schema.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 foundExactMatch := false 313 requestedGroupResource := resource.GroupResource() 314 for currResource, currKind := range m.resourceToKind { 315 if currResource.GroupResource() == requestedGroupResource { 316 foundExactMatch = true 317 ret = append(ret, currKind) 318 } 319 } 320 321 // if you didn't find an exact match, match on group prefixing. This allows storageclass.storage to match 322 // storageclass.storage.k8s.io 323 if !foundExactMatch { 324 for currResource, currKind := range m.resourceToKind { 325 if !strings.HasPrefix(currResource.Group, requestedGroupResource.Group) { 326 continue 327 } 328 if currResource.Resource == requestedGroupResource.Resource { 329 ret = append(ret, currKind) 330 } 331 } 332 333 } 334 335 case hasVersion: 336 for currResource, currKind := range m.resourceToKind { 337 if currResource.Version == resource.Version && currResource.Resource == resource.Resource { 338 ret = append(ret, currKind) 339 } 340 } 341 342 default: 343 for currResource, currKind := range m.resourceToKind { 344 if currResource.Resource == resource.Resource { 345 ret = append(ret, currKind) 346 } 347 } 348 } 349 350 if len(ret) == 0 { 351 return nil, &NoResourceMatchError{PartialResource: input} 352 } 353 354 sort.Sort(kindByPreferredGroupVersion{ret, m.defaultGroupVersions}) 355 return ret, nil 356 } 357 358 func (m *DefaultRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { 359 kinds, err := m.KindsFor(resource) 360 if err != nil { 361 return schema.GroupVersionKind{}, err 362 } 363 if len(kinds) == 1 { 364 return kinds[0], nil 365 } 366 367 return schema.GroupVersionKind{}, &AmbiguousResourceError{PartialResource: resource, MatchingKinds: kinds} 368 } 369 370 type kindByPreferredGroupVersion struct { 371 list []schema.GroupVersionKind 372 sortOrder []schema.GroupVersion 373 } 374 375 func (o kindByPreferredGroupVersion) Len() int { return len(o.list) } 376 func (o kindByPreferredGroupVersion) Swap(i, j int) { o.list[i], o.list[j] = o.list[j], o.list[i] } 377 func (o kindByPreferredGroupVersion) Less(i, j int) bool { 378 lhs := o.list[i] 379 rhs := o.list[j] 380 if lhs == rhs { 381 return false 382 } 383 384 if lhs.GroupVersion() == rhs.GroupVersion() { 385 return lhs.Kind < rhs.Kind 386 } 387 388 // otherwise, the difference is in the GroupVersion, so we need to sort with respect to the preferred order 389 lhsIndex := -1 390 rhsIndex := -1 391 392 for i := range o.sortOrder { 393 if o.sortOrder[i] == lhs.GroupVersion() { 394 lhsIndex = i 395 } 396 if o.sortOrder[i] == rhs.GroupVersion() { 397 rhsIndex = i 398 } 399 } 400 401 if rhsIndex == -1 { 402 return true 403 } 404 405 return lhsIndex < rhsIndex 406 } 407 408 type resourceByPreferredGroupVersion struct { 409 list []schema.GroupVersionResource 410 sortOrder []schema.GroupVersion 411 } 412 413 func (o resourceByPreferredGroupVersion) Len() int { return len(o.list) } 414 func (o resourceByPreferredGroupVersion) Swap(i, j int) { o.list[i], o.list[j] = o.list[j], o.list[i] } 415 func (o resourceByPreferredGroupVersion) Less(i, j int) bool { 416 lhs := o.list[i] 417 rhs := o.list[j] 418 if lhs == rhs { 419 return false 420 } 421 422 if lhs.GroupVersion() == rhs.GroupVersion() { 423 return lhs.Resource < rhs.Resource 424 } 425 426 // otherwise, the difference is in the GroupVersion, so we need to sort with respect to the preferred order 427 lhsIndex := -1 428 rhsIndex := -1 429 430 for i := range o.sortOrder { 431 if o.sortOrder[i] == lhs.GroupVersion() { 432 lhsIndex = i 433 } 434 if o.sortOrder[i] == rhs.GroupVersion() { 435 rhsIndex = i 436 } 437 } 438 439 if rhsIndex == -1 { 440 return true 441 } 442 443 return lhsIndex < rhsIndex 444 } 445 446 // RESTMapping returns a struct representing the resource path and conversion interfaces a 447 // RESTClient should use to operate on the provided group/kind in order of versions. If a version search 448 // order is not provided, the search order provided to DefaultRESTMapper will be used to resolve which 449 // version should be used to access the named group/kind. 450 func (m *DefaultRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*RESTMapping, error) { 451 mappings, err := m.RESTMappings(gk, versions...) 452 if err != nil { 453 return nil, err 454 } 455 if len(mappings) == 0 { 456 return nil, &NoKindMatchError{GroupKind: gk, SearchedVersions: versions} 457 } 458 // since we rely on RESTMappings method 459 // take the first match and return to the caller 460 // as this was the existing behavior. 461 return mappings[0], nil 462 } 463 464 // RESTMappings returns the RESTMappings for the provided group kind. If a version search order 465 // is not provided, the search order provided to DefaultRESTMapper will be used. 466 func (m *DefaultRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) { 467 mappings := make([]*RESTMapping, 0) 468 potentialGVK := make([]schema.GroupVersionKind, 0) 469 hadVersion := false 470 471 // Pick an appropriate version 472 for _, version := range versions { 473 if len(version) == 0 || version == runtime.APIVersionInternal { 474 continue 475 } 476 currGVK := gk.WithVersion(version) 477 hadVersion = true 478 if _, ok := m.kindToPluralResource[currGVK]; ok { 479 potentialGVK = append(potentialGVK, currGVK) 480 break 481 } 482 } 483 // Use the default preferred versions 484 if !hadVersion && len(potentialGVK) == 0 { 485 for _, gv := range m.defaultGroupVersions { 486 if gv.Group != gk.Group { 487 continue 488 } 489 potentialGVK = append(potentialGVK, gk.WithVersion(gv.Version)) 490 } 491 } 492 493 if len(potentialGVK) == 0 { 494 return nil, &NoKindMatchError{GroupKind: gk, SearchedVersions: versions} 495 } 496 497 for _, gvk := range potentialGVK { 498 //Ensure we have a REST mapping 499 res, ok := m.kindToPluralResource[gvk] 500 if !ok { 501 continue 502 } 503 504 // Ensure we have a REST scope 505 scope, ok := m.kindToScope[gvk] 506 if !ok { 507 return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported scope", gvk.GroupVersion(), gvk.Kind) 508 } 509 510 mappings = append(mappings, &RESTMapping{ 511 Resource: res, 512 GroupVersionKind: gvk, 513 Scope: scope, 514 }) 515 } 516 517 if len(mappings) == 0 { 518 return nil, &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Group: gk.Group, Resource: gk.Kind}} 519 } 520 return mappings, nil 521 } 522 523 // MaybeResetRESTMapper calls Reset() on the mapper if it is a ResettableRESTMapper. 524 func MaybeResetRESTMapper(mapper RESTMapper) { 525 m, ok := mapper.(ResettableRESTMapper) 526 if ok { 527 m.Reset() 528 } 529 }