blob: 66331a7ad3b08250f1ac24443e9329118361e7b0 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001/*
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/golang/glog"
28 "github.com/imdario/mergo"
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
179 // mergo is a first write wins for map value and a last writing wins for interface values
180 // NOTE: This behavior changed with https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a.
181 // Our mergo.Merge version is older than this change.
182 var persister restclient.AuthProviderConfigPersister
183 if config.configAccess != nil {
184 authInfoName, _ := config.getAuthInfoName()
185 persister = PersisterForUser(config.configAccess, authInfoName)
186 }
187 userAuthPartialConfig, err := config.getUserIdentificationPartialConfig(configAuthInfo, config.fallbackReader, persister)
188 if err != nil {
189 return nil, err
190 }
191 mergo.Merge(clientConfig, userAuthPartialConfig)
192
193 serverAuthPartialConfig, err := getServerIdentificationPartialConfig(configAuthInfo, configClusterInfo)
194 if err != nil {
195 return nil, err
196 }
197 mergo.Merge(clientConfig, serverAuthPartialConfig)
198 }
199
200 return clientConfig, nil
201}
202
203// clientauth.Info object contain both user identification and server identification. We want different precedence orders for
204// both, so we have to split the objects and merge them separately
205// we want this order of precedence for the server identification
206// 1. configClusterInfo (the final result of command line flags and merged .kubeconfig files)
207// 2. configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
208// 3. load the ~/.kubernetes_auth file as a default
209func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, configClusterInfo clientcmdapi.Cluster) (*restclient.Config, error) {
210 mergedConfig := &restclient.Config{}
211
212 // configClusterInfo holds the information identify the server provided by .kubeconfig
213 configClientConfig := &restclient.Config{}
214 configClientConfig.CAFile = configClusterInfo.CertificateAuthority
215 configClientConfig.CAData = configClusterInfo.CertificateAuthorityData
216 configClientConfig.Insecure = configClusterInfo.InsecureSkipTLSVerify
217 mergo.Merge(mergedConfig, configClientConfig)
218
219 return mergedConfig, nil
220}
221
222// clientauth.Info object contain both user identification and server identification. We want different precedence orders for
223// both, so we have to split the objects and merge them separately
224// we want this order of precedence for user identification
225// 1. configAuthInfo minus auth-path (the final result of command line flags and merged .kubeconfig files)
226// 2. configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
227// 3. if there is not enough information to identify the user, load try the ~/.kubernetes_auth file
228// 4. if there is not enough information to identify the user, prompt if possible
229func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fallbackReader io.Reader, persistAuthConfig restclient.AuthProviderConfigPersister) (*restclient.Config, error) {
230 mergedConfig := &restclient.Config{}
231
232 // blindly overwrite existing values based on precedence
233 if len(configAuthInfo.Token) > 0 {
234 mergedConfig.BearerToken = configAuthInfo.Token
235 } else if len(configAuthInfo.TokenFile) > 0 {
236 tokenBytes, err := ioutil.ReadFile(configAuthInfo.TokenFile)
237 if err != nil {
238 return nil, err
239 }
240 mergedConfig.BearerToken = string(tokenBytes)
241 }
242 if len(configAuthInfo.Impersonate) > 0 {
243 mergedConfig.Impersonate = restclient.ImpersonationConfig{
244 UserName: configAuthInfo.Impersonate,
245 Groups: configAuthInfo.ImpersonateGroups,
246 Extra: configAuthInfo.ImpersonateUserExtra,
247 }
248 }
249 if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 {
250 mergedConfig.CertFile = configAuthInfo.ClientCertificate
251 mergedConfig.CertData = configAuthInfo.ClientCertificateData
252 mergedConfig.KeyFile = configAuthInfo.ClientKey
253 mergedConfig.KeyData = configAuthInfo.ClientKeyData
254 }
255 if len(configAuthInfo.Username) > 0 || len(configAuthInfo.Password) > 0 {
256 mergedConfig.Username = configAuthInfo.Username
257 mergedConfig.Password = configAuthInfo.Password
258 }
259 if configAuthInfo.AuthProvider != nil {
260 mergedConfig.AuthProvider = configAuthInfo.AuthProvider
261 mergedConfig.AuthConfigPersister = persistAuthConfig
262 }
263 if configAuthInfo.Exec != nil {
264 mergedConfig.ExecProvider = configAuthInfo.Exec
265 }
266
267 // if there still isn't enough information to authenticate the user, try prompting
268 if !canIdentifyUser(*mergedConfig) && (fallbackReader != nil) {
269 if len(config.promptedCredentials.username) > 0 && len(config.promptedCredentials.password) > 0 {
270 mergedConfig.Username = config.promptedCredentials.username
271 mergedConfig.Password = config.promptedCredentials.password
272 return mergedConfig, nil
273 }
274 prompter := NewPromptingAuthLoader(fallbackReader)
275 promptedAuthInfo, err := prompter.Prompt()
276 if err != nil {
277 return nil, err
278 }
279 promptedConfig := makeUserIdentificationConfig(*promptedAuthInfo)
280 previouslyMergedConfig := mergedConfig
281 mergedConfig = &restclient.Config{}
282 mergo.Merge(mergedConfig, promptedConfig)
283 mergo.Merge(mergedConfig, previouslyMergedConfig)
284 config.promptedCredentials.username = mergedConfig.Username
285 config.promptedCredentials.password = mergedConfig.Password
286 }
287
288 return mergedConfig, nil
289}
290
291// makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only user identification information
292func makeUserIdentificationConfig(info clientauth.Info) *restclient.Config {
293 config := &restclient.Config{}
294 config.Username = info.User
295 config.Password = info.Password
296 config.CertFile = info.CertFile
297 config.KeyFile = info.KeyFile
298 config.BearerToken = info.BearerToken
299 return config
300}
301
302// makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only server identification information
303func makeServerIdentificationConfig(info clientauth.Info) restclient.Config {
304 config := restclient.Config{}
305 config.CAFile = info.CAFile
306 if info.Insecure != nil {
307 config.Insecure = *info.Insecure
308 }
309 return config
310}
311
312func canIdentifyUser(config restclient.Config) bool {
313 return len(config.Username) > 0 ||
314 (len(config.CertFile) > 0 || len(config.CertData) > 0) ||
315 len(config.BearerToken) > 0 ||
316 config.AuthProvider != nil ||
317 config.ExecProvider != nil
318}
319
320// Namespace implements ClientConfig
321func (config *DirectClientConfig) Namespace() (string, bool, error) {
322 if config.overrides != nil && config.overrides.Context.Namespace != "" {
323 // In the event we have an empty config but we do have a namespace override, we should return
324 // the namespace override instead of having config.ConfirmUsable() return an error. This allows
325 // things like in-cluster clients to execute `kubectl get pods --namespace=foo` and have the
326 // --namespace flag honored instead of being ignored.
327 return config.overrides.Context.Namespace, true, nil
328 }
329
330 if err := config.ConfirmUsable(); err != nil {
331 return "", false, err
332 }
333
334 configContext, err := config.getContext()
335 if err != nil {
336 return "", false, err
337 }
338
339 if len(configContext.Namespace) == 0 {
340 return "default", false, nil
341 }
342
343 return configContext.Namespace, false, nil
344}
345
346// ConfigAccess implements ClientConfig
347func (config *DirectClientConfig) ConfigAccess() ConfigAccess {
348 return config.configAccess
349}
350
351// ConfirmUsable looks a particular context and determines if that particular part of the config is useable. There might still be errors in the config,
352// but no errors in the sections requested or referenced. It does not return early so that it can find as many errors as possible.
353func (config *DirectClientConfig) ConfirmUsable() error {
354 validationErrors := make([]error, 0)
355
356 var contextName string
357 if len(config.contextName) != 0 {
358 contextName = config.contextName
359 } else {
360 contextName = config.config.CurrentContext
361 }
362
363 if len(contextName) > 0 {
364 _, exists := config.config.Contexts[contextName]
365 if !exists {
366 validationErrors = append(validationErrors, &errContextNotFound{contextName})
367 }
368 }
369
370 authInfoName, _ := config.getAuthInfoName()
371 authInfo, _ := config.getAuthInfo()
372 validationErrors = append(validationErrors, validateAuthInfo(authInfoName, authInfo)...)
373 clusterName, _ := config.getClusterName()
374 cluster, _ := config.getCluster()
375 validationErrors = append(validationErrors, validateClusterInfo(clusterName, cluster)...)
376 // when direct client config is specified, and our only error is that no server is defined, we should
377 // return a standard "no config" error
378 if len(validationErrors) == 1 && validationErrors[0] == ErrEmptyCluster {
379 return newErrConfigurationInvalid([]error{ErrEmptyConfig})
380 }
381 return newErrConfigurationInvalid(validationErrors)
382}
383
384// getContextName returns the default, or user-set context name, and a boolean that indicates
385// whether the default context name has been overwritten by a user-set flag, or left as its default value
386func (config *DirectClientConfig) getContextName() (string, bool) {
387 if len(config.overrides.CurrentContext) != 0 {
388 return config.overrides.CurrentContext, true
389 }
390 if len(config.contextName) != 0 {
391 return config.contextName, false
392 }
393
394 return config.config.CurrentContext, false
395}
396
397// getAuthInfoName returns a string containing the current authinfo name for the current context,
398// and a boolean indicating whether the default authInfo name is overwritten by a user-set flag, or
399// left as its default value
400func (config *DirectClientConfig) getAuthInfoName() (string, bool) {
401 if len(config.overrides.Context.AuthInfo) != 0 {
402 return config.overrides.Context.AuthInfo, true
403 }
404 context, _ := config.getContext()
405 return context.AuthInfo, false
406}
407
408// getClusterName returns a string containing the default, or user-set cluster name, and a boolean
409// indicating whether the default clusterName has been overwritten by a user-set flag, or left as
410// its default value
411func (config *DirectClientConfig) getClusterName() (string, bool) {
412 if len(config.overrides.Context.Cluster) != 0 {
413 return config.overrides.Context.Cluster, true
414 }
415 context, _ := config.getContext()
416 return context.Cluster, false
417}
418
419// getContext returns the clientcmdapi.Context, or an error if a required context is not found.
420func (config *DirectClientConfig) getContext() (clientcmdapi.Context, error) {
421 contexts := config.config.Contexts
422 contextName, required := config.getContextName()
423
424 mergedContext := clientcmdapi.NewContext()
425 if configContext, exists := contexts[contextName]; exists {
426 mergo.Merge(mergedContext, configContext)
427 } else if required {
428 return clientcmdapi.Context{}, fmt.Errorf("context %q does not exist", contextName)
429 }
430 mergo.Merge(mergedContext, config.overrides.Context)
431
432 return *mergedContext, nil
433}
434
435// getAuthInfo returns the clientcmdapi.AuthInfo, or an error if a required auth info is not found.
436func (config *DirectClientConfig) getAuthInfo() (clientcmdapi.AuthInfo, error) {
437 authInfos := config.config.AuthInfos
438 authInfoName, required := config.getAuthInfoName()
439
440 mergedAuthInfo := clientcmdapi.NewAuthInfo()
441 if configAuthInfo, exists := authInfos[authInfoName]; exists {
442 mergo.Merge(mergedAuthInfo, configAuthInfo)
443 } else if required {
444 return clientcmdapi.AuthInfo{}, fmt.Errorf("auth info %q does not exist", authInfoName)
445 }
446 mergo.Merge(mergedAuthInfo, config.overrides.AuthInfo)
447
448 return *mergedAuthInfo, nil
449}
450
451// getCluster returns the clientcmdapi.Cluster, or an error if a required cluster is not found.
452func (config *DirectClientConfig) getCluster() (clientcmdapi.Cluster, error) {
453 clusterInfos := config.config.Clusters
454 clusterInfoName, required := config.getClusterName()
455
456 mergedClusterInfo := clientcmdapi.NewCluster()
457 mergo.Merge(mergedClusterInfo, config.overrides.ClusterDefaults)
458 if configClusterInfo, exists := clusterInfos[clusterInfoName]; exists {
459 mergo.Merge(mergedClusterInfo, configClusterInfo)
460 } else if required {
461 return clientcmdapi.Cluster{}, fmt.Errorf("cluster %q does not exist", clusterInfoName)
462 }
463 mergo.Merge(mergedClusterInfo, config.overrides.ClusterInfo)
464 // An override of --insecure-skip-tls-verify=true and no accompanying CA/CA data should clear already-set CA/CA data
465 // otherwise, a kubeconfig containing a CA reference would return an error that "CA and insecure-skip-tls-verify couldn't both be set"
466 caLen := len(config.overrides.ClusterInfo.CertificateAuthority)
467 caDataLen := len(config.overrides.ClusterInfo.CertificateAuthorityData)
468 if config.overrides.ClusterInfo.InsecureSkipTLSVerify && caLen == 0 && caDataLen == 0 {
469 mergedClusterInfo.CertificateAuthority = ""
470 mergedClusterInfo.CertificateAuthorityData = nil
471 }
472
473 return *mergedClusterInfo, nil
474}
475
476// inClusterClientConfig makes a config that will work from within a kubernetes cluster container environment.
477// Can take options overrides for flags explicitly provided to the command inside the cluster container.
478type inClusterClientConfig struct {
479 overrides *ConfigOverrides
480 inClusterConfigProvider func() (*restclient.Config, error)
481}
482
483var _ ClientConfig = &inClusterClientConfig{}
484
485func (config *inClusterClientConfig) RawConfig() (clientcmdapi.Config, error) {
486 return clientcmdapi.Config{}, fmt.Errorf("inCluster environment config doesn't support multiple clusters")
487}
488
489func (config *inClusterClientConfig) ClientConfig() (*restclient.Config, error) {
490 if config.inClusterConfigProvider == nil {
491 config.inClusterConfigProvider = restclient.InClusterConfig
492 }
493
494 icc, err := config.inClusterConfigProvider()
495 if err != nil {
496 return nil, err
497 }
498
499 // in-cluster configs only takes a host, token, or CA file
500 // if any of them were individually provided, overwrite anything else
501 if config.overrides != nil {
502 if server := config.overrides.ClusterInfo.Server; len(server) > 0 {
503 icc.Host = server
504 }
505 if token := config.overrides.AuthInfo.Token; len(token) > 0 {
506 icc.BearerToken = token
507 }
508 if certificateAuthorityFile := config.overrides.ClusterInfo.CertificateAuthority; len(certificateAuthorityFile) > 0 {
509 icc.TLSClientConfig.CAFile = certificateAuthorityFile
510 }
511 }
512
513 return icc, err
514}
515
516func (config *inClusterClientConfig) Namespace() (string, bool, error) {
517 // This way assumes you've set the POD_NAMESPACE environment variable using the downward API.
518 // This check has to be done first for backwards compatibility with the way InClusterConfig was originally set up
519 if ns := os.Getenv("POD_NAMESPACE"); ns != "" {
520 return ns, false, nil
521 }
522
523 // Fall back to the namespace associated with the service account token, if available
524 if data, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil {
525 if ns := strings.TrimSpace(string(data)); len(ns) > 0 {
526 return ns, false, nil
527 }
528 }
529
530 return "default", false, nil
531}
532
533func (config *inClusterClientConfig) ConfigAccess() ConfigAccess {
534 return NewDefaultClientConfigLoadingRules()
535}
536
537// Possible returns true if loading an inside-kubernetes-cluster is possible.
538func (config *inClusterClientConfig) Possible() bool {
539 fi, err := os.Stat("/var/run/secrets/kubernetes.io/serviceaccount/token")
540 return os.Getenv("KUBERNETES_SERVICE_HOST") != "" &&
541 os.Getenv("KUBERNETES_SERVICE_PORT") != "" &&
542 err == nil && !fi.IsDir()
543}
544
545// BuildConfigFromFlags is a helper function that builds configs from a master
546// url or a kubeconfig filepath. These are passed in as command line flags for cluster
547// components. Warnings should reflect this usage. If neither masterUrl or kubeconfigPath
548// are passed in we fallback to inClusterConfig. If inClusterConfig fails, we fallback
549// to the default config.
550func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) {
551 if kubeconfigPath == "" && masterUrl == "" {
552 glog.Warningf("Neither --kubeconfig nor --master was specified. Using the inClusterConfig. This might not work.")
553 kubeconfig, err := restclient.InClusterConfig()
554 if err == nil {
555 return kubeconfig, nil
556 }
557 glog.Warning("error creating inClusterConfig, falling back to default config: ", err)
558 }
559 return NewNonInteractiveDeferredLoadingClientConfig(
560 &ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
561 &ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
562}
563
564// BuildConfigFromKubeconfigGetter is a helper function that builds configs from a master
565// url and a kubeconfigGetter.
566func BuildConfigFromKubeconfigGetter(masterUrl string, kubeconfigGetter KubeconfigGetter) (*restclient.Config, error) {
567 // TODO: We do not need a DeferredLoader here. Refactor code and see if we can use DirectClientConfig here.
568 cc := NewNonInteractiveDeferredLoadingClientConfig(
569 &ClientConfigGetter{kubeconfigGetter: kubeconfigGetter},
570 &ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}})
571 return cc.ClientConfig()
572}