From 8448f4cc3aebd1b481fc695c2aa0d02e18689a20 Mon Sep 17 00:00:00 2001 From: garethgeorge Date: Sat, 4 May 2024 17:09:31 -0700 Subject: [PATCH] feat: support env variable substitution e.g. FOO=${MY_FOO_VAR} --- go.mod | 4 +- go.sum | 4 + internal/orchestrator/repo/env.go | 18 ++++ internal/orchestrator/repo/repo.go | 4 +- internal/orchestrator/repo/repo_test.go | 50 +++++++++++ webui/src/views/AddRepoModal.tsx | 108 ++++++++++++------------ 6 files changed, 132 insertions(+), 56 deletions(-) create mode 100644 internal/orchestrator/repo/env.go diff --git a/go.mod b/go.mod index 65c92472..2821dec1 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/stretchr/testify v1.8.4 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // indirect ) diff --git a/go.sum b/go.sum index ecdadfe5..b8468dc3 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,12 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6 h1:DTJM0R8LECCgFeUwApvcEJHz85HLagW8uRENYxHh1ww= diff --git a/internal/orchestrator/repo/env.go b/internal/orchestrator/repo/env.go new file mode 100644 index 00000000..25638e84 --- /dev/null +++ b/internal/orchestrator/repo/env.go @@ -0,0 +1,18 @@ +package repo + +import ( + "os" + "regexp" +) + +var ( + envVarSubstRegex = regexp.MustCompile(`\${[^}]*}`) +) + +// ExpandEnv expands environment variables of the form ${VAR} in a string. +func ExpandEnv(s string) string { + return envVarSubstRegex.ReplaceAllStringFunc(s, func(match string) string { + e, _ := os.LookupEnv(match[2 : len(match)-1]) + return e + }) +} diff --git a/internal/orchestrator/repo/repo.go b/internal/orchestrator/repo/repo.go index 0ef92d40..6467fdaa 100644 --- a/internal/orchestrator/repo/repo.go +++ b/internal/orchestrator/repo/repo.go @@ -59,7 +59,9 @@ func NewRepoOrchestrator(config *v1.Config, repoConfig *v1.Repo, resticPath stri } if env := repoConfig.GetEnv(); len(env) != 0 { - opts = append(opts, restic.WithEnv(repoConfig.GetEnv()...)) + for _, e := range env { + opts = append(opts, restic.WithEnv(ExpandEnv(e))) + } } repo := restic.NewRepo(resticPath, repoConfig.GetUri(), opts...) diff --git a/internal/orchestrator/repo/repo_test.go b/internal/orchestrator/repo/repo_test.go index 2e47fc4f..ae49c8ba 100644 --- a/internal/orchestrator/repo/repo_test.go +++ b/internal/orchestrator/repo/repo_test.go @@ -2,7 +2,9 @@ package repo import ( "context" + "os" "slices" + "strings" "testing" v1 "github.com/garethgeorge/backrest/gen/go/v1" @@ -140,3 +142,51 @@ func TestSnapshotParenting(t *testing.T) { t.Errorf("expected 8 snapshots, got %d", len(snapshots)) } } + +func TestEnvVarPropagation(t *testing.T) { + t.Parallel() + + repo := t.TempDir() + testData := test.CreateTestData(t) + + // create a new repo with cache disabled for testing + r := &v1.Repo{ + Id: "test", + Uri: repo, + Password: "test", + Flags: []string{"--no-cache"}, + Env: []string{"RESTIC_PASSWORD=${MY_FOO}"}, + } + + plan := &v1.Plan{ + Id: "test", + Repo: "test", + Paths: []string{testData}, + } + + orchestrator, err := NewRepoOrchestrator(configForTest, r, helpers.ResticBinary(t)) + if err != nil { + t.Fatalf("failed to create repo orchestrator: %v", err) + } + + _, err = orchestrator.Backup(context.Background(), plan, nil) + if err == nil || !strings.Contains(err.Error(), "an empty password is not a password") { + t.Fatalf("expected error about RESTIC_PASSWORD, got: %v", err) + } + + // set the env var + os.Setenv("MY_FOO", "bar") + orchestrator, err = NewRepoOrchestrator(configForTest, r, helpers.ResticBinary(t)) + if err != nil { + t.Fatalf("failed to create repo orchestrator: %v", err) + } + + summary, err := orchestrator.Backup(context.Background(), plan, nil) + if err != nil { + t.Fatalf("backup error: %v", err) + } + + if summary.SnapshotId == "" { + t.Fatal("expected snapshot id") + } +} diff --git a/webui/src/views/AddRepoModal.tsx b/webui/src/views/AddRepoModal.tsx index e4bae662..fed52867 100644 --- a/webui/src/views/AddRepoModal.tsx +++ b/webui/src/views/AddRepoModal.tsx @@ -282,63 +282,65 @@ export const AddRepoModal = ({ {/* Repo.env */} - - { - return await envVarSetValidator(form, envVars); + + + { + return await envVarSetValidator(form, envVars); + }, }, - }, - ]} - > - {(fields, { add, remove }, { errors }) => ( - <> - {fields.map((field, index) => ( - - - form.validateFields()} - style={{ width: "90%" }} + ]} + > + {(fields, { add, remove }, { errors }) => ( + <> + {fields.map((field, index) => ( + + + form.validateFields()} + style={{ width: "90%" }} + /> + + remove(index)} + style={{ paddingLeft: "5px" }} /> - remove(index)} - style={{ paddingLeft: "5px" }} - /> + ))} + + + - ))} - - - - - - )} - - + + )} + + + {/* Repo.flags */}