From 060a6daa7aad5ceec7ee71f68d10b0db490bc8fb Mon Sep 17 00:00:00 2001 From: Frederik Ring Date: Wed, 21 Feb 2024 19:34:57 +0100 Subject: [PATCH] Use proper path expansion --- .github/workflows/unit.yml | 21 +++++++++ cmd/backup/config_provider.go | 44 ++++++++++++++++--- cmd/backup/config_provider_test.go | 68 ++++++++++++++++++++++++++++++ cmd/backup/testdata/braces.env | 3 ++ cmd/backup/testdata/default.env | 2 + cmd/backup/testdata/expansion.env | 4 ++ go.mod | 1 + go.sum | 2 + test/confd/02backup.env | 1 + test/confd/run.sh | 2 +- 10 files changed, 142 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/unit.yml create mode 100644 cmd/backup/config_provider_test.go create mode 100644 cmd/backup/testdata/braces.env create mode 100644 cmd/backup/testdata/default.env create mode 100644 cmd/backup/testdata/expansion.env diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml new file mode 100644 index 0000000..04b35ec --- /dev/null +++ b/.github/workflows/unit.yml @@ -0,0 +1,21 @@ +name: Run Unit Tests + +on: + push: + branches: + - main + pull_request: + +jobs: + build: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: '1.22.x' + - name: Install dependencies + run: go mod download + - name: Test with the Go CLI + run: go test -v ./... diff --git a/cmd/backup/config_provider.go b/cmd/backup/config_provider.go index 4225d70..fb42e0d 100644 --- a/cmd/backup/config_provider.go +++ b/cmd/backup/config_provider.go @@ -4,6 +4,7 @@ package main import ( + "bufio" "fmt" "os" "path/filepath" @@ -11,6 +12,7 @@ import ( "github.com/joho/godotenv" "github.com/offen/docker-volume-backup/internal/errwrap" "github.com/offen/envconfig" + shell "mvdan.cc/sh/v3/shell" ) type configStrategy string @@ -99,11 +101,7 @@ func loadConfigsFromEnvFiles(directory string) ([]*Config, error) { continue } p := filepath.Join(directory, item.Name()) - f, err := os.ReadFile(p) - if err != nil { - return nil, errwrap.Wrap(err, fmt.Sprintf("error reading %s", item.Name())) - } - envFile, err := godotenv.Unmarshal(os.ExpandEnv(string(f))) + envFile, err := source(p) if err != nil { return nil, errwrap.Wrap(err, fmt.Sprintf("error reading config file %s", p)) } @@ -125,3 +123,39 @@ func loadConfigsFromEnvFiles(directory string) ([]*Config, error) { return configs, nil } + +// source tries to mimic the pre v2.37.0 behavior of calling +// `set +a; source $path; set -a` and returns the env vars as a map +func source(path string) (map[string]string, error) { + f, err := os.Open(path) + if err != nil { + return nil, errwrap.Wrap(err, fmt.Sprintf("error opening %s", path)) + } + + result := map[string]string{} + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + withExpansion, err := shell.Expand(line, nil) + if err != nil { + return nil, errwrap.Wrap(err, "error expanding env") + } + m, err := godotenv.Unmarshal(withExpansion) + if err != nil { + return nil, errwrap.Wrap(err, fmt.Sprintf("error sourcing %s", path)) + } + for key, value := range m { + currentValue, currentOk := os.LookupEnv(key) + defer func() { + if currentOk { + os.Setenv(key, currentValue) + return + } + os.Unsetenv(key) + }() + result[key] = value + os.Setenv(key, value) + } + } + return result, nil +} diff --git a/cmd/backup/config_provider_test.go b/cmd/backup/config_provider_test.go new file mode 100644 index 0000000..234de67 --- /dev/null +++ b/cmd/backup/config_provider_test.go @@ -0,0 +1,68 @@ +package main + +import ( + "os" + "reflect" + "testing" +) + +func TestSource(t *testing.T) { + tests := []struct { + name string + input string + expectError bool + expectedOutput map[string]string + }{ + { + "default", + "testdata/default.env", + false, + map[string]string{ + "FOO": "bar", + "BAZ": "qux", + }, + }, + { + "not found", + "testdata/nope.env", + true, + nil, + }, + { + "braces", + "testdata/braces.env", + false, + map[string]string{ + "FOO": "qux", + "BAR": "xxx", + "BAZ": "", + }, + }, + { + "expansion", + "testdata/expansion.env", + false, + map[string]string{ + "BAR": "xxx", + "FOO": "xxx", + "BAZ": "xxx", + "QUX": "yyy", + }, + }, + } + + os.Setenv("QUX", "yyy") + defer os.Unsetenv("QUX") + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result, err := source(test.input) + if (err != nil) != test.expectError { + t.Errorf("Unexpected error value %v", err) + } + if !reflect.DeepEqual(test.expectedOutput, result) { + t.Errorf("Expected %v, got %v", test.expectedOutput, result) + } + }) + } +} diff --git a/cmd/backup/testdata/braces.env b/cmd/backup/testdata/braces.env new file mode 100644 index 0000000..11b5bdc --- /dev/null +++ b/cmd/backup/testdata/braces.env @@ -0,0 +1,3 @@ +FOO=${bar:-qux} +BAR=xxx +BAZ=$NOPE diff --git a/cmd/backup/testdata/default.env b/cmd/backup/testdata/default.env new file mode 100644 index 0000000..214ea78 --- /dev/null +++ b/cmd/backup/testdata/default.env @@ -0,0 +1,2 @@ +FOO=bar +BAZ=qux diff --git a/cmd/backup/testdata/expansion.env b/cmd/backup/testdata/expansion.env new file mode 100644 index 0000000..cba0e6d --- /dev/null +++ b/cmd/backup/testdata/expansion.env @@ -0,0 +1,4 @@ +BAR=xxx +FOO=${BAR} +BAZ=$BAR +QUX=${QUX} diff --git a/go.mod b/go.mod index cedbb00..e68b052 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,7 @@ require ( golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.31.0 // indirect + mvdan.cc/sh/v3 v3.8.0 // indirect ) require ( diff --git a/go.sum b/go.sum index d4d4bac..f9cbeeb 100644 --- a/go.sum +++ b/go.sum @@ -1259,6 +1259,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +mvdan.cc/sh/v3 v3.8.0 h1:ZxuJipLZwr/HLbASonmXtcvvC9HXY9d2lXZHnKGjFc8= +mvdan.cc/sh/v3 v3.8.0/go.mod h1:w04623xkgBVo7/IUK89E0g8hBykgEpN0vgOj3RJr6MY= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/test/confd/02backup.env b/test/confd/02backup.env index 4278acb..09bab41 100644 --- a/test/confd/02backup.env +++ b/test/confd/02backup.env @@ -1,2 +1,3 @@ NAME="other" BACKUP_CRON_EXPRESSION="*/1 * * * *" +BACKUP_FILENAME="override-$NAME.tar.gz" diff --git a/test/confd/run.sh b/test/confd/run.sh index f81407a..b722542 100755 --- a/test/confd/run.sh +++ b/test/confd/run.sh @@ -20,7 +20,7 @@ if [ ! -f "$LOCAL_DIR/conf.tar.gz" ]; then fi pass "Config from file was used." -if [ ! -f "$LOCAL_DIR/other.tar.gz" ]; then +if [ ! -f "$LOCAL_DIR/override-other.tar.gz" ]; then fail "Run on same schedule did not succeed." fi pass "Run on same schedule succeeded."