config.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package clientcmd
  14. import (
  15. "errors"
  16. "os"
  17. "path"
  18. "path/filepath"
  19. "reflect"
  20. "sort"
  21. "k8s.io/klog"
  22. restclient "k8s.io/client-go/rest"
  23. clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
  24. )
  25. // ConfigAccess is used by subcommands and methods in this package to load and modify the appropriate config files
  26. type ConfigAccess interface {
  27. // GetLoadingPrecedence returns the slice of files that should be used for loading and inspecting the config
  28. GetLoadingPrecedence() []string
  29. // GetStartingConfig returns the config that subcommands should being operating against. It may or may not be merged depending on loading rules
  30. GetStartingConfig() (*clientcmdapi.Config, error)
  31. // 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.
  32. GetDefaultFilename() string
  33. // 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
  34. IsExplicitFile() bool
  35. // 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
  36. GetExplicitFile() string
  37. }
  38. type PathOptions struct {
  39. // GlobalFile is the full path to the file to load as the global (final) option
  40. GlobalFile string
  41. // EnvVar is the env var name that points to the list of kubeconfig files to load
  42. EnvVar string
  43. // ExplicitFileFlag is the name of the flag to use for prompting for the kubeconfig file
  44. ExplicitFileFlag string
  45. // GlobalFileSubpath is an optional value used for displaying help
  46. GlobalFileSubpath string
  47. LoadingRules *ClientConfigLoadingRules
  48. }
  49. func (o *PathOptions) GetEnvVarFiles() []string {
  50. if len(o.EnvVar) == 0 {
  51. return []string{}
  52. }
  53. envVarValue := os.Getenv(o.EnvVar)
  54. if len(envVarValue) == 0 {
  55. return []string{}
  56. }
  57. fileList := filepath.SplitList(envVarValue)
  58. // prevent the same path load multiple times
  59. return deduplicate(fileList)
  60. }
  61. func (o *PathOptions) GetLoadingPrecedence() []string {
  62. if envVarFiles := o.GetEnvVarFiles(); len(envVarFiles) > 0 {
  63. return envVarFiles
  64. }
  65. return []string{o.GlobalFile}
  66. }
  67. func (o *PathOptions) GetStartingConfig() (*clientcmdapi.Config, error) {
  68. // don't mutate the original
  69. loadingRules := *o.LoadingRules
  70. loadingRules.Precedence = o.GetLoadingPrecedence()
  71. clientConfig := NewNonInteractiveDeferredLoadingClientConfig(&loadingRules, &ConfigOverrides{})
  72. rawConfig, err := clientConfig.RawConfig()
  73. if os.IsNotExist(err) {
  74. return clientcmdapi.NewConfig(), nil
  75. }
  76. if err != nil {
  77. return nil, err
  78. }
  79. return &rawConfig, nil
  80. }
  81. func (o *PathOptions) GetDefaultFilename() string {
  82. if o.IsExplicitFile() {
  83. return o.GetExplicitFile()
  84. }
  85. if envVarFiles := o.GetEnvVarFiles(); len(envVarFiles) > 0 {
  86. if len(envVarFiles) == 1 {
  87. return envVarFiles[0]
  88. }
  89. // if any of the envvar files already exists, return it
  90. for _, envVarFile := range envVarFiles {
  91. if _, err := os.Stat(envVarFile); err == nil {
  92. return envVarFile
  93. }
  94. }
  95. // otherwise, return the last one in the list
  96. return envVarFiles[len(envVarFiles)-1]
  97. }
  98. return o.GlobalFile
  99. }
  100. func (o *PathOptions) IsExplicitFile() bool {
  101. if len(o.LoadingRules.ExplicitPath) > 0 {
  102. return true
  103. }
  104. return false
  105. }
  106. func (o *PathOptions) GetExplicitFile() string {
  107. return o.LoadingRules.ExplicitPath
  108. }
  109. func NewDefaultPathOptions() *PathOptions {
  110. ret := &PathOptions{
  111. GlobalFile: RecommendedHomeFile,
  112. EnvVar: RecommendedConfigPathEnvVar,
  113. ExplicitFileFlag: RecommendedConfigPathFlag,
  114. GlobalFileSubpath: path.Join(RecommendedHomeDir, RecommendedFileName),
  115. LoadingRules: NewDefaultClientConfigLoadingRules(),
  116. }
  117. ret.LoadingRules.DoNotResolvePaths = true
  118. return ret
  119. }
  120. // ModifyConfig takes a Config object, iterates through Clusters, AuthInfos, and Contexts, uses the LocationOfOrigin if specified or
  121. // uses the default destination file to write the results into. This results in multiple file reads, but it's very easy to follow.
  122. // Preferences and CurrentContext should always be set in the default destination file. Since we can't distinguish between empty and missing values
  123. // (no nil strings), we're forced have separate handling for them. In the kubeconfig cases, newConfig should have at most one difference,
  124. // 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
  125. // modified element.
  126. func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config, relativizePaths bool) error {
  127. possibleSources := configAccess.GetLoadingPrecedence()
  128. // sort the possible kubeconfig files so we always "lock" in the same order
  129. // to avoid deadlock (note: this can fail w/ symlinks, but... come on).
  130. sort.Strings(possibleSources)
  131. for _, filename := range possibleSources {
  132. if err := lockFile(filename); err != nil {
  133. return err
  134. }
  135. defer unlockFile(filename)
  136. }
  137. startingConfig, err := configAccess.GetStartingConfig()
  138. if err != nil {
  139. return err
  140. }
  141. // We need to find all differences, locate their original files, read a partial config to modify only that stanza and write out the file.
  142. // Special case the test for current context and preferences since those always write to the default file.
  143. if reflect.DeepEqual(*startingConfig, newConfig) {
  144. // nothing to do
  145. return nil
  146. }
  147. if startingConfig.CurrentContext != newConfig.CurrentContext {
  148. if err := writeCurrentContext(configAccess, newConfig.CurrentContext); err != nil {
  149. return err
  150. }
  151. }
  152. if !reflect.DeepEqual(startingConfig.Preferences, newConfig.Preferences) {
  153. if err := writePreferences(configAccess, newConfig.Preferences); err != nil {
  154. return err
  155. }
  156. }
  157. // Search every cluster, authInfo, and context. First from new to old for differences, then from old to new for deletions
  158. for key, cluster := range newConfig.Clusters {
  159. startingCluster, exists := startingConfig.Clusters[key]
  160. if !reflect.DeepEqual(cluster, startingCluster) || !exists {
  161. destinationFile := cluster.LocationOfOrigin
  162. if len(destinationFile) == 0 {
  163. destinationFile = configAccess.GetDefaultFilename()
  164. }
  165. configToWrite, err := getConfigFromFile(destinationFile)
  166. if err != nil {
  167. return err
  168. }
  169. t := *cluster
  170. configToWrite.Clusters[key] = &t
  171. configToWrite.Clusters[key].LocationOfOrigin = destinationFile
  172. if relativizePaths {
  173. if err := RelativizeClusterLocalPaths(configToWrite.Clusters[key]); err != nil {
  174. return err
  175. }
  176. }
  177. if err := WriteToFile(*configToWrite, destinationFile); err != nil {
  178. return err
  179. }
  180. }
  181. }
  182. // seenConfigs stores a map of config source filenames to computed config objects
  183. seenConfigs := map[string]*clientcmdapi.Config{}
  184. for key, context := range newConfig.Contexts {
  185. startingContext, exists := startingConfig.Contexts[key]
  186. if !reflect.DeepEqual(context, startingContext) || !exists {
  187. destinationFile := context.LocationOfOrigin
  188. if len(destinationFile) == 0 {
  189. destinationFile = configAccess.GetDefaultFilename()
  190. }
  191. // we only obtain a fresh config object from its source file
  192. // if we have not seen it already - this prevents us from
  193. // reading and writing to the same number of files repeatedly
  194. // when multiple / all contexts share the same destination file.
  195. configToWrite, seen := seenConfigs[destinationFile]
  196. if !seen {
  197. var err error
  198. configToWrite, err = getConfigFromFile(destinationFile)
  199. if err != nil {
  200. return err
  201. }
  202. seenConfigs[destinationFile] = configToWrite
  203. }
  204. configToWrite.Contexts[key] = context
  205. }
  206. }
  207. // actually persist config object changes
  208. for destinationFile, configToWrite := range seenConfigs {
  209. if err := WriteToFile(*configToWrite, destinationFile); err != nil {
  210. return err
  211. }
  212. }
  213. for key, authInfo := range newConfig.AuthInfos {
  214. startingAuthInfo, exists := startingConfig.AuthInfos[key]
  215. if !reflect.DeepEqual(authInfo, startingAuthInfo) || !exists {
  216. destinationFile := authInfo.LocationOfOrigin
  217. if len(destinationFile) == 0 {
  218. destinationFile = configAccess.GetDefaultFilename()
  219. }
  220. configToWrite, err := getConfigFromFile(destinationFile)
  221. if err != nil {
  222. return err
  223. }
  224. t := *authInfo
  225. configToWrite.AuthInfos[key] = &t
  226. configToWrite.AuthInfos[key].LocationOfOrigin = destinationFile
  227. if relativizePaths {
  228. if err := RelativizeAuthInfoLocalPaths(configToWrite.AuthInfos[key]); err != nil {
  229. return err
  230. }
  231. }
  232. if err := WriteToFile(*configToWrite, destinationFile); err != nil {
  233. return err
  234. }
  235. }
  236. }
  237. for key, cluster := range startingConfig.Clusters {
  238. if _, exists := newConfig.Clusters[key]; !exists {
  239. destinationFile := cluster.LocationOfOrigin
  240. if len(destinationFile) == 0 {
  241. destinationFile = configAccess.GetDefaultFilename()
  242. }
  243. configToWrite, err := getConfigFromFile(destinationFile)
  244. if err != nil {
  245. return err
  246. }
  247. delete(configToWrite.Clusters, key)
  248. if err := WriteToFile(*configToWrite, destinationFile); err != nil {
  249. return err
  250. }
  251. }
  252. }
  253. for key, context := range startingConfig.Contexts {
  254. if _, exists := newConfig.Contexts[key]; !exists {
  255. destinationFile := context.LocationOfOrigin
  256. if len(destinationFile) == 0 {
  257. destinationFile = configAccess.GetDefaultFilename()
  258. }
  259. configToWrite, err := getConfigFromFile(destinationFile)
  260. if err != nil {
  261. return err
  262. }
  263. delete(configToWrite.Contexts, key)
  264. if err := WriteToFile(*configToWrite, destinationFile); err != nil {
  265. return err
  266. }
  267. }
  268. }
  269. for key, authInfo := range startingConfig.AuthInfos {
  270. if _, exists := newConfig.AuthInfos[key]; !exists {
  271. destinationFile := authInfo.LocationOfOrigin
  272. if len(destinationFile) == 0 {
  273. destinationFile = configAccess.GetDefaultFilename()
  274. }
  275. configToWrite, err := getConfigFromFile(destinationFile)
  276. if err != nil {
  277. return err
  278. }
  279. delete(configToWrite.AuthInfos, key)
  280. if err := WriteToFile(*configToWrite, destinationFile); err != nil {
  281. return err
  282. }
  283. }
  284. }
  285. return nil
  286. }
  287. func PersisterForUser(configAccess ConfigAccess, user string) restclient.AuthProviderConfigPersister {
  288. return &persister{configAccess, user}
  289. }
  290. type persister struct {
  291. configAccess ConfigAccess
  292. user string
  293. }
  294. func (p *persister) Persist(config map[string]string) error {
  295. newConfig, err := p.configAccess.GetStartingConfig()
  296. if err != nil {
  297. return err
  298. }
  299. authInfo, ok := newConfig.AuthInfos[p.user]
  300. if ok && authInfo.AuthProvider != nil {
  301. authInfo.AuthProvider.Config = config
  302. ModifyConfig(p.configAccess, *newConfig, false)
  303. }
  304. return nil
  305. }
  306. // writeCurrentContext takes three possible paths.
  307. // If newCurrentContext is the same as the startingConfig's current context, then we exit.
  308. // If newCurrentContext has a value, then that value is written into the default destination file.
  309. // If newCurrentContext is empty, then we find the config file that is setting the CurrentContext and clear the value from that file
  310. func writeCurrentContext(configAccess ConfigAccess, newCurrentContext string) error {
  311. if startingConfig, err := configAccess.GetStartingConfig(); err != nil {
  312. return err
  313. } else if startingConfig.CurrentContext == newCurrentContext {
  314. return nil
  315. }
  316. if configAccess.IsExplicitFile() {
  317. file := configAccess.GetExplicitFile()
  318. currConfig, err := getConfigFromFile(file)
  319. if err != nil {
  320. return err
  321. }
  322. currConfig.CurrentContext = newCurrentContext
  323. if err := WriteToFile(*currConfig, file); err != nil {
  324. return err
  325. }
  326. return nil
  327. }
  328. if len(newCurrentContext) > 0 {
  329. destinationFile := configAccess.GetDefaultFilename()
  330. config, err := getConfigFromFile(destinationFile)
  331. if err != nil {
  332. return err
  333. }
  334. config.CurrentContext = newCurrentContext
  335. if err := WriteToFile(*config, destinationFile); err != nil {
  336. return err
  337. }
  338. return nil
  339. }
  340. // 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
  341. for _, file := range configAccess.GetLoadingPrecedence() {
  342. if _, err := os.Stat(file); err == nil {
  343. currConfig, err := getConfigFromFile(file)
  344. if err != nil {
  345. return err
  346. }
  347. if len(currConfig.CurrentContext) > 0 {
  348. currConfig.CurrentContext = newCurrentContext
  349. if err := WriteToFile(*currConfig, file); err != nil {
  350. return err
  351. }
  352. return nil
  353. }
  354. }
  355. }
  356. return errors.New("no config found to write context")
  357. }
  358. func writePreferences(configAccess ConfigAccess, newPrefs clientcmdapi.Preferences) error {
  359. if startingConfig, err := configAccess.GetStartingConfig(); err != nil {
  360. return err
  361. } else if reflect.DeepEqual(startingConfig.Preferences, newPrefs) {
  362. return nil
  363. }
  364. if configAccess.IsExplicitFile() {
  365. file := configAccess.GetExplicitFile()
  366. currConfig, err := getConfigFromFile(file)
  367. if err != nil {
  368. return err
  369. }
  370. currConfig.Preferences = newPrefs
  371. if err := WriteToFile(*currConfig, file); err != nil {
  372. return err
  373. }
  374. return nil
  375. }
  376. for _, file := range configAccess.GetLoadingPrecedence() {
  377. currConfig, err := getConfigFromFile(file)
  378. if err != nil {
  379. return err
  380. }
  381. if !reflect.DeepEqual(currConfig.Preferences, newPrefs) {
  382. currConfig.Preferences = newPrefs
  383. if err := WriteToFile(*currConfig, file); err != nil {
  384. return err
  385. }
  386. return nil
  387. }
  388. }
  389. return errors.New("no config found to write preferences")
  390. }
  391. // 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.
  392. func getConfigFromFile(filename string) (*clientcmdapi.Config, error) {
  393. config, err := LoadFromFile(filename)
  394. if err != nil && !os.IsNotExist(err) {
  395. return nil, err
  396. }
  397. if config == nil {
  398. config = clientcmdapi.NewConfig()
  399. }
  400. return config, nil
  401. }
  402. // 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
  403. func GetConfigFromFileOrDie(filename string) *clientcmdapi.Config {
  404. config, err := getConfigFromFile(filename)
  405. if err != nil {
  406. klog.FatalDepth(1, err)
  407. }
  408. return config
  409. }