k8s.io/apiserver@v0.31.1/pkg/endpoints/installer.go (about) 1 /* 2 Copyright 2015 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 package endpoints 18 19 import ( 20 "fmt" 21 "net/http" 22 "reflect" 23 "sort" 24 "strings" 25 "time" 26 "unicode" 27 28 restful "github.com/emicklei/go-restful/v3" 29 "sigs.k8s.io/structured-merge-diff/v4/fieldpath" 30 31 apidiscoveryv2 "k8s.io/api/apidiscovery/v2" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/conversion" 34 "k8s.io/apimachinery/pkg/runtime" 35 "k8s.io/apimachinery/pkg/runtime/schema" 36 "k8s.io/apimachinery/pkg/types" 37 "k8s.io/apimachinery/pkg/util/managedfields" 38 "k8s.io/apimachinery/pkg/util/sets" 39 "k8s.io/apiserver/pkg/admission" 40 "k8s.io/apiserver/pkg/endpoints/deprecation" 41 "k8s.io/apiserver/pkg/endpoints/discovery" 42 "k8s.io/apiserver/pkg/endpoints/handlers" 43 "k8s.io/apiserver/pkg/endpoints/handlers/negotiation" 44 "k8s.io/apiserver/pkg/endpoints/metrics" 45 utilwarning "k8s.io/apiserver/pkg/endpoints/warning" 46 "k8s.io/apiserver/pkg/features" 47 "k8s.io/apiserver/pkg/registry/rest" 48 "k8s.io/apiserver/pkg/storageversion" 49 utilfeature "k8s.io/apiserver/pkg/util/feature" 50 versioninfo "k8s.io/component-base/version" 51 ) 52 53 const ( 54 RouteMetaGVK = "x-kubernetes-group-version-kind" 55 RouteMetaSelectableFields = "x-kubernetes-selectable-fields" 56 RouteMetaAction = "x-kubernetes-action" 57 ) 58 59 type APIInstaller struct { 60 group *APIGroupVersion 61 prefix string // Path prefix where API resources are to be registered. 62 minRequestTimeout time.Duration 63 } 64 65 // Struct capturing information about an action ("GET", "POST", "WATCH", "PROXY", etc). 66 type action struct { 67 Verb string // Verb identifying the action ("GET", "POST", "WATCH", "PROXY", etc). 68 Path string // The path of the action 69 Params []*restful.Parameter // List of parameters associated with the action. 70 Namer handlers.ScopeNamer 71 AllNamespaces bool // true iff the action is namespaced but works on aggregate result for all namespaces 72 } 73 74 func ConvertGroupVersionIntoToDiscovery(list []metav1.APIResource) ([]apidiscoveryv2.APIResourceDiscovery, error) { 75 var apiResourceList []apidiscoveryv2.APIResourceDiscovery 76 parentResources := make(map[string]int) 77 78 // Loop through all top-level resources 79 for _, r := range list { 80 if strings.Contains(r.Name, "/") { 81 // Skip subresources for now so we can get the list of resources 82 continue 83 } 84 85 var scope apidiscoveryv2.ResourceScope 86 if r.Namespaced { 87 scope = apidiscoveryv2.ScopeNamespace 88 } else { 89 scope = apidiscoveryv2.ScopeCluster 90 } 91 92 resource := apidiscoveryv2.APIResourceDiscovery{ 93 Resource: r.Name, 94 Scope: scope, 95 ResponseKind: &metav1.GroupVersionKind{ 96 Group: r.Group, 97 Version: r.Version, 98 Kind: r.Kind, 99 }, 100 Verbs: r.Verbs, 101 ShortNames: r.ShortNames, 102 Categories: r.Categories, 103 SingularResource: r.SingularName, 104 } 105 apiResourceList = append(apiResourceList, resource) 106 parentResources[r.Name] = len(apiResourceList) - 1 107 } 108 109 // Loop through all subresources 110 for _, r := range list { 111 // Split resource name and subresource name 112 split := strings.SplitN(r.Name, "/", 2) 113 114 if len(split) != 2 { 115 // Skip parent resources 116 continue 117 } 118 119 var scope apidiscoveryv2.ResourceScope 120 if r.Namespaced { 121 scope = apidiscoveryv2.ScopeNamespace 122 } else { 123 scope = apidiscoveryv2.ScopeCluster 124 } 125 126 parentidx, exists := parentResources[split[0]] 127 if !exists { 128 // If a subresource exists without a parent, create a parent 129 apiResourceList = append(apiResourceList, apidiscoveryv2.APIResourceDiscovery{ 130 Resource: split[0], 131 Scope: scope, 132 // avoid nil panics in v0.26.0-v0.26.3 client-go clients 133 // see https://github.com/kubernetes/kubernetes/issues/118361 134 ResponseKind: &metav1.GroupVersionKind{}, 135 }) 136 parentidx = len(apiResourceList) - 1 137 parentResources[split[0]] = parentidx 138 } 139 140 if apiResourceList[parentidx].Scope != scope { 141 return nil, fmt.Errorf("Error: Parent %s (scope: %s) and subresource %s (scope: %s) scope do not match", split[0], apiResourceList[parentidx].Scope, split[1], scope) 142 // 143 } 144 145 subresource := apidiscoveryv2.APISubresourceDiscovery{ 146 Subresource: split[1], 147 Verbs: r.Verbs, 148 // avoid nil panics in v0.26.0-v0.26.3 client-go clients 149 // see https://github.com/kubernetes/kubernetes/issues/118361 150 ResponseKind: &metav1.GroupVersionKind{}, 151 } 152 if r.Kind != "" { 153 subresource.ResponseKind = &metav1.GroupVersionKind{ 154 Group: r.Group, 155 Version: r.Version, 156 Kind: r.Kind, 157 } 158 } 159 apiResourceList[parentidx].Subresources = append(apiResourceList[parentidx].Subresources, subresource) 160 } 161 162 return apiResourceList, nil 163 } 164 165 // An interface to see if one storage supports override its default verb for monitoring 166 type StorageMetricsOverride interface { 167 // OverrideMetricsVerb gives a storage object an opportunity to override the verb reported to the metrics endpoint 168 OverrideMetricsVerb(oldVerb string) (newVerb string) 169 } 170 171 // An interface to see if an object supports swagger documentation as a method 172 type documentable interface { 173 SwaggerDoc() map[string]string 174 } 175 176 // toDiscoveryKubeVerb maps an action.Verb to the logical kube verb, used for discovery 177 var toDiscoveryKubeVerb = map[string]string{ 178 "CONNECT": "", // do not list in discovery. 179 "DELETE": "delete", 180 "DELETECOLLECTION": "deletecollection", 181 "GET": "get", 182 "LIST": "list", 183 "PATCH": "patch", 184 "POST": "create", 185 "PROXY": "proxy", 186 "PUT": "update", 187 "WATCH": "watch", 188 "WATCHLIST": "watch", 189 } 190 191 // Install handlers for API resources. 192 func (a *APIInstaller) Install() ([]metav1.APIResource, []*storageversion.ResourceInfo, *restful.WebService, []error) { 193 var apiResources []metav1.APIResource 194 var resourceInfos []*storageversion.ResourceInfo 195 var errors []error 196 ws := a.newWebService() 197 198 // Register the paths in a deterministic (sorted) order to get a deterministic swagger spec. 199 paths := make([]string, len(a.group.Storage)) 200 var i int = 0 201 for path := range a.group.Storage { 202 paths[i] = path 203 i++ 204 } 205 sort.Strings(paths) 206 for _, path := range paths { 207 apiResource, resourceInfo, err := a.registerResourceHandlers(path, a.group.Storage[path], ws) 208 if err != nil { 209 errors = append(errors, fmt.Errorf("error in registering resource: %s, %v", path, err)) 210 } 211 if apiResource != nil { 212 apiResources = append(apiResources, *apiResource) 213 } 214 if resourceInfo != nil { 215 resourceInfos = append(resourceInfos, resourceInfo) 216 } 217 } 218 return apiResources, resourceInfos, ws, errors 219 } 220 221 // newWebService creates a new restful webservice with the api installer's prefix and version. 222 func (a *APIInstaller) newWebService() *restful.WebService { 223 ws := new(restful.WebService) 224 ws.Path(a.prefix) 225 // a.prefix contains "prefix/group/version" 226 ws.Doc("API at " + a.prefix) 227 // Backwards compatibility, we accepted objects with empty content-type at V1. 228 // If we stop using go-restful, we can default empty content-type to application/json on an 229 // endpoint by endpoint basis 230 ws.Consumes("*/*") 231 mediaTypes, streamMediaTypes := negotiation.MediaTypesForSerializer(a.group.Serializer) 232 ws.Produces(append(mediaTypes, streamMediaTypes...)...) 233 ws.ApiVersion(a.group.GroupVersion.String()) 234 235 return ws 236 } 237 238 // calculate the storage gvk, the gvk objects are converted to before persisted to the etcd. 239 func getStorageVersionKind(storageVersioner runtime.GroupVersioner, storage rest.Storage, typer runtime.ObjectTyper) (schema.GroupVersionKind, error) { 240 object := storage.New() 241 fqKinds, _, err := typer.ObjectKinds(object) 242 if err != nil { 243 return schema.GroupVersionKind{}, err 244 } 245 gvk, ok := storageVersioner.KindForGroupVersionKinds(fqKinds) 246 if !ok { 247 return schema.GroupVersionKind{}, fmt.Errorf("cannot find the storage version kind for %v", reflect.TypeOf(object)) 248 } 249 return gvk, nil 250 } 251 252 // GetResourceKind returns the external group version kind registered for the given storage 253 // object. If the storage object is a subresource and has an override supplied for it, it returns 254 // the group version kind supplied in the override. 255 func GetResourceKind(groupVersion schema.GroupVersion, storage rest.Storage, typer runtime.ObjectTyper) (schema.GroupVersionKind, error) { 256 // Let the storage tell us exactly what GVK it has 257 if gvkProvider, ok := storage.(rest.GroupVersionKindProvider); ok { 258 return gvkProvider.GroupVersionKind(groupVersion), nil 259 } 260 261 object := storage.New() 262 fqKinds, _, err := typer.ObjectKinds(object) 263 if err != nil { 264 return schema.GroupVersionKind{}, err 265 } 266 267 // a given go type can have multiple potential fully qualified kinds. Find the one that corresponds with the group 268 // we're trying to register here 269 fqKindToRegister := schema.GroupVersionKind{} 270 for _, fqKind := range fqKinds { 271 if fqKind.Group == groupVersion.Group { 272 fqKindToRegister = groupVersion.WithKind(fqKind.Kind) 273 break 274 } 275 } 276 if fqKindToRegister.Empty() { 277 return schema.GroupVersionKind{}, fmt.Errorf("unable to locate fully qualified kind for %v: found %v when registering for %v", reflect.TypeOf(object), fqKinds, groupVersion) 278 } 279 280 // group is guaranteed to match based on the check above 281 return fqKindToRegister, nil 282 } 283 284 func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService) (*metav1.APIResource, *storageversion.ResourceInfo, error) { 285 admit := a.group.Admit 286 287 optionsExternalVersion := a.group.GroupVersion 288 if a.group.OptionsExternalVersion != nil { 289 optionsExternalVersion = *a.group.OptionsExternalVersion 290 } 291 292 resource, subresource, err := splitSubresource(path) 293 if err != nil { 294 return nil, nil, err 295 } 296 297 group, version := a.group.GroupVersion.Group, a.group.GroupVersion.Version 298 299 fqKindToRegister, err := GetResourceKind(a.group.GroupVersion, storage, a.group.Typer) 300 if err != nil { 301 return nil, nil, err 302 } 303 304 versionedPtr, err := a.group.Creater.New(fqKindToRegister) 305 if err != nil { 306 return nil, nil, err 307 } 308 defaultVersionedObject := indirectArbitraryPointer(versionedPtr) 309 kind := fqKindToRegister.Kind 310 isSubresource := len(subresource) > 0 311 312 // If there is a subresource, namespace scoping is defined by the parent resource 313 var namespaceScoped bool 314 if isSubresource { 315 parentStorage, ok := a.group.Storage[resource] 316 if !ok { 317 return nil, nil, fmt.Errorf("missing parent storage: %q", resource) 318 } 319 scoper, ok := parentStorage.(rest.Scoper) 320 if !ok { 321 return nil, nil, fmt.Errorf("%q must implement scoper", resource) 322 } 323 namespaceScoped = scoper.NamespaceScoped() 324 325 } else { 326 scoper, ok := storage.(rest.Scoper) 327 if !ok { 328 return nil, nil, fmt.Errorf("%q must implement scoper", resource) 329 } 330 namespaceScoped = scoper.NamespaceScoped() 331 } 332 333 // what verbs are supported by the storage, used to know what verbs we support per path 334 creater, isCreater := storage.(rest.Creater) 335 namedCreater, isNamedCreater := storage.(rest.NamedCreater) 336 lister, isLister := storage.(rest.Lister) 337 getter, isGetter := storage.(rest.Getter) 338 getterWithOptions, isGetterWithOptions := storage.(rest.GetterWithOptions) 339 gracefulDeleter, isGracefulDeleter := storage.(rest.GracefulDeleter) 340 collectionDeleter, isCollectionDeleter := storage.(rest.CollectionDeleter) 341 updater, isUpdater := storage.(rest.Updater) 342 patcher, isPatcher := storage.(rest.Patcher) 343 watcher, isWatcher := storage.(rest.Watcher) 344 connecter, isConnecter := storage.(rest.Connecter) 345 storageMeta, isMetadata := storage.(rest.StorageMetadata) 346 storageVersionProvider, isStorageVersionProvider := storage.(rest.StorageVersionProvider) 347 gvAcceptor, _ := storage.(rest.GroupVersionAcceptor) 348 if !isMetadata { 349 storageMeta = defaultStorageMetadata{} 350 } 351 352 if isNamedCreater { 353 isCreater = true 354 } 355 356 var versionedList interface{} 357 if isLister { 358 list := lister.NewList() 359 listGVKs, _, err := a.group.Typer.ObjectKinds(list) 360 if err != nil { 361 return nil, nil, err 362 } 363 versionedListPtr, err := a.group.Creater.New(a.group.GroupVersion.WithKind(listGVKs[0].Kind)) 364 if err != nil { 365 return nil, nil, err 366 } 367 versionedList = indirectArbitraryPointer(versionedListPtr) 368 } 369 370 versionedListOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("ListOptions")) 371 if err != nil { 372 return nil, nil, err 373 } 374 versionedCreateOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("CreateOptions")) 375 if err != nil { 376 return nil, nil, err 377 } 378 versionedPatchOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("PatchOptions")) 379 if err != nil { 380 return nil, nil, err 381 } 382 versionedUpdateOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("UpdateOptions")) 383 if err != nil { 384 return nil, nil, err 385 } 386 387 var versionedDeleteOptions runtime.Object 388 var versionedDeleterObject interface{} 389 deleteReturnsDeletedObject := false 390 if isGracefulDeleter { 391 versionedDeleteOptions, err = a.group.Creater.New(optionsExternalVersion.WithKind("DeleteOptions")) 392 if err != nil { 393 return nil, nil, err 394 } 395 versionedDeleterObject = indirectArbitraryPointer(versionedDeleteOptions) 396 397 if mayReturnFullObjectDeleter, ok := storage.(rest.MayReturnFullObjectDeleter); ok { 398 deleteReturnsDeletedObject = mayReturnFullObjectDeleter.DeleteReturnsDeletedObject() 399 } 400 } 401 402 versionedStatusPtr, err := a.group.Creater.New(optionsExternalVersion.WithKind("Status")) 403 if err != nil { 404 return nil, nil, err 405 } 406 versionedStatus := indirectArbitraryPointer(versionedStatusPtr) 407 var ( 408 getOptions runtime.Object 409 versionedGetOptions runtime.Object 410 getOptionsInternalKind schema.GroupVersionKind 411 getSubpath bool 412 ) 413 if isGetterWithOptions { 414 getOptions, getSubpath, _ = getterWithOptions.NewGetOptions() 415 getOptionsInternalKinds, _, err := a.group.Typer.ObjectKinds(getOptions) 416 if err != nil { 417 return nil, nil, err 418 } 419 getOptionsInternalKind = getOptionsInternalKinds[0] 420 versionedGetOptions, err = a.group.Creater.New(a.group.GroupVersion.WithKind(getOptionsInternalKind.Kind)) 421 if err != nil { 422 versionedGetOptions, err = a.group.Creater.New(optionsExternalVersion.WithKind(getOptionsInternalKind.Kind)) 423 if err != nil { 424 return nil, nil, err 425 } 426 } 427 isGetter = true 428 } 429 430 var versionedWatchEvent interface{} 431 if isWatcher { 432 versionedWatchEventPtr, err := a.group.Creater.New(a.group.GroupVersion.WithKind("WatchEvent")) 433 if err != nil { 434 return nil, nil, err 435 } 436 versionedWatchEvent = indirectArbitraryPointer(versionedWatchEventPtr) 437 } 438 439 var ( 440 connectOptions runtime.Object 441 versionedConnectOptions runtime.Object 442 connectOptionsInternalKind schema.GroupVersionKind 443 connectSubpath bool 444 ) 445 if isConnecter { 446 connectOptions, connectSubpath, _ = connecter.NewConnectOptions() 447 if connectOptions != nil { 448 connectOptionsInternalKinds, _, err := a.group.Typer.ObjectKinds(connectOptions) 449 if err != nil { 450 return nil, nil, err 451 } 452 453 connectOptionsInternalKind = connectOptionsInternalKinds[0] 454 versionedConnectOptions, err = a.group.Creater.New(a.group.GroupVersion.WithKind(connectOptionsInternalKind.Kind)) 455 if err != nil { 456 versionedConnectOptions, err = a.group.Creater.New(optionsExternalVersion.WithKind(connectOptionsInternalKind.Kind)) 457 if err != nil { 458 return nil, nil, err 459 } 460 } 461 } 462 } 463 464 allowWatchList := isWatcher && isLister // watching on lists is allowed only for kinds that support both watch and list. 465 nameParam := ws.PathParameter("name", "name of the "+kind).DataType("string") 466 pathParam := ws.PathParameter("path", "path to the resource").DataType("string") 467 468 params := []*restful.Parameter{} 469 actions := []action{} 470 471 var resourceKind string 472 kindProvider, ok := storage.(rest.KindProvider) 473 if ok { 474 resourceKind = kindProvider.Kind() 475 } else { 476 resourceKind = kind 477 } 478 479 tableProvider, isTableProvider := storage.(rest.TableConvertor) 480 if isLister && !isTableProvider { 481 // All listers must implement TableProvider 482 return nil, nil, fmt.Errorf("%q must implement TableConvertor", resource) 483 } 484 485 var apiResource metav1.APIResource 486 if utilfeature.DefaultFeatureGate.Enabled(features.StorageVersionHash) && 487 isStorageVersionProvider && 488 storageVersionProvider.StorageVersion() != nil { 489 versioner := storageVersionProvider.StorageVersion() 490 gvk, err := getStorageVersionKind(versioner, storage, a.group.Typer) 491 if err != nil { 492 return nil, nil, err 493 } 494 apiResource.StorageVersionHash = discovery.StorageVersionHash(gvk.Group, gvk.Version, gvk.Kind) 495 } 496 497 // Get the list of actions for the given scope. 498 switch { 499 case !namespaceScoped: 500 // Handle non-namespace scoped resources like nodes. 501 resourcePath := resource 502 resourceParams := params 503 itemPath := resourcePath + "/{name}" 504 nameParams := append(params, nameParam) 505 proxyParams := append(nameParams, pathParam) 506 suffix := "" 507 if isSubresource { 508 suffix = "/" + subresource 509 itemPath = itemPath + suffix 510 resourcePath = itemPath 511 resourceParams = nameParams 512 } 513 apiResource.Name = path 514 apiResource.Namespaced = false 515 apiResource.Kind = resourceKind 516 namer := handlers.ContextBasedNaming{ 517 Namer: a.group.Namer, 518 ClusterScoped: true, 519 } 520 521 // Handler for standard REST verbs (GET, PUT, POST and DELETE). 522 // Add actions at the resource path: /api/apiVersion/resource 523 actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer, false}, isLister) 524 actions = appendIf(actions, action{"POST", resourcePath, resourceParams, namer, false}, isCreater) 525 actions = appendIf(actions, action{"DELETECOLLECTION", resourcePath, resourceParams, namer, false}, isCollectionDeleter) 526 // DEPRECATED in 1.11 527 actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, resourceParams, namer, false}, allowWatchList) 528 529 // Add actions at the item path: /api/apiVersion/resource/{name} 530 actions = appendIf(actions, action{"GET", itemPath, nameParams, namer, false}, isGetter) 531 if getSubpath { 532 actions = appendIf(actions, action{"GET", itemPath + "/{path:*}", proxyParams, namer, false}, isGetter) 533 } 534 actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer, false}, isUpdater) 535 actions = appendIf(actions, action{"PATCH", itemPath, nameParams, namer, false}, isPatcher) 536 actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer, false}, isGracefulDeleter) 537 // DEPRECATED in 1.11 538 actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer, false}, isWatcher) 539 actions = appendIf(actions, action{"CONNECT", itemPath, nameParams, namer, false}, isConnecter) 540 actions = appendIf(actions, action{"CONNECT", itemPath + "/{path:*}", proxyParams, namer, false}, isConnecter && connectSubpath) 541 default: 542 namespaceParamName := "namespaces" 543 // Handler for standard REST verbs (GET, PUT, POST and DELETE). 544 namespaceParam := ws.PathParameter("namespace", "object name and auth scope, such as for teams and projects").DataType("string") 545 namespacedPath := namespaceParamName + "/{namespace}/" + resource 546 namespaceParams := []*restful.Parameter{namespaceParam} 547 548 resourcePath := namespacedPath 549 resourceParams := namespaceParams 550 itemPath := namespacedPath + "/{name}" 551 nameParams := append(namespaceParams, nameParam) 552 proxyParams := append(nameParams, pathParam) 553 itemPathSuffix := "" 554 if isSubresource { 555 itemPathSuffix = "/" + subresource 556 itemPath = itemPath + itemPathSuffix 557 resourcePath = itemPath 558 resourceParams = nameParams 559 } 560 apiResource.Name = path 561 apiResource.Namespaced = true 562 apiResource.Kind = resourceKind 563 namer := handlers.ContextBasedNaming{ 564 Namer: a.group.Namer, 565 ClusterScoped: false, 566 } 567 568 actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer, false}, isLister) 569 actions = appendIf(actions, action{"POST", resourcePath, resourceParams, namer, false}, isCreater) 570 actions = appendIf(actions, action{"DELETECOLLECTION", resourcePath, resourceParams, namer, false}, isCollectionDeleter) 571 // DEPRECATED in 1.11 572 actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, resourceParams, namer, false}, allowWatchList) 573 574 actions = appendIf(actions, action{"GET", itemPath, nameParams, namer, false}, isGetter) 575 if getSubpath { 576 actions = appendIf(actions, action{"GET", itemPath + "/{path:*}", proxyParams, namer, false}, isGetter) 577 } 578 actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer, false}, isUpdater) 579 actions = appendIf(actions, action{"PATCH", itemPath, nameParams, namer, false}, isPatcher) 580 actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer, false}, isGracefulDeleter) 581 // DEPRECATED in 1.11 582 actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer, false}, isWatcher) 583 actions = appendIf(actions, action{"CONNECT", itemPath, nameParams, namer, false}, isConnecter) 584 actions = appendIf(actions, action{"CONNECT", itemPath + "/{path:*}", proxyParams, namer, false}, isConnecter && connectSubpath) 585 586 // list or post across namespace. 587 // For ex: LIST all pods in all namespaces by sending a LIST request at /api/apiVersion/pods. 588 // TODO: more strongly type whether a resource allows these actions on "all namespaces" (bulk delete) 589 if !isSubresource { 590 actions = appendIf(actions, action{"LIST", resource, params, namer, true}, isLister) 591 // DEPRECATED in 1.11 592 actions = appendIf(actions, action{"WATCHLIST", "watch/" + resource, params, namer, true}, allowWatchList) 593 } 594 } 595 596 var resourceInfo *storageversion.ResourceInfo 597 if utilfeature.DefaultFeatureGate.Enabled(features.StorageVersionAPI) && 598 utilfeature.DefaultFeatureGate.Enabled(features.APIServerIdentity) && 599 isStorageVersionProvider && 600 storageVersionProvider.StorageVersion() != nil { 601 602 versioner := storageVersionProvider.StorageVersion() 603 encodingGVK, err := getStorageVersionKind(versioner, storage, a.group.Typer) 604 if err != nil { 605 return nil, nil, err 606 } 607 decodableVersions := []schema.GroupVersion{} 608 if a.group.ConvertabilityChecker != nil { 609 decodableVersions = a.group.ConvertabilityChecker.VersionsForGroupKind(fqKindToRegister.GroupKind()) 610 } 611 612 resourceInfo = &storageversion.ResourceInfo{ 613 GroupResource: schema.GroupResource{ 614 Group: a.group.GroupVersion.Group, 615 Resource: apiResource.Name, 616 }, 617 EncodingVersion: encodingGVK.GroupVersion().String(), 618 // We record EquivalentResourceMapper first instead of calculate 619 // DecodableVersions immediately because API installation must 620 // be completed first for us to know equivalent APIs 621 EquivalentResourceMapper: a.group.EquivalentResourceRegistry, 622 623 DirectlyDecodableVersions: decodableVersions, 624 625 ServedVersions: a.group.AllServedVersionsByResource[path], 626 } 627 } 628 629 // Create Routes for the actions. 630 // TODO: Add status documentation using Returns() 631 // Errors (see api/errors/errors.go as well as go-restful router): 632 // http.StatusNotFound, http.StatusMethodNotAllowed, 633 // http.StatusUnsupportedMediaType, http.StatusNotAcceptable, 634 // http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, 635 // http.StatusRequestTimeout, http.StatusConflict, http.StatusPreconditionFailed, 636 // http.StatusUnprocessableEntity, http.StatusInternalServerError, 637 // http.StatusServiceUnavailable 638 // and api error codes 639 // Note that if we specify a versioned Status object here, we may need to 640 // create one for the tests, also 641 // Success: 642 // http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNoContent 643 // 644 // test/integration/auth_test.go is currently the most comprehensive status code test 645 646 for _, s := range a.group.Serializer.SupportedMediaTypes() { 647 if len(s.MediaTypeSubType) == 0 || len(s.MediaTypeType) == 0 { 648 return nil, nil, fmt.Errorf("all serializers in the group Serializer must have MediaTypeType and MediaTypeSubType set: %s", s.MediaType) 649 } 650 } 651 mediaTypes, streamMediaTypes := negotiation.MediaTypesForSerializer(a.group.Serializer) 652 allMediaTypes := append(mediaTypes, streamMediaTypes...) 653 ws.Produces(allMediaTypes...) 654 655 kubeVerbs := map[string]struct{}{} 656 reqScope := handlers.RequestScope{ 657 Serializer: a.group.Serializer, 658 ParameterCodec: a.group.ParameterCodec, 659 Creater: a.group.Creater, 660 Convertor: a.group.Convertor, 661 Defaulter: a.group.Defaulter, 662 Typer: a.group.Typer, 663 UnsafeConvertor: a.group.UnsafeConvertor, 664 Authorizer: a.group.Authorizer, 665 666 EquivalentResourceMapper: a.group.EquivalentResourceRegistry, 667 668 // TODO: Check for the interface on storage 669 TableConvertor: tableProvider, 670 671 // TODO: This seems wrong for cross-group subresources. It makes an assumption that a subresource and its parent are in the same group version. Revisit this. 672 Resource: a.group.GroupVersion.WithResource(resource), 673 Subresource: subresource, 674 Kind: fqKindToRegister, 675 676 AcceptsGroupVersionDelegate: gvAcceptor, 677 678 HubGroupVersion: schema.GroupVersion{Group: fqKindToRegister.Group, Version: runtime.APIVersionInternal}, 679 680 MetaGroupVersion: metav1.SchemeGroupVersion, 681 682 MaxRequestBodyBytes: a.group.MaxRequestBodyBytes, 683 } 684 if a.group.MetaGroupVersion != nil { 685 reqScope.MetaGroupVersion = *a.group.MetaGroupVersion 686 } 687 688 var resetFields map[fieldpath.APIVersion]*fieldpath.Set 689 if resetFieldsStrategy, isResetFieldsStrategy := storage.(rest.ResetFieldsStrategy); isResetFieldsStrategy { 690 resetFields = resetFieldsStrategy.GetResetFields() 691 } 692 693 reqScope.FieldManager, err = managedfields.NewDefaultFieldManager( 694 a.group.TypeConverter, 695 a.group.UnsafeConvertor, 696 a.group.Defaulter, 697 a.group.Creater, 698 fqKindToRegister, 699 reqScope.HubGroupVersion, 700 subresource, 701 resetFields, 702 ) 703 if err != nil { 704 return nil, nil, fmt.Errorf("failed to create field manager: %v", err) 705 } 706 707 for _, action := range actions { 708 producedObject := storageMeta.ProducesObject(action.Verb) 709 if producedObject == nil { 710 producedObject = defaultVersionedObject 711 } 712 reqScope.Namer = action.Namer 713 714 requestScope := "cluster" 715 var namespaced string 716 var operationSuffix string 717 if apiResource.Namespaced { 718 requestScope = "namespace" 719 namespaced = "Namespaced" 720 } 721 if strings.HasSuffix(action.Path, "/{path:*}") { 722 requestScope = "resource" 723 operationSuffix = operationSuffix + "WithPath" 724 } 725 if strings.Contains(action.Path, "/{name}") || action.Verb == "POST" { 726 requestScope = "resource" 727 } 728 if action.AllNamespaces { 729 requestScope = "cluster" 730 operationSuffix = operationSuffix + "ForAllNamespaces" 731 namespaced = "" 732 } 733 734 if kubeVerb, found := toDiscoveryKubeVerb[action.Verb]; found { 735 if len(kubeVerb) != 0 { 736 kubeVerbs[kubeVerb] = struct{}{} 737 } 738 } else { 739 return nil, nil, fmt.Errorf("unknown action verb for discovery: %s", action.Verb) 740 } 741 742 routes := []*restful.RouteBuilder{} 743 744 // If there is a subresource, kind should be the parent's kind. 745 if isSubresource { 746 parentStorage, ok := a.group.Storage[resource] 747 if !ok { 748 return nil, nil, fmt.Errorf("missing parent storage: %q", resource) 749 } 750 751 fqParentKind, err := GetResourceKind(a.group.GroupVersion, parentStorage, a.group.Typer) 752 if err != nil { 753 return nil, nil, err 754 } 755 kind = fqParentKind.Kind 756 } 757 758 verbOverrider, needOverride := storage.(StorageMetricsOverride) 759 760 // accumulate endpoint-level warnings 761 var ( 762 warnings []string 763 deprecated bool 764 removedRelease string 765 ) 766 767 { 768 versionedPtrWithGVK := versionedPtr.DeepCopyObject() 769 versionedPtrWithGVK.GetObjectKind().SetGroupVersionKind(fqKindToRegister) 770 currentMajor, currentMinor, _ := deprecation.MajorMinor(versioninfo.Get()) 771 deprecated = deprecation.IsDeprecated(versionedPtrWithGVK, currentMajor, currentMinor) 772 if deprecated { 773 removedRelease = deprecation.RemovedRelease(versionedPtrWithGVK) 774 warnings = append(warnings, deprecation.WarningMessage(versionedPtrWithGVK)) 775 } 776 } 777 778 switch action.Verb { 779 case "GET": // Get a resource. 780 var handler restful.RouteFunction 781 if isGetterWithOptions { 782 handler = restfulGetResourceWithOptions(getterWithOptions, reqScope, isSubresource) 783 } else { 784 handler = restfulGetResource(getter, reqScope) 785 } 786 787 if needOverride { 788 // need change the reported verb 789 handler = metrics.InstrumentRouteFunc(verbOverrider.OverrideMetricsVerb(action.Verb), group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, handler) 790 } else { 791 handler = metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, handler) 792 } 793 handler = utilwarning.AddWarningsHandler(handler, warnings) 794 795 doc := "read the specified " + kind 796 if isSubresource { 797 doc = "read " + subresource + " of the specified " + kind 798 } 799 route := ws.GET(action.Path).To(handler). 800 Doc(doc). 801 Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).")). 802 Operation("read"+namespaced+kind+strings.Title(subresource)+operationSuffix). 803 Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...). 804 Returns(http.StatusOK, "OK", producedObject). 805 Writes(producedObject) 806 if isGetterWithOptions { 807 if err := AddObjectParams(ws, route, versionedGetOptions); err != nil { 808 return nil, nil, err 809 } 810 } 811 addParams(route, action.Params) 812 routes = append(routes, route) 813 case "LIST": // List all resources of a kind. 814 doc := "list objects of kind " + kind 815 if isSubresource { 816 doc = "list " + subresource + " of objects of kind " + kind 817 } 818 handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, restfulListResource(lister, watcher, reqScope, false, a.minRequestTimeout)) 819 handler = utilwarning.AddWarningsHandler(handler, warnings) 820 route := ws.GET(action.Path).To(handler). 821 Doc(doc). 822 Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).")). 823 Operation("list"+namespaced+kind+strings.Title(subresource)+operationSuffix). 824 Produces(append(storageMeta.ProducesMIMETypes(action.Verb), allMediaTypes...)...). 825 Returns(http.StatusOK, "OK", versionedList). 826 Writes(versionedList) 827 if err := AddObjectParams(ws, route, versionedListOptions); err != nil { 828 return nil, nil, err 829 } 830 switch { 831 case isLister && isWatcher: 832 doc := "list or watch objects of kind " + kind 833 if isSubresource { 834 doc = "list or watch " + subresource + " of objects of kind " + kind 835 } 836 route.Doc(doc) 837 case isWatcher: 838 doc := "watch objects of kind " + kind 839 if isSubresource { 840 doc = "watch " + subresource + "of objects of kind " + kind 841 } 842 route.Doc(doc) 843 } 844 addParams(route, action.Params) 845 routes = append(routes, route) 846 case "PUT": // Update a resource. 847 doc := "replace the specified " + kind 848 if isSubresource { 849 doc = "replace " + subresource + " of the specified " + kind 850 } 851 handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, restfulUpdateResource(updater, reqScope, admit)) 852 handler = utilwarning.AddWarningsHandler(handler, warnings) 853 route := ws.PUT(action.Path).To(handler). 854 Doc(doc). 855 Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).")). 856 Operation("replace"+namespaced+kind+strings.Title(subresource)+operationSuffix). 857 Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...). 858 Returns(http.StatusOK, "OK", producedObject). 859 // TODO: in some cases, the API may return a v1.Status instead of the versioned object 860 // but currently go-restful can't handle multiple different objects being returned. 861 Returns(http.StatusCreated, "Created", producedObject). 862 Reads(defaultVersionedObject). 863 Writes(producedObject) 864 if err := AddObjectParams(ws, route, versionedUpdateOptions); err != nil { 865 return nil, nil, err 866 } 867 addParams(route, action.Params) 868 routes = append(routes, route) 869 case "PATCH": // Partially update a resource 870 doc := "partially update the specified " + kind 871 if isSubresource { 872 doc = "partially update " + subresource + " of the specified " + kind 873 } 874 supportedTypes := []string{ 875 string(types.JSONPatchType), 876 string(types.MergePatchType), 877 string(types.StrategicMergePatchType), 878 string(types.ApplyPatchType), 879 } 880 handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, restfulPatchResource(patcher, reqScope, admit, supportedTypes)) 881 handler = utilwarning.AddWarningsHandler(handler, warnings) 882 route := ws.PATCH(action.Path).To(handler). 883 Doc(doc). 884 Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).")). 885 Consumes(supportedTypes...). 886 Operation("patch"+namespaced+kind+strings.Title(subresource)+operationSuffix). 887 Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...). 888 Returns(http.StatusOK, "OK", producedObject). 889 // Patch can return 201 when a server side apply is requested 890 Returns(http.StatusCreated, "Created", producedObject). 891 Reads(metav1.Patch{}). 892 Writes(producedObject) 893 if err := AddObjectParams(ws, route, versionedPatchOptions); err != nil { 894 return nil, nil, err 895 } 896 addParams(route, action.Params) 897 routes = append(routes, route) 898 case "POST": // Create a resource. 899 var handler restful.RouteFunction 900 if isNamedCreater { 901 handler = restfulCreateNamedResource(namedCreater, reqScope, admit) 902 } else { 903 handler = restfulCreateResource(creater, reqScope, admit) 904 } 905 handler = metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, handler) 906 handler = utilwarning.AddWarningsHandler(handler, warnings) 907 article := GetArticleForNoun(kind, " ") 908 doc := "create" + article + kind 909 if isSubresource { 910 doc = "create " + subresource + " of" + article + kind 911 } 912 route := ws.POST(action.Path).To(handler). 913 Doc(doc). 914 Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).")). 915 Operation("create"+namespaced+kind+strings.Title(subresource)+operationSuffix). 916 Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...). 917 Returns(http.StatusOK, "OK", producedObject). 918 // TODO: in some cases, the API may return a v1.Status instead of the versioned object 919 // but currently go-restful can't handle multiple different objects being returned. 920 Returns(http.StatusCreated, "Created", producedObject). 921 Returns(http.StatusAccepted, "Accepted", producedObject). 922 Reads(defaultVersionedObject). 923 Writes(producedObject) 924 if err := AddObjectParams(ws, route, versionedCreateOptions); err != nil { 925 return nil, nil, err 926 } 927 addParams(route, action.Params) 928 routes = append(routes, route) 929 case "DELETE": // Delete a resource. 930 article := GetArticleForNoun(kind, " ") 931 doc := "delete" + article + kind 932 if isSubresource { 933 doc = "delete " + subresource + " of" + article + kind 934 } 935 deleteReturnType := versionedStatus 936 if deleteReturnsDeletedObject { 937 deleteReturnType = producedObject 938 } 939 handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, restfulDeleteResource(gracefulDeleter, isGracefulDeleter, reqScope, admit)) 940 handler = utilwarning.AddWarningsHandler(handler, warnings) 941 route := ws.DELETE(action.Path).To(handler). 942 Doc(doc). 943 Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).")). 944 Operation("delete"+namespaced+kind+strings.Title(subresource)+operationSuffix). 945 Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...). 946 Writes(deleteReturnType). 947 Returns(http.StatusOK, "OK", deleteReturnType). 948 Returns(http.StatusAccepted, "Accepted", deleteReturnType) 949 if isGracefulDeleter { 950 route.Reads(versionedDeleterObject) 951 route.ParameterNamed("body").Required(false) 952 if err := AddObjectParams(ws, route, versionedDeleteOptions); err != nil { 953 return nil, nil, err 954 } 955 } 956 addParams(route, action.Params) 957 routes = append(routes, route) 958 case "DELETECOLLECTION": 959 doc := "delete collection of " + kind 960 if isSubresource { 961 doc = "delete collection of " + subresource + " of a " + kind 962 } 963 handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, restfulDeleteCollection(collectionDeleter, isCollectionDeleter, reqScope, admit)) 964 handler = utilwarning.AddWarningsHandler(handler, warnings) 965 route := ws.DELETE(action.Path).To(handler). 966 Doc(doc). 967 Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).")). 968 Operation("deletecollection"+namespaced+kind+strings.Title(subresource)+operationSuffix). 969 Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...). 970 Writes(versionedStatus). 971 Returns(http.StatusOK, "OK", versionedStatus) 972 if isCollectionDeleter { 973 route.Reads(versionedDeleterObject) 974 route.ParameterNamed("body").Required(false) 975 if err := AddObjectParams(ws, route, versionedDeleteOptions); err != nil { 976 return nil, nil, err 977 } 978 } 979 if err := AddObjectParams(ws, route, versionedListOptions, "watch", "allowWatchBookmarks"); err != nil { 980 return nil, nil, err 981 } 982 addParams(route, action.Params) 983 routes = append(routes, route) 984 // deprecated in 1.11 985 case "WATCH": // Watch a resource. 986 doc := "watch changes to an object of kind " + kind 987 if isSubresource { 988 doc = "watch changes to " + subresource + " of an object of kind " + kind 989 } 990 doc += ". deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter." 991 handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, restfulListResource(lister, watcher, reqScope, true, a.minRequestTimeout)) 992 handler = utilwarning.AddWarningsHandler(handler, warnings) 993 route := ws.GET(action.Path).To(handler). 994 Doc(doc). 995 Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).")). 996 Operation("watch"+namespaced+kind+strings.Title(subresource)+operationSuffix). 997 Produces(allMediaTypes...). 998 Returns(http.StatusOK, "OK", versionedWatchEvent). 999 Writes(versionedWatchEvent) 1000 if err := AddObjectParams(ws, route, versionedListOptions); err != nil { 1001 return nil, nil, err 1002 } 1003 addParams(route, action.Params) 1004 routes = append(routes, route) 1005 // deprecated in 1.11 1006 case "WATCHLIST": // Watch all resources of a kind. 1007 doc := "watch individual changes to a list of " + kind 1008 if isSubresource { 1009 doc = "watch individual changes to a list of " + subresource + " of " + kind 1010 } 1011 doc += ". deprecated: use the 'watch' parameter with a list operation instead." 1012 handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, restfulListResource(lister, watcher, reqScope, true, a.minRequestTimeout)) 1013 handler = utilwarning.AddWarningsHandler(handler, warnings) 1014 route := ws.GET(action.Path).To(handler). 1015 Doc(doc). 1016 Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).")). 1017 Operation("watch"+namespaced+kind+strings.Title(subresource)+"List"+operationSuffix). 1018 Produces(allMediaTypes...). 1019 Returns(http.StatusOK, "OK", versionedWatchEvent). 1020 Writes(versionedWatchEvent) 1021 if err := AddObjectParams(ws, route, versionedListOptions); err != nil { 1022 return nil, nil, err 1023 } 1024 addParams(route, action.Params) 1025 routes = append(routes, route) 1026 case "CONNECT": 1027 for _, method := range connecter.ConnectMethods() { 1028 connectProducedObject := storageMeta.ProducesObject(method) 1029 if connectProducedObject == nil { 1030 connectProducedObject = "string" 1031 } 1032 doc := "connect " + method + " requests to " + kind 1033 if isSubresource { 1034 doc = "connect " + method + " requests to " + subresource + " of " + kind 1035 } 1036 handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, deprecated, removedRelease, restfulConnectResource(connecter, reqScope, admit, path, isSubresource)) 1037 handler = utilwarning.AddWarningsHandler(handler, warnings) 1038 route := ws.Method(method).Path(action.Path). 1039 To(handler). 1040 Doc(doc). 1041 Operation("connect" + strings.Title(strings.ToLower(method)) + namespaced + kind + strings.Title(subresource) + operationSuffix). 1042 Produces("*/*"). 1043 Consumes("*/*"). 1044 Writes(connectProducedObject) 1045 if versionedConnectOptions != nil { 1046 if err := AddObjectParams(ws, route, versionedConnectOptions); err != nil { 1047 return nil, nil, err 1048 } 1049 } 1050 addParams(route, action.Params) 1051 routes = append(routes, route) 1052 1053 // transform ConnectMethods to kube verbs 1054 if kubeVerb, found := toDiscoveryKubeVerb[method]; found { 1055 if len(kubeVerb) != 0 { 1056 kubeVerbs[kubeVerb] = struct{}{} 1057 } 1058 } 1059 } 1060 default: 1061 return nil, nil, fmt.Errorf("unrecognized action verb: %s", action.Verb) 1062 } 1063 for _, route := range routes { 1064 route.Metadata(RouteMetaGVK, metav1.GroupVersionKind{ 1065 Group: reqScope.Kind.Group, 1066 Version: reqScope.Kind.Version, 1067 Kind: reqScope.Kind.Kind, 1068 }) 1069 route.Metadata(RouteMetaAction, strings.ToLower(action.Verb)) 1070 ws.Route(route) 1071 } 1072 // Note: update GetAuthorizerAttributes() when adding a custom handler. 1073 } 1074 1075 apiResource.Verbs = make([]string, 0, len(kubeVerbs)) 1076 for kubeVerb := range kubeVerbs { 1077 apiResource.Verbs = append(apiResource.Verbs, kubeVerb) 1078 } 1079 sort.Strings(apiResource.Verbs) 1080 1081 if shortNamesProvider, ok := storage.(rest.ShortNamesProvider); ok { 1082 apiResource.ShortNames = shortNamesProvider.ShortNames() 1083 } 1084 if categoriesProvider, ok := storage.(rest.CategoriesProvider); ok { 1085 apiResource.Categories = categoriesProvider.Categories() 1086 } 1087 if !isSubresource { 1088 singularNameProvider, ok := storage.(rest.SingularNameProvider) 1089 if !ok { 1090 return nil, nil, fmt.Errorf("resource %s must implement SingularNameProvider", resource) 1091 } 1092 apiResource.SingularName = singularNameProvider.GetSingularName() 1093 } 1094 1095 if gvkProvider, ok := storage.(rest.GroupVersionKindProvider); ok { 1096 gvk := gvkProvider.GroupVersionKind(a.group.GroupVersion) 1097 apiResource.Group = gvk.Group 1098 apiResource.Version = gvk.Version 1099 apiResource.Kind = gvk.Kind 1100 } 1101 1102 // Record the existence of the GVR and the corresponding GVK 1103 a.group.EquivalentResourceRegistry.RegisterKindFor(reqScope.Resource, reqScope.Subresource, fqKindToRegister) 1104 1105 return &apiResource, resourceInfo, nil 1106 } 1107 1108 // indirectArbitraryPointer returns *ptrToObject for an arbitrary pointer 1109 func indirectArbitraryPointer(ptrToObject interface{}) interface{} { 1110 return reflect.Indirect(reflect.ValueOf(ptrToObject)).Interface() 1111 } 1112 1113 func appendIf(actions []action, a action, shouldAppend bool) []action { 1114 if shouldAppend { 1115 actions = append(actions, a) 1116 } 1117 return actions 1118 } 1119 1120 func addParams(route *restful.RouteBuilder, params []*restful.Parameter) { 1121 for _, param := range params { 1122 route.Param(param) 1123 } 1124 } 1125 1126 // AddObjectParams converts a runtime.Object into a set of go-restful Param() definitions on the route. 1127 // The object must be a pointer to a struct; only fields at the top level of the struct that are not 1128 // themselves interfaces or structs are used; only fields with a json tag that is non empty (the standard 1129 // Go JSON behavior for omitting a field) become query parameters. The name of the query parameter is 1130 // the JSON field name. If a description struct tag is set on the field, that description is used on the 1131 // query parameter. In essence, it converts a standard JSON top level object into a query param schema. 1132 func AddObjectParams(ws *restful.WebService, route *restful.RouteBuilder, obj interface{}, excludedNames ...string) error { 1133 sv, err := conversion.EnforcePtr(obj) 1134 if err != nil { 1135 return err 1136 } 1137 st := sv.Type() 1138 excludedNameSet := sets.NewString(excludedNames...) 1139 switch st.Kind() { 1140 case reflect.Struct: 1141 for i := 0; i < st.NumField(); i++ { 1142 name := st.Field(i).Name 1143 sf, ok := st.FieldByName(name) 1144 if !ok { 1145 continue 1146 } 1147 switch sf.Type.Kind() { 1148 case reflect.Interface, reflect.Struct: 1149 case reflect.Pointer: 1150 // TODO: This is a hack to let metav1.Time through. This needs to be fixed in a more generic way eventually. bug #36191 1151 if (sf.Type.Elem().Kind() == reflect.Interface || sf.Type.Elem().Kind() == reflect.Struct) && strings.TrimPrefix(sf.Type.String(), "*") != "metav1.Time" { 1152 continue 1153 } 1154 fallthrough 1155 default: 1156 jsonTag := sf.Tag.Get("json") 1157 if len(jsonTag) == 0 { 1158 continue 1159 } 1160 jsonName := strings.SplitN(jsonTag, ",", 2)[0] 1161 if len(jsonName) == 0 { 1162 continue 1163 } 1164 if excludedNameSet.Has(jsonName) { 1165 continue 1166 } 1167 var desc string 1168 if docable, ok := obj.(documentable); ok { 1169 desc = docable.SwaggerDoc()[jsonName] 1170 } 1171 route.Param(ws.QueryParameter(jsonName, desc).DataType(typeToJSON(sf.Type.String()))) 1172 } 1173 } 1174 } 1175 return nil 1176 } 1177 1178 // TODO: this is incomplete, expand as needed. 1179 // Convert the name of a golang type to the name of a JSON type 1180 func typeToJSON(typeName string) string { 1181 switch typeName { 1182 case "bool", "*bool": 1183 return "boolean" 1184 case "uint8", "*uint8", "int", "*int", "int32", "*int32", "int64", "*int64", "uint32", "*uint32", "uint64", "*uint64": 1185 return "integer" 1186 case "float64", "*float64", "float32", "*float32": 1187 return "number" 1188 case "metav1.Time", "*metav1.Time": 1189 return "string" 1190 case "byte", "*byte": 1191 return "string" 1192 case "v1.DeletionPropagation", "*v1.DeletionPropagation": 1193 return "string" 1194 case "v1.ResourceVersionMatch", "*v1.ResourceVersionMatch": 1195 return "string" 1196 case "v1.IncludeObjectPolicy", "*v1.IncludeObjectPolicy": 1197 return "string" 1198 1199 // TODO: Fix these when go-restful supports a way to specify an array query param: 1200 // https://github.com/emicklei/go-restful/issues/225 1201 case "[]string", "[]*string": 1202 return "string" 1203 case "[]int32", "[]*int32": 1204 return "integer" 1205 1206 default: 1207 return typeName 1208 } 1209 } 1210 1211 // defaultStorageMetadata provides default answers to rest.StorageMetadata. 1212 type defaultStorageMetadata struct{} 1213 1214 // defaultStorageMetadata implements rest.StorageMetadata 1215 var _ rest.StorageMetadata = defaultStorageMetadata{} 1216 1217 func (defaultStorageMetadata) ProducesMIMETypes(verb string) []string { 1218 return nil 1219 } 1220 1221 func (defaultStorageMetadata) ProducesObject(verb string) interface{} { 1222 return nil 1223 } 1224 1225 // splitSubresource checks if the given storage path is the path of a subresource and returns 1226 // the resource and subresource components. 1227 func splitSubresource(path string) (string, string, error) { 1228 var resource, subresource string 1229 switch parts := strings.Split(path, "/"); len(parts) { 1230 case 2: 1231 resource, subresource = parts[0], parts[1] 1232 case 1: 1233 resource = parts[0] 1234 default: 1235 // TODO: support deeper paths 1236 return "", "", fmt.Errorf("api_installer allows only one or two segment paths (resource or resource/subresource)") 1237 } 1238 return resource, subresource, nil 1239 } 1240 1241 // GetArticleForNoun returns the article needed for the given noun. 1242 func GetArticleForNoun(noun string, padding string) string { 1243 if !strings.HasSuffix(noun, "ss") && strings.HasSuffix(noun, "s") { 1244 // Plurals don't have an article. 1245 // Don't catch words like class 1246 return fmt.Sprintf("%v", padding) 1247 } 1248 1249 article := "a" 1250 if isVowel(rune(noun[0])) { 1251 article = "an" 1252 } 1253 1254 return fmt.Sprintf("%s%s%s", padding, article, padding) 1255 } 1256 1257 // isVowel returns true if the rune is a vowel (case insensitive). 1258 func isVowel(c rune) bool { 1259 vowels := []rune{'a', 'e', 'i', 'o', 'u'} 1260 for _, value := range vowels { 1261 if value == unicode.ToLower(c) { 1262 return true 1263 } 1264 } 1265 return false 1266 } 1267 1268 func restfulListResource(r rest.Lister, rw rest.Watcher, scope handlers.RequestScope, forceWatch bool, minRequestTimeout time.Duration) restful.RouteFunction { 1269 return func(req *restful.Request, res *restful.Response) { 1270 handlers.ListResource(r, rw, &scope, forceWatch, minRequestTimeout)(res.ResponseWriter, req.Request) 1271 } 1272 } 1273 1274 func restfulCreateNamedResource(r rest.NamedCreater, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction { 1275 return func(req *restful.Request, res *restful.Response) { 1276 handlers.CreateNamedResource(r, &scope, admit)(res.ResponseWriter, req.Request) 1277 } 1278 } 1279 1280 func restfulCreateResource(r rest.Creater, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction { 1281 return func(req *restful.Request, res *restful.Response) { 1282 handlers.CreateResource(r, &scope, admit)(res.ResponseWriter, req.Request) 1283 } 1284 } 1285 1286 func restfulDeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction { 1287 return func(req *restful.Request, res *restful.Response) { 1288 handlers.DeleteResource(r, allowsOptions, &scope, admit)(res.ResponseWriter, req.Request) 1289 } 1290 } 1291 1292 func restfulDeleteCollection(r rest.CollectionDeleter, checkBody bool, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction { 1293 return func(req *restful.Request, res *restful.Response) { 1294 handlers.DeleteCollection(r, checkBody, &scope, admit)(res.ResponseWriter, req.Request) 1295 } 1296 } 1297 1298 func restfulUpdateResource(r rest.Updater, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction { 1299 return func(req *restful.Request, res *restful.Response) { 1300 handlers.UpdateResource(r, &scope, admit)(res.ResponseWriter, req.Request) 1301 } 1302 } 1303 1304 func restfulPatchResource(r rest.Patcher, scope handlers.RequestScope, admit admission.Interface, supportedTypes []string) restful.RouteFunction { 1305 return func(req *restful.Request, res *restful.Response) { 1306 handlers.PatchResource(r, &scope, admit, supportedTypes)(res.ResponseWriter, req.Request) 1307 } 1308 } 1309 1310 func restfulGetResource(r rest.Getter, scope handlers.RequestScope) restful.RouteFunction { 1311 return func(req *restful.Request, res *restful.Response) { 1312 handlers.GetResource(r, &scope)(res.ResponseWriter, req.Request) 1313 } 1314 } 1315 1316 func restfulGetResourceWithOptions(r rest.GetterWithOptions, scope handlers.RequestScope, isSubresource bool) restful.RouteFunction { 1317 return func(req *restful.Request, res *restful.Response) { 1318 handlers.GetResourceWithOptions(r, &scope, isSubresource)(res.ResponseWriter, req.Request) 1319 } 1320 } 1321 1322 func restfulConnectResource(connecter rest.Connecter, scope handlers.RequestScope, admit admission.Interface, restPath string, isSubresource bool) restful.RouteFunction { 1323 return func(req *restful.Request, res *restful.Response) { 1324 handlers.ConnectResource(connecter, &scope, admit, restPath, isSubresource)(res.ResponseWriter, req.Request) 1325 } 1326 }