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