From 18791a4e6e08de406e9c1e257cc4be2a85f29eea Mon Sep 17 00:00:00 2001 From: Dave Henderson Date: Mon, 30 Sep 2024 11:16:28 -0400 Subject: [PATCH] feat(config): Allow avoiding reading default config file (#2227) Signed-off-by: Dave Henderson --- .github/workflows/docs.yml | 2 ++ docs/content/usage.md | 2 +- env/env.go | 10 +++++++ internal/cmd/config.go | 26 ++++++++++++----- internal/cmd/config_test.go | 28 ++++++++++++++++--- internal/datafs/getenv.go | 20 ++++++++----- internal/tests/integration/config_test.go | 14 ++++++++++ .../integration/datasources_consul_test.go | 3 +- 8 files changed, 85 insertions(+), 20 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 89d376b76..76b0113a0 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -52,5 +52,7 @@ jobs: --exclude="https://docs\.aws.*" \ --exclude="https://linux.die\.net.*" \ --exclude="https://jqplay\.org.*" \ + --exclude="https://json\.org.*" \ + --exclude="https://goessner\.net.*" kill %1 diff --git a/docs/content/usage.md b/docs/content/usage.md index df59d3452..280778feb 100644 --- a/docs/content/usage.md +++ b/docs/content/usage.md @@ -21,7 +21,7 @@ Hello, hairyhenderson ### `--config` -Specify the path to a [gomplate config file](../config). The default is `.gomplate.yaml`. Can also be set with the `GOMPLATE_CONFIG` environment variable. +Specify the path to a [gomplate config file](../config). The default is `.gomplate.yaml`. Can also be set with the `GOMPLATE_CONFIG` environment variable. Setting `--config` or `GOMPLATE_CONFIG` to an empty string (`--config=""` or `export GOMPLATE_CONFIG=""`) will disable the use of a config file, skipping the default `.gomplate.yaml` file. For example: diff --git a/env/env.go b/env/env.go index 598361bfe..16e121fca 100644 --- a/env/env.go +++ b/env/env.go @@ -20,3 +20,13 @@ func ExpandEnv(s string) string { fsys := datafs.WrapWdFS(osfs.NewFS()) return datafs.ExpandEnvFsys(fsys, s) } + +// LookupEnv - retrieves the value of the environment variable named by the key. +// If the variable is unset, but the same variable ending in `_FILE` is set, the +// referenced file will be read into the value. If the key is not set, the +// second return value will be false. +// Otherwise the provided default (or an emptry string) is returned. +func LookupEnv(key string) (string, bool) { + fsys := datafs.WrapWdFS(osfs.NewFS()) + return datafs.LookupEnvFsys(fsys, key) +} diff --git a/internal/cmd/config.go b/internal/cmd/config.go index 683c78dd9..11886bbaa 100644 --- a/internal/cmd/config.go +++ b/internal/cmd/config.go @@ -57,22 +57,34 @@ func loadConfig(ctx context.Context, cmd *cobra.Command, args []string) (*gompla return cfg, nil } -func pickConfigFile(cmd *cobra.Command) (cfgFile string, required bool) { +func pickConfigFile(cmd *cobra.Command) (cfgFile string, required, skip bool) { cfgFile = defaultConfigFile - if c := env.Getenv("GOMPLATE_CONFIG"); c != "" { + if c, found := env.LookupEnv("GOMPLATE_CONFIG"); found { cfgFile = c - required = true + if cfgFile == "" { + skip = true + } else { + required = true + } } - if cmd.Flags().Changed("config") && cmd.Flag("config").Value.String() != "" { + if cmd.Flags().Changed("config") { // Use config file from the flag if specified cfgFile = cmd.Flag("config").Value.String() - required = true + if cfgFile == "" { + skip = true + } else { + required = true + } } - return cfgFile, required + return cfgFile, required, skip } func readConfigFile(ctx context.Context, cmd *cobra.Command) (*gomplate.Config, error) { - cfgFile, configRequired := pickConfigFile(cmd) + cfgFile, configRequired, skip := pickConfigFile(cmd) + if skip { + // --config was specified with an empty value + return nil, nil + } // we only support loading configs from the local filesystem for now fsys, err := datafs.FSysForPath(ctx, cfgFile) diff --git a/internal/cmd/config_test.go b/internal/cmd/config_test.go index ce59f0272..760766fdd 100644 --- a/internal/cmd/config_test.go +++ b/internal/cmd/config_test.go @@ -188,29 +188,49 @@ func TestPickConfigFile(t *testing.T) { cmd.Flags().String("config", defaultConfigFile, "foo") t.Run("default", func(t *testing.T) { - cf, req := pickConfigFile(cmd) + cf, req, skip := pickConfigFile(cmd) assert.False(t, req) + assert.False(t, skip) assert.Equal(t, defaultConfigFile, cf) }) t.Run("GOMPLATE_CONFIG env var", func(t *testing.T) { t.Setenv("GOMPLATE_CONFIG", "foo.yaml") - cf, req := pickConfigFile(cmd) + cf, req, skip := pickConfigFile(cmd) assert.True(t, req) + assert.False(t, skip) assert.Equal(t, "foo.yaml", cf) }) t.Run("--config flag", func(t *testing.T) { cmd.ParseFlags([]string{"--config", "config.file"}) - cf, req := pickConfigFile(cmd) + cf, req, skip := pickConfigFile(cmd) assert.True(t, req) + assert.False(t, skip) assert.Equal(t, "config.file", cf) t.Setenv("GOMPLATE_CONFIG", "ignored.yaml") - cf, req = pickConfigFile(cmd) + cf, req, skip = pickConfigFile(cmd) assert.True(t, req) + assert.False(t, skip) assert.Equal(t, "config.file", cf) }) + + t.Run("--config flag with empty value should skip reading", func(t *testing.T) { + cmd.ParseFlags([]string{"--config", ""}) + cf, req, skip := pickConfigFile(cmd) + assert.False(t, req) + assert.True(t, skip) + assert.Equal(t, "", cf) + }) + + t.Run("GOMPLATE_CONFIG env var with empty value should skip reading", func(t *testing.T) { + t.Setenv("GOMPLATE_CONFIG", "") + cf, req, skip := pickConfigFile(cmd) + assert.False(t, req) + assert.True(t, skip) + assert.Equal(t, "", cf) + }) } func TestApplyEnvVars(t *testing.T) { diff --git a/internal/datafs/getenv.go b/internal/datafs/getenv.go index 5f9761d45..6af035080 100644 --- a/internal/datafs/getenv.go +++ b/internal/datafs/getenv.go @@ -15,7 +15,7 @@ func ExpandEnvFsys(fsys fs.FS, s string) string { // GetenvFsys - a convenience function intended for internal use only! func GetenvFsys(fsys fs.FS, key string, def ...string) string { - val := getenvFile(fsys, key) + val, _ := getenvFile(fsys, key) if val == "" && len(def) > 0 { return def[0] } @@ -23,22 +23,28 @@ func GetenvFsys(fsys fs.FS, key string, def ...string) string { return val } -func getenvFile(fsys fs.FS, key string) string { - val := os.Getenv(key) +// LookupEnvFsys - a convenience function intended for internal use only! +func LookupEnvFsys(fsys fs.FS, key string) (string, bool) { + return getenvFile(fsys, key) +} + +func getenvFile(fsys fs.FS, key string) (string, bool) { + val, found := os.LookupEnv(key) if val != "" { - return val + return val, true } p := os.Getenv(key + "_FILE") if p != "" { val, err := readFile(fsys, p) if err != nil { - return "" + return "", false } - return strings.TrimSpace(val) + + return strings.TrimSpace(val), true } - return "" + return "", found } func readFile(fsys fs.FS, p string) (string, error) { diff --git a/internal/tests/integration/config_test.go b/internal/tests/integration/config_test.go index 1ae19e003..80c9d9043 100644 --- a/internal/tests/integration/config_test.go +++ b/internal/tests/integration/config_test.go @@ -153,6 +153,20 @@ func TestConfig_EnvConfigFile(t *testing.T) { assertSuccess(t, o, e, err, "yet another alternate config") } +func TestConfig_SkipConfigFile(t *testing.T) { + tmpDir := setupConfigTest(t) + + // first set a poisoned default config to prove that it's not being read + writeFile(t, tmpDir, ".gomplate.yaml", `badyaml`) + + o, e, err := cmd(t, "--config", "", "--in", "foo").withDir(tmpDir.Path()).run() + assertSuccess(t, o, e, err, "foo") + + o, e, err = cmd(t, "--in", "foo").withDir(tmpDir.Path()). + withEnv("GOMPLATE_CONFIG", "").run() + assertSuccess(t, o, e, err, "foo") +} + func TestConfig_ConfigOverridesEnvDelim(t *testing.T) { if isWindows { t.Skip() diff --git a/internal/tests/integration/datasources_consul_test.go b/internal/tests/integration/datasources_consul_test.go index f04d50209..741f91df8 100644 --- a/internal/tests/integration/datasources_consul_test.go +++ b/internal/tests/integration/datasources_consul_test.go @@ -42,7 +42,8 @@ func setupDatasourcesConsulTest(t *testing.T) (string, *vaultClient) { "serf_lan": `+strconv.Itoa(serfLanPort)+`, "serf_wan": -1, "dns": -1, - "grpc": -1 + "grpc": -1, + "grpc_tls": -1 }, "connect": { "enabled": false } }`,