kubectl/pkg/cmd/testing/fake.go
Arda Güçlü 507fbad069 Add shortname ambiguity warning in shortcut expander (#117668)
* Add warning handler callback function in shortcut expander

Currently, errors in client-go are propagated back to the callers via
function returns. However, there is no elegant way for just warning users.
For example, when user wants to get a resource with it's short name format
and if there are multiple resources belonging to this short name, we need to
warn user about this ambugity which one is picked and which ones are discarded.

Not only to overcome this particular case mentioned above, but also propose a
way for the possible warnings in the future, this commit adds a warningHandler
callback function in shortcutExpander.

* Add warningPrinter functionality in ConfigFlags

ConfigFlags has neither warning user in a standardized
format functionality nor passing warning callback functions to other upper level
libraries such as client-go.

This commit adds an ability that user can set warningPrinters
according to their IOStreams and this warningPrinters will be used
to raise possible warnings happening not only in cli-runtime but
also in client-go.

* Pass warning callback function in ConfigFlags to shortcutExpander

This commit passes warning callback function to print possible
warnings happened in shortcut expander to warn user in a
standardized format.

* Add integration test for CRDs having ambiguous short names

This commit adds integration test to assure that warning message
related to this ambiguity is printed when resources are being retrieved via their short name
representations in cases where multiple resources have same
short names.

This integration test also ensures that the logic behind which resource
will be selected hasn't been changed which may cause disperancies in
clusters.

* Remove defaultConfigFlag global variable

* Move default config flags initialization into function

* Skip warning for versions of same group/resource

* Run update-vendor

* Warn only once when there are multiple versions registered for ambiguous resource

* Apply gocritic review

* Add multi-resource multi-version ambiguity unit test

Kubernetes-commit: a504aed54d028dbc8ea2508142c94d309f5f1ec6
2023-10-11 19:58:38 +00:00

837 lines
29 KiB
Go

/*
Copyright 2016 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 testing
import (
"bytes"
"fmt"
"os"
"path/filepath"
"time"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/meta/testrestmapper"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/discovery"
diskcached "k8s.io/client-go/discovery/cached/disk"
"k8s.io/client-go/dynamic"
fakedynamic "k8s.io/client-go/dynamic/fake"
"k8s.io/client-go/kubernetes"
openapiclient "k8s.io/client-go/openapi"
"k8s.io/client-go/openapi/openapitest"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/client-go/restmapper"
scaleclient "k8s.io/client-go/scale"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/openapi"
openapitesting "k8s.io/kubectl/pkg/util/openapi/testing"
"k8s.io/kubectl/pkg/validation"
)
// InternalType is the schema for internal type
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type InternalType struct {
Kind string
APIVersion string
Name string
}
// ExternalType is the schema for external type
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type ExternalType struct {
Kind string `json:"kind"`
APIVersion string `json:"apiVersion"`
Name string `json:"name"`
}
// ExternalType2 is another schema for external type
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type ExternalType2 struct {
Kind string `json:"kind"`
APIVersion string `json:"apiVersion"`
Name string `json:"name"`
}
// GetObjectKind returns the ObjectKind schema
func (obj *InternalType) GetObjectKind() schema.ObjectKind { return obj }
// SetGroupVersionKind sets the version and kind
func (obj *InternalType) SetGroupVersionKind(gvk schema.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
}
// GroupVersionKind returns GroupVersionKind schema
func (obj *InternalType) GroupVersionKind() schema.GroupVersionKind {
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}
// GetObjectKind returns the ObjectKind schema
func (obj *ExternalType) GetObjectKind() schema.ObjectKind { return obj }
// SetGroupVersionKind returns the GroupVersionKind schema
func (obj *ExternalType) SetGroupVersionKind(gvk schema.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
}
// GroupVersionKind returns the GroupVersionKind schema
func (obj *ExternalType) GroupVersionKind() schema.GroupVersionKind {
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}
// GetObjectKind returns the ObjectKind schema
func (obj *ExternalType2) GetObjectKind() schema.ObjectKind { return obj }
// SetGroupVersionKind sets the API version and obj kind from schema
func (obj *ExternalType2) SetGroupVersionKind(gvk schema.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
}
// GroupVersionKind returns the FromAPIVersionAndKind schema
func (obj *ExternalType2) GroupVersionKind() schema.GroupVersionKind {
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}
// NewInternalType returns an initialized InternalType instance
func NewInternalType(kind, apiversion, name string) *InternalType {
item := InternalType{Kind: kind,
APIVersion: apiversion,
Name: name}
return &item
}
func convertInternalTypeToExternalType(in *InternalType, out *ExternalType, s conversion.Scope) error {
out.Kind = in.Kind
out.APIVersion = in.APIVersion
out.Name = in.Name
return nil
}
func convertInternalTypeToExternalType2(in *InternalType, out *ExternalType2, s conversion.Scope) error {
out.Kind = in.Kind
out.APIVersion = in.APIVersion
out.Name = in.Name
return nil
}
func convertExternalTypeToInternalType(in *ExternalType, out *InternalType, s conversion.Scope) error {
out.Kind = in.Kind
out.APIVersion = in.APIVersion
out.Name = in.Name
return nil
}
func convertExternalType2ToInternalType(in *ExternalType2, out *InternalType, s conversion.Scope) error {
out.Kind = in.Kind
out.APIVersion = in.APIVersion
out.Name = in.Name
return nil
}
// InternalNamespacedType schema for internal namespaced types
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type InternalNamespacedType struct {
Kind string
APIVersion string
Name string
Namespace string
}
// ExternalNamespacedType schema for external namespaced types
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type ExternalNamespacedType struct {
Kind string `json:"kind"`
APIVersion string `json:"apiVersion"`
Name string `json:"name"`
Namespace string `json:"namespace"`
}
// ExternalNamespacedType2 schema for external namespaced types
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type ExternalNamespacedType2 struct {
Kind string `json:"kind"`
APIVersion string `json:"apiVersion"`
Name string `json:"name"`
Namespace string `json:"namespace"`
}
// GetObjectKind returns the ObjectKind schema
func (obj *InternalNamespacedType) GetObjectKind() schema.ObjectKind { return obj }
// SetGroupVersionKind sets the API group and kind from schema
func (obj *InternalNamespacedType) SetGroupVersionKind(gvk schema.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
}
// GroupVersionKind returns the GroupVersionKind schema
func (obj *InternalNamespacedType) GroupVersionKind() schema.GroupVersionKind {
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}
// GetObjectKind returns the ObjectKind schema
func (obj *ExternalNamespacedType) GetObjectKind() schema.ObjectKind { return obj }
// SetGroupVersionKind sets the API version and kind from schema
func (obj *ExternalNamespacedType) SetGroupVersionKind(gvk schema.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
}
// GroupVersionKind returns the GroupVersionKind schema
func (obj *ExternalNamespacedType) GroupVersionKind() schema.GroupVersionKind {
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}
// GetObjectKind returns the ObjectKind schema
func (obj *ExternalNamespacedType2) GetObjectKind() schema.ObjectKind { return obj }
// SetGroupVersionKind sets the API version and kind from schema
func (obj *ExternalNamespacedType2) SetGroupVersionKind(gvk schema.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
}
// GroupVersionKind returns the GroupVersionKind schema
func (obj *ExternalNamespacedType2) GroupVersionKind() schema.GroupVersionKind {
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}
// NewInternalNamespacedType returns an initialized instance of InternalNamespacedType
func NewInternalNamespacedType(kind, apiversion, name, namespace string) *InternalNamespacedType {
item := InternalNamespacedType{Kind: kind,
APIVersion: apiversion,
Name: name,
Namespace: namespace}
return &item
}
func convertInternalNamespacedTypeToExternalNamespacedType(in *InternalNamespacedType, out *ExternalNamespacedType, s conversion.Scope) error {
out.Kind = in.Kind
out.APIVersion = in.APIVersion
out.Name = in.Name
out.Namespace = in.Namespace
return nil
}
func convertInternalNamespacedTypeToExternalNamespacedType2(in *InternalNamespacedType, out *ExternalNamespacedType2, s conversion.Scope) error {
out.Kind = in.Kind
out.APIVersion = in.APIVersion
out.Name = in.Name
out.Namespace = in.Namespace
return nil
}
func convertExternalNamespacedTypeToInternalNamespacedType(in *ExternalNamespacedType, out *InternalNamespacedType, s conversion.Scope) error {
out.Kind = in.Kind
out.APIVersion = in.APIVersion
out.Name = in.Name
out.Namespace = in.Namespace
return nil
}
func convertExternalNamespacedType2ToInternalNamespacedType(in *ExternalNamespacedType2, out *InternalNamespacedType, s conversion.Scope) error {
out.Kind = in.Kind
out.APIVersion = in.APIVersion
out.Name = in.Name
out.Namespace = in.Namespace
return nil
}
// ValidVersion of API
var ValidVersion = "v1"
// InternalGV is the internal group version object
var InternalGV = schema.GroupVersion{Group: "apitest", Version: runtime.APIVersionInternal}
// UnlikelyGV is a group version object for unrecognised version
var UnlikelyGV = schema.GroupVersion{Group: "apitest", Version: "unlikelyversion"}
// ValidVersionGV is the valid group version object
var ValidVersionGV = schema.GroupVersion{Group: "apitest", Version: ValidVersion}
// NewExternalScheme returns required objects for ExternalScheme
func NewExternalScheme() (*runtime.Scheme, meta.RESTMapper, runtime.Codec) {
scheme := runtime.NewScheme()
mapper, codec := AddToScheme(scheme)
return scheme, mapper, codec
}
func registerConversions(s *runtime.Scheme) error {
if err := s.AddConversionFunc((*InternalType)(nil), (*ExternalType)(nil), func(a, b interface{}, scope conversion.Scope) error {
return convertInternalTypeToExternalType(a.(*InternalType), b.(*ExternalType), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*InternalType)(nil), (*ExternalType2)(nil), func(a, b interface{}, scope conversion.Scope) error {
return convertInternalTypeToExternalType2(a.(*InternalType), b.(*ExternalType2), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*ExternalType)(nil), (*InternalType)(nil), func(a, b interface{}, scope conversion.Scope) error {
return convertExternalTypeToInternalType(a.(*ExternalType), b.(*InternalType), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*ExternalType2)(nil), (*InternalType)(nil), func(a, b interface{}, scope conversion.Scope) error {
return convertExternalType2ToInternalType(a.(*ExternalType2), b.(*InternalType), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*InternalNamespacedType)(nil), (*ExternalNamespacedType)(nil), func(a, b interface{}, scope conversion.Scope) error {
return convertInternalNamespacedTypeToExternalNamespacedType(a.(*InternalNamespacedType), b.(*ExternalNamespacedType), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*InternalNamespacedType)(nil), (*ExternalNamespacedType2)(nil), func(a, b interface{}, scope conversion.Scope) error {
return convertInternalNamespacedTypeToExternalNamespacedType2(a.(*InternalNamespacedType), b.(*ExternalNamespacedType2), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*ExternalNamespacedType)(nil), (*InternalNamespacedType)(nil), func(a, b interface{}, scope conversion.Scope) error {
return convertExternalNamespacedTypeToInternalNamespacedType(a.(*ExternalNamespacedType), b.(*InternalNamespacedType), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*ExternalNamespacedType2)(nil), (*InternalNamespacedType)(nil), func(a, b interface{}, scope conversion.Scope) error {
return convertExternalNamespacedType2ToInternalNamespacedType(a.(*ExternalNamespacedType2), b.(*InternalNamespacedType), scope)
}); err != nil {
return err
}
return nil
}
// AddToScheme adds required objects into scheme
func AddToScheme(scheme *runtime.Scheme) (meta.RESTMapper, runtime.Codec) {
scheme.AddKnownTypeWithName(InternalGV.WithKind("Type"), &InternalType{})
scheme.AddKnownTypeWithName(UnlikelyGV.WithKind("Type"), &ExternalType{})
// This tests that kubectl will not confuse the external scheme with the internal scheme, even when they accidentally have versions of the same name.
scheme.AddKnownTypeWithName(ValidVersionGV.WithKind("Type"), &ExternalType2{})
scheme.AddKnownTypeWithName(InternalGV.WithKind("NamespacedType"), &InternalNamespacedType{})
scheme.AddKnownTypeWithName(UnlikelyGV.WithKind("NamespacedType"), &ExternalNamespacedType{})
// This tests that kubectl will not confuse the external scheme with the internal scheme, even when they accidentally have versions of the same name.
scheme.AddKnownTypeWithName(ValidVersionGV.WithKind("NamespacedType"), &ExternalNamespacedType2{})
utilruntime.Must(registerConversions(scheme))
codecs := serializer.NewCodecFactory(scheme)
codec := codecs.LegacyCodec(UnlikelyGV)
mapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{UnlikelyGV, ValidVersionGV})
for _, gv := range []schema.GroupVersion{UnlikelyGV, ValidVersionGV} {
for kind := range scheme.KnownTypes(gv) {
gvk := gv.WithKind(kind)
scope := meta.RESTScopeNamespace
mapper.Add(gvk, scope)
}
}
return mapper, codec
}
type FakeCachedDiscoveryClient struct {
discovery.DiscoveryInterface
Groups []*metav1.APIGroup
Resources []*metav1.APIResourceList
PreferredResources []*metav1.APIResourceList
Invalidations int
}
func NewFakeCachedDiscoveryClient() *FakeCachedDiscoveryClient {
return &FakeCachedDiscoveryClient{
Groups: []*metav1.APIGroup{},
Resources: []*metav1.APIResourceList{},
PreferredResources: []*metav1.APIResourceList{},
Invalidations: 0,
}
}
func (d *FakeCachedDiscoveryClient) Fresh() bool {
return true
}
func (d *FakeCachedDiscoveryClient) Invalidate() {
d.Invalidations++
}
func (d *FakeCachedDiscoveryClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
return d.Groups, d.Resources, nil
}
func (d *FakeCachedDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
groupList := &metav1.APIGroupList{Groups: []metav1.APIGroup{}}
for _, g := range d.Groups {
groupList.Groups = append(groupList.Groups, *g)
}
return groupList, nil
}
func (d *FakeCachedDiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
return d.PreferredResources, nil
}
// TestFactory extends cmdutil.Factory
type TestFactory struct {
cmdutil.Factory
kubeConfigFlags *genericclioptions.TestConfigFlags
Client RESTClient
ScaleGetter scaleclient.ScalesGetter
UnstructuredClient RESTClient
ClientConfigVal *restclient.Config
FakeDynamicClient *fakedynamic.FakeDynamicClient
tempConfigFile *os.File
UnstructuredClientForMappingFunc resource.FakeClientFunc
OpenAPISchemaFunc func() (openapi.Resources, error)
OpenAPIV3ClientFunc func() (openapiclient.Client, error)
}
// NewTestFactory returns an initialized TestFactory instance
func NewTestFactory() *TestFactory {
// specify an optionalClientConfig to explicitly use in testing
// to avoid polluting an existing user config.
tmpFile, err := os.CreateTemp(os.TempDir(), "cmdtests_temp")
if err != nil {
panic(fmt.Sprintf("unable to create a fake client config: %v", err))
}
loadingRules := &clientcmd.ClientConfigLoadingRules{
Precedence: []string{tmpFile.Name()},
MigrationRules: map[string]string{},
}
overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmdapi.Cluster{Server: "http://localhost:8080"}}
fallbackReader := bytes.NewBuffer([]byte{})
clientConfig := clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, fallbackReader)
configFlags := genericclioptions.NewTestConfigFlags().
WithClientConfig(clientConfig).
WithRESTMapper(testRESTMapper())
restConfig, err := clientConfig.ClientConfig()
if err != nil {
panic(fmt.Sprintf("unable to create a fake restclient config: %v", err))
}
return &TestFactory{
Factory: cmdutil.NewFactory(configFlags),
kubeConfigFlags: configFlags,
FakeDynamicClient: fakedynamic.NewSimpleDynamicClient(scheme.Scheme),
tempConfigFile: tmpFile,
ClientConfigVal: restConfig,
}
}
// WithNamespace is used to mention namespace reactively
func (f *TestFactory) WithNamespace(ns string) *TestFactory {
f.kubeConfigFlags.WithNamespace(ns)
return f
}
// WithClientConfig sets the client config to use
func (f *TestFactory) WithClientConfig(clientConfig clientcmd.ClientConfig) *TestFactory {
f.kubeConfigFlags.WithClientConfig(clientConfig)
return f
}
func (f *TestFactory) WithDiscoveryClient(discoveryClient discovery.CachedDiscoveryInterface) *TestFactory {
f.kubeConfigFlags.WithDiscoveryClient(discoveryClient)
return f
}
// Cleanup cleans up TestFactory temp config file
func (f *TestFactory) Cleanup() {
if f.tempConfigFile == nil {
return
}
f.tempConfigFile.Close()
os.Remove(f.tempConfigFile.Name())
}
// ToRESTConfig is used to get ClientConfigVal from a TestFactory
func (f *TestFactory) ToRESTConfig() (*restclient.Config, error) {
return f.ClientConfigVal, nil
}
// ClientForMapping is used to Client from a TestFactory
func (f *TestFactory) ClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) {
return f.Client, nil
}
// PathOptions returns a new PathOptions with a temp file
func (f *TestFactory) PathOptions() *clientcmd.PathOptions {
pathOptions := clientcmd.NewDefaultPathOptions()
pathOptions.GlobalFile = f.tempConfigFile.Name()
pathOptions.EnvVar = ""
return pathOptions
}
// PathOptionsWithConfig writes a config to a temp file and returns PathOptions
func (f *TestFactory) PathOptionsWithConfig(config clientcmdapi.Config) (*clientcmd.PathOptions, error) {
pathOptions := f.PathOptions()
err := clientcmd.WriteToFile(config, pathOptions.GlobalFile)
if err != nil {
return nil, err
}
return pathOptions, nil
}
// UnstructuredClientForMapping is used to get UnstructuredClient from a TestFactory
func (f *TestFactory) UnstructuredClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) {
if f.UnstructuredClientForMappingFunc != nil {
return f.UnstructuredClientForMappingFunc(mapping.GroupVersionKind.GroupVersion())
}
return f.UnstructuredClient, nil
}
// Validator returns a validation schema
func (f *TestFactory) Validator(validateDirective string) (validation.Schema, error) {
return validation.NullSchema{}, nil
}
// OpenAPISchema returns openapi resources
func (f *TestFactory) OpenAPISchema() (openapi.Resources, error) {
if f.OpenAPISchemaFunc != nil {
return f.OpenAPISchemaFunc()
}
return openapitesting.EmptyResources{}, nil
}
func (f *TestFactory) OpenAPIV3Client() (openapiclient.Client, error) {
if f.OpenAPIV3ClientFunc != nil {
return f.OpenAPIV3ClientFunc()
}
return openapitest.NewFakeClient(), nil
}
// NewBuilder returns an initialized resource.Builder instance
func (f *TestFactory) NewBuilder() *resource.Builder {
return resource.NewFakeBuilder(
func(version schema.GroupVersion) (resource.RESTClient, error) {
if f.UnstructuredClientForMappingFunc != nil {
return f.UnstructuredClientForMappingFunc(version)
}
if f.UnstructuredClient != nil {
return f.UnstructuredClient, nil
}
return f.Client, nil
},
f.ToRESTMapper,
func() (restmapper.CategoryExpander, error) {
return resource.FakeCategoryExpander, nil
},
)
}
// KubernetesClientSet initializes and returns the Clientset using TestFactory
func (f *TestFactory) KubernetesClientSet() (*kubernetes.Clientset, error) {
fakeClient := f.Client.(*fake.RESTClient)
clientset := kubernetes.NewForConfigOrDie(f.ClientConfigVal)
clientset.CoreV1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.AuthorizationV1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.AuthorizationV1beta1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.AuthorizationV1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.AuthorizationV1beta1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.AuthenticationV1alpha1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.AutoscalingV1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.AutoscalingV2().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.BatchV1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.CertificatesV1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.CertificatesV1beta1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.ExtensionsV1beta1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.RbacV1alpha1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.RbacV1beta1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.StorageV1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.StorageV1beta1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.AppsV1beta1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.AppsV1beta2().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.AppsV1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.PolicyV1beta1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.PolicyV1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.DiscoveryClient.RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
return clientset, nil
}
// DynamicClient returns a dynamic client from TestFactory
func (f *TestFactory) DynamicClient() (dynamic.Interface, error) {
if f.FakeDynamicClient != nil {
return f.FakeDynamicClient, nil
}
return f.Factory.DynamicClient()
}
// RESTClient returns a REST client from TestFactory
func (f *TestFactory) RESTClient() (*restclient.RESTClient, error) {
// Swap out the HTTP client out of the client with the fake's version.
fakeClient := f.Client.(*fake.RESTClient)
restClient, err := restclient.RESTClientFor(f.ClientConfigVal)
if err != nil {
panic(err)
}
restClient.Client = fakeClient.Client
return restClient, nil
}
// DiscoveryClient returns a discovery client from TestFactory
func (f *TestFactory) DiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
fakeClient := f.Client.(*fake.RESTClient)
cacheDir := filepath.Join("", ".kube", "cache", "discovery")
cachedClient, err := diskcached.NewCachedDiscoveryClientForConfig(f.ClientConfigVal, cacheDir, "", time.Duration(10*time.Minute))
if err != nil {
return nil, err
}
cachedClient.RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
return cachedClient, nil
}
func testRESTMapper() meta.RESTMapper {
groupResources := testDynamicResources()
mapper := restmapper.NewDiscoveryRESTMapper(groupResources)
// for backwards compatibility with existing tests, allow rest mappings from the scheme to show up
// TODO: make this opt-in?
mapper = meta.FirstHitRESTMapper{
MultiRESTMapper: meta.MultiRESTMapper{
mapper,
testrestmapper.TestOnlyStaticRESTMapper(scheme.Scheme),
},
}
fakeDs := NewFakeCachedDiscoveryClient()
expander := restmapper.NewShortcutExpander(mapper, fakeDs, nil)
return expander
}
// ScaleClient returns the ScalesGetter from a TestFactory
func (f *TestFactory) ScaleClient() (scaleclient.ScalesGetter, error) {
return f.ScaleGetter, nil
}
func testDynamicResources() []*restmapper.APIGroupResources {
return []*restmapper.APIGroupResources{
{
Group: metav1.APIGroup{
Versions: []metav1.GroupVersionForDiscovery{
{Version: "v1"},
},
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1"},
},
VersionedResources: map[string][]metav1.APIResource{
"v1": {
{Name: "pods", Namespaced: true, Kind: "Pod"},
{Name: "services", Namespaced: true, Kind: "Service"},
{Name: "replicationcontrollers", Namespaced: true, Kind: "ReplicationController"},
{Name: "componentstatuses", Namespaced: false, Kind: "ComponentStatus"},
{Name: "nodes", Namespaced: false, Kind: "Node"},
{Name: "secrets", Namespaced: true, Kind: "Secret"},
{Name: "configmaps", Namespaced: true, Kind: "ConfigMap"},
{Name: "namespacedtype", Namespaced: true, Kind: "NamespacedType"},
{Name: "namespaces", Namespaced: false, Kind: "Namespace"},
{Name: "resourcequotas", Namespaced: true, Kind: "ResourceQuota"},
},
},
},
{
Group: metav1.APIGroup{
Name: "extensions",
Versions: []metav1.GroupVersionForDiscovery{
{Version: "v1beta1"},
},
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1beta1"},
},
VersionedResources: map[string][]metav1.APIResource{
"v1beta1": {
{Name: "deployments", Namespaced: true, Kind: "Deployment"},
{Name: "replicasets", Namespaced: true, Kind: "ReplicaSet"},
},
},
},
{
Group: metav1.APIGroup{
Name: "apps",
Versions: []metav1.GroupVersionForDiscovery{
{Version: "v1beta1"},
{Version: "v1beta2"},
{Version: "v1"},
},
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1"},
},
VersionedResources: map[string][]metav1.APIResource{
"v1beta1": {
{Name: "deployments", Namespaced: true, Kind: "Deployment"},
{Name: "replicasets", Namespaced: true, Kind: "ReplicaSet"},
},
"v1beta2": {
{Name: "deployments", Namespaced: true, Kind: "Deployment"},
},
"v1": {
{Name: "deployments", Namespaced: true, Kind: "Deployment"},
{Name: "replicasets", Namespaced: true, Kind: "ReplicaSet"},
},
},
},
{
Group: metav1.APIGroup{
Name: "batch",
Versions: []metav1.GroupVersionForDiscovery{
{Version: "v1beta1"},
{Version: "v1"},
},
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1"},
},
VersionedResources: map[string][]metav1.APIResource{
"v1beta1": {
{Name: "cronjobs", Namespaced: true, Kind: "CronJob"},
},
"v1": {
{Name: "jobs", Namespaced: true, Kind: "Job"},
},
},
},
{
Group: metav1.APIGroup{
Name: "autoscaling",
Versions: []metav1.GroupVersionForDiscovery{
{Version: "v1"},
{Version: "v2"},
},
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v2"},
},
VersionedResources: map[string][]metav1.APIResource{
"v1": {
{Name: "horizontalpodautoscalers", Namespaced: true, Kind: "HorizontalPodAutoscaler"},
},
"v2": {
{Name: "horizontalpodautoscalers", Namespaced: true, Kind: "HorizontalPodAutoscaler"},
},
},
},
{
Group: metav1.APIGroup{
Name: "storage.k8s.io",
Versions: []metav1.GroupVersionForDiscovery{
{Version: "v1beta1"},
{Version: "v0"},
},
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1beta1"},
},
VersionedResources: map[string][]metav1.APIResource{
"v1beta1": {
{Name: "storageclasses", Namespaced: false, Kind: "StorageClass"},
},
// bogus version of a known group/version/resource to make sure kubectl falls back to generic object mode
"v0": {
{Name: "storageclasses", Namespaced: false, Kind: "StorageClass"},
},
},
},
{
Group: metav1.APIGroup{
Name: "rbac.authorization.k8s.io",
Versions: []metav1.GroupVersionForDiscovery{
{Version: "v1beta1"},
{Version: "v1"},
},
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1"},
},
VersionedResources: map[string][]metav1.APIResource{
"v1": {
{Name: "clusterroles", Namespaced: false, Kind: "ClusterRole"},
},
"v1beta1": {
{Name: "clusterrolebindings", Namespaced: false, Kind: "ClusterRoleBinding"},
},
},
},
{
Group: metav1.APIGroup{
Name: "company.com",
Versions: []metav1.GroupVersionForDiscovery{
{Version: "v1"},
},
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1"},
},
VersionedResources: map[string][]metav1.APIResource{
"v1": {
{Name: "bars", Namespaced: true, Kind: "Bar"},
{Name: "applysets", Namespaced: false, Kind: "ApplySet"},
},
},
},
{
Group: metav1.APIGroup{
Name: "unit-test.test.com",
Versions: []metav1.GroupVersionForDiscovery{
{GroupVersion: "unit-test.test.com/v1", Version: "v1"},
},
PreferredVersion: metav1.GroupVersionForDiscovery{
GroupVersion: "unit-test.test.com/v1",
Version: "v1"},
},
VersionedResources: map[string][]metav1.APIResource{
"v1": {
{Name: "widgets", Namespaced: true, Kind: "Widget"},
},
},
},
{
Group: metav1.APIGroup{
Name: "apitest",
Versions: []metav1.GroupVersionForDiscovery{
{GroupVersion: "apitest/unlikelyversion", Version: "unlikelyversion"},
},
PreferredVersion: metav1.GroupVersionForDiscovery{
GroupVersion: "apitest/unlikelyversion",
Version: "unlikelyversion"},
},
VersionedResources: map[string][]metav1.APIResource{
"unlikelyversion": {
{Name: "types", SingularName: "type", Namespaced: false, Kind: "Type"},
},
},
},
}
}