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