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	"io"
21	"sync"
22
23	"k8s.io/klog/v2"
24
25	restclient "k8s.io/client-go/rest"
26	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
27)
28
29// DeferredLoadingClientConfig is a ClientConfig interface that is backed by a client config loader.
30// It is used in cases where the loading rules may change after you've instantiated them and you want to be sure that
31// the most recent rules are used.  This is useful in cases where you bind flags to loading rule parameters before
32// the parse happens and you want your calling code to be ignorant of how the values are being mutated to avoid
33// passing extraneous information down a call stack
34type DeferredLoadingClientConfig struct {
35	loader         ClientConfigLoader
36	overrides      *ConfigOverrides
37	fallbackReader io.Reader
38
39	clientConfig ClientConfig
40	loadingLock  sync.Mutex
41
42	// provided for testing
43	icc InClusterConfig
44}
45
46// InClusterConfig abstracts details of whether the client is running in a cluster for testing.
47type InClusterConfig interface {
48	ClientConfig
49	Possible() bool
50}
51
52// NewNonInteractiveDeferredLoadingClientConfig creates a ConfigClientClientConfig using the passed context name
53func NewNonInteractiveDeferredLoadingClientConfig(loader ClientConfigLoader, overrides *ConfigOverrides) ClientConfig {
54	return &DeferredLoadingClientConfig{loader: loader, overrides: overrides, icc: &inClusterClientConfig{overrides: overrides}}
55}
56
57// NewInteractiveDeferredLoadingClientConfig creates a ConfigClientClientConfig using the passed context name and the fallback auth reader
58func NewInteractiveDeferredLoadingClientConfig(loader ClientConfigLoader, overrides *ConfigOverrides, fallbackReader io.Reader) ClientConfig {
59	return &DeferredLoadingClientConfig{loader: loader, overrides: overrides, icc: &inClusterClientConfig{overrides: overrides}, fallbackReader: fallbackReader}
60}
61
62func (config *DeferredLoadingClientConfig) createClientConfig() (ClientConfig, error) {
63	config.loadingLock.Lock()
64	defer config.loadingLock.Unlock()
65
66	if config.clientConfig != nil {
67		return config.clientConfig, nil
68	}
69	mergedConfig, err := config.loader.Load()
70	if err != nil {
71		return nil, err
72	}
73
74	var currentContext string
75	if config.overrides != nil {
76		currentContext = config.overrides.CurrentContext
77	}
78	if config.fallbackReader != nil {
79		config.clientConfig = NewInteractiveClientConfig(*mergedConfig, currentContext, config.overrides, config.fallbackReader, config.loader)
80	} else {
81		config.clientConfig = NewNonInteractiveClientConfig(*mergedConfig, currentContext, config.overrides, config.loader)
82	}
83	return config.clientConfig, nil
84}
85
86func (config *DeferredLoadingClientConfig) RawConfig() (clientcmdapi.Config, error) {
87	mergedConfig, err := config.createClientConfig()
88	if err != nil {
89		return clientcmdapi.Config{}, err
90	}
91
92	return mergedConfig.RawConfig()
93}
94
95// ClientConfig implements ClientConfig
96func (config *DeferredLoadingClientConfig) ClientConfig() (*restclient.Config, error) {
97	mergedClientConfig, err := config.createClientConfig()
98	if err != nil {
99		return nil, err
100	}
101
102	// load the configuration and return on non-empty errors and if the
103	// content differs from the default config
104	mergedConfig, err := mergedClientConfig.ClientConfig()
105	switch {
106	case err != nil:
107		if !IsEmptyConfig(err) {
108			// return on any error except empty config
109			return nil, err
110		}
111	case mergedConfig != nil:
112		// the configuration is valid, but if this is equal to the defaults we should try
113		// in-cluster configuration
114		if !config.loader.IsDefaultConfig(mergedConfig) {
115			return mergedConfig, nil
116		}
117	}
118
119	// check for in-cluster configuration and use it
120	if config.icc.Possible() {
121		klog.V(4).Infof("Using in-cluster configuration")
122		return config.icc.ClientConfig()
123	}
124
125	// return the result of the merged client config
126	return mergedConfig, err
127}
128
129// Namespace implements KubeConfig
130func (config *DeferredLoadingClientConfig) Namespace() (string, bool, error) {
131	mergedKubeConfig, err := config.createClientConfig()
132	if err != nil {
133		return "", false, err
134	}
135
136	ns, overridden, err := mergedKubeConfig.Namespace()
137	// if we get an error and it is not empty config, or if the merged config defined an explicit namespace, or
138	// if in-cluster config is not possible, return immediately
139	if (err != nil && !IsEmptyConfig(err)) || overridden || !config.icc.Possible() {
140		// return on any error except empty config
141		return ns, overridden, err
142	}
143
144	if len(ns) > 0 {
145		// if we got a non-default namespace from the kubeconfig, use it
146		if ns != "default" {
147			return ns, false, nil
148		}
149
150		// if we got a default namespace, determine whether it was explicit or implicit
151		if raw, err := mergedKubeConfig.RawConfig(); err == nil {
152			// determine the current context
153			currentContext := raw.CurrentContext
154			if config.overrides != nil && len(config.overrides.CurrentContext) > 0 {
155				currentContext = config.overrides.CurrentContext
156			}
157			if context := raw.Contexts[currentContext]; context != nil && len(context.Namespace) > 0 {
158				return ns, false, nil
159			}
160		}
161	}
162
163	klog.V(4).Infof("Using in-cluster namespace")
164
165	// allow the namespace from the service account token directory to be used.
166	return config.icc.Namespace()
167}
168
169// ConfigAccess implements ClientConfig
170func (config *DeferredLoadingClientConfig) ConfigAccess() ConfigAccess {
171	return config.loader
172}
173