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  }