Merge pull request #63602 from deads2k/cli-54-categories

Automatic merge from submit-queue (batch tested with PRs 59284, 63602). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

 category expansion can only come from the server

A couple release ago we moved category expansion to the server instead of hardcoding it in the client.  Similar to restmappings, there is no valid client-side expansion anymore, so this removes the code that hardcoded the list and moves the category expansion to the same package as our discovery based restmappers.

@kubernetes/sig-cli-maintainers 

```release-note
NONE
```
This commit is contained in:
Kubernetes Submit Queue 2018-05-09 13:51:13 -07:00 committed by GitHub
commit ca92b73a65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 96 additions and 219 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,41 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
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/discovery:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["categories_test.go"],
embed = [":go_default_library"],
deps = [
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
"//vendor/k8s.io/client-go/discovery:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/rest/fake: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

@ -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() categories.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) {
@ -335,6 +334,7 @@ func (f *TestFactory) Command(*cobra.Command, bool) string {
func (f *TestFactory) NewBuilder() *resource.Builder { func (f *TestFactory) NewBuilder() *resource.Builder {
mapper, err := f.RESTMapper() mapper, err := f.RESTMapper()
categoryExpander, err2 := f.CategoryExpander()
return resource.NewFakeBuilder( return resource.NewFakeBuilder(
func(version schema.GroupVersion) (resource.RESTClient, error) { func(version schema.GroupVersion) (resource.RESTClient, error) {
@ -347,8 +347,8 @@ func (f *TestFactory) NewBuilder() *resource.Builder {
return f.Client, nil return f.Client, nil
}, },
mapper, mapper,
f.CategoryExpander(), categoryExpander,
).AddError(err) ).AddError(err).AddError(err2)
} }
func (f *TestFactory) KubernetesClientSet() (*kubernetes.Clientset, error) { func (f *TestFactory) KubernetesClientSet() (*kubernetes.Clientset, 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

@ -36,13 +36,13 @@ import (
"k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
scaleclient "k8s.io/client-go/scale" scaleclient "k8s.io/client-go/scale"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
apiv1 "k8s.io/kubernetes/pkg/apis/core/v1" apiv1 "k8s.io/kubernetes/pkg/apis/core/v1"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"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"
"k8s.io/kubernetes/pkg/kubectl/plugins" "k8s.io/kubernetes/pkg/kubectl/plugins"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
@ -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() categories.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

@ -40,6 +40,7 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
"k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/batch"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
@ -47,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"
@ -75,23 +75,13 @@ func NewObjectMappingFactory(clientAccessFactory ClientAccessFactory) ObjectMapp
return f return f
} }
func (f *ring1Factory) CategoryExpander() categories.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 := categories.NewDiscoveryFilteredExpander(legacyExpander, discoveryClient)
CheckErr(err)
// by default use the expander that discovers based on "categories" field from the API
discoveryCategoryExpander, err := categories.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",
@ -22,7 +23,6 @@ go_library(
"//build/visible_to:pkg_kubectl_resource_CONSUMERS", "//build/visible_to:pkg_kubectl_resource_CONSUMERS",
], ],
deps = [ deps = [
"//pkg/kubectl/categories:go_default_library",
"//pkg/kubectl/validation:go_default_library", "//pkg/kubectl/validation:go_default_library",
"//vendor/golang.org/x/text/encoding/unicode:go_default_library", "//vendor/golang.org/x/text/encoding/unicode:go_default_library",
"//vendor/golang.org/x/text/transform:go_default_library", "//vendor/golang.org/x/text/transform:go_default_library",
@ -44,6 +44,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library", "//vendor/k8s.io/client-go/dynamic:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/restmapper:go_default_library",
], ],
) )
@ -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

@ -34,7 +34,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/runtime/serializer"
utilerrors "k8s.io/apimachinery/pkg/util/errors" utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/kubectl/categories" "k8s.io/client-go/restmapper"
"k8s.io/kubernetes/pkg/kubectl/validation" "k8s.io/kubernetes/pkg/kubectl/validation"
) )
@ -47,7 +47,7 @@ const defaultHttpGetAttempts int = 3
// from the command line and converting them to a list of resources to iterate // from the command line and converting them to a list of resources to iterate
// over using the Visitor interface. // over using the Visitor interface.
type Builder struct { type Builder struct {
categoryExpander categories.CategoryExpander categoryExpander restmapper.CategoryExpander
// mapper is set explicitly by resource builders // mapper is set explicitly by resource builders
mapper *mapper mapper *mapper
@ -143,7 +143,7 @@ type resourceTuple struct {
type FakeClientFunc func(version schema.GroupVersion) (RESTClient, error) type FakeClientFunc func(version schema.GroupVersion) (RESTClient, error)
func NewFakeBuilder(fakeClientFn FakeClientFunc, restMapper meta.RESTMapper, categoryExpander categories.CategoryExpander) *Builder { func NewFakeBuilder(fakeClientFn FakeClientFunc, restMapper meta.RESTMapper, categoryExpander restmapper.CategoryExpander) *Builder {
ret := NewBuilder(nil, restMapper, categoryExpander) ret := NewBuilder(nil, restMapper, categoryExpander)
ret.fakeClientFn = fakeClientFn ret.fakeClientFn = fakeClientFn
return ret return ret
@ -153,7 +153,7 @@ func NewFakeBuilder(fakeClientFn FakeClientFunc, restMapper meta.RESTMapper, cat
// internal or unstructured must be specified. // internal or unstructured must be specified.
// TODO: Add versioned client (although versioned is still lossy) // TODO: Add versioned client (although versioned is still lossy)
// TODO remove internal and unstructured mapper and instead have them set the negotiated serializer for use in the client // TODO remove internal and unstructured mapper and instead have them set the negotiated serializer for use in the client
func NewBuilder(clientConfigFn ClientConfigFunc, restMapper meta.RESTMapper, categoryExpander categories.CategoryExpander) *Builder { func NewBuilder(clientConfigFn ClientConfigFunc, restMapper meta.RESTMapper, categoryExpander restmapper.CategoryExpander) *Builder {
return &Builder{ return &Builder{
clientConfigFn: clientConfigFn, clientConfigFn: clientConfigFn,
restMapper: restMapper, restMapper: restMapper,
@ -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

@ -3,6 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library( go_library(
name = "go_default_library", name = "go_default_library",
srcs = [ srcs = [
"category_expansion.go",
"discovery.go", "discovery.go",
"shortcut.go", "shortcut.go",
], ],
@ -20,6 +21,7 @@ go_library(
go_test( go_test(
name = "go_default_test", name = "go_default_test",
srcs = [ srcs = [
"category_expansion_test.go",
"discovery_test.go", "discovery_test.go",
"shortcut_test.go", "shortcut_test.go",
], ],

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package categories package restmapper
import ( import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
@ -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
@ -166,27 +117,3 @@ func (u UnionCategoryExpander) Expand(category string) ([]schema.GroupResource,
return ret, ok return ret, ok
} }
// 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 CategoryExpander = SimpleCategoryExpander{
Expansions: map[string][]schema.GroupResource{
"all": legacyUserResources,
},
}

View File

@ -14,19 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package categories package restmapper
import ( import (
"reflect" "reflect"
"testing" "testing"
"github.com/googleapis/gnostic/OpenAPIv2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/discovery"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
) )
func TestCategoryExpansion(t *testing.T) { func TestCategoryExpansion(t *testing.T) {
@ -46,23 +41,28 @@ func TestCategoryExpansion(t *testing.T) {
name: "all-replacement", name: "all-replacement",
arg: "all", arg: "all",
expected: []schema.GroupResource{ expected: []schema.GroupResource{
{Resource: "pods"}, {Resource: "one"},
{Resource: "replicationcontrollers"}, {Resource: "two"},
{Resource: "services"}, {Resource: "three", Group: "alpha"},
{Resource: "statefulsets", Group: "apps"}, {Resource: "four", Group: "bravo"},
{Resource: "horizontalpodautoscalers", Group: "autoscaling"},
{Resource: "jobs", Group: "batch"},
{Resource: "cronjobs", Group: "batch"},
{Resource: "daemonsets", Group: "extensions"},
{Resource: "deployments", Group: "extensions"},
{Resource: "replicasets", Group: "extensions"},
}, },
expectedOk: true, expectedOk: true,
}, },
} }
for _, test := range tests { for _, test := range tests {
actual, actualOk := LegacyCategoryExpander.Expand(test.arg) simpleCategoryExpander := SimpleCategoryExpander{
Expansions: map[string][]schema.GroupResource{
"all": {
{Group: "", Resource: "one"},
{Group: "", Resource: "two"},
{Group: "alpha", Resource: "three"},
{Group: "bravo", Resource: "four"},
},
},
}
actual, actualOk := simpleCategoryExpander.Expand(test.arg)
if e, a := test.expected, actual; !reflect.DeepEqual(e, a) { if e, a := test.expected, actual; !reflect.DeepEqual(e, a) {
t.Errorf("%s: expected %s, got %s", test.name, e, a) t.Errorf("%s: expected %s, got %s", test.name, e, a)
} }
@ -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)
} }
@ -146,41 +146,3 @@ func TestDiscoveryCategoryExpander(t *testing.T) {
} }
} }
type fakeDiscoveryClient struct {
serverResourcesHandler func() ([]*metav1.APIResourceList, error)
}
var _ discovery.DiscoveryInterface = &fakeDiscoveryClient{}
func (c *fakeDiscoveryClient) RESTClient() restclient.Interface {
return &fake.RESTClient{}
}
func (c *fakeDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
return nil, nil
}
func (c *fakeDiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
return &metav1.APIResourceList{}, nil
}
func (c *fakeDiscoveryClient) ServerResources() ([]*metav1.APIResourceList, error) {
return c.serverResourcesHandler()
}
func (c *fakeDiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
return nil, nil
}
func (c *fakeDiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
return nil, nil
}
func (c *fakeDiscoveryClient) ServerVersion() (*version.Info, error) {
return &version.Info{}, nil
}
func (c *fakeDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
return &openapi_v2.Document{}, nil
}