mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-01-04 23:17:50 +00:00
change kubeconfig loading order
This commit is contained in:
@@ -18,10 +18,10 @@ package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
@@ -33,41 +33,54 @@ import (
|
||||
)
|
||||
|
||||
type PathOptions struct {
|
||||
Local bool
|
||||
Global bool
|
||||
UseEnvVar bool
|
||||
|
||||
LocalFile string
|
||||
// GlobalFile is the full path to the file to load as the global (final) option
|
||||
GlobalFile string
|
||||
EnvVarFile string
|
||||
|
||||
EnvVar string
|
||||
// EnvVar is the env var name that points to the list of kubeconfig files to load
|
||||
EnvVar string
|
||||
// ExplicitFileFlag is the name of the flag to use for prompting for the kubeconfig file
|
||||
ExplicitFileFlag string
|
||||
|
||||
// GlobalFileSubpath is an optional value used for displaying help
|
||||
GlobalFileSubpath string
|
||||
|
||||
LoadingRules *clientcmd.ClientConfigLoadingRules
|
||||
}
|
||||
|
||||
// ConfigAccess is used by subcommands and methods in this package to load and modify the appropriate config files
|
||||
type ConfigAccess interface {
|
||||
// GetLoadingPrecedence returns the slice of files that should be used for loading and inspecting the config
|
||||
GetLoadingPrecedence() []string
|
||||
// GetStartingConfig returns the config that subcommands should being operating against. It may or may not be merged depending on loading rules
|
||||
GetStartingConfig() (*clientcmdapi.Config, error)
|
||||
// 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.
|
||||
GetDefaultFilename() string
|
||||
// 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
|
||||
IsExplicitFile() bool
|
||||
// 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
|
||||
GetExplicitFile() string
|
||||
}
|
||||
|
||||
func NewCmdConfig(pathOptions *PathOptions, out io.Writer) *cobra.Command {
|
||||
if len(pathOptions.ExplicitFileFlag) == 0 {
|
||||
pathOptions.ExplicitFileFlag = clientcmd.RecommendedConfigPathFlag
|
||||
}
|
||||
if len(pathOptions.EnvVar) > 0 {
|
||||
pathOptions.EnvVarFile = os.Getenv(pathOptions.EnvVar)
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "config SUBCOMMAND",
|
||||
Short: "config modifies kubeconfig files",
|
||||
Long: `config modifies kubeconfig files using subcommands like "kubectl config set current-context my-context"`,
|
||||
Long: `config modifies kubeconfig files using subcommands like "kubectl config set current-context my-context"
|
||||
|
||||
The loading order follows these rules:
|
||||
1. If the --` + pathOptions.ExplicitFileFlag + ` flag is set, then only that file is loaded. The flag may only be set once and no merging takes place.
|
||||
2. If $` + pathOptions.EnvVar + ` environment variable is set, then it is used a list of paths (normal path delimitting rules for your system). These paths are merged together. When a value is modified, it is modified in the file that defines the stanza. When a value is created, it is created in the first file that exists. If no files in the chain exist, then it creates the last file in the list.
|
||||
3. Otherwise, ` + path.Join("${HOME}", pathOptions.GlobalFileSubpath) + ` is used and no merging takes place.
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
},
|
||||
}
|
||||
|
||||
// file paths are common to all sub commands
|
||||
cmd.PersistentFlags().BoolVar(&pathOptions.Local, "local", pathOptions.Local, "use the kubeconfig in the current directory")
|
||||
cmd.PersistentFlags().BoolVar(&pathOptions.Global, "global", pathOptions.Global, "use the kubeconfig from "+pathOptions.GlobalFile)
|
||||
cmd.PersistentFlags().BoolVar(&pathOptions.UseEnvVar, "envvar", pathOptions.UseEnvVar, "use the kubeconfig from $"+pathOptions.EnvVar)
|
||||
cmd.PersistentFlags().StringVar(&pathOptions.LoadingRules.ExplicitPath, pathOptions.ExplicitFileFlag, pathOptions.LoadingRules.ExplicitPath, "use a particular kubeconfig file")
|
||||
|
||||
cmd.AddCommand(NewCmdConfigView(out, pathOptions))
|
||||
@@ -83,13 +96,12 @@ func NewCmdConfig(pathOptions *PathOptions, out io.Writer) *cobra.Command {
|
||||
|
||||
func NewDefaultPathOptions() *PathOptions {
|
||||
ret := &PathOptions{
|
||||
LocalFile: ".kubeconfig",
|
||||
GlobalFile: path.Join(os.Getenv("HOME"), "/.kube/.kubeconfig"),
|
||||
EnvVar: clientcmd.RecommendedConfigPathEnvVar,
|
||||
EnvVarFile: os.Getenv(clientcmd.RecommendedConfigPathEnvVar),
|
||||
|
||||
GlobalFile: clientcmd.RecommendedHomeFile,
|
||||
EnvVar: clientcmd.RecommendedConfigPathEnvVar,
|
||||
ExplicitFileFlag: clientcmd.RecommendedConfigPathFlag,
|
||||
|
||||
GlobalFileSubpath: clientcmd.RecommendedHomeFileName,
|
||||
|
||||
LoadingRules: clientcmd.NewDefaultClientConfigLoadingRules(),
|
||||
}
|
||||
ret.LoadingRules.DoNotResolvePaths = true
|
||||
@@ -97,125 +109,78 @@ func NewDefaultPathOptions() *PathOptions {
|
||||
return ret
|
||||
}
|
||||
|
||||
func (o PathOptions) Validate() error {
|
||||
if len(o.LoadingRules.ExplicitPath) > 0 {
|
||||
if o.Global {
|
||||
return errors.New("cannot specify both --" + o.ExplicitFileFlag + " and --global")
|
||||
}
|
||||
if o.Local {
|
||||
return errors.New("cannot specify both --" + o.ExplicitFileFlag + " and --local")
|
||||
}
|
||||
if o.UseEnvVar {
|
||||
return errors.New("cannot specify both --" + o.ExplicitFileFlag + " and --envvar")
|
||||
}
|
||||
func (o *PathOptions) GetEnvVarFiles() []string {
|
||||
if len(o.EnvVar) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
if o.Global {
|
||||
if o.Local {
|
||||
return errors.New("cannot specify both --global and --local")
|
||||
}
|
||||
if o.UseEnvVar {
|
||||
return errors.New("cannot specify both --global and --envvar")
|
||||
}
|
||||
envVarValue := os.Getenv(o.EnvVar)
|
||||
if len(envVarValue) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
if o.Local {
|
||||
if o.UseEnvVar {
|
||||
return errors.New("cannot specify both --local and --envvar")
|
||||
}
|
||||
}
|
||||
|
||||
if o.UseEnvVar {
|
||||
if len(o.EnvVarFile) == 0 {
|
||||
return fmt.Errorf("environment variable %v does not have a value", o.EnvVar)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
return filepath.SplitList(envVarValue)
|
||||
}
|
||||
|
||||
func (o *PathOptions) getStartingConfig() (*clientcmdapi.Config, error) {
|
||||
if err := o.Validate(); err != nil {
|
||||
func (o *PathOptions) GetLoadingPrecedence() []string {
|
||||
if envVarFiles := o.GetEnvVarFiles(); len(envVarFiles) > 0 {
|
||||
return envVarFiles
|
||||
}
|
||||
|
||||
return []string{o.GlobalFile}
|
||||
}
|
||||
|
||||
func (o *PathOptions) GetStartingConfig() (*clientcmdapi.Config, error) {
|
||||
// don't mutate the original
|
||||
loadingRules := *o.LoadingRules
|
||||
loadingRules.Precedence = o.GetLoadingPrecedence()
|
||||
|
||||
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(&loadingRules, &clientcmd.ConfigOverrides{})
|
||||
rawConfig, err := clientConfig.RawConfig()
|
||||
if os.IsNotExist(err) {
|
||||
return clientcmdapi.NewConfig(), nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := clientcmdapi.NewConfig()
|
||||
|
||||
switch {
|
||||
case o.Global:
|
||||
config = getConfigFromFileOrDie(o.GlobalFile)
|
||||
|
||||
case o.UseEnvVar:
|
||||
config = getConfigFromFileOrDie(o.EnvVarFile)
|
||||
|
||||
case o.Local:
|
||||
config = getConfigFromFileOrDie(o.LocalFile)
|
||||
|
||||
// no specific flag was set, load according to the loading rules
|
||||
default:
|
||||
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(o.LoadingRules, &clientcmd.ConfigOverrides{})
|
||||
rawConfig, err := clientConfig.RawConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config = &rawConfig
|
||||
|
||||
}
|
||||
|
||||
return config, nil
|
||||
return &rawConfig, nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (o *PathOptions) GetDefaultFilename() string {
|
||||
if o.IsExplicitFile() {
|
||||
return o.GetExplicitFile()
|
||||
}
|
||||
|
||||
if len(o.EnvVarFile) > 0 {
|
||||
return o.EnvVarFile
|
||||
}
|
||||
if envVarFiles := o.GetEnvVarFiles(); len(envVarFiles) > 0 {
|
||||
if len(envVarFiles) == 1 {
|
||||
return envVarFiles[0]
|
||||
}
|
||||
|
||||
if _, err := os.Stat(o.LocalFile); err == nil {
|
||||
return o.LocalFile
|
||||
// if any of the envvar files already exists, return it
|
||||
for _, envVarFile := range envVarFiles {
|
||||
if _, err := os.Stat(envVarFile); err == nil {
|
||||
return envVarFile
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise, return the last one in the list
|
||||
return envVarFiles[len(envVarFiles)-1]
|
||||
}
|
||||
|
||||
return o.GlobalFile
|
||||
|
||||
}
|
||||
|
||||
func (o *PathOptions) IsExplicitFile() bool {
|
||||
switch {
|
||||
case len(o.LoadingRules.ExplicitPath) > 0 ||
|
||||
o.Global ||
|
||||
o.UseEnvVar ||
|
||||
o.Local:
|
||||
if len(o.LoadingRules.ExplicitPath) > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *PathOptions) GetExplicitFile() string {
|
||||
if !o.IsExplicitFile() {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(o.LoadingRules.ExplicitPath) > 0:
|
||||
return o.LoadingRules.ExplicitPath
|
||||
|
||||
case o.Global:
|
||||
return o.GlobalFile
|
||||
|
||||
case o.UseEnvVar:
|
||||
return o.EnvVarFile
|
||||
|
||||
case o.Local:
|
||||
return o.LocalFile
|
||||
}
|
||||
|
||||
return ""
|
||||
return o.LoadingRules.ExplicitPath
|
||||
}
|
||||
|
||||
// ModifyConfig takes a Config object, iterates through Clusters, AuthInfos, and Contexts, uses the LocationOfOrigin if specified or
|
||||
@@ -223,8 +188,8 @@ func (o *PathOptions) GetExplicitFile() string {
|
||||
// Preferences and CurrentContext should always be set in the default destination file. Since we can't distinguish between empty and missing values
|
||||
// (no nil strings), we're forced have separate handling for them. In all the currently known cases, newConfig should have, at most, one difference,
|
||||
// that means that this code will only write into a single file.
|
||||
func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error {
|
||||
startingConfig, err := o.getStartingConfig()
|
||||
func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config) error {
|
||||
startingConfig, err := configAccess.GetStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -237,12 +202,12 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error {
|
||||
// nothing to do
|
||||
|
||||
case startingConfig.CurrentContext != newConfig.CurrentContext:
|
||||
if err := o.writeCurrentContext(newConfig.CurrentContext); err != nil {
|
||||
if err := writeCurrentContext(configAccess, newConfig.CurrentContext); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case !reflect.DeepEqual(startingConfig.Preferences, newConfig.Preferences):
|
||||
if err := o.writePreferences(newConfig.Preferences); err != nil {
|
||||
if err := writePreferences(configAccess, newConfig.Preferences); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -253,7 +218,7 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error {
|
||||
if !reflect.DeepEqual(cluster, startingCluster) || !exists {
|
||||
destinationFile := cluster.LocationOfOrigin
|
||||
if len(destinationFile) == 0 {
|
||||
destinationFile = o.GetDefaultFilename()
|
||||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite := getConfigFromFileOrDie(destinationFile)
|
||||
@@ -270,7 +235,7 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error {
|
||||
if !reflect.DeepEqual(context, startingContext) || !exists {
|
||||
destinationFile := context.LocationOfOrigin
|
||||
if len(destinationFile) == 0 {
|
||||
destinationFile = o.GetDefaultFilename()
|
||||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite := getConfigFromFileOrDie(destinationFile)
|
||||
@@ -287,7 +252,7 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error {
|
||||
if !reflect.DeepEqual(authInfo, startingAuthInfo) || !exists {
|
||||
destinationFile := authInfo.LocationOfOrigin
|
||||
if len(destinationFile) == 0 {
|
||||
destinationFile = o.GetDefaultFilename()
|
||||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite := getConfigFromFileOrDie(destinationFile)
|
||||
@@ -303,7 +268,7 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error {
|
||||
if _, exists := newConfig.Clusters[key]; !exists {
|
||||
destinationFile := cluster.LocationOfOrigin
|
||||
if len(destinationFile) == 0 {
|
||||
destinationFile = o.GetDefaultFilename()
|
||||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite := getConfigFromFileOrDie(destinationFile)
|
||||
@@ -319,7 +284,7 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error {
|
||||
if _, exists := newConfig.Contexts[key]; !exists {
|
||||
destinationFile := context.LocationOfOrigin
|
||||
if len(destinationFile) == 0 {
|
||||
destinationFile = o.GetDefaultFilename()
|
||||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite := getConfigFromFileOrDie(destinationFile)
|
||||
@@ -335,7 +300,7 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error {
|
||||
if _, exists := newConfig.AuthInfos[key]; !exists {
|
||||
destinationFile := authInfo.LocationOfOrigin
|
||||
if len(destinationFile) == 0 {
|
||||
destinationFile = o.GetDefaultFilename()
|
||||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite := getConfigFromFileOrDie(destinationFile)
|
||||
@@ -356,15 +321,26 @@ func (o *PathOptions) ModifyConfig(newConfig clientcmdapi.Config) error {
|
||||
// If newCurrentContext is the same as the startingConfig's current context, then we exit.
|
||||
// If newCurrentContext has a value, then that value is written into the default destination file.
|
||||
// If newCurrentContext is empty, then we find the config file that is setting the CurrentContext and clear the value from that file
|
||||
func (o *PathOptions) writeCurrentContext(newCurrentContext string) error {
|
||||
if startingConfig, err := o.getStartingConfig(); err != nil {
|
||||
func writeCurrentContext(configAccess ConfigAccess, newCurrentContext string) error {
|
||||
if startingConfig, err := configAccess.GetStartingConfig(); err != nil {
|
||||
return err
|
||||
} else if startingConfig.CurrentContext == newCurrentContext {
|
||||
return nil
|
||||
}
|
||||
|
||||
if configAccess.IsExplicitFile() {
|
||||
file := configAccess.GetExplicitFile()
|
||||
currConfig := getConfigFromFileOrDie(file)
|
||||
currConfig.CurrentContext = newCurrentContext
|
||||
if err := clientcmd.WriteToFile(*currConfig, file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(newCurrentContext) > 0 {
|
||||
destinationFile := o.GetDefaultFilename()
|
||||
destinationFile := configAccess.GetDefaultFilename()
|
||||
config := getConfigFromFileOrDie(destinationFile)
|
||||
config.CurrentContext = newCurrentContext
|
||||
|
||||
@@ -375,46 +351,34 @@ func (o *PathOptions) writeCurrentContext(newCurrentContext string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if o.IsExplicitFile() {
|
||||
file := o.GetExplicitFile()
|
||||
currConfig := getConfigFromFileOrDie(file)
|
||||
currConfig.CurrentContext = newCurrentContext
|
||||
if err := clientcmd.WriteToFile(*currConfig, file); err != nil {
|
||||
return err
|
||||
}
|
||||
// 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
|
||||
for _, file := range configAccess.GetLoadingPrecedence() {
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
currConfig := getConfigFromFileOrDie(file)
|
||||
|
||||
return nil
|
||||
}
|
||||
if len(currConfig.CurrentContext) > 0 {
|
||||
currConfig.CurrentContext = newCurrentContext
|
||||
if err := clientcmd.WriteToFile(*currConfig, file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filesToCheck := make([]string, 0, len(o.LoadingRules.Precedence)+1)
|
||||
filesToCheck = append(filesToCheck, o.LoadingRules.ExplicitPath)
|
||||
filesToCheck = append(filesToCheck, o.LoadingRules.Precedence...)
|
||||
|
||||
for _, file := range filesToCheck {
|
||||
currConfig := getConfigFromFileOrDie(file)
|
||||
|
||||
if len(currConfig.CurrentContext) > 0 {
|
||||
currConfig.CurrentContext = newCurrentContext
|
||||
if err := clientcmd.WriteToFile(*currConfig, file); err != nil {
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return errors.New("no config found to write context")
|
||||
}
|
||||
|
||||
func (o *PathOptions) writePreferences(newPrefs clientcmdapi.Preferences) error {
|
||||
if startingConfig, err := o.getStartingConfig(); err != nil {
|
||||
func writePreferences(configAccess ConfigAccess, newPrefs clientcmdapi.Preferences) error {
|
||||
if startingConfig, err := configAccess.GetStartingConfig(); err != nil {
|
||||
return err
|
||||
} else if reflect.DeepEqual(startingConfig.Preferences, newPrefs) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if o.IsExplicitFile() {
|
||||
file := o.GetExplicitFile()
|
||||
if configAccess.IsExplicitFile() {
|
||||
file := configAccess.GetExplicitFile()
|
||||
currConfig := getConfigFromFileOrDie(file)
|
||||
currConfig.Preferences = newPrefs
|
||||
if err := clientcmd.WriteToFile(*currConfig, file); err != nil {
|
||||
@@ -424,11 +388,7 @@ func (o *PathOptions) writePreferences(newPrefs clientcmdapi.Preferences) error
|
||||
return nil
|
||||
}
|
||||
|
||||
filesToCheck := make([]string, 0, len(o.LoadingRules.Precedence)+1)
|
||||
filesToCheck = append(filesToCheck, o.LoadingRules.ExplicitPath)
|
||||
filesToCheck = append(filesToCheck, o.LoadingRules.Precedence...)
|
||||
|
||||
for _, file := range filesToCheck {
|
||||
for _, file := range configAccess.GetLoadingPrecedence() {
|
||||
currConfig := getConfigFromFileOrDie(file)
|
||||
|
||||
if !reflect.DeepEqual(currConfig.Preferences, newPrefs) {
|
||||
@@ -441,7 +401,7 @@ func (o *PathOptions) writePreferences(newPrefs clientcmdapi.Preferences) error
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return errors.New("no config found to write preferences")
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user