category expansion can only come from the server

pull/8/head
David Eads 2018-05-09 11:12:32 -04:00
parent 37f6cb7230
commit ad87219b2c
17 changed files with 66 additions and 162 deletions

View File

@ -134,7 +134,6 @@ pkg/kubeapiserver/authorizer/modes
pkg/kubeapiserver/options pkg/kubeapiserver/options
pkg/kubeapiserver/server pkg/kubeapiserver/server
pkg/kubectl pkg/kubectl
pkg/kubectl/categories
pkg/kubectl/cmd pkg/kubectl/cmd
pkg/kubectl/cmd/auth pkg/kubectl/cmd/auth
pkg/kubectl/cmd/config pkg/kubectl/cmd/config

View File

@ -200,7 +200,6 @@ filegroup(
":package-srcs", ":package-srcs",
"//pkg/kubectl/apply:all-srcs", "//pkg/kubectl/apply:all-srcs",
"//pkg/kubectl/apps:all-srcs", "//pkg/kubectl/apps:all-srcs",
"//pkg/kubectl/categories:all-srcs",
"//pkg/kubectl/cmd:all-srcs", "//pkg/kubectl/cmd:all-srcs",
"//pkg/kubectl/explain:all-srcs", "//pkg/kubectl/explain:all-srcs",
"//pkg/kubectl/genericclioptions:all-srcs", "//pkg/kubectl/genericclioptions:all-srcs",

View File

@ -1,26 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["categories.go"],
importpath = "k8s.io/kubernetes/pkg/kubectl/categories",
visibility = ["//visibility:public"],
deps = [
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/client-go/restmapper:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -1,46 +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 categories
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/restmapper"
)
// legacyUserResources are the resource names that apply to the primary, user facing resources used by
// client tools. They are in deletion-first order - dependent resources should be last.
// Should remain exported in order to expose a current list of resources to downstream
// composition that wants to build on the concept of 'all' for their CLIs.
var legacyUserResources = []schema.GroupResource{
{Group: "", Resource: "pods"},
{Group: "", Resource: "replicationcontrollers"},
{Group: "", Resource: "services"},
{Group: "apps", Resource: "statefulsets"},
{Group: "autoscaling", Resource: "horizontalpodautoscalers"},
{Group: "batch", Resource: "jobs"},
{Group: "batch", Resource: "cronjobs"},
{Group: "extensions", Resource: "daemonsets"},
{Group: "extensions", Resource: "deployments"},
{Group: "extensions", Resource: "replicasets"},
}
// LegacyCategoryExpander is the old hardcoded expansion
var LegacyCategoryExpander restmapper.CategoryExpander = restmapper.SimpleCategoryExpander{
Expansions: map[string][]schema.GroupResource{
"all": legacyUserResources,
},
}

View File

@ -13,7 +13,6 @@ go_library(
"//pkg/apis/core:go_default_library", "//pkg/apis/core:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/kubectl:go_default_library", "//pkg/kubectl:go_default_library",
"//pkg/kubectl/categories:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/cmd/util/openapi:go_default_library", "//pkg/kubectl/cmd/util/openapi:go_default_library",
"//pkg/kubectl/cmd/util/openapi/testing:go_default_library", "//pkg/kubectl/cmd/util/openapi/testing:go_default_library",

View File

@ -47,7 +47,6 @@ import (
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/categories"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
openapitesting "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing" openapitesting "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/testing"
@ -287,8 +286,8 @@ func (f *TestFactory) Cleanup() {
os.Remove(f.tempConfigFile.Name()) os.Remove(f.tempConfigFile.Name())
} }
func (f *TestFactory) CategoryExpander() restmapper.CategoryExpander { func (f *TestFactory) CategoryExpander() (restmapper.CategoryExpander, error) {
return categories.LegacyCategoryExpander return resource.FakeCategoryExpander, nil
} }
func (f *TestFactory) ClientConfig() (*restclient.Config, error) { func (f *TestFactory) ClientConfig() (*restclient.Config, error) {

View File

@ -26,7 +26,6 @@ go_library(
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/controller:go_default_library", "//pkg/controller:go_default_library",
"//pkg/kubectl:go_default_library", "//pkg/kubectl:go_default_library",
"//pkg/kubectl/categories:go_default_library",
"//pkg/kubectl/cmd/templates:go_default_library", "//pkg/kubectl/cmd/templates:go_default_library",
"//pkg/kubectl/cmd/util/openapi:go_default_library", "//pkg/kubectl/cmd/util/openapi:go_default_library",
"//pkg/kubectl/cmd/util/openapi/validation:go_default_library", "//pkg/kubectl/cmd/util/openapi/validation:go_default_library",
@ -97,7 +96,6 @@ go_test(
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library", "//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
"//pkg/controller:go_default_library", "//pkg/controller:go_default_library",
"//pkg/kubectl:go_default_library", "//pkg/kubectl:go_default_library",
"//pkg/kubectl/categories:go_default_library",
"//pkg/kubectl/resource:go_default_library", "//pkg/kubectl/resource:go_default_library",
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library", "//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library",

View File

@ -169,7 +169,7 @@ type ClientAccessFactory interface {
// Generally they provide object typing and functions that build requests based on the negotiated clients. // Generally they provide object typing and functions that build requests based on the negotiated clients.
type ObjectMappingFactory interface { type ObjectMappingFactory interface {
// Returns interface for expanding categories like `all`. // Returns interface for expanding categories like `all`.
CategoryExpander() restmapper.CategoryExpander CategoryExpander() (restmapper.CategoryExpander, error)
// Returns a RESTClient for working with the specified RESTMapping or an error. This is intended // Returns a RESTClient for working with the specified RESTMapping or an error. This is intended
// for working with arbitrary resources and is not guaranteed to point to a Kubernetes APIServer. // for working with arbitrary resources and is not guaranteed to point to a Kubernetes APIServer.
ClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) ClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error)

View File

@ -46,13 +46,15 @@ func NewBuilderFactory(clientAccessFactory ClientAccessFactory, objectMappingFac
// NewBuilder returns a new resource builder for structured api objects. // NewBuilder returns a new resource builder for structured api objects.
func (f *ring2Factory) NewBuilder() *resource.Builder { func (f *ring2Factory) NewBuilder() *resource.Builder {
mapper, mapperErr := f.clientAccessFactory.RESTMapper() mapper, mapperErr := f.clientAccessFactory.RESTMapper()
categoryExpander, categoryExpanderError := f.objectMappingFactory.CategoryExpander()
categoryExpander := f.objectMappingFactory.CategoryExpander()
return resource.NewBuilder( return resource.NewBuilder(
f.clientAccessFactory.ClientConfig, f.clientAccessFactory.ClientConfig,
mapper, mapper,
categoryExpander, categoryExpander,
).AddError(mapperErr) ).
AddError(mapperErr).
AddError(categoryExpanderError)
} }
// PluginLoader loads plugins from a path set by the KUBECTL_PLUGINS_PATH env var. // PluginLoader loads plugins from a path set by the KUBECTL_PLUGINS_PATH env var.

View File

@ -48,7 +48,6 @@ import (
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/categories"
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
openapivalidation "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/validation" openapivalidation "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/validation"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
@ -76,23 +75,13 @@ func NewObjectMappingFactory(clientAccessFactory ClientAccessFactory) ObjectMapp
return f return f
} }
func (f *ring1Factory) CategoryExpander() restmapper.CategoryExpander { func (f *ring1Factory) CategoryExpander() (restmapper.CategoryExpander, error) {
legacyExpander := categories.LegacyCategoryExpander
discoveryClient, err := f.clientAccessFactory.DiscoveryClient() discoveryClient, err := f.clientAccessFactory.DiscoveryClient()
if err == nil { if err != nil {
// fallback is the legacy expander wrapped with discovery based filtering return nil, err
fallbackExpander, err := restmapper.NewDiscoveryFilteredExpander(legacyExpander, discoveryClient)
CheckErr(err)
// by default use the expander that discovers based on "categories" field from the API
discoveryCategoryExpander, err := restmapper.NewDiscoveryCategoryExpander(fallbackExpander, discoveryClient)
CheckErr(err)
return discoveryCategoryExpander
} }
return legacyExpander return restmapper.NewDiscoveryCategoryExpander(discoveryClient)
} }
func (f *ring1Factory) ClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) { func (f *ring1Factory) ClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) {

View File

@ -40,7 +40,6 @@ import (
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/categories"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
) )
@ -472,7 +471,7 @@ func TestDiscoveryReplaceAliases(t *testing.T) {
ds := &fakeDiscoveryClient{} ds := &fakeDiscoveryClient{}
mapper := restmapper.NewShortcutExpander(testrestmapper.TestOnlyStaticRESTMapper(legacyscheme.Scheme), ds) mapper := restmapper.NewShortcutExpander(testrestmapper.TestOnlyStaticRESTMapper(legacyscheme.Scheme), ds)
b := resource.NewFakeBuilder(fakeClient(), mapper, categories.LegacyCategoryExpander) b := resource.NewFakeBuilder(fakeClient(), mapper, resource.FakeCategoryExpander)
for _, test := range tests { for _, test := range tests {
replaced := b.ReplaceAliases(test.arg) replaced := b.ReplaceAliases(test.arg)

View File

@ -10,6 +10,7 @@ go_library(
"builder.go", "builder.go",
"client.go", "client.go",
"doc.go", "doc.go",
"fake.go",
"helper.go", "helper.go",
"interfaces.go", "interfaces.go",
"mapper.go", "mapper.go",
@ -61,7 +62,6 @@ go_test(
embed = [":go_default_library"], embed = [":go_default_library"],
deps = [ deps = [
"//pkg/apis/core/install:go_default_library", "//pkg/apis/core/install:go_default_library",
"//pkg/kubectl/categories:go_default_library",
"//pkg/kubectl/scheme:go_default_library", "//pkg/kubectl/scheme:go_default_library",
"//vendor/github.com/davecgh/go-spew/spew:go_default_library", "//vendor/github.com/davecgh/go-spew/spew:go_default_library",
"//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/github.com/ghodss/yaml:go_default_library",

View File

@ -543,6 +543,9 @@ func (b *Builder) ResourceTypeOrNameArgs(allowEmptySelector bool, args ...string
func (b *Builder) ReplaceAliases(input string) string { func (b *Builder) ReplaceAliases(input string) string {
replaced := []string{} replaced := []string{}
for _, arg := range strings.Split(input, ",") { for _, arg := range strings.Split(input, ",") {
if b.categoryExpander == nil {
continue
}
if resources, ok := b.categoryExpander.Expand(arg); ok { if resources, ok := b.categoryExpander.Expand(arg); ok {
asStrings := []string{} asStrings := []string{}
for _, resource := range resources { for _, resource := range resources {

View File

@ -47,7 +47,6 @@ import (
"k8s.io/client-go/rest/fake" "k8s.io/client-go/rest/fake"
restclientwatch "k8s.io/client-go/rest/watch" restclientwatch "k8s.io/client-go/rest/watch"
utiltesting "k8s.io/client-go/util/testing" utiltesting "k8s.io/client-go/util/testing"
"k8s.io/kubernetes/pkg/kubectl/categories"
"k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/scheme"
// install the pod scheme into the legacy scheme for test typer resolution // install the pod scheme into the legacy scheme for test typer resolution
@ -59,7 +58,6 @@ var (
corev1GV = schema.GroupVersion{Version: "v1"} corev1GV = schema.GroupVersion{Version: "v1"}
corev1Codec = scheme.Codecs.CodecForVersions(scheme.Codecs.LegacyCodec(corev1GV), scheme.Codecs.UniversalDecoder(corev1GV), corev1GV, corev1GV) corev1Codec = scheme.Codecs.CodecForVersions(scheme.Codecs.LegacyCodec(corev1GV), scheme.Codecs.UniversalDecoder(corev1GV), corev1GV, corev1GV)
metaAccessor = meta.NewAccessor() metaAccessor = meta.NewAccessor()
restmapper = testrestmapper.TestOnlyStaticRESTMapper(scheme.Scheme)
) )
func stringBody(body string) io.ReadCloser { func stringBody(body string) io.ReadCloser {
@ -273,7 +271,7 @@ func newDefaultBuilder() *Builder {
} }
func newDefaultBuilderWith(fakeClientFn FakeClientFunc) *Builder { func newDefaultBuilderWith(fakeClientFn FakeClientFunc) *Builder {
return NewFakeBuilder(fakeClientFn, restmapper, categories.LegacyCategoryExpander). return NewFakeBuilder(fakeClientFn, testrestmapper.TestOnlyStaticRESTMapper(scheme.Scheme), FakeCategoryExpander).
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...) WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...)
} }

View File

@ -0,0 +1,40 @@
/*
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 resource
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/restmapper"
)
// FakeCategoryExpander is for testing only
var FakeCategoryExpander restmapper.CategoryExpander = restmapper.SimpleCategoryExpander{
Expansions: map[string][]schema.GroupResource{
"all": {
{Group: "", Resource: "pods"},
{Group: "", Resource: "replicationcontrollers"},
{Group: "", Resource: "services"},
{Group: "apps", Resource: "statefulsets"},
{Group: "autoscaling", Resource: "horizontalpodautoscalers"},
{Group: "batch", Resource: "jobs"},
{Group: "batch", Resource: "cronjobs"},
{Group: "extensions", Resource: "daemonsets"},
{Group: "extensions", Resource: "deployments"},
{Group: "extensions", Resource: "replicasets"},
},
},
}

View File

@ -33,6 +33,7 @@ type SimpleCategoryExpander struct {
Expansions map[string][]schema.GroupResource Expansions map[string][]schema.GroupResource
} }
// Expand fulfills CategoryExpander
func (e SimpleCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) { func (e SimpleCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) {
ret, ok := e.Expansions[category] ret, ok := e.Expansions[category]
return ret, ok return ret, ok
@ -41,33 +42,32 @@ func (e SimpleCategoryExpander) Expand(category string) ([]schema.GroupResource,
// discoveryCategoryExpander struct lets a REST Client wrapper (discoveryClient) to retrieve list of APIResourceList, // discoveryCategoryExpander struct lets a REST Client wrapper (discoveryClient) to retrieve list of APIResourceList,
// and then convert to fallbackExpander // and then convert to fallbackExpander
type discoveryCategoryExpander struct { type discoveryCategoryExpander struct {
fallbackExpander CategoryExpander discoveryClient discovery.DiscoveryInterface
discoveryClient discovery.DiscoveryInterface
} }
// NewDiscoveryCategoryExpander returns a category expander that makes use of the "categories" fields from // NewDiscoveryCategoryExpander returns a category expander that makes use of the "categories" fields from
// the API, found through the discovery client. In case of any error or no category found (which likely // the API, found through the discovery client. In case of any error or no category found (which likely
// means we're at a cluster prior to categories support, fallback to the expander provided. // means we're at a cluster prior to categories support, fallback to the expander provided.
func NewDiscoveryCategoryExpander(fallbackExpander CategoryExpander, client discovery.DiscoveryInterface) (discoveryCategoryExpander, error) { func NewDiscoveryCategoryExpander(client discovery.DiscoveryInterface) (CategoryExpander, error) {
if client == nil { if client == nil {
panic("Please provide discovery client to shortcut expander") panic("Please provide discovery client to shortcut expander")
} }
return discoveryCategoryExpander{fallbackExpander: fallbackExpander, discoveryClient: client}, nil return discoveryCategoryExpander{discoveryClient: client}, nil
} }
// Expand fulfills CategoryExpander
func (e discoveryCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) { func (e discoveryCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) {
// Get all supported resources for groups and versions from server, if no resource found, fallback anyway. // Get all supported resources for groups and versions from server, if no resource found, fallback anyway.
apiResourceLists, _ := e.discoveryClient.ServerResources() apiResourceLists, _ := e.discoveryClient.ServerResources()
if len(apiResourceLists) == 0 { if len(apiResourceLists) == 0 {
return e.fallbackExpander.Expand(category) return nil, false
} }
discoveredExpansions := map[string][]schema.GroupResource{} discoveredExpansions := map[string][]schema.GroupResource{}
for _, apiResourceList := range apiResourceLists { for _, apiResourceList := range apiResourceLists {
gv, err := schema.ParseGroupVersion(apiResourceList.GroupVersion) gv, err := schema.ParseGroupVersion(apiResourceList.GroupVersion)
if err != nil { if err != nil {
return e.fallbackExpander.Expand(category) continue
} }
// Collect GroupVersions by categories // Collect GroupVersions by categories
for _, apiResource := range apiResourceList.APIResources { for _, apiResource := range apiResourceList.APIResources {
@ -83,64 +83,15 @@ func (e discoveryCategoryExpander) Expand(category string) ([]schema.GroupResour
} }
} }
if len(discoveredExpansions) == 0 {
// We don't know if the server really don't have any resource with categories,
// or we're on a cluster version prior to categories support. Anyways, fallback.
return e.fallbackExpander.Expand(category)
}
ret, ok := discoveredExpansions[category] ret, ok := discoveredExpansions[category]
return ret, ok return ret, ok
} }
// discoveryFilteredExpander expands the given CategoryExpander (delegate) to filter group and resource returned from server
type discoveryFilteredExpander struct {
delegate CategoryExpander
discoveryClient discovery.DiscoveryInterface
}
// NewDiscoveryFilteredExpander returns a category expander that filters the returned groupresources
// by what the server has available
func NewDiscoveryFilteredExpander(delegate CategoryExpander, client discovery.DiscoveryInterface) (discoveryFilteredExpander, error) {
if client == nil {
panic("Please provide discovery client to shortcut expander")
}
return discoveryFilteredExpander{delegate: delegate, discoveryClient: client}, nil
}
func (e discoveryFilteredExpander) Expand(category string) ([]schema.GroupResource, bool) {
delegateExpansion, ok := e.delegate.Expand(category)
// Check if we have access to server resources
apiResources, err := e.discoveryClient.ServerResources()
if err != nil {
return delegateExpansion, ok
}
availableResources, err := discovery.GroupVersionResources(apiResources)
if err != nil {
return delegateExpansion, ok
}
available := []schema.GroupResource{}
for _, requestedResource := range delegateExpansion {
for availableResource := range availableResources {
if requestedResource.Group == availableResource.Group &&
requestedResource.Resource == availableResource.Resource {
available = append(available, requestedResource)
break
}
}
}
return available, ok
}
// UnionCategoryExpander implements CategoryExpander interface. // UnionCategoryExpander implements CategoryExpander interface.
// It maps given category string to union of expansions returned by all the CategoryExpanders in the list. // It maps given category string to union of expansions returned by all the CategoryExpanders in the list.
type UnionCategoryExpander []CategoryExpander type UnionCategoryExpander []CategoryExpander
// Expand fulfills CategoryExpander
func (u UnionCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) { func (u UnionCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) {
ret := []schema.GroupResource{} ret := []schema.GroupResource{}
ok := false ok := false

View File

@ -135,7 +135,7 @@ func TestDiscoveryCategoryExpander(t *testing.T) {
dc.serverResourcesHandler = func() ([]*metav1.APIResourceList, error) { dc.serverResourcesHandler = func() ([]*metav1.APIResourceList, error) {
return test.serverResponse, nil return test.serverResponse, nil
} }
expander, err := NewDiscoveryCategoryExpander(SimpleCategoryExpander{}, dc) expander, err := NewDiscoveryCategoryExpander(dc)
if err != nil { if err != nil {
t.Fatalf("unexpected error %v", err) t.Fatalf("unexpected error %v", err)
} }