config.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  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/v2"
  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. var (
  50. // UseModifyConfigLock ensures that access to kubeconfig file using ModifyConfig method
  51. // is being guarded by a lock file.
  52. // This variable is intentionaly made public so other consumers of this library
  53. // can modify its default behavior, but be caution when disabling it since
  54. // this will make your code not threadsafe.
  55. UseModifyConfigLock = true
  56. )
  57. func (o *PathOptions) GetEnvVarFiles() []string {
  58. if len(o.EnvVar) == 0 {
  59. return []string{}
  60. }
  61. envVarValue := os.Getenv(o.EnvVar)
  62. if len(envVarValue) == 0 {
  63. return []string{}
  64. }
  65. fileList := filepath.SplitList(envVarValue)
  66. // prevent the same path load multiple times
  67. return deduplicate(fileList)
  68. }
  69. func (o *PathOptions) GetLoadingPrecedence() []string {
  70. if envVarFiles := o.GetEnvVarFiles(); len(envVarFiles) > 0 {
  71. return envVarFiles
  72. }
  73. return []string{o.GlobalFile}
  74. }
  75. func (o *PathOptions) GetStartingConfig() (*clientcmdapi.Config, error) {
  76. // don't mutate the original
  77. loadingRules := *o.LoadingRules
  78. loadingRules.Precedence = o.GetLoadingPrecedence()
  79. clientConfig := NewNonInteractiveDeferredLoadingClientConfig(&loadingRules, &ConfigOverrides{})
  80. rawConfig, err := clientConfig.RawConfig()
  81. if os.IsNotExist(err) {
  82. return clientcmdapi.NewConfig(), nil
  83. }
  84. if err != nil {
  85. return nil, err
  86. }
  87. return &rawConfig, nil
  88. }
  89. func (o *PathOptions) GetDefaultFilename() string {
  90. if o.IsExplicitFile() {
  91. return o.GetExplicitFile()
  92. }
  93. if envVarFiles := o.GetEnvVarFiles(); len(envVarFiles) > 0 {
  94. if len(envVarFiles) == 1 {
  95. return envVarFiles[0]
  96. }
  97. // if any of the envvar files already exists, return it
  98. for _, envVarFile := range envVarFiles {
  99. if _, err := os.Stat(envVarFile); err == nil {
  100. return envVarFile
  101. }
  102. }
  103. // otherwise, return the last one in the list
  104. return envVarFiles[len(envVarFiles)-1]
  105. }
  106. return o.GlobalFile
  107. }
  108. func (o *PathOptions) IsExplicitFile() bool {
  109. if len(o.LoadingRules.ExplicitPath) > 0 {
  110. return true
  111. }
  112. return false
  113. }
  114. func (o *PathOptions) GetExplicitFile() string {
  115. return o.LoadingRules.ExplicitPath
  116. }
  117. func NewDefaultPathOptions() *PathOptions {
  118. ret := &PathOptions{
  119. GlobalFile: RecommendedHomeFile,
  120. EnvVar: RecommendedConfigPathEnvVar,
  121. ExplicitFileFlag: RecommendedConfigPathFlag,
  122. GlobalFileSubpath: path.Join(RecommendedHomeDir, RecommendedFileName),
  123. LoadingRules: NewDefaultClientConfigLoadingRules(),
  124. }
  125. ret.LoadingRules.DoNotResolvePaths = true
  126. return ret
  127. }
  128. // ModifyConfig takes a Config object, iterates through Clusters, AuthInfos, and Contexts, uses the LocationOfOrigin if specified or
  129. // uses the default destination file to write the results into. This results in multiple file reads, but it's very easy to follow.
  130. // Preferences and CurrentContext should always be set in the default destination file. Since we can't distinguish between empty and missing values
  131. // (no nil strings), we're forced have separate handling for them. In the kubeconfig cases, newConfig should have at most one difference,
  132. // 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
  133. // modified element.
  134. func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config, relativizePaths bool) error {
  135. if UseModifyConfigLock {
  136. possibleSources := configAccess.GetLoadingPrecedence()
  137. // sort the possible kubeconfig files so we always "lock" in the same order
  138. // to avoid deadlock (note: this can fail w/ symlinks, but... come on).
  139. sort.Strings(possibleSources)
  140. for _, filename := range possibleSources {
  141. if err := lockFile(filename); err != nil {
  142. return err
  143. }
  144. defer unlockFile(filename)
  145. }
  146. }
  147. startingConfig, err := configAccess.GetStartingConfig()
  148. if err != nil {
  149. return err
  150. }
  151. // We need to find all differences, locate their original files, read a partial config to modify only that stanza and write out the file.
  152. // Special case the test for current context and preferences since those always write to the default file.
  153. if reflect.DeepEqual(*startingConfig, newConfig) {
  154. // nothing to do
  155. return nil
  156. }
  157. if startingConfig.CurrentContext != newConfig.CurrentContext {
  158. if err := writeCurrentContext(configAccess, newConfig.CurrentContext); err != nil {
  159. return err
  160. }
  161. }
  162. if !reflect.DeepEqual(startingConfig.Preferences, newConfig.Preferences) {
  163. if err := writePreferences(configAccess, newConfig.Preferences); err != nil {
  164. return err
  165. }
  166. }
  167. // Search every cluster, authInfo, and context. First from new to old for differences, then from old to new for deletions
  168. for key, cluster := range newConfig.Clusters {
  169. startingCluster, exists := startingConfig.Clusters[key]
  170. if !reflect.DeepEqual(cluster, startingCluster) || !exists {
  171. destinationFile := cluster.LocationOfOrigin
  172. if len(destinationFile) == 0 {
  173. destinationFile = configAccess.GetDefaultFilename()
  174. }
  175. configToWrite, err := getConfigFromFile(destinationFile)
  176. if err != nil {
  177. return err
  178. }
  179. t := *cluster
  180. configToWrite.Clusters[key] = &t
  181. configToWrite.Clusters[key].LocationOfOrigin = destinationFile
  182. if relativizePaths {
  183. if err := RelativizeClusterLocalPaths(configToWrite.Clusters[key]); err != nil {
  184. return err
  185. }
  186. }
  187. if err := WriteToFile(*configToWrite, destinationFile); err != nil {
  188. return err
  189. }
  190. }
  191. }
  192. // seenConfigs stores a map of config source filenames to computed config objects
  193. seenConfigs := map[string]*clientcmdapi.Config{}
  194. for key, context := range newConfig.Contexts {
  195. startingContext, exists := startingConfig.Contexts[key]
  196. if !reflect.DeepEqual(context, startingContext) || !exists {
  197. destinationFile := context.LocationOfOrigin
  198. if len(destinationFile) == 0 {
  199. destinationFile = configAccess.GetDefaultFilename()
  200. }
  201. // we only obtain a fresh config object from its source file
  202. // if we have not seen it already - this prevents us from
  203. // reading and writing to the same number of files repeatedly
  204. // when multiple / all contexts share the same destination file.
  205. configToWrite, seen := seenConfigs[destinationFile]
  206. if !seen {
  207. var err error
  208. configToWrite, err = getConfigFromFile(destinationFile)
  209. if err != nil {
  210. return err
  211. }
  212. seenConfigs[destinationFile] = configToWrite
  213. }
  214. configToWrite.Contexts[key] = context
  215. }
  216. }
  217. // actually persist config object changes
  218. for destinationFile, configToWrite := range seenConfigs {
  219. if err := WriteToFile(*configToWrite, destinationFile); err != nil {
  220. return err
  221. }
  222. }
  223. for key, authInfo := range newConfig.AuthInfos {
  224. startingAuthInfo, exists := startingConfig.AuthInfos[key]
  225. if !reflect.DeepEqual(authInfo, startingAuthInfo) || !exists {
  226. destinationFile := authInfo.LocationOfOrigin
  227. if len(destinationFile) == 0 {
  228. destinationFile = configAccess.GetDefaultFilename()
  229. }
  230. configToWrite, err := getConfigFromFile(destinationFile)
  231. if err != nil {
  232. return err
  233. }
  234. t := *authInfo
  235. configToWrite.AuthInfos[key] = &t
  236. configToWrite.AuthInfos[key].LocationOfOrigin = destinationFile
  237. if relativizePaths {
  238. if err := RelativizeAuthInfoLocalPaths(configToWrite.AuthInfos[key]); err != nil {
  239. return err
  240. }
  241. }
  242. if err := WriteToFile(*configToWrite, destinationFile); err != nil {
  243. return err
  244. }
  245. }
  246. }
  247. for key, cluster := range startingConfig.Clusters {
  248. if _, exists := newConfig.Clusters[key]; !exists {
  249. destinationFile := cluster.LocationOfOrigin
  250. if len(destinationFile) == 0 {
  251. destinationFile = configAccess.GetDefaultFilename()
  252. }
  253. configToWrite, err := getConfigFromFile(destinationFile)
  254. if err != nil {
  255. return err
  256. }
  257. delete(configToWrite.Clusters, key)
  258. if err := WriteToFile(*configToWrite, destinationFile); err != nil {
  259. return err
  260. }
  261. }
  262. }
  263. for key, context := range startingConfig.Contexts {
  264. if _, exists := newConfig.Contexts[key]; !exists {
  265. destinationFile := context.LocationOfOrigin
  266. if len(destinationFile) == 0 {
  267. destinationFile = configAccess.GetDefaultFilename()
  268. }
  269. configToWrite, err := getConfigFromFile(destinationFile)
  270. if err != nil {
  271. return err
  272. }
  273. delete(configToWrite.Contexts, key)
  274. if err := WriteToFile(*configToWrite, destinationFile); err != nil {
  275. return err
  276. }
  277. }
  278. }
  279. for key, authInfo := range startingConfig.AuthInfos {
  280. if _, exists := newConfig.AuthInfos[key]; !exists {
  281. destinationFile := authInfo.LocationOfOrigin
  282. if len(destinationFile) == 0 {
  283. destinationFile = configAccess.GetDefaultFilename()
  284. }
  285. configToWrite, err := getConfigFromFile(destinationFile)
  286. if err != nil {
  287. return err
  288. }
  289. delete(configToWrite.AuthInfos, key)
  290. if err := WriteToFile(*configToWrite, destinationFile); err != nil {
  291. return err
  292. }
  293. }
  294. }
  295. return nil
  296. }
  297. func PersisterForUser(configAccess ConfigAccess, user string) restclient.AuthProviderConfigPersister {
  298. return &persister{configAccess, user}
  299. }
  300. type persister struct {
  301. configAccess ConfigAccess
  302. user string
  303. }
  304. func (p *persister) Persist(config map[string]string) error {
  305. newConfig, err := p.configAccess.GetStartingConfig()
  306. if err != nil {
  307. return err
  308. }
  309. authInfo, ok := newConfig.AuthInfos[p.user]
  310. if ok && authInfo.AuthProvider != nil {
  311. authInfo.AuthProvider.Config = config
  312. ModifyConfig(p.configAccess, *newConfig, false)
  313. }
  314. return nil
  315. }
  316. // writeCurrentContext takes three possible paths.
  317. // If newCurrentContext is the same as the startingConfig's current context, then we exit.
  318. // If newCurrentContext has a value, then that value is written into the default destination file.
  319. // If newCurrentContext is empty, then we find the config file that is setting the CurrentContext and clear the value from that file
  320. func writeCurrentContext(configAccess ConfigAccess, newCurrentContext string) error {
  321. if startingConfig, err := configAccess.GetStartingConfig(); err != nil {
  322. return err
  323. } else if startingConfig.CurrentContext == newCurrentContext {
  324. return nil
  325. }
  326. if configAccess.IsExplicitFile() {
  327. file := configAccess.GetExplicitFile()
  328. currConfig, err := getConfigFromFile(file)
  329. if err != nil {
  330. return err
  331. }
  332. currConfig.CurrentContext = newCurrentContext
  333. if err := WriteToFile(*currConfig, file); err != nil {
  334. return err
  335. }
  336. return nil
  337. }
  338. if len(newCurrentContext) > 0 {
  339. destinationFile := configAccess.GetDefaultFilename()
  340. config, err := getConfigFromFile(destinationFile)
  341. if err != nil {
  342. return err
  343. }
  344. config.CurrentContext = newCurrentContext
  345. if err := WriteToFile(*config, destinationFile); err != nil {
  346. return err
  347. }
  348. return nil
  349. }
  350. // 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
  351. for _, file := range configAccess.GetLoadingPrecedence() {
  352. if _, err := os.Stat(file); err == nil {
  353. currConfig, err := getConfigFromFile(file)
  354. if err != nil {
  355. return err
  356. }
  357. if len(currConfig.CurrentContext) > 0 {
  358. currConfig.CurrentContext = newCurrentContext
  359. if err := WriteToFile(*currConfig, file); err != nil {
  360. return err
  361. }
  362. return nil
  363. }
  364. }
  365. }
  366. return errors.New("no config found to write context")
  367. }
  368. func writePreferences(configAccess ConfigAccess, newPrefs clientcmdapi.Preferences) error {
  369. if startingConfig, err := configAccess.GetStartingConfig(); err != nil {
  370. return err
  371. } else if reflect.DeepEqual(startingConfig.Preferences, newPrefs) {
  372. return nil
  373. }
  374. if configAccess.IsExplicitFile() {
  375. file := configAccess.GetExplicitFile()
  376. currConfig, err := getConfigFromFile(file)
  377. if err != nil {
  378. return err
  379. }
  380. currConfig.Preferences = newPrefs
  381. if err := WriteToFile(*currConfig, file); err != nil {
  382. return err
  383. }
  384. return nil
  385. }
  386. for _, file := range configAccess.GetLoadingPrecedence() {
  387. currConfig, err := getConfigFromFile(file)
  388. if err != nil {
  389. return err
  390. }
  391. if !reflect.DeepEqual(currConfig.Preferences, newPrefs) {
  392. currConfig.Preferences = newPrefs
  393. if err := WriteToFile(*currConfig, file); err != nil {
  394. return err
  395. }
  396. return nil
  397. }
  398. }
  399. return errors.New("no config found to write preferences")
  400. }
  401. // 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.
  402. func getConfigFromFile(filename string) (*clientcmdapi.Config, error) {
  403. config, err := LoadFromFile(filename)
  404. if err != nil && !os.IsNotExist(err) {
  405. return nil, err
  406. }
  407. if config == nil {
  408. config = clientcmdapi.NewConfig()
  409. }
  410. return config, nil
  411. }
  412. // 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
  413. func GetConfigFromFileOrDie(filename string) *clientcmdapi.Config {
  414. config, err := getConfigFromFile(filename)
  415. if err != nil {
  416. klog.FatalDepth(1, err)
  417. }
  418. return config
  419. }