Code refactoring for bpa operator
[icn.git] / cmd / bpa-operator / vendor / k8s.io / client-go / tools / clientcmd / loader.go
1 /*
2 Copyright 2014 The Kubernetes Authors.
3
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
7
8     http://www.apache.org/licenses/LICENSE-2.0
9
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.
15 */
16
17 package clientcmd
18
19 import (
20         "fmt"
21         "io"
22         "io/ioutil"
23         "os"
24         "path"
25         "path/filepath"
26         "reflect"
27         goruntime "runtime"
28         "strings"
29
30         "github.com/imdario/mergo"
31         "k8s.io/klog"
32
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"
40 )
41
42 const (
43         RecommendedConfigPathFlag   = "kubeconfig"
44         RecommendedConfigPathEnvVar = "KUBECONFIG"
45         RecommendedHomeDir          = ".kube"
46         RecommendedFileName         = "config"
47         RecommendedSchemaName       = "schema"
48 )
49
50 var (
51         RecommendedConfigDir  = path.Join(homedir.HomeDir(), RecommendedHomeDir)
52         RecommendedHomeFile   = path.Join(RecommendedConfigDir, RecommendedFileName)
53         RecommendedSchemaFile = path.Join(RecommendedConfigDir, RecommendedSchemaName)
54 )
55
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)
62
63         migrationRules := map[string]string{}
64         migrationRules[RecommendedHomeFile] = oldRecommendedHomeFile
65         if goruntime.GOOS == "windows" {
66                 migrationRules[RecommendedHomeFile] = oldRecommendedWindowsHomeFile
67         }
68         return migrationRules
69 }
70
71 type ClientConfigLoader interface {
72         ConfigAccess
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)
77 }
78
79 type KubeconfigGetter func() (*clientcmdapi.Config, error)
80
81 type ClientConfigGetter struct {
82         kubeconfigGetter KubeconfigGetter
83 }
84
85 // ClientConfigGetter implements the ClientConfigLoader interface.
86 var _ ClientConfigLoader = &ClientConfigGetter{}
87
88 func (g *ClientConfigGetter) Load() (*clientcmdapi.Config, error) {
89         return g.kubeconfigGetter()
90 }
91
92 func (g *ClientConfigGetter) GetLoadingPrecedence() []string {
93         return nil
94 }
95 func (g *ClientConfigGetter) GetStartingConfig() (*clientcmdapi.Config, error) {
96         return g.kubeconfigGetter()
97 }
98 func (g *ClientConfigGetter) GetDefaultFilename() string {
99         return ""
100 }
101 func (g *ClientConfigGetter) IsExplicitFile() bool {
102         return false
103 }
104 func (g *ClientConfigGetter) GetExplicitFile() string {
105         return ""
106 }
107 func (g *ClientConfigGetter) IsDefaultConfig(config *restclient.Config) bool {
108         return false
109 }
110
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 {
116         ExplicitPath string
117         Precedence   []string
118
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
122
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
126
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
130 }
131
132 // ClientConfigLoadingRules implements the ClientConfigLoader interface.
133 var _ ClientConfigLoader = &ClientConfigLoadingRules{}
134
135 // NewDefaultClientConfigLoadingRules returns a ClientConfigLoadingRules object with default fields filled in.  You are not required to
136 // use this constructor
137 func NewDefaultClientConfigLoadingRules() *ClientConfigLoadingRules {
138         chain := []string{}
139
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)...)
145
146         } else {
147                 chain = append(chain, RecommendedHomeFile)
148         }
149
150         return &ClientConfigLoadingRules{
151                 Precedence:     chain,
152                 MigrationRules: currentMigrationRules(),
153         }
154 }
155
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 {
171                 return nil, err
172         }
173
174         errlist := []error{}
175
176         kubeConfigFiles := []string{}
177
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) {
181                         return nil, err
182                 }
183                 kubeConfigFiles = append(kubeConfigFiles, rules.ExplicitPath)
184
185         } else {
186                 kubeConfigFiles = append(kubeConfigFiles, rules.Precedence...)
187         }
188
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 {
193                         // no work to do
194                         continue
195                 }
196
197                 config, err := LoadFromFile(filename)
198                 if os.IsNotExist(err) {
199                         // skip missing files
200                         continue
201                 }
202                 if err != nil {
203                         errlist = append(errlist, fmt.Errorf("Error loading config file \"%s\": %v", filename, err))
204                         continue
205                 }
206
207                 kubeconfigs = append(kubeconfigs, config)
208         }
209
210         // first merge all of our maps
211         mapConfig := clientcmdapi.NewConfig()
212
213         for _, kubeconfig := range kubeconfigs {
214                 mergo.MergeWithOverwrite(mapConfig, kubeconfig)
215         }
216
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)
223         }
224
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)
230
231         if rules.ResolvePaths() {
232                 if err := ResolveLocalPaths(config); err != nil {
233                         errlist = append(errlist, err)
234                 }
235         }
236         return config, utilerrors.NewAggregate(errlist)
237 }
238
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 {
243                 return nil
244         }
245
246         for destination, source := range rules.MigrationRules {
247                 if _, err := os.Stat(destination); err == nil {
248                         // if the destination already exists, do nothing
249                         continue
250                 } else if os.IsPermission(err) {
251                         // if we can't access the file, skip it
252                         continue
253                 } else if !os.IsNotExist(err) {
254                         // if we had an error other than non-existence, fail
255                         return err
256                 }
257
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.
261                                 continue
262                         }
263
264                         // if we had an error other than non-existence, fail
265                         return err
266                 } else if sourceInfo.IsDir() {
267                         return fmt.Errorf("cannot migrate %v to %v because it is a directory", source, destination)
268                 }
269
270                 in, err := os.Open(source)
271                 if err != nil {
272                         return err
273                 }
274                 defer in.Close()
275                 out, err := os.Create(destination)
276                 if err != nil {
277                         return err
278                 }
279                 defer out.Close()
280
281                 if _, err = io.Copy(out, in); err != nil {
282                         return err
283                 }
284         }
285
286         return nil
287 }
288
289 // GetLoadingPrecedence implements ConfigAccess
290 func (rules *ClientConfigLoadingRules) GetLoadingPrecedence() []string {
291         return rules.Precedence
292 }
293
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
300         }
301         if err != nil {
302                 return nil, err
303         }
304
305         return &rawConfig, nil
306 }
307
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()
313         }
314         // Otherwise, first existing file from precedence.
315         for _, filename := range rules.GetLoadingPrecedence() {
316                 if _, err := os.Stat(filename); err == nil {
317                         return filename
318                 }
319         }
320         // If none exists, use the first from precedence.
321         if len(rules.Precedence) > 0 {
322                 return rules.Precedence[0]
323         }
324         return ""
325 }
326
327 // IsExplicitFile implements ConfigAccess
328 func (rules *ClientConfigLoadingRules) IsExplicitFile() bool {
329         return len(rules.ExplicitPath) > 0
330 }
331
332 // GetExplicitFile implements ConfigAccess
333 func (rules *ClientConfigLoadingRules) GetExplicitFile() string {
334         return rules.ExplicitPath
335 }
336
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 {
340                 return false
341         }
342         defaultConfig, err := rules.DefaultClientConfig.ClientConfig()
343         if err != nil {
344                 return false
345         }
346         return reflect.DeepEqual(config, defaultConfig)
347 }
348
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)
352         if err != nil {
353                 return nil, err
354         }
355         config, err := Load(kubeconfigBytes)
356         if err != nil {
357                 return nil, err
358         }
359         klog.V(6).Infoln("Config loaded from file", filename)
360
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
365         }
366         for key, obj := range config.Clusters {
367                 obj.LocationOfOrigin = filename
368                 config.Clusters[key] = obj
369         }
370         for key, obj := range config.Contexts {
371                 obj.LocationOfOrigin = filename
372                 config.Contexts[key] = obj
373         }
374
375         if config.AuthInfos == nil {
376                 config.AuthInfos = map[string]*clientcmdapi.AuthInfo{}
377         }
378         if config.Clusters == nil {
379                 config.Clusters = map[string]*clientcmdapi.Cluster{}
380         }
381         if config.Contexts == nil {
382                 config.Contexts = map[string]*clientcmdapi.Context{}
383         }
384
385         return config, nil
386 }
387
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)
393         if len(data) == 0 {
394                 return config, nil
395         }
396         decoded, _, err := clientcmdlatest.Codec.Decode(data, &schema.GroupVersionKind{Version: clientcmdlatest.Version, Kind: "Config"}, config)
397         if err != nil {
398                 return nil, err
399         }
400         return decoded.(*clientcmdapi.Config), nil
401 }
402
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)
407         if err != nil {
408                 return err
409         }
410         dir := filepath.Dir(filename)
411         if _, err := os.Stat(dir); os.IsNotExist(err) {
412                 if err = os.MkdirAll(dir, 0755); err != nil {
413                         return err
414                 }
415         }
416
417         if err := ioutil.WriteFile(filename, content, 0600); err != nil {
418                 return err
419         }
420         return nil
421 }
422
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.
426
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 {
431                         return err
432                 }
433         }
434         f, err := os.OpenFile(lockName(filename), os.O_CREATE|os.O_EXCL, 0)
435         if err != nil {
436                 return err
437         }
438         f.Close()
439         return nil
440 }
441
442 func unlockFile(filename string) error {
443         return os.Remove(lockName(filename))
444 }
445
446 func lockName(filename string) string {
447         return filename + ".lock"
448 }
449
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)
454 }
455
456 func (rules ClientConfigLoadingRules) ResolvePaths() bool {
457         return !rules.DoNotResolvePaths
458 }
459
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 {
466                         continue
467                 }
468                 base, err := filepath.Abs(filepath.Dir(cluster.LocationOfOrigin))
469                 if err != nil {
470                         return fmt.Errorf("Could not determine the absolute path of config file %s: %v", cluster.LocationOfOrigin, err)
471                 }
472
473                 if err := ResolvePaths(GetClusterFileReferences(cluster), base); err != nil {
474                         return err
475                 }
476         }
477         for _, authInfo := range config.AuthInfos {
478                 if len(authInfo.LocationOfOrigin) == 0 {
479                         continue
480                 }
481                 base, err := filepath.Abs(filepath.Dir(authInfo.LocationOfOrigin))
482                 if err != nil {
483                         return fmt.Errorf("Could not determine the absolute path of config file %s: %v", authInfo.LocationOfOrigin, err)
484                 }
485
486                 if err := ResolvePaths(GetAuthInfoFileReferences(authInfo), base); err != nil {
487                         return err
488                 }
489         }
490
491         return nil
492 }
493
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)
499         }
500         base, err := filepath.Abs(filepath.Dir(cluster.LocationOfOrigin))
501         if err != nil {
502                 return fmt.Errorf("could not determine the absolute path of config file %s: %v", cluster.LocationOfOrigin, err)
503         }
504
505         if err := ResolvePaths(GetClusterFileReferences(cluster), base); err != nil {
506                 return err
507         }
508         if err := RelativizePathWithNoBacksteps(GetClusterFileReferences(cluster), base); err != nil {
509                 return err
510         }
511
512         return nil
513 }
514
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)
520         }
521         base, err := filepath.Abs(filepath.Dir(authInfo.LocationOfOrigin))
522         if err != nil {
523                 return fmt.Errorf("could not determine the absolute path of config file %s: %v", authInfo.LocationOfOrigin, err)
524         }
525
526         if err := ResolvePaths(GetAuthInfoFileReferences(authInfo), base); err != nil {
527                 return err
528         }
529         if err := RelativizePathWithNoBacksteps(GetAuthInfoFileReferences(authInfo), base); err != nil {
530                 return err
531         }
532
533         return nil
534 }
535
536 func RelativizeConfigPaths(config *clientcmdapi.Config, base string) error {
537         return RelativizePathWithNoBacksteps(GetConfigFileReferences(config), base)
538 }
539
540 func ResolveConfigPaths(config *clientcmdapi.Config, base string) error {
541         return ResolvePaths(GetConfigFileReferences(config), base)
542 }
543
544 func GetConfigFileReferences(config *clientcmdapi.Config) []*string {
545         refs := []*string{}
546
547         for _, cluster := range config.Clusters {
548                 refs = append(refs, GetClusterFileReferences(cluster)...)
549         }
550         for _, authInfo := range config.AuthInfos {
551                 refs = append(refs, GetAuthInfoFileReferences(authInfo)...)
552         }
553
554         return refs
555 }
556
557 func GetClusterFileReferences(cluster *clientcmdapi.Cluster) []*string {
558         return []*string{&cluster.CertificateAuthority}
559 }
560
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)
566         }
567         return s
568 }
569
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
574                 if len(*ref) > 0 {
575                         // Don't resolve absolute paths
576                         if !filepath.IsAbs(*ref) {
577                                 *ref = filepath.Join(base, *ref)
578                         }
579                 }
580         }
581         return nil
582 }
583
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
589                 if len(*ref) > 0 {
590                         rel, err := MakeRelative(*ref, base)
591                         if err != nil {
592                                 return err
593                         }
594
595                         // if we have a backstep, don't mess with the path
596                         if strings.HasPrefix(rel, "../") {
597                                 if filepath.IsAbs(*ref) {
598                                         continue
599                                 }
600
601                                 return fmt.Errorf("%v requires backsteps and is not absolute", *ref)
602                         }
603
604                         *ref = rel
605                 }
606         }
607         return nil
608 }
609
610 func MakeRelative(path, base string) (string, error) {
611         if len(path) > 0 {
612                 rel, err := filepath.Rel(base, path)
613                 if err != nil {
614                         return path, err
615                 }
616                 return rel, nil
617         }
618         return path, nil
619 }
620
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)
625         for i := range s {
626                 if encountered[s[i]] {
627                         continue
628                 }
629                 encountered[s[i]] = true
630                 ret = append(ret, s[i])
631         }
632         return ret
633 }