mirror of
https://github.com/kubernetes/kubectl.git
synced 2026-04-22 06:36:58 -04:00
* 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
837 lines
29 KiB
Go
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"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|