diff --git a/cmd/clusterctl/client/config/reader_viper.go b/cmd/clusterctl/client/config/reader_viper.go index 077a9d58b1c8..c375dbc95461 100644 --- a/cmd/clusterctl/client/config/reader_viper.go +++ b/cmd/clusterctl/client/config/reader_viper.go @@ -17,6 +17,8 @@ limitations under the License. package config import ( + "fmt" + "os" "path/filepath" "strings" @@ -26,17 +28,36 @@ import ( logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log" ) -// ConfigFolder defines the name of the config folder under $home -const ConfigFolder = ".cluster-api" +const ( + // ConfigFolder defines the name of the config folder under $home + ConfigFolder = ".cluster-api" + // ConfigName defines the name of the config file under ConfigFolder + ConfigName = "clusterctl" +) // viperReader implements Reader using viper as backend for reading from environment variables // and from a clusterctl config file. type viperReader struct { + configPaths []string +} + +type viperReaderOption func(*viperReader) + +func InjectConfigPaths(configPaths []string) viperReaderOption { + return func(vr *viperReader) { + vr.configPaths = configPaths + } } // newViperReader returns a viperReader. -func newViperReader() Reader { - return &viperReader{} +func newViperReader(opts ...viperReaderOption) Reader { + vr := &viperReader{ + configPaths: []string{filepath.Join(homedir.HomeDir(), ConfigFolder)}, + } + for _, o := range opts { + o(vr) + } + return vr } // Init initialize the viperReader. @@ -44,12 +65,23 @@ func (v *viperReader) Init(path string) error { log := logf.Log if path != "" { + if _, err := os.Stat(path); err != nil { + return err + } // Use path file from the flag. viper.SetConfigFile(path) } else { // Configure for searching .cluster-api/clusterctl{.extension} in home directory - viper.SetConfigName("clusterctl") - viper.AddConfigPath(filepath.Join(homedir.HomeDir(), ConfigFolder)) + viper.SetConfigName(ConfigName) + for _, p := range v.configPaths { + viper.AddConfigPath(p) + } + if !v.checkDefaultConfig() { + // since there is no default config to read from, just skip + // reading in config + log.V(5).Info("No default config file available") + return nil + } } // Configure for reading environment variables as well, and more specifically: @@ -82,3 +114,19 @@ func (v *viperReader) Set(key, value string) { func (v *viperReader) UnmarshalKey(key string, rawval interface{}) error { return viper.UnmarshalKey(key, rawval) } + +// checkDefaultConfig checks the existence of the default config. +// Returns true if it finds a supported config file in the available config +// folders. +func (v *viperReader) checkDefaultConfig() bool { + for _, path := range v.configPaths { + for _, ext := range viper.SupportedExts { + f := fmt.Sprintf("%s%s.%s", path, ConfigName, ext) + _, err := os.Stat(f) + if err == nil { + return true + } + } + } + return false +} diff --git a/cmd/clusterctl/client/config/reader_viper_test.go b/cmd/clusterctl/client/config/reader_viper_test.go index 53d6fe4e203c..52db5d1165c6 100644 --- a/cmd/clusterctl/client/config/reader_viper_test.go +++ b/cmd/clusterctl/client/config/reader_viper_test.go @@ -26,44 +26,70 @@ import ( ) func Test_viperReader_Init(t *testing.T) { - g := NewWithT(t) + + // Change HOME dir and do not specify config file + // (.cluster-api/clusterctl) in it. + clusterctlHomeDir, err := ioutil.TempDir("", "clusterctl-default") + g.Expect(err).NotTo(HaveOccurred()) + defer os.RemoveAll(clusterctlHomeDir) + dir, err := ioutil.TempDir("", "clusterctl") g.Expect(err).NotTo(HaveOccurred()) defer os.RemoveAll(dir) configFile := filepath.Join(dir, ".clusterctl.yaml") g.Expect(ioutil.WriteFile(configFile, []byte("bar: bar"), 0640)).To(Succeed()) + + configFileBadContents := filepath.Join(dir, ".clusterctl-bad.yaml") + g.Expect(ioutil.WriteFile(configFileBadContents, []byte("bad-contents"), 0640)).To(Succeed()) + tests := []struct { - name string - cfgPath string - expectErr bool + name string + configPath string + configDirs []string + expectErr bool }{ { - name: "reads in config successfully", - cfgPath: configFile, - expectErr: false, + name: "reads in config successfully", + configPath: configFile, + configDirs: []string{clusterctlHomeDir}, + expectErr: false, }, { - name: "returns error for invalid config file path", - cfgPath: "do-not-exist.yaml", - expectErr: true, + name: "returns error for invalid config file path", + configPath: "do-not-exist.yaml", + configDirs: []string{clusterctlHomeDir}, + expectErr: true, + }, + { + name: "does not return error if default file doesn't exist", + configPath: "", + configDirs: []string{clusterctlHomeDir}, + expectErr: false, + }, + { + name: "returns error for malformed config", + configPath: configFileBadContents, + configDirs: []string{clusterctlHomeDir}, + expectErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gg := NewWithT(t) - v := &viperReader{} + v := newViperReader(InjectConfigPaths(tt.configDirs)) if tt.expectErr { - gg.Expect(v.Init(tt.cfgPath)).ToNot(Succeed()) + gg.Expect(v.Init(tt.configPath)).ToNot(Succeed()) return } - gg.Expect(v.Init(tt.cfgPath)).To(Succeed()) + gg.Expect(v.Init(tt.configPath)).To(Succeed()) }) } } + func Test_viperReader_Get(t *testing.T) { g := NewWithT(t)