Skip to content

Commit

Permalink
config: support expand env in yaml
Browse files Browse the repository at this point in the history
  • Loading branch information
andydunstall committed May 1, 2024
1 parent 5ebac35 commit 7b83ae9
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 6 deletions.
17 changes: 16 additions & 1 deletion cli/agent/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,27 @@ Examples:
YAML config file path.`,
)

var configExpandEnv bool
cmd.Flags().BoolVar(
&configExpandEnv,
"config.expand-env",
false,
`
Whether to expand environment variables in the config file.
This will replaces references to ${VAR} or $VAR with the corresponding
environment variable. The replacement is case-sensitive.
References to undefined variables will be replaced with an empty string. A
default value can be given using form ${VAR:default}.`,
)

// Register flags and set default values.
conf.RegisterFlags(cmd.Flags())

cmd.Run = func(cmd *cobra.Command, args []string) {
if configPath != "" {
if err := picoconfig.Load(configPath, &conf); err != nil {
if err := picoconfig.Load(configPath, &conf, configExpandEnv); err != nil {
fmt.Printf("load config: %s\n", err.Error())
os.Exit(1)
}
Expand Down
17 changes: 16 additions & 1 deletion cli/server/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,27 @@ Examples:
YAML config file path.`,
)

var configExpandEnv bool
cmd.Flags().BoolVar(
&configExpandEnv,
"config.expand-env",
false,
`
Whether to expand environment variables in the config file.
This will replaces references to ${VAR} or $VAR with the corresponding
environment variable. The replacement is case-sensitive.
References to undefined variables will be replaced with an empty string. A
default value can be given using form ${VAR:default}.`,
)

// Register flags and set default values.
conf.RegisterFlags(cmd.Flags())

cmd.Run = func(cmd *cobra.Command, args []string) {
if configPath != "" {
if err := picoconfig.Load(configPath, &conf); err != nil {
if err := picoconfig.Load(configPath, &conf, configExpandEnv); err != nil {
fmt.Printf("load config: %s\n", err.Error())
os.Exit(1)
}
Expand Down
29 changes: 28 additions & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,24 @@ import (
"bytes"
"fmt"
"os"
"strings"

"gopkg.in/yaml.v3"
)

func Load(path string, conf interface{}) error {
// Load load the YAML configuration from the file at the given path.
//
// This will expand environment VARiables if expand is true.
func Load(path string, conf interface{}, expand bool) error {
buf, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read file: %s: %w", path, err)
}

if expand {
buf = []byte(expandEnv(string(buf)))
}

dec := yaml.NewDecoder(bytes.NewReader(buf))
dec.KnownFields(true)

Expand All @@ -23,3 +31,22 @@ func Load(path string, conf interface{}) error {

return nil
}

// expandEnv replaces ${VAR} or $VAR in the given string with the corresponding
// environment variable. The replacement is case-sensitive.
//
// References to undefined variables are replaced with an empty string. A
// default value can be given using form ${VAR:default}
func expandEnv(s string) string {
return os.Expand(s, func(v string) string {
elems := strings.SplitN(v, ":", 2)
key := elems[0]

env := os.Getenv(key)
if env == "" && len(elems) == 2 {
// If no env exists use the default.
return elems[1]
}
return env
})
}
27 changes: 24 additions & 3 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,28 @@ sub:
assert.NoError(t, err)

var conf fakeConfig
assert.NoError(t, Load(f.Name(), &conf))
assert.NoError(t, Load(f.Name(), &conf, false))

assert.Equal(t, "val1", conf.Foo)
assert.Equal(t, "val2", conf.Bar)
assert.Equal(t, 5, conf.Sub.Car)
})

t.Run("expand env", func(t *testing.T) {
f, err := os.CreateTemp("", "pico")
assert.NoError(t, err)

_ = os.Setenv("PICO_VAL1", "val1")
_ = os.Setenv("PICO_VAL2", "val2")

_, err = f.WriteString(`foo: $PICO_VAL1
bar: ${PICO_VAL2}
sub:
car: ${PICO_VAL3:5}`)
assert.NoError(t, err)

var conf fakeConfig
assert.NoError(t, Load(f.Name(), &conf, true))

assert.Equal(t, "val1", conf.Foo)
assert.Equal(t, "val2", conf.Bar)
Expand All @@ -44,11 +65,11 @@ sub:
assert.NoError(t, err)

var conf fakeConfig
assert.Error(t, Load(f.Name(), &conf))
assert.Error(t, Load(f.Name(), &conf, false))
})

t.Run("not found", func(t *testing.T) {
var conf fakeConfig
assert.Error(t, Load("notfound", &conf))
assert.Error(t, Load("notfound", &conf, false))
})
}

0 comments on commit 7b83ae9

Please sign in to comment.