Merge pull request #63373 from juanvallejo/jvallejo/begin-wiring-config-flags-struct

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

Wire ConfigFlags struct through cmdutil.Factory

**Release note**:
```release-note
NONE
```

Continues the Flags struct pattern established with PrintFlags.
Wires ConfigFlags through the client_access_factory

cc @deads2k @soltysh
This commit is contained in:
Kubernetes Submit Queue 2018-05-07 12:22:07 -07:00 committed by GitHub
commit 81b96b822e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 426 additions and 188 deletions

View File

@ -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",
],
)

View File

@ -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++

View File

@ -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",
],
)

View File

@ -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)
}

View File

@ -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",

View File

@ -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)

View File

@ -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",

View File

@ -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)

View File

@ -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}

View File

@ -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")

View File

@ -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()

View File

@ -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)

View File

@ -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, "")
}

View File

@ -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
}

View File

@ -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"],

View File

@ -0,0 +1,332 @@
/*
Copyright 2018 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 util
import (
"net/http"
"os"
"path/filepath"
"time"
"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/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 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
loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
if f.KubeConfig != nil {
loadingRules.ExplicitPath = *f.KubeConfig
}
overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults}
// 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 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 {
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.
config.Burst = 100
cacheDir := filepath.Join(homedir.HomeDir(), ".kube", "http-cache")
if f.CacheDir != nil {
cacheDir = *f.CacheDir
}
if len(cacheDir) > 0 {
wt := config.WrapTransport
config.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
if wt != nil {
rt = wt(rt)
}
return transport.NewCacheRoundTripper(cacheDir, rt)
}
}
discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
return nil, err
}
cacheDir = computeDiscoverCacheDir(filepath.Join(homedir.HomeDir(), ".kube", "cache", "discovery"), config.Host)
return NewCachedDiscoveryClient(discoveryClient, cacheDir, time.Duration(10*time.Minute)), nil
}
func (f *ConfigFlags) AddFlags(flags *pflag.FlagSet) {
flags.SetNormalizeFunc(utilflag.WarnWordSepNormalizeFunc) // Warn for "_" flags
// 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 {
flags.StringVar(f.KubeConfig, "kubeconfig", *f.KubeConfig, "Path to the kubeconfig file to use for CLI requests.")
}
if f.CacheDir != nil {
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 {
impersonateGroup := []string{}
insecure := false
return &ConfigFlags{
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{}
}

View File

@ -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)

View File

@ -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 (

View File

@ -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

View File

@ -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",

View File

@ -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"],

View File

@ -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",

View File

@ -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)