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