Use proper path expansion

This commit is contained in:
Frederik Ring 2024-02-21 19:34:57 +01:00
parent 4b3ca2ebb0
commit 060a6daa7a
10 changed files with 142 additions and 6 deletions

21
.github/workflows/unit.yml vendored Normal file
View File

@ -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 ./...

View File

@ -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
}

View File

@ -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)
}
})
}
}

3
cmd/backup/testdata/braces.env vendored Normal file
View File

@ -0,0 +1,3 @@
FOO=${bar:-qux}
BAR=xxx
BAZ=$NOPE

2
cmd/backup/testdata/default.env vendored Normal file
View File

@ -0,0 +1,2 @@
FOO=bar
BAZ=qux

4
cmd/backup/testdata/expansion.env vendored Normal file
View File

@ -0,0 +1,4 @@
BAR=xxx
FOO=${BAR}
BAZ=$BAR
QUX=${QUX}

1
go.mod
View File

@ -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 (

2
go.sum
View File

@ -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=

View File

@ -1,2 +1,3 @@
NAME="other"
BACKUP_CRON_EXPRESSION="*/1 * * * *"
BACKUP_FILENAME="override-$NAME.tar.gz"

View File

@ -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."