client_config.go 21 KB

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