From 30653d52f4bd217e37edb5e81ad799123a1ce8e8 Mon Sep 17 00:00:00 2001 From: Srinivas Chippagiri Date: Thu, 5 Nov 2026 02:13:50 -0800 Subject: [PATCH 1/3] fix: warn when the config file can't be read When .fga.yaml existed but couldn't be parsed, for example because it was indented with tabs, the CLI ignored it without a word and fell back to flags and environment variables. Print a warning with the file path and the parse error instead. A missing config file is still silent. Fixes #608 Signed-off-by: Srinivas Chippagiri --- cmd/root.go | 23 +++++++++++++++--- cmd/root_test.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 cmd/root_test.go diff --git a/cmd/root.go b/cmd/root.go index 3bbc7d91..f28805e5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -18,7 +18,9 @@ limitations under the License. package cmd import ( + "errors" "fmt" + "io" "os" "strings" @@ -119,10 +121,7 @@ func initConfig() { viperInstance.SetConfigName(".fga") } - // If a config file is found, read it in. - if err := viperInstance.ReadInConfig(); err == nil { - fmt.Fprintln(os.Stderr, "Using config file:", viperInstance.ConfigFileUsed()) - } + readConfig(viperInstance, os.Stderr) viperInstance.SetEnvPrefix("FGA") viperInstance.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) @@ -131,3 +130,19 @@ func initConfig() { cmdutils.BindViperToFlags(rootCmd, viperInstance) } + +// readConfig reads the config file into viperInstance, if one is found, and reports on stderr which file is +// used. A config file that exists but can't be read, e.g. because of invalid YAML, is reported as a +// warning instead of being ignored silently. +func readConfig(viperInstance *viper.Viper, stderr io.Writer) { + err := viperInstance.ReadInConfig() + if err == nil { + fmt.Fprintln(stderr, "Using config file:", viperInstance.ConfigFileUsed()) + + return + } + + if !errors.As(err, &viper.ConfigFileNotFoundError{}) { + fmt.Fprintf(stderr, "Warning: ignoring config file %s: %v\n", viperInstance.ConfigFileUsed(), err) + } +} diff --git a/cmd/root_test.go b/cmd/root_test.go new file mode 100644 index 00000000..78ea3bab --- /dev/null +++ b/cmd/root_test.go @@ -0,0 +1,63 @@ +package cmd + +import ( + "bytes" + "os" + "path/filepath" + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestReadConfig(t *testing.T) { + t.Parallel() + + t.Run("valid config file", func(t *testing.T) { + t.Parallel() + + configFile := filepath.Join(t.TempDir(), ".fga.yaml") + require.NoError(t, os.WriteFile(configFile, []byte("api-url: https://api.fga.example\n"), 0o600)) + + viperInstance := viper.New() + viperInstance.SetConfigFile(configFile) + + var stderr bytes.Buffer + readConfig(viperInstance, &stderr) + + assert.Equal(t, "Using config file: "+configFile+"\n", stderr.String()) + assert.Equal(t, "https://api.fga.example", viperInstance.GetString("api-url")) + }) + + t.Run("invalid config file", func(t *testing.T) { + t.Parallel() + + configFile := filepath.Join(t.TempDir(), ".fga.yaml") + // YAML doesn't allow tabs for indentation + content := "api-url: https://api.fga.example\nstore-id: 01H0H015178Y2V4CX10C2KGHF4\n\tapi-token: token\n" + require.NoError(t, os.WriteFile(configFile, []byte(content), 0o600)) + + viperInstance := viper.New() + viperInstance.SetConfigFile(configFile) + + var stderr bytes.Buffer + readConfig(viperInstance, &stderr) + + assert.Contains(t, stderr.String(), "Warning: ignoring config file "+configFile+": ") + assert.Empty(t, viperInstance.GetString("api-url")) + }) + + t.Run("no config file found", func(t *testing.T) { + t.Parallel() + + viperInstance := viper.New() + viperInstance.AddConfigPath(t.TempDir()) + viperInstance.SetConfigName(".fga") + + var stderr bytes.Buffer + readConfig(viperInstance, &stderr) + + assert.Empty(t, stderr.String()) + }) +} From a068d2a10ac2427cd48d78cee8c503c44fe5ba36 Mon Sep 17 00:00:00 2001 From: Srinivas Chippagiri Date: Thu, 5 Nov 2026 02:39:03 -0800 Subject: [PATCH 2/3] test: cover a missing file given with --config Signed-off-by: Srinivas Chippagiri --- cmd/root_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cmd/root_test.go b/cmd/root_test.go index 78ea3bab..0218a294 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -48,6 +48,20 @@ func TestReadConfig(t *testing.T) { assert.Empty(t, viperInstance.GetString("api-url")) }) + t.Run("config file given with --config doesn't exist", func(t *testing.T) { + t.Parallel() + + configFile := filepath.Join(t.TempDir(), "missing.fga.yaml") + + viperInstance := viper.New() + viperInstance.SetConfigFile(configFile) + + var stderr bytes.Buffer + readConfig(viperInstance, &stderr) + + assert.Contains(t, stderr.String(), "Warning: ignoring config file "+configFile+": ") + }) + t.Run("no config file found", func(t *testing.T) { t.Parallel() From f584c065408c661c7127a3c405c414b5779afb5a Mon Sep 17 00:00:00 2001 From: Srinivas Chippagiri Date: Sat, 7 Nov 2026 00:01:32 -0800 Subject: [PATCH 3/3] test: cover an invalid config file found in the search paths Signed-off-by: Srinivas Chippagiri --- cmd/root_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/cmd/root_test.go b/cmd/root_test.go index 0218a294..100f0950 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -48,6 +48,27 @@ func TestReadConfig(t *testing.T) { assert.Empty(t, viperInstance.GetString("api-url")) }) + t.Run("invalid config file found in the search paths", func(t *testing.T) { + t.Parallel() + + configDir := t.TempDir() + configFile := filepath.Join(configDir, ".fga.yaml") + // YAML doesn't allow tabs for indentation + content := "api-url: https://api.fga.example\nstore-id: 01H0H015178Y2V4CX10C2KGHF4\n\tapi-token: token\n" + require.NoError(t, os.WriteFile(configFile, []byte(content), 0o600)) + + viperInstance := viper.New() + viperInstance.AddConfigPath(configDir) + viperInstance.SetConfigName(".fga") + viperInstance.SetConfigType("yaml") + + var stderr bytes.Buffer + readConfig(viperInstance, &stderr) + + assert.Contains(t, stderr.String(), "Warning: ignoring config file "+configFile+": ") + assert.Empty(t, viperInstance.GetString("api-url")) + }) + t.Run("config file given with --config doesn't exist", func(t *testing.T) { t.Parallel()