2 Copyright 2014 The Kubernetes Authors.
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
30 "github.com/imdario/mergo"
33 "k8s.io/apimachinery/pkg/runtime"
34 "k8s.io/apimachinery/pkg/runtime/schema"
35 utilerrors "k8s.io/apimachinery/pkg/util/errors"
36 restclient "k8s.io/client-go/rest"
37 clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
38 clientcmdlatest "k8s.io/client-go/tools/clientcmd/api/latest"
39 "k8s.io/client-go/util/homedir"
43 RecommendedConfigPathFlag = "kubeconfig"
44 RecommendedConfigPathEnvVar = "KUBECONFIG"
45 RecommendedHomeDir = ".kube"
46 RecommendedFileName = "config"
47 RecommendedSchemaName = "schema"
51 RecommendedConfigDir = path.Join(homedir.HomeDir(), RecommendedHomeDir)
52 RecommendedHomeFile = path.Join(RecommendedConfigDir, RecommendedFileName)
53 RecommendedSchemaFile = path.Join(RecommendedConfigDir, RecommendedSchemaName)
56 // currentMigrationRules returns a map that holds the history of recommended home directories used in previous versions.
57 // Any future changes to RecommendedHomeFile and related are expected to add a migration rule here, in order to make
58 // sure existing config files are migrated to their new locations properly.
59 func currentMigrationRules() map[string]string {
60 oldRecommendedHomeFile := path.Join(os.Getenv("HOME"), "/.kube/.kubeconfig")
61 oldRecommendedWindowsHomeFile := path.Join(os.Getenv("HOME"), RecommendedHomeDir, RecommendedFileName)
63 migrationRules := map[string]string{}
64 migrationRules[RecommendedHomeFile] = oldRecommendedHomeFile
65 if goruntime.GOOS == "windows" {
66 migrationRules[RecommendedHomeFile] = oldRecommendedWindowsHomeFile
71 type ClientConfigLoader interface {
73 // IsDefaultConfig returns true if the returned config matches the defaults.
74 IsDefaultConfig(*restclient.Config) bool
75 // Load returns the latest config
76 Load() (*clientcmdapi.Config, error)
79 type KubeconfigGetter func() (*clientcmdapi.Config, error)
81 type ClientConfigGetter struct {
82 kubeconfigGetter KubeconfigGetter
85 // ClientConfigGetter implements the ClientConfigLoader interface.
86 var _ ClientConfigLoader = &ClientConfigGetter{}
88 func (g *ClientConfigGetter) Load() (*clientcmdapi.Config, error) {
89 return g.kubeconfigGetter()
92 func (g *ClientConfigGetter) GetLoadingPrecedence() []string {
95 func (g *ClientConfigGetter) GetStartingConfig() (*clientcmdapi.Config, error) {
96 return g.kubeconfigGetter()
98 func (g *ClientConfigGetter) GetDefaultFilename() string {
101 func (g *ClientConfigGetter) IsExplicitFile() bool {
104 func (g *ClientConfigGetter) GetExplicitFile() string {
107 func (g *ClientConfigGetter) IsDefaultConfig(config *restclient.Config) bool {
111 // ClientConfigLoadingRules is an ExplicitPath and string slice of specific locations that are used for merging together a Config
112 // Callers can put the chain together however they want, but we'd recommend:
113 // EnvVarPathFiles if set (a list of files if set) OR the HomeDirectoryPath
114 // ExplicitPath is special, because if a user specifically requests a certain file be used and error is reported if this file is not present
115 type ClientConfigLoadingRules struct {
119 // MigrationRules is a map of destination files to source files. If a destination file is not present, then the source file is checked.
120 // If the source file is present, then it is copied to the destination file BEFORE any further loading happens.
121 MigrationRules map[string]string
123 // DoNotResolvePaths indicates whether or not to resolve paths with respect to the originating files. This is phrased as a negative so
124 // that a default object that doesn't set this will usually get the behavior it wants.
125 DoNotResolvePaths bool
127 // DefaultClientConfig is an optional field indicating what rules to use to calculate a default configuration.
128 // This should match the overrides passed in to ClientConfig loader.
129 DefaultClientConfig ClientConfig
132 // ClientConfigLoadingRules implements the ClientConfigLoader interface.
133 var _ ClientConfigLoader = &ClientConfigLoadingRules{}
135 // NewDefaultClientConfigLoadingRules returns a ClientConfigLoadingRules object with default fields filled in. You are not required to
136 // use this constructor
137 func NewDefaultClientConfigLoadingRules() *ClientConfigLoadingRules {
140 envVarFiles := os.Getenv(RecommendedConfigPathEnvVar)
141 if len(envVarFiles) != 0 {
142 fileList := filepath.SplitList(envVarFiles)
143 // prevent the same path load multiple times
144 chain = append(chain, deduplicate(fileList)...)
147 chain = append(chain, RecommendedHomeFile)
150 return &ClientConfigLoadingRules{
152 MigrationRules: currentMigrationRules(),
156 // Load starts by running the MigrationRules and then
157 // takes the loading rules and returns a Config object based on following rules.
158 // if the ExplicitPath, return the unmerged explicit file
159 // Otherwise, return a merged config based on the Precedence slice
160 // A missing ExplicitPath file produces an error. Empty filenames or other missing files are ignored.
161 // Read errors or files with non-deserializable content produce errors.
162 // The first file to set a particular map key wins and map key's value is never changed.
163 // BUT, if you set a struct value that is NOT contained inside of map, the value WILL be changed.
164 // This results in some odd looking logic to merge in one direction, merge in the other, and then merge the two.
165 // It also means that if two files specify a "red-user", only values from the first file's red-user are used. Even
166 // non-conflicting entries from the second file's "red-user" are discarded.
167 // Relative paths inside of the .kubeconfig files are resolved against the .kubeconfig file's parent folder
168 // and only absolute file paths are returned.
169 func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
170 if err := rules.Migrate(); err != nil {
176 kubeConfigFiles := []string{}
178 // Make sure a file we were explicitly told to use exists
179 if len(rules.ExplicitPath) > 0 {
180 if _, err := os.Stat(rules.ExplicitPath); os.IsNotExist(err) {
183 kubeConfigFiles = append(kubeConfigFiles, rules.ExplicitPath)
186 kubeConfigFiles = append(kubeConfigFiles, rules.Precedence...)
189 kubeconfigs := []*clientcmdapi.Config{}
190 // read and cache the config files so that we only look at them once
191 for _, filename := range kubeConfigFiles {
192 if len(filename) == 0 {
197 config, err := LoadFromFile(filename)
198 if os.IsNotExist(err) {
199 // skip missing files
203 errlist = append(errlist, fmt.Errorf("Error loading config file \"%s\": %v", filename, err))
207 kubeconfigs = append(kubeconfigs, config)
210 // first merge all of our maps
211 mapConfig := clientcmdapi.NewConfig()
213 for _, kubeconfig := range kubeconfigs {
214 mergo.MergeWithOverwrite(mapConfig, kubeconfig)
217 // merge all of the struct values in the reverse order so that priority is given correctly
218 // errors are not added to the list the second time
219 nonMapConfig := clientcmdapi.NewConfig()
220 for i := len(kubeconfigs) - 1; i >= 0; i-- {
221 kubeconfig := kubeconfigs[i]
222 mergo.MergeWithOverwrite(nonMapConfig, kubeconfig)
225 // since values are overwritten, but maps values are not, we can merge the non-map config on top of the map config and
226 // get the values we expect.
227 config := clientcmdapi.NewConfig()
228 mergo.MergeWithOverwrite(config, mapConfig)
229 mergo.MergeWithOverwrite(config, nonMapConfig)
231 if rules.ResolvePaths() {
232 if err := ResolveLocalPaths(config); err != nil {
233 errlist = append(errlist, err)
236 return config, utilerrors.NewAggregate(errlist)
239 // Migrate uses the MigrationRules map. If a destination file is not present, then the source file is checked.
240 // If the source file is present, then it is copied to the destination file BEFORE any further loading happens.
241 func (rules *ClientConfigLoadingRules) Migrate() error {
242 if rules.MigrationRules == nil {
246 for destination, source := range rules.MigrationRules {
247 if _, err := os.Stat(destination); err == nil {
248 // if the destination already exists, do nothing
250 } else if os.IsPermission(err) {
251 // if we can't access the file, skip it
253 } else if !os.IsNotExist(err) {
254 // if we had an error other than non-existence, fail
258 if sourceInfo, err := os.Stat(source); err != nil {
259 if os.IsNotExist(err) || os.IsPermission(err) {
260 // if the source file doesn't exist or we can't access it, there's no work to do.
264 // if we had an error other than non-existence, fail
266 } else if sourceInfo.IsDir() {
267 return fmt.Errorf("cannot migrate %v to %v because it is a directory", source, destination)
270 in, err := os.Open(source)
275 out, err := os.Create(destination)
281 if _, err = io.Copy(out, in); err != nil {
289 // GetLoadingPrecedence implements ConfigAccess
290 func (rules *ClientConfigLoadingRules) GetLoadingPrecedence() []string {
291 return rules.Precedence
294 // GetStartingConfig implements ConfigAccess
295 func (rules *ClientConfigLoadingRules) GetStartingConfig() (*clientcmdapi.Config, error) {
296 clientConfig := NewNonInteractiveDeferredLoadingClientConfig(rules, &ConfigOverrides{})
297 rawConfig, err := clientConfig.RawConfig()
298 if os.IsNotExist(err) {
299 return clientcmdapi.NewConfig(), nil
305 return &rawConfig, nil
308 // GetDefaultFilename implements ConfigAccess
309 func (rules *ClientConfigLoadingRules) GetDefaultFilename() string {
310 // Explicit file if we have one.
311 if rules.IsExplicitFile() {
312 return rules.GetExplicitFile()
314 // Otherwise, first existing file from precedence.
315 for _, filename := range rules.GetLoadingPrecedence() {
316 if _, err := os.Stat(filename); err == nil {
320 // If none exists, use the first from precedence.
321 if len(rules.Precedence) > 0 {
322 return rules.Precedence[0]
327 // IsExplicitFile implements ConfigAccess
328 func (rules *ClientConfigLoadingRules) IsExplicitFile() bool {
329 return len(rules.ExplicitPath) > 0
332 // GetExplicitFile implements ConfigAccess
333 func (rules *ClientConfigLoadingRules) GetExplicitFile() string {
334 return rules.ExplicitPath
337 // IsDefaultConfig returns true if the provided configuration matches the default
338 func (rules *ClientConfigLoadingRules) IsDefaultConfig(config *restclient.Config) bool {
339 if rules.DefaultClientConfig == nil {
342 defaultConfig, err := rules.DefaultClientConfig.ClientConfig()
346 return reflect.DeepEqual(config, defaultConfig)
349 // LoadFromFile takes a filename and deserializes the contents into Config object
350 func LoadFromFile(filename string) (*clientcmdapi.Config, error) {
351 kubeconfigBytes, err := ioutil.ReadFile(filename)
355 config, err := Load(kubeconfigBytes)
359 klog.V(6).Infoln("Config loaded from file", filename)
361 // set LocationOfOrigin on every Cluster, User, and Context
362 for key, obj := range config.AuthInfos {
363 obj.LocationOfOrigin = filename
364 config.AuthInfos[key] = obj
366 for key, obj := range config.Clusters {
367 obj.LocationOfOrigin = filename
368 config.Clusters[key] = obj
370 for key, obj := range config.Contexts {
371 obj.LocationOfOrigin = filename
372 config.Contexts[key] = obj
375 if config.AuthInfos == nil {
376 config.AuthInfos = map[string]*clientcmdapi.AuthInfo{}
378 if config.Clusters == nil {
379 config.Clusters = map[string]*clientcmdapi.Cluster{}
381 if config.Contexts == nil {
382 config.Contexts = map[string]*clientcmdapi.Context{}
388 // Load takes a byte slice and deserializes the contents into Config object.
389 // Encapsulates deserialization without assuming the source is a file.
390 func Load(data []byte) (*clientcmdapi.Config, error) {
391 config := clientcmdapi.NewConfig()
392 // if there's no data in a file, return the default object instead of failing (DecodeInto reject empty input)
396 decoded, _, err := clientcmdlatest.Codec.Decode(data, &schema.GroupVersionKind{Version: clientcmdlatest.Version, Kind: "Config"}, config)
400 return decoded.(*clientcmdapi.Config), nil
403 // WriteToFile serializes the config to yaml and writes it out to a file. If not present, it creates the file with the mode 0600. If it is present
404 // it stomps the contents
405 func WriteToFile(config clientcmdapi.Config, filename string) error {
406 content, err := Write(config)
410 dir := filepath.Dir(filename)
411 if _, err := os.Stat(dir); os.IsNotExist(err) {
412 if err = os.MkdirAll(dir, 0755); err != nil {
417 if err := ioutil.WriteFile(filename, content, 0600); err != nil {
423 func lockFile(filename string) error {
424 // TODO: find a way to do this with actual file locks. Will
425 // probably need separate solution for windows and Linux.
427 // Make sure the dir exists before we try to create a lock file.
428 dir := filepath.Dir(filename)
429 if _, err := os.Stat(dir); os.IsNotExist(err) {
430 if err = os.MkdirAll(dir, 0755); err != nil {
434 f, err := os.OpenFile(lockName(filename), os.O_CREATE|os.O_EXCL, 0)
442 func unlockFile(filename string) error {
443 return os.Remove(lockName(filename))
446 func lockName(filename string) string {
447 return filename + ".lock"
450 // Write serializes the config to yaml.
451 // Encapsulates serialization without assuming the destination is a file.
452 func Write(config clientcmdapi.Config) ([]byte, error) {
453 return runtime.Encode(clientcmdlatest.Codec, &config)
456 func (rules ClientConfigLoadingRules) ResolvePaths() bool {
457 return !rules.DoNotResolvePaths
460 // ResolveLocalPaths resolves all relative paths in the config object with respect to the stanza's LocationOfOrigin
461 // this cannot be done directly inside of LoadFromFile because doing so there would make it impossible to load a file without
462 // modification of its contents.
463 func ResolveLocalPaths(config *clientcmdapi.Config) error {
464 for _, cluster := range config.Clusters {
465 if len(cluster.LocationOfOrigin) == 0 {
468 base, err := filepath.Abs(filepath.Dir(cluster.LocationOfOrigin))
470 return fmt.Errorf("Could not determine the absolute path of config file %s: %v", cluster.LocationOfOrigin, err)
473 if err := ResolvePaths(GetClusterFileReferences(cluster), base); err != nil {
477 for _, authInfo := range config.AuthInfos {
478 if len(authInfo.LocationOfOrigin) == 0 {
481 base, err := filepath.Abs(filepath.Dir(authInfo.LocationOfOrigin))
483 return fmt.Errorf("Could not determine the absolute path of config file %s: %v", authInfo.LocationOfOrigin, err)
486 if err := ResolvePaths(GetAuthInfoFileReferences(authInfo), base); err != nil {
494 // RelativizeClusterLocalPaths first absolutizes the paths by calling ResolveLocalPaths. This assumes that any NEW path is already
495 // absolute, but any existing path will be resolved relative to LocationOfOrigin
496 func RelativizeClusterLocalPaths(cluster *clientcmdapi.Cluster) error {
497 if len(cluster.LocationOfOrigin) == 0 {
498 return fmt.Errorf("no location of origin for %s", cluster.Server)
500 base, err := filepath.Abs(filepath.Dir(cluster.LocationOfOrigin))
502 return fmt.Errorf("could not determine the absolute path of config file %s: %v", cluster.LocationOfOrigin, err)
505 if err := ResolvePaths(GetClusterFileReferences(cluster), base); err != nil {
508 if err := RelativizePathWithNoBacksteps(GetClusterFileReferences(cluster), base); err != nil {
515 // RelativizeAuthInfoLocalPaths first absolutizes the paths by calling ResolveLocalPaths. This assumes that any NEW path is already
516 // absolute, but any existing path will be resolved relative to LocationOfOrigin
517 func RelativizeAuthInfoLocalPaths(authInfo *clientcmdapi.AuthInfo) error {
518 if len(authInfo.LocationOfOrigin) == 0 {
519 return fmt.Errorf("no location of origin for %v", authInfo)
521 base, err := filepath.Abs(filepath.Dir(authInfo.LocationOfOrigin))
523 return fmt.Errorf("could not determine the absolute path of config file %s: %v", authInfo.LocationOfOrigin, err)
526 if err := ResolvePaths(GetAuthInfoFileReferences(authInfo), base); err != nil {
529 if err := RelativizePathWithNoBacksteps(GetAuthInfoFileReferences(authInfo), base); err != nil {
536 func RelativizeConfigPaths(config *clientcmdapi.Config, base string) error {
537 return RelativizePathWithNoBacksteps(GetConfigFileReferences(config), base)
540 func ResolveConfigPaths(config *clientcmdapi.Config, base string) error {
541 return ResolvePaths(GetConfigFileReferences(config), base)
544 func GetConfigFileReferences(config *clientcmdapi.Config) []*string {
547 for _, cluster := range config.Clusters {
548 refs = append(refs, GetClusterFileReferences(cluster)...)
550 for _, authInfo := range config.AuthInfos {
551 refs = append(refs, GetAuthInfoFileReferences(authInfo)...)
557 func GetClusterFileReferences(cluster *clientcmdapi.Cluster) []*string {
558 return []*string{&cluster.CertificateAuthority}
561 func GetAuthInfoFileReferences(authInfo *clientcmdapi.AuthInfo) []*string {
562 s := []*string{&authInfo.ClientCertificate, &authInfo.ClientKey, &authInfo.TokenFile}
563 // Only resolve exec command if it isn't PATH based.
564 if authInfo.Exec != nil && strings.ContainsRune(authInfo.Exec.Command, filepath.Separator) {
565 s = append(s, &authInfo.Exec.Command)
570 // ResolvePaths updates the given refs to be absolute paths, relative to the given base directory
571 func ResolvePaths(refs []*string, base string) error {
572 for _, ref := range refs {
573 // Don't resolve empty paths
575 // Don't resolve absolute paths
576 if !filepath.IsAbs(*ref) {
577 *ref = filepath.Join(base, *ref)
584 // RelativizePathWithNoBacksteps updates the given refs to be relative paths, relative to the given base directory as long as they do not require backsteps.
585 // Any path requiring a backstep is left as-is as long it is absolute. Any non-absolute path that can't be relativized produces an error
586 func RelativizePathWithNoBacksteps(refs []*string, base string) error {
587 for _, ref := range refs {
588 // Don't relativize empty paths
590 rel, err := MakeRelative(*ref, base)
595 // if we have a backstep, don't mess with the path
596 if strings.HasPrefix(rel, "../") {
597 if filepath.IsAbs(*ref) {
601 return fmt.Errorf("%v requires backsteps and is not absolute", *ref)
610 func MakeRelative(path, base string) (string, error) {
612 rel, err := filepath.Rel(base, path)
621 // deduplicate removes any duplicated values and returns a new slice, keeping the order unchanged
622 func deduplicate(s []string) []string {
623 encountered := map[string]bool{}
624 ret := make([]string, 0)
626 if encountered[s[i]] {
629 encountered[s[i]] = true
630 ret = append(ret, s[i])