diff --git a/.gitignore b/.gitignore index f9d5cc391..6cee6c60e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.swp *~ sample-main +vendor diff --git a/glide.lock b/glide.lock index 2b782546d..e701df5ce 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ hash: 807f261d8170be02e0c3802e4d005c334a04419a8b2e5f75d68498385202f5ff -updated: 2017-04-05T18:21:53.389585483-04:00 +updated: 2017-06-07T14:38:39.211756323-04:00 imports: - name: bitbucket.org/ww/goautoneg version: 75cd24fc2f2c2a2088577d12123ddee5f54e0675 @@ -8,7 +8,7 @@ imports: subpackages: - quantile - name: github.com/coreos/etcd - version: cc198e22d3b8fd7ec98304c95e68ee375be54589 + version: 20490caaf0dcd96bb4a95e40625559def8ef5b04 subpackages: - alarm - auth @@ -37,12 +37,14 @@ imports: - mvcc/mvccpb - pkg/adt - pkg/contention + - pkg/cpuutil - pkg/crc - pkg/fileutil - pkg/httputil - pkg/idutil - pkg/ioutil - pkg/logutil + - pkg/monotime - pkg/netutil - pkg/pathutil - pkg/pbutil @@ -53,6 +55,8 @@ imports: - pkg/transport - pkg/types - pkg/wait + - proxy/grpcproxy + - proxy/grpcproxy/cache - raft - raft/raftpb - rafthttp @@ -86,46 +90,63 @@ imports: - name: github.com/elazarl/go-bindata-assetfs version: 3dcc96556217539f50599357fb481ac0dc7439b9 - name: github.com/emicklei/go-restful - version: 09691a3b6378b740595c1002f40c34dd5f218a22 + version: ff4f55a206334ef123e4f79bbf348980da81ca46 subpackages: - log - - swagger +- name: github.com/emicklei/go-restful-swagger12 + version: dcef7f55730566d41eae5db10e7d6981829720f6 - name: github.com/evanphx/json-patch version: ba18e35c5c1b36ef6334cad706eb681153d2d379 - name: github.com/ghodss/yaml version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee +- name: github.com/go-openapi/analysis + version: b44dc874b601d9e4e2f6e19140e794ba24bead3b - name: github.com/go-openapi/jsonpointer version: 46af16f9f7b149af66e5d1bd010e3574dc06de98 - name: github.com/go-openapi/jsonreference version: 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272 +- name: github.com/go-openapi/loads + version: 18441dfa706d924a39a030ee2c3b1d8d81917b38 - name: github.com/go-openapi/spec version: 6aced65f8501fe1217321abf0749d354824ba2ff - name: github.com/go-openapi/swag version: 1d0bd113de87027671077d3c71eb3ac5d7dbba72 - name: github.com/gogo/protobuf - version: e18d7aa8f8c624c915db340349aad4c49b10d173 + version: c0656edd0d9eab7c66d1eb0c568f9039345796f7 subpackages: - proto - sortkeys - name: github.com/golang/glog version: 44145f04b68cf362d9c4df2182967c2275eaefed -- name: github.com/golang/groupcache - version: 02826c3e79038b59d737d3b1c0a1d937f71a4433 - subpackages: - - lru - name: github.com/golang/protobuf - version: 8616e8ee5e20a1704615e6c8d7afcdac06087a67 + version: 4bd1920723d7b7c925de087aa32e2187708897f7 subpackages: - jsonpb - proto + - ptypes + - ptypes/any + - ptypes/duration + - ptypes/timestamp - name: github.com/google/gofuzz version: 44d81051d367757e1c7c6a5a86423ece9afcf63c +- name: github.com/googleapis/gnostic + version: 68f4ded48ba9414dab2ae69b3f0d69971da73aa5 + subpackages: + - OpenAPIv2 + - compiler + - extensions +- name: github.com/grpc-ecosystem/go-grpc-prometheus + version: 2500245aa6110c562d17020fb31a2c133d737799 - name: github.com/grpc-ecosystem/grpc-gateway - version: f52d055dc48aec25854ed7d31862f78913cf17d1 + version: 84398b94e188ee336f307779b57b3aa91af7063c subpackages: - runtime - runtime/internal - utilities +- name: github.com/hashicorp/golang-lru + version: a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4 + subpackages: + - simplelru - name: github.com/howeyc/gopass version: 3ca23474a7c7203e0a0a070fd33508f6efdb9b3d - name: github.com/imdario/mergo @@ -182,7 +203,7 @@ imports: - blowfish - ssh/terminal - name: golang.org/x/net - version: e90d6d0afc4c315a0d87a568ae68577cc15149a0 + version: f2499483f923065a842d38eb4c7f1927e6fc6e6d subpackages: - context - html @@ -212,7 +233,7 @@ imports: - unicode/norm - width - name: google.golang.org/grpc - version: 231b4cfea0e79843053a33f5fe90bd4d84b23cd3 + version: 777daa17ff9b5daef1cfdf915088a2ada3332bf0 subpackages: - codes - credentials @@ -229,7 +250,7 @@ imports: - name: gopkg.in/yaml.v2 version: 53feefa2559fb8dfa8d81baad31be332c97d6c77 - name: k8s.io/apimachinery - version: 20e10d54608f05c3059443a6c0afb9979641e88d + version: 2de00c78cb6d6127fb51b9531c1b3def1cbcac8c subpackages: - pkg/api/equality - pkg/api/errors @@ -244,6 +265,7 @@ imports: - pkg/apis/meta/v1 - pkg/apis/meta/v1/unstructured - pkg/apis/meta/v1/validation + - pkg/apis/meta/v1alpha1 - pkg/conversion - pkg/conversion/queryparams - pkg/conversion/unstructured @@ -260,6 +282,8 @@ imports: - pkg/runtime/serializer/versioning - pkg/selection - pkg/types + - pkg/util/cache + - pkg/util/clock - pkg/util/diff - pkg/util/errors - pkg/util/framer @@ -283,9 +307,10 @@ imports: - third_party/forked/golang/netutil - third_party/forked/golang/reflect - name: k8s.io/apiserver - version: dcf548fbe26dacc3a78d18e1135adf17006552e9 + version: c809cf8581e1e44c6174bf5ab4415e6ee39965ca subpackages: - pkg/admission + - pkg/admission/initializer - pkg/apis/apiserver - pkg/apis/apiserver/install - pkg/apis/apiserver/v1alpha1 @@ -304,6 +329,7 @@ imports: - pkg/authorization/authorizerfactory - pkg/authorization/union - pkg/endpoints + - pkg/endpoints/discovery - pkg/endpoints/filters - pkg/endpoints/handlers - pkg/endpoints/handlers/negotiation @@ -334,7 +360,7 @@ imports: - pkg/storage/names - pkg/storage/storagebackend - pkg/storage/storagebackend/factory - - pkg/util/cache + - pkg/storage/value - pkg/util/feature - pkg/util/flag - pkg/util/flushwriter @@ -347,23 +373,79 @@ imports: - plugin/pkg/authenticator/token/webhook - plugin/pkg/authorizer/webhook - name: k8s.io/client-go - version: dabf37f5df16a224729883d9f616ce4a2c282e95 + version: 450baa5d60f8d6a251c7682cb6f86e939b750b2d subpackages: + - discovery + - informers + - informers/apps + - informers/apps/v1beta1 + - informers/autoscaling + - informers/autoscaling/v1 + - informers/autoscaling/v2alpha1 + - informers/batch + - informers/batch/v1 + - informers/batch/v2alpha1 + - informers/certificates + - informers/certificates/v1beta1 + - informers/core + - informers/core/v1 + - informers/extensions + - informers/extensions/v1beta1 + - informers/internalinterfaces + - informers/policy + - informers/policy/v1beta1 + - informers/rbac + - informers/rbac/v1alpha1 + - informers/rbac/v1beta1 + - informers/settings + - informers/settings/v1alpha1 + - informers/storage + - informers/storage/v1 + - informers/storage/v1beta1 + - kubernetes - kubernetes/scheme + - kubernetes/typed/apps/v1beta1 + - kubernetes/typed/authentication/v1 - kubernetes/typed/authentication/v1beta1 + - kubernetes/typed/authorization/v1 - kubernetes/typed/authorization/v1beta1 + - kubernetes/typed/autoscaling/v1 + - kubernetes/typed/autoscaling/v2alpha1 + - kubernetes/typed/batch/v1 + - kubernetes/typed/batch/v2alpha1 + - kubernetes/typed/certificates/v1beta1 - kubernetes/typed/core/v1 + - kubernetes/typed/extensions/v1beta1 + - kubernetes/typed/policy/v1beta1 + - kubernetes/typed/rbac/v1alpha1 + - kubernetes/typed/rbac/v1beta1 + - kubernetes/typed/settings/v1alpha1 + - kubernetes/typed/storage/v1 + - kubernetes/typed/storage/v1beta1 + - listers/apps/v1beta1 + - listers/autoscaling/v1 + - listers/autoscaling/v2alpha1 + - listers/batch/v1 + - listers/batch/v2alpha1 + - listers/certificates/v1beta1 + - listers/core/v1 + - listers/extensions/v1beta1 + - listers/policy/v1beta1 + - listers/rbac/v1alpha1 + - listers/rbac/v1beta1 + - listers/settings/v1alpha1 + - listers/storage/v1 + - listers/storage/v1beta1 - pkg/api - pkg/api/install - pkg/api/v1 + - pkg/api/v1/ref - pkg/apis/apps - pkg/apis/apps/v1beta1 - pkg/apis/authentication - - pkg/apis/authentication/install - pkg/apis/authentication/v1 - pkg/apis/authentication/v1beta1 - pkg/apis/authorization - - pkg/apis/authorization/install - pkg/apis/authorization/v1 - pkg/apis/authorization/v1beta1 - pkg/apis/autoscaling @@ -400,7 +482,6 @@ imports: - tools/metrics - transport - util/cert - - util/clock - util/flowcontrol - util/homedir - util/integer diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index b9e090f1f..e6c58f593 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -88,7 +88,7 @@ func (c *Config) SkipComplete() completedConfig { // New returns a new instance of CustomMetricsAdapterServer from the given config. func (c completedConfig) New(cmProvider provider.CustomMetricsProvider) (*CustomMetricsAdapterServer, error) { - genericServer, err := c.Config.GenericConfig.SkipComplete().New() // completion is done in Complete, no need for a second time + genericServer, err := c.Config.GenericConfig.SkipComplete().New(genericapiserver.EmptyDelegate) // completion is done in Complete, no need for a second time if err != nil { return nil, err } diff --git a/pkg/apiserver/cmapis.go b/pkg/apiserver/cmapis.go index 2c234bc2c..d0c0bd5a2 100644 --- a/pkg/apiserver/cmapis.go +++ b/pkg/apiserver/cmapis.go @@ -23,6 +23,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" genericapi "k8s.io/apiserver/pkg/endpoints" genericapiserver "k8s.io/apiserver/pkg/server" + "k8s.io/apiserver/pkg/endpoints/discovery" specificapi "k8s.io/custom-metrics-boilerplate/pkg/apiserver/installer" "k8s.io/custom-metrics-boilerplate/pkg/provider" @@ -43,19 +44,19 @@ func (s *CustomMetricsAdapterServer) InstallCustomMetricsAPI() error { Version: groupMeta.GroupVersion.Version, } apiGroup := metav1.APIGroup{ - Name: groupMeta.GroupVersion.String(), + Name: groupMeta.GroupVersion.Group, Versions: []metav1.GroupVersionForDiscovery{groupVersion}, PreferredVersion: preferredVersionForDiscovery, } cmAPI := s.cmAPI(groupMeta, &groupMeta.GroupVersion) - if err := cmAPI.InstallREST(s.GenericAPIServer.HandlerContainer.Container); err != nil { + if err := cmAPI.InstallREST(s.GenericAPIServer.Handler.GoRestfulContainer); err != nil { return err } - path := genericapiserver.APIGroupPrefix + "/" + groupMeta.GroupVersion.Group - s.GenericAPIServer.HandlerContainer.Add(genericapi.NewGroupWebService(s.GenericAPIServer.Serializer, path, apiGroup)) + s.GenericAPIServer.DiscoveryGroupManager.AddGroup(apiGroup) + s.GenericAPIServer.Handler.GoRestfulContainer.Add(discovery.NewAPIGroupHandler(s.GenericAPIServer.Serializer, apiGroup).WebService()) return nil } diff --git a/pkg/apiserver/installer/context/context.go b/pkg/apiserver/installer/context/context.go deleted file mode 100644 index 042995565..000000000 --- a/pkg/apiserver/installer/context/context.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package context - -import ( - "k8s.io/apiserver/pkg/endpoints/request" -) - -// resourceInformation holds the resource and subresource for a request in the context. -type resourceInformation struct { - resource string - subresource string -} - -// contextKey is the type of the keys for the context in this file. -// It's private to avoid conflicts across packages. -type contextKey int - -const resourceKey contextKey = iota - -// WithResourceInformation returns a copy of parent in which the resource and subresource values are set -func WithResourceInformation(parent request.Context, resource, subresource string) request.Context { - return request.WithValue(parent, resourceKey, resourceInformation{resource, subresource}) -} - -// ResourceInformationFrom returns resource and subresource on the ctx -func ResourceInformationFrom(ctx request.Context) (resource string, subresource string, ok bool) { - resourceInfo, ok := ctx.Value(resourceKey).(resourceInformation) - if !ok { - return "", "", ok - } - - return resourceInfo.resource, resourceInfo.subresource, ok -} diff --git a/pkg/apiserver/installer/installer.go b/pkg/apiserver/installer/installer.go index a56d93f53..369ac6d5a 100644 --- a/pkg/apiserver/installer/installer.go +++ b/pkg/apiserver/installer/installer.go @@ -17,32 +17,28 @@ limitations under the License. package installer import ( - "bytes" "fmt" "net/http" - "net/url" gpath "path" "reflect" "strings" "time" - "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + utilerrors "k8s.io/apimachinery/pkg/util/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apiserver/pkg/endpoints" "k8s.io/apiserver/pkg/endpoints/handlers" "k8s.io/apiserver/pkg/endpoints/handlers/negotiation" "k8s.io/apiserver/pkg/endpoints/metrics" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/endpoints/discovery" "github.com/emicklei/go-restful" - - specificcontext "k8s.io/custom-metrics-boilerplate/pkg/apiserver/installer/context" ) // NB: the contents of this file should mostly be a subset of the functionality @@ -74,7 +70,8 @@ func (g *MetricsAPIGroupVersion) InstallREST(container *restful.Container) error if lister == nil { return fmt.Errorf("must provide a dynamic lister for dynamic API groups") } - endpoints.AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, lister) + versionDiscoveryHandler := discovery.NewAPIVersionHandler(g.Serializer, g.GroupVersion, lister) + versionDiscoveryHandler.AddToWebService(ws) container.Add(ws) return utilerrors.NewAggregate(registrationErrors) } @@ -174,26 +171,11 @@ func (a *MetricsAPIInstaller) registerResourceHandlers(storage rest.Storage, ws return err } - ctxFn := func(req *restful.Request) request.Context { - var ctx request.Context - if ctx != nil { - if existingCtx, ok := context.Get(req.Request); ok { - ctx = existingCtx - } + ctxFn := func(req *http.Request) request.Context { + if ctx, ok := context.Get(req); ok { + return request.WithUserAgent(ctx, req.Header.Get("User-Agent")) } - if ctx == nil { - ctx = request.NewContext() - } - - ctx = request.WithUserAgent(ctx, req.HeaderParameter("User-Agent")) - - // inject the resource, subresource, and name here so that - // we don't have to write custom handler logic - resource := req.PathParameter("resource") - subresource := req.PathParameter("subresource") - ctx = specificcontext.WithResourceInformation(ctx, resource, subresource) - - return ctx + return request.WithUserAgent(request.NewContext(), req.Header.Get("User-Agent")) } scope := mapping.Scope @@ -218,52 +200,12 @@ func (a *MetricsAPIInstaller) registerResourceHandlers(storage rest.Storage, ws subresourceParam, } namespacedPath := scope.ParamName() + "/{" + scope.ArgumentName() + "}/{resource}/{name}/{subresource}" - namespacedPathPrefix := gpath.Join(a.prefix, scope.ParamName()) + "/" - itemPathFn := func(name, namespace, resource, subresource string) bytes.Buffer { - var buf bytes.Buffer - buf.WriteString(namespacedPathPrefix) - buf.WriteString(url.QueryEscape(namespace)) - buf.WriteString("/") - buf.WriteString(url.QueryEscape(resource)) - buf.WriteString("/") - buf.WriteString(url.QueryEscape(name)) - buf.WriteString("/") - buf.WriteString(url.QueryEscape(subresource)) - return buf - } namespaceSpecificPath := scope.ParamName() + "/{" + scope.ArgumentName() + "}/metrics/{name}" namespaceSpecificParams := []*restful.Parameter{ namespaceParam, nameParam, } - namespaceSpecificItemPathFn := func(name, namespace, resource, subresource string) bytes.Buffer { - var buf bytes.Buffer - buf.WriteString(namespacedPathPrefix) - buf.WriteString(url.QueryEscape(namespace)) - buf.WriteString("/metrics/") - buf.WriteString(url.QueryEscape(name)) - return buf - } - namespaceSpecificCtxFn := func(req *restful.Request) request.Context { - var ctx request.Context - if ctx != nil { - if existingCtx, ok := context.Get(req.Request); ok { - ctx = existingCtx - } - } - if ctx == nil { - ctx = request.NewContext() - } - - ctx = request.WithUserAgent(ctx, req.HeaderParameter("User-Agent")) - - // inject the resource, subresource, and name here so that - // we don't have to write custom handler logic - ctx = specificcontext.WithResourceInformation(ctx, "metrics", "") - - return ctx - } mediaTypes, streamMediaTypes := negotiation.MediaTypesForSerializer(a.group.Serializer) allMediaTypes := append(mediaTypes, streamMediaTypes...) @@ -292,8 +234,16 @@ func (a *MetricsAPIInstaller) registerResourceHandlers(storage rest.Storage, ws // we need one path for namespaced resources, one for non-namespaced resources doc := "list custom metrics describing an object or objects" - reqScope.Namer = rootScopeNaming{scope, a.group.Linker, gpath.Join(a.prefix, rootScopedPath, "/"), "/{subresource}"} - rootScopedHandler := metrics.InstrumentRouteFunc("LIST", "custom-metrics", handlers.ListResource(lister, nil, reqScope, false, a.minRequestTimeout)) + reqScope.Namer = MetricsNaming{ + handlers.ContextBasedNaming{ + GetContext: ctxFn, + SelfLinker: a.group.Linker, + ClusterScoped: true, + SelfLinkPathPrefix: a.prefix + "/", + }, + } + + rootScopedHandler := metrics.InstrumentRouteFunc("LIST", "custom-metrics", restfulListResource(lister, nil, reqScope, false, a.minRequestTimeout)) // install the root-scoped route rootScopedRoute := ws.GET(rootScopedPath).To(rootScopedHandler). @@ -310,8 +260,15 @@ func (a *MetricsAPIInstaller) registerResourceHandlers(storage rest.Storage, ws ws.Route(rootScopedRoute) // install the namespace-scoped route - reqScope.Namer = scopeNaming{scope, a.group.Linker, itemPathFn, false} - namespacedHandler := metrics.InstrumentRouteFunc("LIST", "custom-metrics-namespaced", handlers.ListResource(lister, nil, reqScope, false, a.minRequestTimeout)) + reqScope.Namer = MetricsNaming{ + handlers.ContextBasedNaming{ + GetContext: ctxFn, + SelfLinker: a.group.Linker, + ClusterScoped: false, + SelfLinkPathPrefix: gpath.Join(a.prefix, scope.ParamName()) + "/", + }, + } + namespacedHandler := metrics.InstrumentRouteFunc("LIST", "custom-metrics-namespaced", restfulListResource(lister, nil, reqScope, false, a.minRequestTimeout)) namespacedRoute := ws.GET(namespacedPath).To(namespacedHandler). Doc(doc). Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). @@ -326,9 +283,16 @@ func (a *MetricsAPIInstaller) registerResourceHandlers(storage rest.Storage, ws ws.Route(namespacedRoute) // install the special route for metrics describing namespaces (last b/c we modify the context func) - reqScope.ContextFunc = namespaceSpecificCtxFn - reqScope.Namer = scopeNaming{scope, a.group.Linker, namespaceSpecificItemPathFn, false} - namespaceSpecificHandler := metrics.InstrumentRouteFunc("LIST", "custom-metrics-for-namespace", handlers.ListResource(lister, nil, reqScope, false, a.minRequestTimeout)) + reqScope.ContextFunc = ctxFn + reqScope.Namer = MetricsNaming{ + handlers.ContextBasedNaming{ + GetContext: ctxFn, + SelfLinker: a.group.Linker, + ClusterScoped: false, + SelfLinkPathPrefix: gpath.Join(a.prefix, scope.ParamName()) + "/", + }, + } + namespaceSpecificHandler := metrics.InstrumentRouteFunc("LIST", "custom-metrics-for-namespace", restfulListResource(lister, nil, reqScope, false, a.minRequestTimeout)) namespaceSpecificRoute := ws.GET(namespaceSpecificPath).To(namespaceSpecificHandler). Doc(doc). Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). @@ -474,160 +438,39 @@ func typeToJSON(typeName string) string { } } -// rootScopeNaming reads only names from a request and ignores namespaces. It implements ScopeNamer -// for root scoped resources. -type rootScopeNaming struct { - scope meta.RESTScope - runtime.SelfLinker - pathPrefix string - pathSuffix string -} - -// rootScopeNaming implements ScopeNamer -var _ handlers.ScopeNamer = rootScopeNaming{} - -// Namespace returns an empty string because root scoped objects have no namespace. -func (n rootScopeNaming) Namespace(req *restful.Request) (namespace string, err error) { - return "", nil -} - -// Name returns the name from the path and an empty string for namespace, or an error if the -// name is empty. -func (n rootScopeNaming) Name(req *restful.Request) (namespace, name string, err error) { - name = req.PathParameter("name") - if len(name) == 0 { - return "", "", errEmptyName - } - return "", name, nil -} - -// GenerateLink returns the appropriate path and query to locate an object by its canonical path. -func (n rootScopeNaming) GenerateLink(req *restful.Request, obj runtime.Object) (uri string, err error) { - _, name, err := n.ObjectName(obj) - if err != nil { - return "", err - } - if len(name) == 0 { - _, name, err = n.Name(req) - if err != nil { - return "", err - } - } - return n.pathPrefix + url.QueryEscape(name) + n.pathSuffix, nil -} - -// GenerateListLink returns the appropriate path and query to locate a list by its canonical path. -func (n rootScopeNaming) GenerateListLink(req *restful.Request) (uri string, err error) { - if len(req.Request.URL.RawPath) > 0 { - return req.Request.URL.RawPath, nil - } - return req.Request.URL.EscapedPath(), nil -} - -// ObjectName returns the name set on the object, or an error if the -// name cannot be returned. Namespace is empty -// TODO: distinguish between objects with name/namespace and without via a specific error. -func (n rootScopeNaming) ObjectName(obj runtime.Object) (namespace, name string, err error) { - name, err = n.SelfLinker.Name(obj) - if err != nil { - return "", "", err - } - if len(name) == 0 { - return "", "", errEmptyName - } - return "", name, nil +// An interface to see if an object supports swagger documentation as a method +type documentable interface { + SwaggerDoc() map[string]string } -// scopeNaming returns naming information from a request. It implements ScopeNamer for -// namespace scoped resources. -type scopeNaming struct { - scope meta.RESTScope - runtime.SelfLinker - itemPathFn func(name, namespace, resource, subresource string) bytes.Buffer - allNamespaces bool +// MetricsNaming is similar to handlers.ContextBasedNaming, except that it handles +// polymorphism over subresources. +type MetricsNaming struct { + handlers.ContextBasedNaming } -// scopeNaming implements ScopeNamer -var _ handlers.ScopeNamer = scopeNaming{} - -// Namespace returns the namespace from the path or the default. -func (n scopeNaming) Namespace(req *restful.Request) (namespace string, err error) { - if n.allNamespaces { - return "", nil +func (n MetricsNaming) GenerateLink(req *http.Request, obj runtime.Object) (uri string, err error) { + requestInfo, ok := request.RequestInfoFrom(n.GetContext(req)) + if !ok { + return "", fmt.Errorf("missing requestInfo") } - namespace = req.PathParameter(n.scope.ArgumentName()) - if len(namespace) == 0 { - // a URL was constructed without the namespace, or this method was invoked - // on an object without a namespace path parameter. - return "", fmt.Errorf("no namespace parameter found on request") - } - return namespace, nil -} -// Name returns the name from the path, the namespace (or default), or an error if the -// name is empty. -func (n scopeNaming) Name(req *restful.Request) (namespace, name string, err error) { - namespace, _ = n.Namespace(req) - name = req.PathParameter("name") - if len(name) == 0 { - return "", "", errEmptyName + if requestInfo.Resource != "metrics" { + n.SelfLinkPathSuffix += "/" + requestInfo.Subresource } - return -} - -func (n scopeNaming) reqResource(req *restful.Request) (resource, subresource string) { - return req.PathParameter("resource"), req.PathParameter("subresource") -} -// GenerateLink returns the appropriate path and query to locate an object by its canonical path. -func (n scopeNaming) GenerateLink(req *restful.Request, obj runtime.Object) (uri string, err error) { - namespace, name, err := n.ObjectName(obj) - if err != nil { - return "", err + // since this is not a pointer receiver, it's ok to modify it here + // (since we copy around every method call) + if n.ClusterScoped { + n.SelfLinkPathPrefix += requestInfo.Resource + "/" + return n.ContextBasedNaming.GenerateLink(req, obj) } - if len(namespace) == 0 && len(name) == 0 { - namespace, name, err = n.Name(req) - if err != nil { - return "", err - } - } - if len(name) == 0 { - return "", errEmptyName - } - - resource, subresource := n.reqResource(req) - result := n.itemPathFn(name, namespace, resource, subresource) - return result.String(), nil + return n.ContextBasedNaming.GenerateLink(req, obj) } -// GenerateListLink returns the appropriate path and query to locate a list by its canonical path. -func (n scopeNaming) GenerateListLink(req *restful.Request) (uri string, err error) { - if len(req.Request.URL.RawPath) > 0 { - return req.Request.URL.RawPath, nil - } - return req.Request.URL.EscapedPath(), nil -} - -// ObjectName returns the name and namespace set on the object, or an error if the -// name cannot be returned. -// TODO: distinguish between objects with name/namespace and without via a specific error. -func (n scopeNaming) ObjectName(obj runtime.Object) (namespace, name string, err error) { - name, err = n.SelfLinker.Name(obj) - if err != nil { - return "", "", err +func restfulListResource(r rest.Lister, rw rest.Watcher, scope handlers.RequestScope, forceWatch bool, minRequestTimeout time.Duration) restful.RouteFunction { + return func(req *restful.Request, res *restful.Response) { + handlers.ListResource(r, rw, scope, forceWatch, minRequestTimeout)(res.ResponseWriter, req.Request) } - namespace, err = n.SelfLinker.Namespace(obj) - if err != nil { - return "", "", err - } - return namespace, name, err -} - -// An interface to see if an object supports swagger documentation as a method -type documentable interface { - SwaggerDoc() map[string]string } - -// errEmptyName is returned when API requests do not fill the name section of the path. -var errEmptyName = errors.NewBadRequest("name must be provided") diff --git a/pkg/provider/resource_lister.go b/pkg/provider/resource_lister.go index dab46e233..783f57f34 100644 --- a/pkg/provider/resource_lister.go +++ b/pkg/provider/resource_lister.go @@ -17,7 +17,7 @@ limitations under the License. package provider import ( - "k8s.io/apiserver/pkg/endpoints/handlers" + "k8s.io/apiserver/pkg/endpoints/discovery" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -25,7 +25,7 @@ type customMetricsResourceLister struct { provider CustomMetricsProvider } -func NewResourceLister(provider CustomMetricsProvider) handlers.APIResourceLister { +func NewResourceLister(provider CustomMetricsProvider) discovery.APIResourceLister { return &customMetricsResourceLister{ provider: provider, } diff --git a/pkg/registry/custom_metrics/reststorage.go b/pkg/registry/custom_metrics/reststorage.go index afcc2e427..8e7d74766 100644 --- a/pkg/registry/custom_metrics/reststorage.go +++ b/pkg/registry/custom_metrics/reststorage.go @@ -25,9 +25,9 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - specificinstaller "k8s.io/custom-metrics-boilerplate/pkg/apiserver/installer/context" "k8s.io/custom-metrics-boilerplate/pkg/provider" "k8s.io/metrics/pkg/apis/custom_metrics" + "k8s.io/apiserver/pkg/endpoints/request" ) type REST struct { @@ -74,11 +74,14 @@ func (r *REST) List(ctx genericapirequest.Context, options *metainternalversion. namespace := genericapirequest.NamespaceValue(ctx) - resourceRaw, metricName, ok := specificinstaller.ResourceInformationFrom(ctx) + requestInfo, ok := request.RequestInfoFrom(ctx) if !ok { return nil, fmt.Errorf("unable to get resource and metric name from request") } + resourceRaw := requestInfo.Resource + metricName := requestInfo.Subresource + groupResource := schema.ParseGroupResource(resourceRaw) // handle metrics describing namespaces