Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(config): Allow avoiding reading default config file #2227

Merged
merged 1 commit into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion docs/content/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
10 changes: 10 additions & 0 deletions env/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
26 changes: 19 additions & 7 deletions internal/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
28 changes: 24 additions & 4 deletions internal/cmd/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
20 changes: 13 additions & 7 deletions internal/datafs/getenv.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,36 @@ 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]
}

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) {
Expand Down
14 changes: 14 additions & 0 deletions internal/tests/integration/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
3 changes: 2 additions & 1 deletion internal/tests/integration/datasources_consul_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}`,
Expand Down