Remove BPA from Makefile
[icn.git] / cmd / bpa-operator / vendor / k8s.io / client-go / tools / clientcmd / config.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         "errors"
21         "os"
22         "path"
23         "path/filepath"
24         "reflect"
25         "sort"
26
27         "k8s.io/klog"
28
29         restclient "k8s.io/client-go/rest"
30         clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
31 )
32
33 // ConfigAccess is used by subcommands and methods in this package to load and modify the appropriate config files
34 type ConfigAccess interface {
35         // GetLoadingPrecedence returns the slice of files that should be used for loading and inspecting the config
36         GetLoadingPrecedence() []string
37         // GetStartingConfig returns the config that subcommands should being operating against.  It may or may not be merged depending on loading rules
38         GetStartingConfig() (*clientcmdapi.Config, error)
39         // GetDefaultFilename returns the name of the file you should write into (create if necessary), if you're trying to create a new stanza as opposed to updating an existing one.
40         GetDefaultFilename() string
41         // IsExplicitFile indicates whether or not this command is interested in exactly one file.  This implementation only ever does that  via a flag, but implementations that handle local, global, and flags may have more
42         IsExplicitFile() bool
43         // GetExplicitFile returns the particular file this command is operating against.  This implementation only ever has one, but implementations that handle local, global, and flags may have more
44         GetExplicitFile() string
45 }
46
47 type PathOptions struct {
48         // GlobalFile is the full path to the file to load as the global (final) option
49         GlobalFile string
50         // EnvVar is the env var name that points to the list of kubeconfig files to load
51         EnvVar string
52         // ExplicitFileFlag is the name of the flag to use for prompting for the kubeconfig file
53         ExplicitFileFlag string
54
55         // GlobalFileSubpath is an optional value used for displaying help
56         GlobalFileSubpath string
57
58         LoadingRules *ClientConfigLoadingRules
59 }
60
61 func (o *PathOptions) GetEnvVarFiles() []string {
62         if len(o.EnvVar) == 0 {
63                 return []string{}
64         }
65
66         envVarValue := os.Getenv(o.EnvVar)
67         if len(envVarValue) == 0 {
68                 return []string{}
69         }
70
71         fileList := filepath.SplitList(envVarValue)
72         // prevent the same path load multiple times
73         return deduplicate(fileList)
74 }
75
76 func (o *PathOptions) GetLoadingPrecedence() []string {
77         if envVarFiles := o.GetEnvVarFiles(); len(envVarFiles) > 0 {
78                 return envVarFiles
79         }
80
81         return []string{o.GlobalFile}
82 }
83
84 func (o *PathOptions) GetStartingConfig() (*clientcmdapi.Config, error) {
85         // don't mutate the original
86         loadingRules := *o.LoadingRules
87         loadingRules.Precedence = o.GetLoadingPrecedence()
88
89         clientConfig := NewNonInteractiveDeferredLoadingClientConfig(&loadingRules, &ConfigOverrides{})
90         rawConfig, err := clientConfig.RawConfig()
91         if os.IsNotExist(err) {
92                 return clientcmdapi.NewConfig(), nil
93         }
94         if err != nil {
95                 return nil, err
96         }
97
98         return &rawConfig, nil
99 }
100
101 func (o *PathOptions) GetDefaultFilename() string {
102         if o.IsExplicitFile() {
103                 return o.GetExplicitFile()
104         }
105
106         if envVarFiles := o.GetEnvVarFiles(); len(envVarFiles) > 0 {
107                 if len(envVarFiles) == 1 {
108                         return envVarFiles[0]
109                 }
110
111                 // if any of the envvar files already exists, return it
112                 for _, envVarFile := range envVarFiles {
113                         if _, err := os.Stat(envVarFile); err == nil {
114                                 return envVarFile
115                         }
116                 }
117
118                 // otherwise, return the last one in the list
119                 return envVarFiles[len(envVarFiles)-1]
120         }
121
122         return o.GlobalFile
123 }
124
125 func (o *PathOptions) IsExplicitFile() bool {
126         if len(o.LoadingRules.ExplicitPath) > 0 {
127                 return true
128         }
129
130         return false
131 }
132
133 func (o *PathOptions) GetExplicitFile() string {
134         return o.LoadingRules.ExplicitPath
135 }
136
137 func NewDefaultPathOptions() *PathOptions {
138         ret := &PathOptions{
139                 GlobalFile:       RecommendedHomeFile,
140                 EnvVar:           RecommendedConfigPathEnvVar,
141                 ExplicitFileFlag: RecommendedConfigPathFlag,
142
143                 GlobalFileSubpath: path.Join(RecommendedHomeDir, RecommendedFileName),
144
145                 LoadingRules: NewDefaultClientConfigLoadingRules(),
146         }
147         ret.LoadingRules.DoNotResolvePaths = true
148
149         return ret
150 }
151
152 // ModifyConfig takes a Config object, iterates through Clusters, AuthInfos, and Contexts, uses the LocationOfOrigin if specified or
153 // uses the default destination file to write the results into.  This results in multiple file reads, but it's very easy to follow.
154 // Preferences and CurrentContext should always be set in the default destination file.  Since we can't distinguish between empty and missing values
155 // (no nil strings), we're forced have separate handling for them.  In the kubeconfig cases, newConfig should have at most one difference,
156 // that means that this code will only write into a single file.  If you want to relativizePaths, you must provide a fully qualified path in any
157 // modified element.
158 func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config, relativizePaths bool) error {
159         possibleSources := configAccess.GetLoadingPrecedence()
160         // sort the possible kubeconfig files so we always "lock" in the same order
161         // to avoid deadlock (note: this can fail w/ symlinks, but... come on).
162         sort.Strings(possibleSources)
163         for _, filename := range possibleSources {
164                 if err := lockFile(filename); err != nil {
165                         return err
166                 }
167                 defer unlockFile(filename)
168         }
169
170         startingConfig, err := configAccess.GetStartingConfig()
171         if err != nil {
172                 return err
173         }
174
175         // We need to find all differences, locate their original files, read a partial config to modify only that stanza and write out the file.
176         // Special case the test for current context and preferences since those always write to the default file.
177         if reflect.DeepEqual(*startingConfig, newConfig) {
178                 // nothing to do
179                 return nil
180         }
181
182         if startingConfig.CurrentContext != newConfig.CurrentContext {
183                 if err := writeCurrentContext(configAccess, newConfig.CurrentContext); err != nil {
184                         return err
185                 }
186         }
187
188         if !reflect.DeepEqual(startingConfig.Preferences, newConfig.Preferences) {
189                 if err := writePreferences(configAccess, newConfig.Preferences); err != nil {
190                         return err
191                 }
192         }
193
194         // Search every cluster, authInfo, and context.  First from new to old for differences, then from old to new for deletions
195         for key, cluster := range newConfig.Clusters {
196                 startingCluster, exists := startingConfig.Clusters[key]
197                 if !reflect.DeepEqual(cluster, startingCluster) || !exists {
198                         destinationFile := cluster.LocationOfOrigin
199                         if len(destinationFile) == 0 {
200                                 destinationFile = configAccess.GetDefaultFilename()
201                         }
202
203                         configToWrite, err := getConfigFromFile(destinationFile)
204                         if err != nil {
205                                 return err
206                         }
207                         t := *cluster
208
209                         configToWrite.Clusters[key] = &t
210                         configToWrite.Clusters[key].LocationOfOrigin = destinationFile
211                         if relativizePaths {
212                                 if err := RelativizeClusterLocalPaths(configToWrite.Clusters[key]); err != nil {
213                                         return err
214                                 }
215                         }
216
217                         if err := WriteToFile(*configToWrite, destinationFile); err != nil {
218                                 return err
219                         }
220                 }
221         }
222
223         // seenConfigs stores a map of config source filenames to computed config objects
224         seenConfigs := map[string]*clientcmdapi.Config{}
225
226         for key, context := range newConfig.Contexts {
227                 startingContext, exists := startingConfig.Contexts[key]
228                 if !reflect.DeepEqual(context, startingContext) || !exists {
229                         destinationFile := context.LocationOfOrigin
230                         if len(destinationFile) == 0 {
231                                 destinationFile = configAccess.GetDefaultFilename()
232                         }
233
234                         // we only obtain a fresh config object from its source file
235                         // if we have not seen it already - this prevents us from
236                         // reading and writing to the same number of files repeatedly
237                         // when multiple / all contexts share the same destination file.
238                         configToWrite, seen := seenConfigs[destinationFile]
239                         if !seen {
240                                 var err error
241                                 configToWrite, err = getConfigFromFile(destinationFile)
242                                 if err != nil {
243                                         return err
244                                 }
245                                 seenConfigs[destinationFile] = configToWrite
246                         }
247
248                         configToWrite.Contexts[key] = context
249                 }
250         }
251
252         // actually persist config object changes
253         for destinationFile, configToWrite := range seenConfigs {
254                 if err := WriteToFile(*configToWrite, destinationFile); err != nil {
255                         return err
256                 }
257         }
258
259         for key, authInfo := range newConfig.AuthInfos {
260                 startingAuthInfo, exists := startingConfig.AuthInfos[key]
261                 if !reflect.DeepEqual(authInfo, startingAuthInfo) || !exists {
262                         destinationFile := authInfo.LocationOfOrigin
263                         if len(destinationFile) == 0 {
264                                 destinationFile = configAccess.GetDefaultFilename()
265                         }
266
267                         configToWrite, err := getConfigFromFile(destinationFile)
268                         if err != nil {
269                                 return err
270                         }
271                         t := *authInfo
272                         configToWrite.AuthInfos[key] = &t
273                         configToWrite.AuthInfos[key].LocationOfOrigin = destinationFile
274                         if relativizePaths {
275                                 if err := RelativizeAuthInfoLocalPaths(configToWrite.AuthInfos[key]); err != nil {
276                                         return err
277                                 }
278                         }
279
280                         if err := WriteToFile(*configToWrite, destinationFile); err != nil {
281                                 return err
282                         }
283                 }
284         }
285
286         for key, cluster := range startingConfig.Clusters {
287                 if _, exists := newConfig.Clusters[key]; !exists {
288                         destinationFile := cluster.LocationOfOrigin
289                         if len(destinationFile) == 0 {
290                                 destinationFile = configAccess.GetDefaultFilename()
291                         }
292
293                         configToWrite, err := getConfigFromFile(destinationFile)
294                         if err != nil {
295                                 return err
296                         }
297                         delete(configToWrite.Clusters, key)
298
299                         if err := WriteToFile(*configToWrite, destinationFile); err != nil {
300                                 return err
301                         }
302                 }
303         }
304
305         for key, context := range startingConfig.Contexts {
306                 if _, exists := newConfig.Contexts[key]; !exists {
307                         destinationFile := context.LocationOfOrigin
308                         if len(destinationFile) == 0 {
309                                 destinationFile = configAccess.GetDefaultFilename()
310                         }
311
312                         configToWrite, err := getConfigFromFile(destinationFile)
313                         if err != nil {
314                                 return err
315                         }
316                         delete(configToWrite.Contexts, key)
317
318                         if err := WriteToFile(*configToWrite, destinationFile); err != nil {
319                                 return err
320                         }
321                 }
322         }
323
324         for key, authInfo := range startingConfig.AuthInfos {
325                 if _, exists := newConfig.AuthInfos[key]; !exists {
326                         destinationFile := authInfo.LocationOfOrigin
327                         if len(destinationFile) == 0 {
328                                 destinationFile = configAccess.GetDefaultFilename()
329                         }
330
331                         configToWrite, err := getConfigFromFile(destinationFile)
332                         if err != nil {
333                                 return err
334                         }
335                         delete(configToWrite.AuthInfos, key)
336
337                         if err := WriteToFile(*configToWrite, destinationFile); err != nil {
338                                 return err
339                         }
340                 }
341         }
342
343         return nil
344 }
345
346 func PersisterForUser(configAccess ConfigAccess, user string) restclient.AuthProviderConfigPersister {
347         return &persister{configAccess, user}
348 }
349
350 type persister struct {
351         configAccess ConfigAccess
352         user         string
353 }
354
355 func (p *persister) Persist(config map[string]string) error {
356         newConfig, err := p.configAccess.GetStartingConfig()
357         if err != nil {
358                 return err
359         }
360         authInfo, ok := newConfig.AuthInfos[p.user]
361         if ok && authInfo.AuthProvider != nil {
362                 authInfo.AuthProvider.Config = config
363                 ModifyConfig(p.configAccess, *newConfig, false)
364         }
365         return nil
366 }
367
368 // writeCurrentContext takes three possible paths.
369 // If newCurrentContext is the same as the startingConfig's current context, then we exit.
370 // If newCurrentContext has a value, then that value is written into the default destination file.
371 // If newCurrentContext is empty, then we find the config file that is setting the CurrentContext and clear the value from that file
372 func writeCurrentContext(configAccess ConfigAccess, newCurrentContext string) error {
373         if startingConfig, err := configAccess.GetStartingConfig(); err != nil {
374                 return err
375         } else if startingConfig.CurrentContext == newCurrentContext {
376                 return nil
377         }
378
379         if configAccess.IsExplicitFile() {
380                 file := configAccess.GetExplicitFile()
381                 currConfig, err := getConfigFromFile(file)
382                 if err != nil {
383                         return err
384                 }
385                 currConfig.CurrentContext = newCurrentContext
386                 if err := WriteToFile(*currConfig, file); err != nil {
387                         return err
388                 }
389
390                 return nil
391         }
392
393         if len(newCurrentContext) > 0 {
394                 destinationFile := configAccess.GetDefaultFilename()
395                 config, err := getConfigFromFile(destinationFile)
396                 if err != nil {
397                         return err
398                 }
399                 config.CurrentContext = newCurrentContext
400
401                 if err := WriteToFile(*config, destinationFile); err != nil {
402                         return err
403                 }
404
405                 return nil
406         }
407
408         // we're supposed to be clearing the current context.  We need to find the first spot in the chain that is setting it and clear it
409         for _, file := range configAccess.GetLoadingPrecedence() {
410                 if _, err := os.Stat(file); err == nil {
411                         currConfig, err := getConfigFromFile(file)
412                         if err != nil {
413                                 return err
414                         }
415
416                         if len(currConfig.CurrentContext) > 0 {
417                                 currConfig.CurrentContext = newCurrentContext
418                                 if err := WriteToFile(*currConfig, file); err != nil {
419                                         return err
420                                 }
421
422                                 return nil
423                         }
424                 }
425         }
426
427         return errors.New("no config found to write context")
428 }
429
430 func writePreferences(configAccess ConfigAccess, newPrefs clientcmdapi.Preferences) error {
431         if startingConfig, err := configAccess.GetStartingConfig(); err != nil {
432                 return err
433         } else if reflect.DeepEqual(startingConfig.Preferences, newPrefs) {
434                 return nil
435         }
436
437         if configAccess.IsExplicitFile() {
438                 file := configAccess.GetExplicitFile()
439                 currConfig, err := getConfigFromFile(file)
440                 if err != nil {
441                         return err
442                 }
443                 currConfig.Preferences = newPrefs
444                 if err := WriteToFile(*currConfig, file); err != nil {
445                         return err
446                 }
447
448                 return nil
449         }
450
451         for _, file := range configAccess.GetLoadingPrecedence() {
452                 currConfig, err := getConfigFromFile(file)
453                 if err != nil {
454                         return err
455                 }
456
457                 if !reflect.DeepEqual(currConfig.Preferences, newPrefs) {
458                         currConfig.Preferences = newPrefs
459                         if err := WriteToFile(*currConfig, file); err != nil {
460                                 return err
461                         }
462
463                         return nil
464                 }
465         }
466
467         return errors.New("no config found to write preferences")
468 }
469
470 // getConfigFromFile tries to read a kubeconfig file and if it can't, returns an error.  One exception, missing files result in empty configs, not an error.
471 func getConfigFromFile(filename string) (*clientcmdapi.Config, error) {
472         config, err := LoadFromFile(filename)
473         if err != nil && !os.IsNotExist(err) {
474                 return nil, err
475         }
476         if config == nil {
477                 config = clientcmdapi.NewConfig()
478         }
479         return config, nil
480 }
481
482 // GetConfigFromFileOrDie tries to read a kubeconfig file and if it can't, it calls exit.  One exception, missing files result in empty configs, not an exit
483 func GetConfigFromFileOrDie(filename string) *clientcmdapi.Config {
484         config, err := getConfigFromFile(filename)
485         if err != nil {
486                 klog.FatalDepth(1, err)
487         }
488
489         return config
490 }