config.go 14 KB

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