blob: dea229c91825117cac60178df64eb6cccbbab57e [file] [log] [blame]
Kent Hagermane566c2e2019-06-03 17:56:42 -04001/*
2Copyright 2014 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package clientcmd
18
19import (
20 "fmt"
21 "io"
22 "io/ioutil"
23 "net/url"
24 "os"
25 "strings"
26
27 "github.com/imdario/mergo"
28 "k8s.io/klog"
29
30 restclient "k8s.io/client-go/rest"
31 clientauth "k8s.io/client-go/tools/auth"
32 clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
33)
34
35var (
36 // ClusterDefaults has the same behavior as the old EnvVar and DefaultCluster fields
37 // DEPRECATED will be replaced
38 ClusterDefaults = clientcmdapi.Cluster{Server: getDefaultServer()}
39 // DefaultClientConfig represents the legacy behavior of this package for defaulting
40 // DEPRECATED will be replace
41 DefaultClientConfig = DirectClientConfig{*clientcmdapi.NewConfig(), "", &ConfigOverrides{
42 ClusterDefaults: ClusterDefaults,
43 }, nil, NewDefaultClientConfigLoadingRules(), promptedCredentials{}}
44)
45
46// getDefaultServer returns a default setting for DefaultClientConfig
47// DEPRECATED
48func getDefaultServer() string {
49 if server := os.Getenv("KUBERNETES_MASTER"); len(server) > 0 {
50 return server
51 }
52 return "http://localhost:8080"
53}
54
55// ClientConfig is used to make it easy to get an api server client
56type ClientConfig interface {
57 // RawConfig returns the merged result of all overrides
58 RawConfig() (clientcmdapi.Config, error)
59 // ClientConfig returns a complete client config
60 ClientConfig() (*restclient.Config, error)
61 // Namespace returns the namespace resulting from the merged
62 // result of all overrides and a boolean indicating if it was
63 // overridden
64 Namespace() (string, bool, error)
65 // ConfigAccess returns the rules for loading/persisting the config.
66 ConfigAccess() ConfigAccess
67}
68
69type PersistAuthProviderConfigForUser func(user string) restclient.AuthProviderConfigPersister
70
71type promptedCredentials struct {
72 username string
73 password string
74}
75
76// DirectClientConfig is a ClientConfig interface that is backed by a clientcmdapi.Config, options overrides, and an optional fallbackReader for auth information
77type DirectClientConfig struct {
78 config clientcmdapi.Config
79 contextName string
80 overrides *ConfigOverrides
81 fallbackReader io.Reader
82 configAccess ConfigAccess
83 // promptedCredentials store the credentials input by the user
84 promptedCredentials promptedCredentials
85}
86
87// NewDefaultClientConfig creates a DirectClientConfig using the config.CurrentContext as the context name
88func NewDefaultClientConfig(config clientcmdapi.Config, overrides *ConfigOverrides) ClientConfig {
89 return &DirectClientConfig{config, config.CurrentContext, overrides, nil, NewDefaultClientConfigLoadingRules(), promptedCredentials{}}
90}
91
92// NewNonInteractiveClientConfig creates a DirectClientConfig using the passed context name and does not have a fallback reader for auth information
93func NewNonInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, configAccess ConfigAccess) ClientConfig {
94 return &DirectClientConfig{config, contextName, overrides, nil, configAccess, promptedCredentials{}}
95}
96
97// NewInteractiveClientConfig creates a DirectClientConfig using the passed context name and a reader in case auth information is not provided via files or flags
98func NewInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, fallbackReader io.Reader, configAccess ConfigAccess) ClientConfig {
99 return &DirectClientConfig{config, contextName, overrides, fallbackReader, configAccess, promptedCredentials{}}
100}
101
102// NewClientConfigFromBytes takes your kubeconfig and gives you back a ClientConfig
103func NewClientConfigFromBytes(configBytes []byte) (ClientConfig, error) {
104 config, err := Load(configBytes)
105 if err != nil {
106 return nil, err
107 }
108
109 return &DirectClientConfig{*config, "", &ConfigOverrides{}, nil, nil, promptedCredentials{}}, nil
110}
111
112// RESTConfigFromKubeConfig is a convenience method to give back a restconfig from your kubeconfig bytes.
113// For programmatic access, this is what you want 80% of the time
114func RESTConfigFromKubeConfig(configBytes []byte) (*restclient.Config, error) {
115 clientConfig, err := NewClientConfigFromBytes(configBytes)
116 if err != nil {
117 return nil, err
118 }
119 return clientConfig.ClientConfig()
120}
121
122func (config *DirectClientConfig) RawConfig() (clientcmdapi.Config, error) {
123 return config.config, nil
124}
125
126// ClientConfig implements ClientConfig
127func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
128 // check that getAuthInfo, getContext, and getCluster do not return an error.
129 // Do this before checking if the current config is usable in the event that an
130 // AuthInfo, Context, or Cluster config with user-defined names are not found.
131 // This provides a user with the immediate cause for error if one is found
132 configAuthInfo, err := config.getAuthInfo()
133 if err != nil {
134 return nil, err
135 }
136
137 _, err = config.getContext()
138 if err != nil {
139 return nil, err
140 }
141
142 configClusterInfo, err := config.getCluster()
143 if err != nil {
144 return nil, err
145 }
146
147 if err := config.ConfirmUsable(); err != nil {
148 return nil, err
149 }
150
151 clientConfig := &restclient.Config{}
152 clientConfig.Host = configClusterInfo.Server
153
154 if len(config.overrides.Timeout) > 0 {
155 timeout, err := ParseTimeout(config.overrides.Timeout)
156 if err != nil {
157 return nil, err
158 }
159 clientConfig.Timeout = timeout
160 }
161
162 if u, err := url.ParseRequestURI(clientConfig.Host); err == nil && u.Opaque == "" && len(u.Path) > 1 {
163 u.RawQuery = ""
164 u.Fragment = ""
165 clientConfig.Host = u.String()
166 }
167 if len(configAuthInfo.Impersonate) > 0 {
168 clientConfig.Impersonate = restclient.ImpersonationConfig{
169 UserName: configAuthInfo.Impersonate,
170 Groups: configAuthInfo.ImpersonateGroups,
171 Extra: configAuthInfo.ImpersonateUserExtra,
172 }
173 }
174
175 // only try to read the auth information if we are secure
176 if restclient.IsConfigTransportTLS(*clientConfig) {
177 var err error
178 var persister restclient.AuthProviderConfigPersister
179 if config.configAccess != nil {
180 authInfoName, _ := config.getAuthInfoName()
181 persister = PersisterForUser(config.configAccess, authInfoName)
182 }
183 userAuthPartialConfig, err := config.getUserIdentificationPartialConfig(configAuthInfo, config.fallbackReader, persister)
184 if err != nil {
185 return nil, err
186 }
187 mergo.MergeWithOverwrite(clientConfig, userAuthPartialConfig)
188
189 serverAuthPartialConfig, err := getServerIdentificationPartialConfig(configAuthInfo, configClusterInfo)
190 if err != nil {
191 return nil, err
192 }
193 mergo.MergeWithOverwrite(clientConfig, serverAuthPartialConfig)
194 }
195
196 return clientConfig, nil
197}
198
199// clientauth.Info object contain both user identification and server identification. We want different precedence orders for
200// both, so we have to split the objects and merge them separately
201// we want this order of precedence for the server identification
202// 1. configClusterInfo (the final result of command line flags and merged .kubeconfig files)
203// 2. configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
204// 3. load the ~/.kubernetes_auth file as a default
205func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, configClusterInfo clientcmdapi.Cluster) (*restclient.Config, error) {
206 mergedConfig := &restclient.Config{}
207
208 // configClusterInfo holds the information identify the server provided by .kubeconfig
209 configClientConfig := &restclient.Config{}
210 configClientConfig.CAFile = configClusterInfo.CertificateAuthority
211 configClientConfig.CAData = configClusterInfo.CertificateAuthorityData
212 configClientConfig.Insecure = configClusterInfo.InsecureSkipTLSVerify
213 mergo.MergeWithOverwrite(mergedConfig, configClientConfig)
214
215 return mergedConfig, nil
216}
217
218// clientauth.Info object contain both user identification and server identification. We want different precedence orders for
219// both, so we have to split the objects and merge them separately
220// we want this order of precedence for user identification
221// 1. configAuthInfo minus auth-path (the final result of command line flags and merged .kubeconfig files)
222// 2. configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
223// 3. if there is not enough information to identify the user, load try the ~/.kubernetes_auth file
224// 4. if there is not enough information to identify the user, prompt if possible
225func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fallbackReader io.Reader, persistAuthConfig restclient.AuthProviderConfigPersister) (*restclient.Config, error) {
226 mergedConfig := &restclient.Config{}
227
228 // blindly overwrite existing values based on precedence
229 if len(configAuthInfo.Token) > 0 {
230 mergedConfig.BearerToken = configAuthInfo.Token
231 } else if len(configAuthInfo.TokenFile) > 0 {
232 ts := restclient.NewCachedFileTokenSource(configAuthInfo.TokenFile)
233 if _, err := ts.Token(); err != nil {
234 return nil, err
235 }
236 mergedConfig.WrapTransport = restclient.TokenSourceWrapTransport(ts)
237 }
238 if len(configAuthInfo.Impersonate) > 0 {
239 mergedConfig.Impersonate = restclient.ImpersonationConfig{
240 UserName: configAuthInfo.Impersonate,
241 Groups: configAuthInfo.ImpersonateGroups,
242 Extra: configAuthInfo.ImpersonateUserExtra,
243 }
244 }
245 if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 {
246 mergedConfig.CertFile = configAuthInfo.ClientCertificate
247 mergedConfig.CertData = configAuthInfo.ClientCertificateData
248 mergedConfig.KeyFile = configAuthInfo.ClientKey
249 mergedConfig.KeyData = configAuthInfo.ClientKeyData
250 }
251 if len(configAuthInfo.Username) > 0 || len(configAuthInfo.Password) > 0 {
252 mergedConfig.Username = configAuthInfo.Username
253 mergedConfig.Password = configAuthInfo.Password
254 }
255 if configAuthInfo.AuthProvider != nil {
256 mergedConfig.AuthProvider = configAuthInfo.AuthProvider
257 mergedConfig.AuthConfigPersister = persistAuthConfig
258 }
259 if configAuthInfo.Exec != nil {
260 mergedConfig.ExecProvider = configAuthInfo.Exec
261 }
262
263 // if there still isn't enough information to authenticate the user, try prompting
264 if !canIdentifyUser(*mergedConfig) && (fallbackReader != nil) {
265 if len(config.promptedCredentials.username) > 0 && len(config.promptedCredentials.password) > 0 {
266 mergedConfig.Username = config.promptedCredentials.username
267 mergedConfig.Password = config.promptedCredentials.password
268 return mergedConfig, nil
269 }
270 prompter := NewPromptingAuthLoader(fallbackReader)
271 promptedAuthInfo, err := prompter.Prompt()
272 if err != nil {
273 return nil, err
274 }
275 promptedConfig := makeUserIdentificationConfig(*promptedAuthInfo)
276 previouslyMergedConfig := mergedConfig
277 mergedConfig = &restclient.Config{}
278 mergo.MergeWithOverwrite(mergedConfig, promptedConfig)
279 mergo.MergeWithOverwrite(mergedConfig, previouslyMergedConfig)
280 config.promptedCredentials.username = mergedConfig.Username
281 config.promptedCredentials.password = mergedConfig.Password
282 }
283
284 return mergedConfig, nil
285}
286
287// makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only user identification information
288func makeUserIdentificationConfig(info clientauth.Info) *restclient.Config {
289 config := &restclient.Config{}
290 config.Username = info.User
291 config.Password = info.Password
292 config.CertFile = info.CertFile
293 config.KeyFile = info.KeyFile
294 config.BearerToken = info.BearerToken
295 return config
296}
297
298// makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only server identification information
299func makeServerIdentificationConfig(info clientauth.Info) restclient.Config {
300 config := restclient.Config{}
301 config.CAFile = info.CAFile
302 if info.Insecure != nil {
303 config.Insecure = *info.Insecure
304 }
305 return config
306}
307
308func canIdentifyUser(config restclient.Config) bool {
309 return len(config.Username) > 0 ||
310 (len(config.CertFile) > 0 || len(config.CertData) > 0) ||
311 len(config.BearerToken) > 0 ||
312 config.AuthProvider != nil ||
313 config.ExecProvider != nil
314}
315
316// Namespace implements ClientConfig
317func (config *DirectClientConfig) Namespace() (string, bool, error) {
318 if config.overrides != nil && config.overrides.Context.Namespace != "" {
319 // In the event we have an empty config but we do have a namespace override, we should return
320 // the namespace override instead of having config.ConfirmUsable() return an error. This allows
321 // things like in-cluster clients to execute `kubectl get pods --namespace=foo` and have the
322 // --namespace flag honored instead of being ignored.
323 return config.overrides.Context.Namespace, true, nil
324 }
325
326 if err := config.ConfirmUsable(); err != nil {
327 return "", false, err
328 }
329
330 configContext, err := config.getContext()
331 if err != nil {
332 return "", false, err
333 }
334
335 if len(configContext.Namespace) == 0 {
336 return "default", false, nil
337 }
338
339 return configContext.Namespace, false, nil
340}
341
342// ConfigAccess implements ClientConfig
343func (config *DirectClientConfig) ConfigAccess() ConfigAccess {
344 return config.configAccess
345}
346
347// ConfirmUsable looks a particular context and determines if that particular part of the config is useable. There might still be errors in the config,
348// but no errors in the sections requested or referenced. It does not return early so that it can find as many errors as possible.
349func (config *DirectClientConfig) ConfirmUsable() error {
350 validationErrors := make([]error, 0)
351
352 var contextName string
353 if len(config.contextName) != 0 {
354 contextName = config.contextName
355 } else {
356 contextName = config.config.CurrentContext
357 }
358
359 if len(contextName) > 0 {
360 _, exists := config.config.Contexts[contextName]
361 if !exists {
362 validationErrors = append(validationErrors, &errContextNotFound{contextName})
363 }
364 }
365
366 authInfoName, _ := config.getAuthInfoName()
367 authInfo, _ := config.getAuthInfo()
368 validationErrors = append(validationErrors, validateAuthInfo(authInfoName, authInfo)...)
369 clusterName, _ := config.getClusterName()
370 cluster, _ := config.getCluster()
371 validationErrors = append(validationErrors, validateClusterInfo(clusterName, cluster)...)
372 // when direct client config is specified, and our only error is that no server is defined, we should
373 // return a standard "no config" error
374 if len(validationErrors) == 1 && validationErrors[0] == ErrEmptyCluster {
375 return newErrConfigurationInvalid([]error{ErrEmptyConfig})
376 }
377 return newErrConfigurationInvalid(validationErrors)
378}
379
380// getContextName returns the default, or user-set context name, and a boolean that indicates
381// whether the default context name has been overwritten by a user-set flag, or left as its default value
382func (config *DirectClientConfig) getContextName() (string, bool) {
383 if len(config.overrides.CurrentContext) != 0 {
384 return config.overrides.CurrentContext, true
385 }
386 if len(config.contextName) != 0 {
387 return config.contextName, false
388 }
389
390 return config.config.CurrentContext, false
391}
392
393// getAuthInfoName returns a string containing the current authinfo name for the current context,
394// and a boolean indicating whether the default authInfo name is overwritten by a user-set flag, or
395// left as its default value
396func (config *DirectClientConfig) getAuthInfoName() (string, bool) {
397 if len(config.overrides.Context.AuthInfo) != 0 {
398 return config.overrides.Context.AuthInfo, true
399 }
400 context, _ := config.getContext()
401 return context.AuthInfo, false
402}
403
404// getClusterName returns a string containing the default, or user-set cluster name, and a boolean
405// indicating whether the default clusterName has been overwritten by a user-set flag, or left as
406// its default value
407func (config *DirectClientConfig) getClusterName() (string, bool) {
408 if len(config.overrides.Context.Cluster) != 0 {
409 return config.overrides.Context.Cluster, true
410 }
411 context, _ := config.getContext()
412 return context.Cluster, false
413}
414
415// getContext returns the clientcmdapi.Context, or an error if a required context is not found.
416func (config *DirectClientConfig) getContext() (clientcmdapi.Context, error) {
417 contexts := config.config.Contexts
418 contextName, required := config.getContextName()
419
420 mergedContext := clientcmdapi.NewContext()
421 if configContext, exists := contexts[contextName]; exists {
422 mergo.MergeWithOverwrite(mergedContext, configContext)
423 } else if required {
424 return clientcmdapi.Context{}, fmt.Errorf("context %q does not exist", contextName)
425 }
426 mergo.MergeWithOverwrite(mergedContext, config.overrides.Context)
427
428 return *mergedContext, nil
429}
430
431// getAuthInfo returns the clientcmdapi.AuthInfo, or an error if a required auth info is not found.
432func (config *DirectClientConfig) getAuthInfo() (clientcmdapi.AuthInfo, error) {
433 authInfos := config.config.AuthInfos
434 authInfoName, required := config.getAuthInfoName()
435
436 mergedAuthInfo := clientcmdapi.NewAuthInfo()
437 if configAuthInfo, exists := authInfos[authInfoName]; exists {
438 mergo.MergeWithOverwrite(mergedAuthInfo, configAuthInfo)
439 } else if required {
440 return clientcmdapi.AuthInfo{}, fmt.Errorf("auth info %q does not exist", authInfoName)
441 }
442 mergo.MergeWithOverwrite(mergedAuthInfo, config.overrides.AuthInfo)
443
444 return *mergedAuthInfo, nil
445}
446
447// getCluster returns the clientcmdapi.Cluster, or an error if a required cluster is not found.
448func (config *DirectClientConfig) getCluster() (clientcmdapi.Cluster, error) {
449 clusterInfos := config.config.Clusters
450 clusterInfoName, required := config.getClusterName()
451
452 mergedClusterInfo := clientcmdapi.NewCluster()
453 mergo.MergeWithOverwrite(mergedClusterInfo, config.overrides.ClusterDefaults)
454 if configClusterInfo, exists := clusterInfos[clusterInfoName]; exists {
455 mergo.MergeWithOverwrite(mergedClusterInfo, configClusterInfo)
456 } else if required {
457 return clientcmdapi.Cluster{}, fmt.Errorf("cluster %q does not exist", clusterInfoName)
458 }
459 mergo.MergeWithOverwrite(mergedClusterInfo, config.overrides.ClusterInfo)
460 // An override of --insecure-skip-tls-verify=true and no accompanying CA/CA data should clear already-set CA/CA data
461 // otherwise, a kubeconfig containing a CA reference would return an error that "CA and insecure-skip-tls-verify couldn't both be set"
462 caLen := len(config.overrides.ClusterInfo.CertificateAuthority)
463 caDataLen := len(config.overrides.ClusterInfo.CertificateAuthorityData)
464 if config.overrides.ClusterInfo.InsecureSkipTLSVerify && caLen == 0 && caDataLen == 0 {
465 mergedClusterInfo.CertificateAuthority = ""
466 mergedClusterInfo.CertificateAuthorityData = nil
467 }
468
469 return *mergedClusterInfo, nil
470}
471
472// inClusterClientConfig makes a config that will work from within a kubernetes cluster container environment.
473// Can take options overrides for flags explicitly provided to the command inside the cluster container.
474type inClusterClientConfig struct {
475 overrides *ConfigOverrides
476 inClusterConfigProvider func() (*restclient.Config, error)
477}
478
479var _ ClientConfig = &inClusterClientConfig{}
480
481func (config *inClusterClientConfig) RawConfig() (clientcmdapi.Config, error) {
482 return clientcmdapi.Config{}, fmt.Errorf("inCluster environment config doesn't support multiple clusters")
483}
484
485func (config *inClusterClientConfig) ClientConfig() (*restclient.Config, error) {
486 if config.inClusterConfigProvider == nil {
487 config.inClusterConfigProvider = restclient.InClusterConfig
488 }
489
490 icc, err := config.inClusterConfigProvider()
491 if err != nil {
492 return nil, err
493 }
494
495 // in-cluster configs only takes a host, token, or CA file
496 // if any of them were individually provided, overwrite anything else
497 if config.overrides != nil {
498 if server := config.overrides.ClusterInfo.Server; len(server) > 0 {
499 icc.Host = server
500 }
501 if token := config.overrides.AuthInfo.Token; len(token) > 0 {
502 icc.BearerToken = token
503 }
504 if certificateAuthorityFile := config.overrides.ClusterInfo.CertificateAuthority; len(certificateAuthorityFile) > 0 {
505 icc.TLSClientConfig.CAFile = certificateAuthorityFile
506 }
507 }
508
509 return icc, err
510}
511
512func (config *inClusterClientConfig) Namespace() (string, bool, error) {
513 // This way assumes you've set the POD_NAMESPACE environment variable using the downward API.
514 // This check has to be done first for backwards compatibility with the way InClusterConfig was originally set up
515 if ns := os.Getenv("POD_NAMESPACE"); ns != "" {
516 return ns, false, nil
517 }
518
519 // Fall back to the namespace associated with the service account token, if available
520 if data, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil {
521 if ns := strings.TrimSpace(string(data)); len(ns) > 0 {
522 return ns, false, nil
523 }
524 }
525
526 return "default", false, nil
527}
528
529func (config *inClusterClientConfig) ConfigAccess() ConfigAccess {
530 return NewDefaultClientConfigLoadingRules()
531}
532
533// Possible returns true if loading an inside-kubernetes-cluster is possible.
534func (config *inClusterClientConfig) Possible() bool {
535 fi, err := os.Stat("/var/run/secrets/kubernetes.io/serviceaccount/token")
536 return os.Getenv("KUBERNETES_SERVICE_HOST") != "" &&
537 os.Getenv("KUBERNETES_SERVICE_PORT") != "" &&
538 err == nil && !fi.IsDir()
539}
540
541// BuildConfigFromFlags is a helper function that builds configs from a master
542// url or a kubeconfig filepath. These are passed in as command line flags for cluster
543// components. Warnings should reflect this usage. If neither masterUrl or kubeconfigPath
544// are passed in we fallback to inClusterConfig. If inClusterConfig fails, we fallback
545// to the default config.
546func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) {
547 if kubeconfigPath == "" && masterUrl == "" {
548 klog.Warningf("Neither --kubeconfig nor --master was specified. Using the inClusterConfig. This might not work.")
549 kubeconfig, err := restclient.InClusterConfig()
550 if err == nil {
551 return kubeconfig, nil
552 }
553 klog.Warning("error creating inClusterConfig, falling back to default config: ", err)
554 }
555 return NewNonInteractiveDeferredLoadingClientConfig(
556 &ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
557 &ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
558}
559
560// BuildConfigFromKubeconfigGetter is a helper function that builds configs from a master
561// url and a kubeconfigGetter.
562func BuildConfigFromKubeconfigGetter(masterUrl string, kubeconfigGetter KubeconfigGetter) (*restclient.Config, error) {
563 // TODO: We do not need a DeferredLoader here. Refactor code and see if we can use DirectClientConfig here.
564 cc := NewNonInteractiveDeferredLoadingClientConfig(
565 &ClientConfigGetter{kubeconfigGetter: kubeconfigGetter},
566 &ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}})
567 return cc.ClientConfig()
568}