From 3ca222b2d951b95deb825c7f62f6c1a446acf908 Mon Sep 17 00:00:00 2001 From: juanvallejo Date: Tue, 24 Apr 2018 19:47:03 -0400 Subject: [PATCH] wire config flags through factory --- cmd/clicheck/BUILD | 1 - cmd/clicheck/check_cli_conventions.go | 3 +- cmd/gendocs/BUILD | 1 - cmd/gendocs/gen_kubectl_docs.go | 3 +- cmd/genman/BUILD | 1 - cmd/genman/gen_kube_man.go | 3 +- cmd/genyaml/BUILD | 1 - cmd/genyaml/gen_kubectl_yaml.go | 3 +- pkg/kubectl/cmd/cmd.go | 16 +- pkg/kubectl/cmd/cmd_test.go | 2 +- pkg/kubectl/cmd/config/config_test.go | 2 +- pkg/kubectl/cmd/config/view_test.go | 2 +- pkg/kubectl/cmd/set/set_test.go | 2 +- pkg/kubectl/cmd/testing/fake.go | 47 ++-- pkg/kubectl/cmd/util/BUILD | 4 +- pkg/kubectl/cmd/util/config_flags.go | 251 ++++++++++++++++-- pkg/kubectl/cmd/util/factory.go | 11 +- pkg/kubectl/cmd/util/factory_client_access.go | 140 ++-------- pkg/kubectl/cmd/util/factory_test.go | 8 +- pkg/kubectl/{ => cmd}/util/transport/BUILD | 2 +- .../{ => cmd}/util/transport/round_tripper.go | 0 .../util/transport/round_tripper_test.go | 0 pkg/kubectl/util/BUILD | 1 - test/integration/apiserver/BUILD | 1 + test/integration/apiserver/print_test.go | 28 +- 25 files changed, 325 insertions(+), 208 deletions(-) rename pkg/kubectl/{ => cmd}/util/transport/BUILD (92%) rename pkg/kubectl/{ => cmd}/util/transport/round_tripper.go (100%) rename pkg/kubectl/{ => cmd}/util/transport/round_tripper_test.go (100%) diff --git a/cmd/clicheck/BUILD b/cmd/clicheck/BUILD index cafcaec3a0..db6e92a3c2 100644 --- a/cmd/clicheck/BUILD +++ b/cmd/clicheck/BUILD @@ -17,7 +17,6 @@ go_library( importpath = "k8s.io/kubernetes/cmd/clicheck", deps = [ "//pkg/kubectl/cmd:go_default_library", - "//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubectl/cmd/util/sanity:go_default_library", ], ) diff --git a/cmd/clicheck/check_cli_conventions.go b/cmd/clicheck/check_cli_conventions.go index 524f9f05dc..17d109d512 100644 --- a/cmd/clicheck/check_cli_conventions.go +++ b/cmd/clicheck/check_cli_conventions.go @@ -22,7 +22,6 @@ import ( "os" "k8s.io/kubernetes/pkg/kubectl/cmd" - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdsanity "k8s.io/kubernetes/pkg/kubectl/cmd/util/sanity" ) @@ -33,7 +32,7 @@ var ( func main() { var errorCount int - kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard) + kubectl := cmd.NewKubectlCommand(os.Stdin, ioutil.Discard, ioutil.Discard) errors := cmdsanity.RunCmdChecks(kubectl, cmdsanity.AllCmdChecks, []string{}) for _, err := range errors { errorCount++ diff --git a/cmd/gendocs/BUILD b/cmd/gendocs/BUILD index 44f9a9a2de..04897fb312 100644 --- a/cmd/gendocs/BUILD +++ b/cmd/gendocs/BUILD @@ -18,7 +18,6 @@ go_library( deps = [ "//cmd/genutils:go_default_library", "//pkg/kubectl/cmd:go_default_library", - "//pkg/kubectl/cmd/util:go_default_library", "//vendor/github.com/spf13/cobra/doc:go_default_library", ], ) diff --git a/cmd/gendocs/gen_kubectl_docs.go b/cmd/gendocs/gen_kubectl_docs.go index 39c1d8f39d..dc03a0e826 100644 --- a/cmd/gendocs/gen_kubectl_docs.go +++ b/cmd/gendocs/gen_kubectl_docs.go @@ -24,7 +24,6 @@ import ( "github.com/spf13/cobra/doc" "k8s.io/kubernetes/cmd/genutils" "k8s.io/kubernetes/pkg/kubectl/cmd" - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) func main() { @@ -47,6 +46,6 @@ func main() { // regardless of where we run. os.Setenv("HOME", "/home/username") // TODO os.Stdin should really be something like ioutil.Discard, but a Reader - kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard) + kubectl := cmd.NewKubectlCommand(os.Stdin, ioutil.Discard, ioutil.Discard) doc.GenMarkdownTree(kubectl, outDir) } diff --git a/cmd/genman/BUILD b/cmd/genman/BUILD index e9ee6dcba4..e71a704e22 100644 --- a/cmd/genman/BUILD +++ b/cmd/genman/BUILD @@ -25,7 +25,6 @@ go_library( "//cmd/kubeadm/app/cmd:go_default_library", "//cmd/kubelet/app:go_default_library", "//pkg/kubectl/cmd:go_default_library", - "//pkg/kubectl/cmd/util:go_default_library", "//vendor/github.com/cpuguy83/go-md2man/md2man:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", diff --git a/cmd/genman/gen_kube_man.go b/cmd/genman/gen_kube_man.go index 416d4a9f9f..8ccc994f31 100644 --- a/cmd/genman/gen_kube_man.go +++ b/cmd/genman/gen_kube_man.go @@ -35,7 +35,6 @@ import ( kubeadmapp "k8s.io/kubernetes/cmd/kubeadm/app/cmd" kubeletapp "k8s.io/kubernetes/cmd/kubelet/app" kubectlcmd "k8s.io/kubernetes/pkg/kubectl/cmd" - kubectlcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) func main() { @@ -106,7 +105,7 @@ func main() { case "kubectl": // generate manpage for kubectl // TODO os.Stdin should really be something like ioutil.Discard, but a Reader - kubectl := kubectlcmd.NewKubectlCommand(kubectlcmdutil.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard) + kubectl := kubectlcmd.NewKubectlCommand(os.Stdin, ioutil.Discard, ioutil.Discard) genMarkdown(kubectl, "", outDir) for _, c := range kubectl.Commands() { genMarkdown(c, "kubectl", outDir) diff --git a/cmd/genyaml/BUILD b/cmd/genyaml/BUILD index 0571d54d89..924e6144d2 100644 --- a/cmd/genyaml/BUILD +++ b/cmd/genyaml/BUILD @@ -18,7 +18,6 @@ go_library( deps = [ "//cmd/genutils:go_default_library", "//pkg/kubectl/cmd:go_default_library", - "//pkg/kubectl/cmd/util:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", "//vendor/gopkg.in/yaml.v2:go_default_library", diff --git a/cmd/genyaml/gen_kubectl_yaml.go b/cmd/genyaml/gen_kubectl_yaml.go index 376a1c7122..c95ee2a3d5 100644 --- a/cmd/genyaml/gen_kubectl_yaml.go +++ b/cmd/genyaml/gen_kubectl_yaml.go @@ -27,7 +27,6 @@ import ( "gopkg.in/yaml.v2" "k8s.io/kubernetes/cmd/genutils" "k8s.io/kubernetes/pkg/kubectl/cmd" - cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) type cmdOption struct { @@ -66,7 +65,7 @@ func main() { // regardless of where we run. os.Setenv("HOME", "/home/username") // TODO os.Stdin should really be something like ioutil.Discard, but a Reader - kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard) + kubectl := cmd.NewKubectlCommand(os.Stdin, ioutil.Discard, ioutil.Discard) genYaml(kubectl, "", outDir) for _, c := range kubectl.Commands() { genYaml(c, "kubectl", outDir) diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index 4cbf38eb36..2302fe0840 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -17,12 +17,13 @@ limitations under the License. package cmd import ( + "flag" "fmt" "io" "os" "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apiserver/pkg/util/flag" + utilflag "k8s.io/apiserver/pkg/util/flag" "k8s.io/client-go/tools/clientcmd" "k8s.io/kubernetes/pkg/kubectl/cmd/auth" cmdconfig "k8s.io/kubernetes/pkg/kubectl/cmd/config" @@ -219,11 +220,11 @@ var ( ) func NewDefaultKubectlCommand() *cobra.Command { - return NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, os.Stdout, os.Stderr) + return NewKubectlCommand(os.Stdin, os.Stdout, os.Stderr) } // NewKubectlCommand creates the `kubectl` command and its nested children. -func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cobra.Command { +func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command { // Parent command to which all subcommands are added. cmds := &cobra.Command{ Use: "kubectl", @@ -237,8 +238,13 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob BashCompletionFunction: bashCompletionFunc, } + kubeConfigFlags := cmdutil.NewConfigFlags() + kubeConfigFlags.AddFlags(cmds.PersistentFlags()) + + cmds.PersistentFlags().AddGoFlagSet(flag.CommandLine) + + f := cmdutil.NewFactory(kubeConfigFlags) f.BindFlags(cmds.PersistentFlags()) - f.BindExternalFlags(cmds.PersistentFlags()) // Sending in 'nil' for the getLanguageFn() results in using // the LANG environment variable. @@ -248,7 +254,7 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob i18n.LoadTranslations("kubectl", nil) // From this point and forward we get warnings on flags that contain "_" separators - cmds.SetGlobalNormalizationFunc(flag.WarnWordSepNormalizeFunc) + cmds.SetGlobalNormalizationFunc(utilflag.WarnWordSepNormalizeFunc) ioStreams := genericclioptions.IOStreams{In: in, Out: out, ErrOut: err} diff --git a/pkg/kubectl/cmd/cmd_test.go b/pkg/kubectl/cmd/cmd_test.go index 1dfe229519..742d892684 100644 --- a/pkg/kubectl/cmd/cmd_test.go +++ b/pkg/kubectl/cmd/cmd_test.go @@ -239,7 +239,7 @@ func newAllPhasePodList() *api.PodList { func TestNormalizationFuncGlobalExistence(t *testing.T) { // This test can be safely deleted when we will not support multiple flag formats - root := NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, os.Stdout, os.Stderr) + root := NewKubectlCommand(os.Stdin, os.Stdout, os.Stderr) if root.Parent() != nil { t.Fatal("We expect the root command to be returned") diff --git a/pkg/kubectl/cmd/config/config_test.go b/pkg/kubectl/cmd/config/config_test.go index 2fc8000278..3c74ff151b 100644 --- a/pkg/kubectl/cmd/config/config_test.go +++ b/pkg/kubectl/cmd/config/config_test.go @@ -865,7 +865,7 @@ func testConfigCommand(args []string, startingConfig clientcmdapi.Config, t *tes buf := bytes.NewBuffer([]byte{}) - cmd := NewCmdConfig(cmdutil.NewFactory(nil), clientcmd.NewDefaultPathOptions(), buf, buf) + cmd := NewCmdConfig(cmdutil.NewFactory(cmdutil.NewTestConfigFlags()), clientcmd.NewDefaultPathOptions(), buf, buf) cmd.SetArgs(argsToUse) cmd.Execute() diff --git a/pkg/kubectl/cmd/config/view_test.go b/pkg/kubectl/cmd/config/view_test.go index 873e5f0797..b8fb9e0df0 100644 --- a/pkg/kubectl/cmd/config/view_test.go +++ b/pkg/kubectl/cmd/config/view_test.go @@ -143,7 +143,7 @@ func (test viewClusterTest) run(t *testing.T) { pathOptions.EnvVar = "" buf := bytes.NewBuffer([]byte{}) errBuf := bytes.NewBuffer([]byte{}) - cmd := NewCmdConfigView(cmdutil.NewFactory(nil), buf, errBuf, pathOptions) + cmd := NewCmdConfigView(cmdutil.NewFactory(cmdutil.NewTestConfigFlags()), buf, errBuf, pathOptions) cmd.Flags().Parse(test.flags) if err := cmd.Execute(); err != nil { t.Fatalf("unexpected error executing command: %v,kubectl config view flags: %v", err, test.flags) diff --git a/pkg/kubectl/cmd/set/set_test.go b/pkg/kubectl/cmd/set/set_test.go index 7432a49bf7..fa985fa65e 100644 --- a/pkg/kubectl/cmd/set/set_test.go +++ b/pkg/kubectl/cmd/set/set_test.go @@ -26,7 +26,7 @@ import ( ) func TestLocalAndDryRunFlags(t *testing.T) { - f := clientcmdutil.NewFactory(nil) + f := clientcmdutil.NewFactory(clientcmdutil.NewTestConfigFlags()) setCmd := NewCmdSet(f, genericclioptions.NewTestIOStreamsDiscard()) ensureLocalAndDryRunFlagsOnChildren(t, setCmd, "") } diff --git a/pkg/kubectl/cmd/testing/fake.go b/pkg/kubectl/cmd/testing/fake.go index 461e2a1754..8633d31006 100644 --- a/pkg/kubectl/cmd/testing/fake.go +++ b/pkg/kubectl/cmd/testing/fake.go @@ -253,11 +253,27 @@ type TestFactory struct { func NewTestFactory() *TestFactory { // specify an optionalClientConfig to explicitly use in testing // to avoid polluting an existing user config. - config, configFile := defaultFakeClientConfig() + tmpFile, err := ioutil.TempFile("", "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 := cmdutil.NewTestConfigFlags(). + WithClientConfig(clientConfig) + return &TestFactory{ - Factory: cmdutil.NewFactory(config), + Factory: cmdutil.NewFactory(configFlags), FakeDynamicClient: fakedynamic.NewSimpleDynamicClient(legacyscheme.Scheme), - tempConfigFile: configFile, + tempConfigFile: tmpFile, } } @@ -269,31 +285,6 @@ func (f *TestFactory) Cleanup() { os.Remove(f.tempConfigFile.Name()) } -func defaultFakeClientConfig() (clientcmd.ClientConfig, *os.File) { - loadingRules, tmpFile, err := newDefaultFakeClientConfigLoadingRules() - if err != nil { - panic(fmt.Sprintf("unable to create a fake client config: %v", err)) - } - - overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmdapi.Cluster{Server: "http://localhost:8080"}} - fallbackReader := bytes.NewBuffer([]byte{}) - - clientConfig := clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, fallbackReader) - return clientConfig, tmpFile -} - -func newDefaultFakeClientConfigLoadingRules() (*clientcmd.ClientConfigLoadingRules, *os.File, error) { - tmpFile, err := ioutil.TempFile("", "cmdtests_temp") - if err != nil { - return nil, nil, err - } - - return &clientcmd.ClientConfigLoadingRules{ - Precedence: []string{tmpFile.Name()}, - MigrationRules: map[string]string{}, - }, tmpFile, nil -} - func (f *TestFactory) CategoryExpander() categories.CategoryExpander { return categories.LegacyCategoryExpander } diff --git a/pkg/kubectl/cmd/util/BUILD b/pkg/kubectl/cmd/util/BUILD index c3ce48d37c..cd093401fb 100644 --- a/pkg/kubectl/cmd/util/BUILD +++ b/pkg/kubectl/cmd/util/BUILD @@ -4,6 +4,7 @@ go_library( name = "go_default_library", srcs = [ "cached_discovery.go", + "config_flags.go", "conversion.go", "factory.go", "factory_builder.go", @@ -30,10 +31,10 @@ go_library( "//pkg/kubectl/cmd/templates:go_default_library", "//pkg/kubectl/cmd/util/openapi:go_default_library", "//pkg/kubectl/cmd/util/openapi/validation:go_default_library", + "//pkg/kubectl/cmd/util/transport:go_default_library", "//pkg/kubectl/plugins:go_default_library", "//pkg/kubectl/resource:go_default_library", "//pkg/kubectl/scheme:go_default_library", - "//pkg/kubectl/util/transport:go_default_library", "//pkg/kubectl/validation:go_default_library", "//pkg/printers:go_default_library", "//pkg/printers/internalversion:go_default_library", @@ -139,6 +140,7 @@ filegroup( "//pkg/kubectl/cmd/util/jsonmerge:all-srcs", "//pkg/kubectl/cmd/util/openapi:all-srcs", "//pkg/kubectl/cmd/util/sanity:all-srcs", + "//pkg/kubectl/cmd/util/transport:all-srcs", ], tags = ["automanaged"], visibility = ["//build/visible_to:pkg_kubectl_cmd_util_CONSUMERS"], diff --git a/pkg/kubectl/cmd/util/config_flags.go b/pkg/kubectl/cmd/util/config_flags.go index 24c8077899..6c413ad39e 100644 --- a/pkg/kubectl/cmd/util/config_flags.go +++ b/pkg/kubectl/cmd/util/config_flags.go @@ -22,27 +22,70 @@ import ( "path/filepath" "time" - "github.com/spf13/cobra" + "github.com/spf13/pflag" + utilflag "k8s.io/apiserver/pkg/util/flag" "k8s.io/client-go/discovery" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" - "k8s.io/kubernetes/pkg/kubectl/util/transport" + "k8s.io/kubernetes/pkg/kubectl/cmd/util/transport" ) +const ( + flagClusterName = "cluster" + flagAuthInfoName = "user" + flagContext = "context" + flagNamespace = "namespace" + flagAPIServer = "server" + flagInsecure = "insecure-skip-tls-verify" + flagCertFile = "client-certificate" + flagKeyFile = "client-key" + flagCAFile = "certificate-authority" + flagBearerToken = "token" + flagImpersonate = "as" + flagImpersonateGroup = "as-group" + flagUsername = "username" + flagPassword = "password" + flagTimeout = "request-timeout" +) + +// TODO(juanvallejo): move to pkg/kubectl/genericclioptions once +// the dependency on cmdutil is broken here. // ConfigFlags composes the set of values necessary // for obtaining a REST client config type ConfigFlags struct { CacheDir *string KubeConfig *string + + // config flags + ClusterName *string + AuthInfoName *string + Context *string + Namespace *string + APIServer *string + Insecure *bool + CertFile *string + KeyFile *string + CAFile *string + BearerToken *string + Impersonate *string + ImpersonateGroup *[]string + Username *string + Password *string + Timeout *string } -// ToRESTConfig returns a REST client configuration based on a provided -// path to a .kubeconfig file, loading rules, and config flag overrides. +// ToRESTConfig implements RESTClientGetter. +// Returns a REST client configuration based on a provided path +// to a .kubeconfig file, loading rules, and config flag overrides. // Expects the AddFlags method to have been called. func (f *ConfigFlags) ToRESTConfig() (*rest.Config, error) { + return f.ToRawKubeConfigLoader().ClientConfig() +} + +func (f *ConfigFlags) ToRawKubeConfigLoader() clientcmd.ClientConfig { loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() // use the standard defaults for this client command // DEPRECATED: remove and replace with something more accurate @@ -53,13 +96,74 @@ func (f *ConfigFlags) ToRESTConfig() (*rest.Config, error) { } overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults} - clientConfig := clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, os.Stdin) - return clientConfig.ClientConfig() + // bind auth info flag values to overrides + if f.CertFile != nil { + overrides.AuthInfo.ClientCertificate = *f.CertFile + } + if f.KeyFile != nil { + overrides.AuthInfo.ClientKey = *f.KeyFile + } + if f.BearerToken != nil { + overrides.AuthInfo.Token = *f.BearerToken + } + if f.Impersonate != nil { + overrides.AuthInfo.Impersonate = *f.Impersonate + } + if f.ImpersonateGroup != nil { + overrides.AuthInfo.ImpersonateGroups = *f.ImpersonateGroup + } + if f.Username != nil { + overrides.AuthInfo.Username = *f.Username + } + if f.Password != nil { + overrides.AuthInfo.Password = *f.Password + } + + // bind cluster flags + if f.APIServer != nil { + overrides.ClusterInfo.Server = *f.APIServer + } + if f.CAFile != nil { + overrides.ClusterInfo.CertificateAuthority = *f.CAFile + } + if f.Insecure != nil { + overrides.ClusterInfo.InsecureSkipTLSVerify = *f.Insecure + } + + // bind context flags + if f.Context != nil { + overrides.CurrentContext = *f.Context + } + if f.ClusterName != nil { + overrides.Context.Cluster = *f.ClusterName + } + if f.AuthInfoName != nil { + overrides.Context.AuthInfo = *f.AuthInfoName + } + if f.Namespace != nil { + overrides.Context.Namespace = *f.Namespace + } + + if f.Timeout != nil { + overrides.Timeout = *f.Timeout + } + + var clientConfig clientcmd.ClientConfig + + // we only have an interactive prompt when a password is allowed + if f.Password == nil { + clientConfig = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides) + } else { + clientConfig = clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, os.Stdin) + } + + return clientConfig } -// ToDiscoveryClient returns a CachedDiscoveryInterface using a computed RESTConfig. +// ToDiscoveryClient implements RESTClientGetter. // Expects the AddFlags method to have been called. +// Returns a CachedDiscoveryInterface using a computed RESTConfig. func (f *ConfigFlags) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) { config, err := f.ToRESTConfig() if err != nil { @@ -71,7 +175,7 @@ func (f *ConfigFlags) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, e // double it just so we don't end up here again for a while. This config is only used for discovery. config.Burst = 100 - cacheDir := "" + cacheDir := filepath.Join(homedir.HomeDir(), ".kube", "http-cache") if f.CacheDir != nil { cacheDir = *f.CacheDir } @@ -94,28 +198,135 @@ func (f *ConfigFlags) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, e return NewCachedDiscoveryClient(discoveryClient, cacheDir, time.Duration(10*time.Minute)), nil } -func (f *ConfigFlags) AddFlags(cmd *cobra.Command) { - flagNames := clientcmd.RecommendedConfigOverrideFlags("") - // short flagnames are disabled by default. These are here for compatibility with existing scripts - flagNames.ClusterOverrideFlags.APIServer.ShortName = "s" +func (f *ConfigFlags) AddFlags(flags *pflag.FlagSet) { + flags.SetNormalizeFunc(utilflag.WarnWordSepNormalizeFunc) // Warn for "_" flags - overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults} - clientcmd.BindOverrideFlags(overrides, cmd.Flags(), flagNames) + // Normalize all flags that are coming from other packages or pre-configurations + // a.k.a. change all "_" to "-". e.g. glog package + flags.SetNormalizeFunc(utilflag.WordSepNormalizeFunc) if f.KubeConfig != nil { - cmd.Flags().StringVar(f.KubeConfig, "kubeconfig", *f.KubeConfig, "Path to the kubeconfig file to use for CLI requests.") + flags.StringVar(f.KubeConfig, "kubeconfig", *f.KubeConfig, "Path to the kubeconfig file to use for CLI requests.") } if f.CacheDir != nil { - cmd.Flags().StringVar(f.CacheDir, FlagHTTPCacheDir, *f.CacheDir, "Default HTTP cache directory") + flags.StringVar(f.CacheDir, FlagHTTPCacheDir, *f.CacheDir, "Default HTTP cache directory") } + + // add config options + if f.CertFile != nil { + flags.StringVar(f.CertFile, flagCertFile, *f.CertFile, "Path to a client certificate file for TLS") + } + if f.KeyFile != nil { + flags.StringVar(f.KeyFile, flagKeyFile, *f.KeyFile, "Path to a client key file for TLS") + } + if f.BearerToken != nil { + flags.StringVar(f.BearerToken, flagBearerToken, *f.BearerToken, "Bearer token for authentication to the API server") + } + if f.Impersonate != nil { + flags.StringVar(f.Impersonate, flagImpersonate, *f.Impersonate, "Username to impersonate for the operation") + } + if f.ImpersonateGroup != nil { + flags.StringArrayVar(f.ImpersonateGroup, flagImpersonateGroup, *f.ImpersonateGroup, "Group to impersonate for the operation, this flag can be repeated to specify multiple groups.") + } + if f.Username != nil { + flags.StringVar(f.Username, flagUsername, *f.Username, "Username for basic authentication to the API server") + } + if f.Password != nil { + flags.StringVar(f.Password, flagPassword, *f.Password, "Password for basic authentication to the API server") + } + if f.ClusterName != nil { + flags.StringVar(f.ClusterName, flagClusterName, *f.ClusterName, "The name of the kubeconfig cluster to use") + } + if f.AuthInfoName != nil { + flags.StringVar(f.AuthInfoName, flagAuthInfoName, *f.AuthInfoName, "The name of the kubeconfig user to use") + } + if f.Namespace != nil { + flags.StringVarP(f.Namespace, flagNamespace, "n", *f.Namespace, "If present, the namespace scope for this CLI request") + } + if f.Context != nil { + flags.StringVar(f.Context, flagContext, *f.Context, "The name of the kubeconfig context to use") + } + + if f.APIServer != nil { + flags.StringVarP(f.APIServer, flagAPIServer, "s", *f.APIServer, "The address and port of the Kubernetes API server") + } + if f.Insecure != nil { + flags.BoolVar(f.Insecure, flagInsecure, *f.Insecure, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure") + } + if f.CAFile != nil { + flags.StringVar(f.CAFile, flagCAFile, *f.CAFile, "Path to a cert file for the certificate authority") + } + if f.Timeout != nil { + flags.StringVar(f.Timeout, flagTimeout, *f.Timeout, "The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests.") + } + +} + +func (f *ConfigFlags) WithDeprecatedPasswordFlag() *ConfigFlags { + f.Username = stringptr("") + f.Password = stringptr("") + return f } func NewConfigFlags() *ConfigFlags { - defaultCacheDir := filepath.Join(homedir.HomeDir(), ".kube", "http-cache") - kubeConfig := "" + impersonateGroup := []string{} + insecure := false return &ConfigFlags{ - CacheDir: &defaultCacheDir, - KubeConfig: &kubeConfig, + Insecure: &insecure, + Timeout: stringptr("0"), + KubeConfig: stringptr(""), + + ClusterName: stringptr(""), + AuthInfoName: stringptr(""), + Context: stringptr(""), + Namespace: stringptr(""), + APIServer: stringptr(""), + CertFile: stringptr(""), + KeyFile: stringptr(""), + CAFile: stringptr(""), + BearerToken: stringptr(""), + Impersonate: stringptr(""), + ImpersonateGroup: &impersonateGroup, } } + +func stringptr(val string) *string { + return &val +} + +// TODO(juanvallejo): move to separate file when config_flags are moved +// to pkg/kubectl/genericclioptions +type TestConfigFlags struct { + clientConfig clientcmd.ClientConfig + discoveryClient discovery.CachedDiscoveryInterface +} + +func (f *TestConfigFlags) ToRawKubeConfigLoader() clientcmd.ClientConfig { + if f.clientConfig == nil { + panic("attempt to obtain a test RawKubeConfigLoader with no clientConfig specified") + } + return f.clientConfig +} + +func (f *TestConfigFlags) ToRESTConfig() (*rest.Config, error) { + return f.ToRawKubeConfigLoader().ClientConfig() +} + +func (f *TestConfigFlags) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) { + return f.discoveryClient, nil +} + +func (f *TestConfigFlags) WithClientConfig(clientConfig clientcmd.ClientConfig) *TestConfigFlags { + f.clientConfig = clientConfig + return f +} + +func (f *TestConfigFlags) WithDiscoveryClient(c discovery.CachedDiscoveryInterface) *TestConfigFlags { + f.discoveryClient = c + return f +} + +func NewTestConfigFlags() *TestConfigFlags { + return &TestConfigFlags{} +} diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index d4a2356f17..049b5b8ecf 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -37,7 +37,6 @@ import ( "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" scaleclient "k8s.io/client-go/scale" - "k8s.io/client-go/tools/clientcmd" api "k8s.io/kubernetes/pkg/apis/core" apiv1 "k8s.io/kubernetes/pkg/apis/core/v1" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" @@ -125,10 +124,9 @@ type ClientAccessFactory interface { // Command will stringify and return all environment arguments ie. a command run by a client // using the factory. Command(cmd *cobra.Command, showSecrets bool) string + // BindFlags adds any flags that are common to all kubectl sub commands. BindFlags(flags *pflag.FlagSet) - // BindExternalFlags adds any flags defined by external projects (not part of pflags) - BindExternalFlags(flags *pflag.FlagSet) // SuggestedPodTemplateResources returns a list of resource types that declare a pod template SuggestedPodTemplateResources() []schema.GroupResource @@ -227,10 +225,9 @@ type factory struct { } // NewFactory creates a factory with the default Kubernetes resources defined -// if optionalClientConfig is nil, then flags will be bound to a new clientcmd.ClientConfig. -// if optionalClientConfig is not nil, then this factory will make use of it. -func NewFactory(optionalClientConfig clientcmd.ClientConfig) Factory { - clientAccessFactory := NewClientAccessFactory(optionalClientConfig) +// Receives a clientGetter capable of providing a discovery client and a REST client configuration. +func NewFactory(clientGetter RESTClientGetter) Factory { + clientAccessFactory := NewClientAccessFactory(clientGetter) objectMappingFactory := NewObjectMappingFactory(clientAccessFactory) builderFactory := NewBuilderFactory(clientAccessFactory, objectMappingFactory) diff --git a/pkg/kubectl/cmd/util/factory_client_access.go b/pkg/kubectl/cmd/util/factory_client_access.go index a5a4dcd32d..1e9cebb278 100644 --- a/pkg/kubectl/cmd/util/factory_client_access.go +++ b/pkg/kubectl/cmd/util/factory_client_access.go @@ -20,23 +20,19 @@ package util import ( "errors" - "flag" "fmt" "io" - "net/http" "os" "path/filepath" "regexp" "strings" - "time" + "sync" "k8s.io/api/core/v1" "github.com/spf13/cobra" "github.com/spf13/pflag" - "sync" - appsv1 "k8s.io/api/apps/v1" appsv1beta1 "k8s.io/api/apps/v1beta1" appsv1beta2 "k8s.io/api/apps/v1beta2" @@ -50,13 +46,11 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - utilflag "k8s.io/apiserver/pkg/util/flag" "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/util/homedir" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/apps" api "k8s.io/kubernetes/pkg/apis/core" @@ -64,124 +58,37 @@ import ( "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/resource" - "k8s.io/kubernetes/pkg/kubectl/util/transport" "k8s.io/kubernetes/pkg/version" ) +type RESTClientGetter interface { + ToRESTConfig() (*restclient.Config, error) + ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) + ToRawKubeConfigLoader() clientcmd.ClientConfig +} + type ring0Factory struct { - flags *pflag.FlagSet - clientConfig clientcmd.ClientConfig - discoveryCacheDir string + clientGetter RESTClientGetter requireMatchedServerVersion bool checkServerVersion sync.Once matchesServerVersionErr error } -func NewClientAccessFactory(optionalClientConfig clientcmd.ClientConfig) ClientAccessFactory { - flags := pflag.NewFlagSet("", pflag.ContinueOnError) - - clientConfig := optionalClientConfig - if optionalClientConfig == nil { - clientConfig = DefaultClientConfig(flags) +func NewClientAccessFactory(clientGetter RESTClientGetter) ClientAccessFactory { + if clientGetter == nil { + panic("attempt to instantiate client_access_factory with nil clientGetter") } - flags.SetNormalizeFunc(utilflag.WarnWordSepNormalizeFunc) // Warn for "_" flags - f := &ring0Factory{ - flags: flags, - clientConfig: clientConfig, + clientGetter: clientGetter, } return f } -// DefaultClientConfig creates a clientcmd.ClientConfig with the following hierarchy: -// 1. Use the kubeconfig builder. The number of merges and overrides here gets a little crazy. Stay with me. -// 1. Merge the kubeconfig itself. This is done with the following hierarchy rules: -// 1. CommandLineLocation - this parsed from the command line, so it must be late bound. If you specify this, -// then no other kubeconfig files are merged. This file must exist. -// 2. If $KUBECONFIG is set, then it is treated as a list of files that should be merged. -// 3. HomeDirectoryLocation -// Empty filenames are ignored. Files with non-deserializable content produced errors. -// The first file to set a particular value or map key wins and the value or map key is never changed. -// This means that the first file to set CurrentContext will have its context preserved. It also means -// that if two files specify a "red-user", only values from the first file's red-user are used. Even -// non-conflicting entries from the second file's "red-user" are discarded. -// 2. Determine the context to use based on the first hit in this chain -// 1. command line argument - again, parsed from the command line, so it must be late bound -// 2. CurrentContext from the merged kubeconfig file -// 3. Empty is allowed at this stage -// 3. Determine the cluster info and auth info to use. At this point, we may or may not have a context. They -// are built based on the first hit in this chain. (run it twice, once for auth, once for cluster) -// 1. command line argument -// 2. If context is present, then use the context value -// 3. Empty is allowed -// 4. Determine the actual cluster info to use. At this point, we may or may not have a cluster info. Build -// each piece of the cluster info based on the chain: -// 1. command line argument -// 2. If cluster info is present and a value for the attribute is present, use it. -// 3. If you don't have a server location, bail. -// 5. Auth info is build using the same rules as cluster info, EXCEPT that you can only have one authentication -// technique per auth info. The following conditions result in an error: -// 1. If there are two conflicting techniques specified from the command line, fail. -// 2. If the command line does not specify one, and the auth info has conflicting techniques, fail. -// 3. If the command line specifies one and the auth info specifies another, honor the command line technique. -// 2. Use default values and potentially prompt for auth information -// -// However, if it appears that we're running in a kubernetes cluster -// container environment, then run with the auth info kubernetes mounted for -// us. Specifically: -// The env vars KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT are -// set, and the file /var/run/secrets/kubernetes.io/serviceaccount/token -// exists and is not a directory. -func DefaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig { - loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() - // use the standard defaults for this client command - // DEPRECATED: remove and replace with something more accurate - loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig - - flags.StringVar(&loadingRules.ExplicitPath, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests.") - - overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults} - - flagNames := clientcmd.RecommendedConfigOverrideFlags("") - // short flagnames are disabled by default. These are here for compatibility with existing scripts - flagNames.ClusterOverrideFlags.APIServer.ShortName = "s" - - clientcmd.BindOverrideFlags(overrides, flags, flagNames) - clientConfig := clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, os.Stdin) - - return clientConfig -} - func (f *ring0Factory) DiscoveryClient() (discovery.CachedDiscoveryInterface, error) { - cfg, err := f.clientConfig.ClientConfig() - if err != nil { - return nil, err - } - - // The more groups you have, the more discovery requests you need to make. - // given 25 groups (our groups + a few custom resources) with one-ish version each, discovery needs to make 50 requests - // double it just so we don't end up here again for a while. This config is only used for discovery. - cfg.Burst = 100 - - if f.discoveryCacheDir != "" { - wt := cfg.WrapTransport - cfg.WrapTransport = func(rt http.RoundTripper) http.RoundTripper { - if wt != nil { - rt = wt(rt) - } - return transport.NewCacheRoundTripper(f.discoveryCacheDir, rt) - } - } - - discoveryClient, err := discovery.NewDiscoveryClientForConfig(cfg) - if err != nil { - return nil, err - } - cacheDir := computeDiscoverCacheDir(filepath.Join(homedir.HomeDir(), ".kube", "cache", "discovery"), cfg.Host) - return NewCachedDiscoveryClient(discoveryClient, cacheDir, time.Duration(10*time.Minute)), nil + return f.clientGetter.ToDiscoveryClient() } func (f *ring0Factory) KubernetesClientSet() (*kubernetes.Clientset, error) { @@ -227,7 +134,7 @@ func (f *ring0Factory) ClientConfig() (*restclient.Config, error) { if err := f.checkMatchingServerVersion(); err != nil { return nil, err } - clientConfig, err := f.clientConfig.ClientConfig() + clientConfig, err := f.clientGetter.ToRESTConfig() if err != nil { return nil, err } @@ -235,7 +142,7 @@ func (f *ring0Factory) ClientConfig() (*restclient.Config, error) { return clientConfig, nil } func (f *ring0Factory) BareClientConfig() (*restclient.Config, error) { - return f.clientConfig.ClientConfig() + return f.clientGetter.ToRESTConfig() } func (f *ring0Factory) RESTClient() (*restclient.RESTClient, error) { @@ -413,26 +320,11 @@ func (f *ring0Factory) Command(cmd *cobra.Command, showSecrets bool) string { } func (f *ring0Factory) BindFlags(flags *pflag.FlagSet) { - // Merge factory's flags - flags.AddFlagSet(f.flags) - // Globally persistent flags across all subcommands. // TODO Change flag names to consts to allow safer lookup from subcommands. // TODO Add a verbose flag that turns on glog logging. Probably need a way // to do that automatically for every subcommand. flags.BoolVar(&f.requireMatchedServerVersion, FlagMatchBinaryVersion, false, "Require server version to match client version") - - defaultCacheDir := filepath.Join(homedir.HomeDir(), ".kube", "http-cache") - flags.StringVar(&f.discoveryCacheDir, FlagHTTPCacheDir, defaultCacheDir, "Default HTTP cache directory") - - // Normalize all flags that are coming from other packages or pre-configurations - // a.k.a. change all "_" to "-". e.g. glog package - flags.SetNormalizeFunc(utilflag.WordSepNormalizeFunc) -} - -func (f *ring0Factory) BindExternalFlags(flags *pflag.FlagSet) { - // any flags defined by external projects (not part of pflags) - flags.AddGoFlagSet(flag.CommandLine) } func (f *ring0Factory) SuggestedPodTemplateResources() []schema.GroupResource { @@ -476,7 +368,7 @@ func (f *ring0Factory) Resumer(info *resource.Info) ([]byte, error) { } func (f *ring0Factory) DefaultNamespace() (string, bool, error) { - return f.clientConfig.Namespace() + return f.clientGetter.ToRawKubeConfigLoader().Namespace() } const ( diff --git a/pkg/kubectl/cmd/util/factory_test.go b/pkg/kubectl/cmd/util/factory_test.go index e936a2d980..34ce267fcb 100644 --- a/pkg/kubectl/cmd/util/factory_test.go +++ b/pkg/kubectl/cmd/util/factory_test.go @@ -44,7 +44,7 @@ import ( ) func TestPortsForObject(t *testing.T) { - f := NewFactory(nil) + f := NewFactory(NewTestConfigFlags()) pod := &api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"}, @@ -75,7 +75,7 @@ func TestPortsForObject(t *testing.T) { } func TestProtocolsForObject(t *testing.T) { - f := NewFactory(nil) + f := NewFactory(NewTestConfigFlags()) pod := &api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"}, @@ -113,7 +113,7 @@ func TestProtocolsForObject(t *testing.T) { } func TestLabelsForObject(t *testing.T) { - f := NewFactory(nil) + f := NewFactory(NewTestConfigFlags()) tests := []struct { name string @@ -164,7 +164,7 @@ func TestLabelsForObject(t *testing.T) { } func TestCanBeExposed(t *testing.T) { - factory := NewFactory(nil) + factory := NewFactory(NewTestConfigFlags()) tests := []struct { kind schema.GroupKind expectErr bool diff --git a/pkg/kubectl/util/transport/BUILD b/pkg/kubectl/cmd/util/transport/BUILD similarity index 92% rename from pkg/kubectl/util/transport/BUILD rename to pkg/kubectl/cmd/util/transport/BUILD index c077a903c5..0e968c1fb2 100644 --- a/pkg/kubectl/util/transport/BUILD +++ b/pkg/kubectl/cmd/util/transport/BUILD @@ -3,7 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = ["round_tripper.go"], - importpath = "k8s.io/kubernetes/pkg/kubectl/util/transport", + importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/util/transport", visibility = ["//visibility:public"], deps = [ "//vendor/github.com/gregjones/httpcache:go_default_library", diff --git a/pkg/kubectl/util/transport/round_tripper.go b/pkg/kubectl/cmd/util/transport/round_tripper.go similarity index 100% rename from pkg/kubectl/util/transport/round_tripper.go rename to pkg/kubectl/cmd/util/transport/round_tripper.go diff --git a/pkg/kubectl/util/transport/round_tripper_test.go b/pkg/kubectl/cmd/util/transport/round_tripper_test.go similarity index 100% rename from pkg/kubectl/util/transport/round_tripper_test.go rename to pkg/kubectl/cmd/util/transport/round_tripper_test.go diff --git a/pkg/kubectl/util/BUILD b/pkg/kubectl/util/BUILD index 16c098d196..ad3c1b7313 100644 --- a/pkg/kubectl/util/BUILD +++ b/pkg/kubectl/util/BUILD @@ -104,7 +104,6 @@ filegroup( "//pkg/kubectl/util/logs:all-srcs", "//pkg/kubectl/util/slice:all-srcs", "//pkg/kubectl/util/term:all-srcs", - "//pkg/kubectl/util/transport:all-srcs", ], tags = ["automanaged"], visibility = ["//build/visible_to:pkg_kubectl_util_CONSUMERS"], diff --git a/test/integration/apiserver/BUILD b/test/integration/apiserver/BUILD index cc1d05cc73..238349f949 100644 --- a/test/integration/apiserver/BUILD +++ b/test/integration/apiserver/BUILD @@ -45,6 +45,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apiserver/pkg/features:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + "//vendor/k8s.io/client-go/discovery:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", diff --git a/test/integration/apiserver/print_test.go b/test/integration/apiserver/print_test.go index 511e05a769..30e7b73e08 100644 --- a/test/integration/apiserver/print_test.go +++ b/test/integration/apiserver/print_test.go @@ -19,9 +19,12 @@ package apiserver import ( "encoding/json" "fmt" + "io/ioutil" + "os" "reflect" "strings" "testing" + "time" batchv2alpha1 "k8s.io/api/batch/v2alpha1" rbacv1alpha1 "k8s.io/api/rbac/v1alpha1" @@ -31,6 +34,7 @@ import ( "k8s.io/apimachinery/pkg/api/meta" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/discovery" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "k8s.io/gengo/examples/set-gen/sets" @@ -146,7 +150,29 @@ func TestServerSidePrint(t *testing.T) { tableParam := fmt.Sprintf("application/json;as=Table;g=%s;v=%s, application/json", metav1beta1.GroupName, metav1beta1.SchemeGroupVersion.Version) printer := newFakePrinter(printersinternal.AddHandlers) - factory := util.NewFactory(clientcmd.NewDefaultClientConfig(*createKubeConfig(s.URL), &clientcmd.ConfigOverrides{})) + configFlags := util.NewTestConfigFlags(). + WithClientConfig(clientcmd.NewDefaultClientConfig(*createKubeConfig(s.URL), &clientcmd.ConfigOverrides{})) + + restConfig, err := configFlags.ToRESTConfig() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + discoveryClient, err := discovery.NewDiscoveryClientForConfig(restConfig) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + cacheDir, err := ioutil.TempDir(os.TempDir(), "test-integration-apiserver-print") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + defer func() { + os.Remove(cacheDir) + }() + + configFlags.WithDiscoveryClient(util.NewCachedDiscoveryClient(discoveryClient, cacheDir, time.Duration(10*time.Minute))) + + factory := util.NewFactory(configFlags) mapper, err := factory.RESTMapper() if err != nil { t.Errorf("unexpected error getting mapper: %v", err)