From 23d3254e7492daf53d5406d4a98c461c3d200342 Mon Sep 17 00:00:00 2001 From: Thomas Guettler Date: Sat, 25 Mar 2023 23:06:54 +0100 Subject: [PATCH 1/2] add --strict. --- cmd/envsubst/main.go | 10 +++++-- eval.go | 14 +++++++--- eval_test.go | 62 +++++++++++++++++++++++++++++++++++++++++--- readme.md | 4 +++ template.go | 12 ++++++--- 5 files changed, 91 insertions(+), 11 deletions(-) diff --git a/cmd/envsubst/main.go b/cmd/envsubst/main.go index 1aaf4fd..57444f2 100644 --- a/cmd/envsubst/main.go +++ b/cmd/envsubst/main.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "flag" "fmt" "log" "os" @@ -9,12 +10,14 @@ import ( "github.com/drone/envsubst/v2" ) +var flagStrict bool = false + func main() { + flag.Parse() stdin := bufio.NewScanner(os.Stdin) stdout := bufio.NewWriter(os.Stdout) - for stdin.Scan() { - line, err := envsubst.EvalEnv(stdin.Text()) + line, err := envsubst.EvalEnv(stdin.Text(), flagStrict) if err != nil { log.Fatalf("Error while envsubst: %v", err) } @@ -26,3 +29,6 @@ func main() { } } +func init() { + flag.BoolVar(&flagStrict, "strict", false, "fail if variable is undefined.") +} \ No newline at end of file diff --git a/eval.go b/eval.go index 375ca4c..704c764 100644 --- a/eval.go +++ b/eval.go @@ -3,7 +3,7 @@ package envsubst import "os" // Eval replaces ${var} in the string based on the mapping function. -func Eval(s string, mapping func(string) string) (string, error) { +func Eval(s string, mapping func(string) (string, bool)) (string, error) { t, err := Parse(s) if err != nil { return s, err @@ -14,6 +14,14 @@ func Eval(s string, mapping func(string) string) (string, error) { // EvalEnv replaces ${var} in the string according to the values of the // current environment variables. References to undefined variables are // replaced by the empty string. -func EvalEnv(s string) (string, error) { - return Eval(s, os.Getenv) +func EvalEnv(s string, strict bool) (string, error) { + mapping := Getenv + if strict{ + mapping = os.LookupEnv + } + return Eval(s, mapping) } + +func Getenv(s string) (string, bool) { + return os.Getenv(s), true +} \ No newline at end of file diff --git a/eval_test.go b/eval_test.go index f7867e5..2b80941 100644 --- a/eval_test.go +++ b/eval_test.go @@ -1,6 +1,9 @@ package envsubst -import "testing" +import ( + "errors" + "testing" +) // test cases sourced from tldp.org // http://www.tldp.org/LDP/abs/html/parameter-substitution.html @@ -210,8 +213,8 @@ func TestExpand(t *testing.T) { for _, expr := range expressions { t.Run(expr.input, func(t *testing.T) { t.Logf(expr.input) - output, err := Eval(expr.input, func(s string) string { - return expr.params[s] + output, err := Eval(expr.input, func(s string) (string, bool) { + return expr.params[s], true }) if err != nil { t.Errorf("Want %q expanded but got error %q", expr.input, err) @@ -226,3 +229,56 @@ func TestExpand(t *testing.T) { }) } } + +func TestExpandStrict(t *testing.T) { + var expressions = []struct { + params map[string]string + input string + output string + wantErr error + }{ + // text-only + { + params: map[string]string{}, + input: "abcdEFGH28ij", + output: "abcdEFGH28ij", + wantErr: nil, + }, + // existing + { + params: map[string]string{"foo": "bar"}, + input: "${foo}", + output: "bar", + wantErr: nil, + }, + // missing + { + params: map[string]string{}, + input: "${missing}", + output: "", + wantErr: errVarNotSet, + }, + } + + for _, expr := range expressions { + t.Run(expr.input, func(t *testing.T) { + t.Logf(expr.input) + output, err := Eval(expr.input, func(s string) (string, bool) { + v, exists := expr.params[s] + return v, exists + }) + if expr.wantErr == nil && err != nil { + t.Errorf("Want %q expanded but got error %q", expr.input, err) + } + if expr.wantErr != nil && !errors.Is(err, expr.wantErr) { + t.Errorf("Want error %q but got error %q", expr.wantErr, err) + } + if output != expr.output { + t.Errorf("Want %q expanded to %q, got %q", + expr.input, + expr.output, + output) + } + }) + } +} diff --git a/readme.md b/readme.md index ecc8482..0acba2d 100644 --- a/readme.md +++ b/readme.md @@ -34,6 +34,10 @@ Includes support for bash string replacement functions. For a deeper reference, see [bash-hackers](https://wiki.bash-hackers.org/syntax/pe#case_modification) or [gnu pattern matching](https://www.gnu.org/software/bash/manual/html_node/Pattern-Matching.html). +## Strict mode + +Use `--strict` if you want `envsubst` to fail, if an undefined variable gets accessed. + ## Unsupported Functions * `${var-default}` diff --git a/template.go b/template.go index 45d9abf..c9173a7 100644 --- a/template.go +++ b/template.go @@ -2,6 +2,7 @@ package envsubst import ( "bytes" + "fmt" "io" "io/ioutil" @@ -16,7 +17,7 @@ type state struct { node parse.Node // current node // maps variable names to values - mapper func(string) string + mapper func(string) (value string, exists bool) } // Template is the representation of a parsed shell format string. @@ -46,7 +47,7 @@ func ParseFile(path string) (*Template, error) { } // Execute applies a parsed template to the specified data mapping. -func (t *Template) Execute(mapping func(string) string) (str string, err error) { +func (t *Template) Execute(mapping func(string) (string, bool)) (str string, err error) { b := new(bytes.Buffer) s := new(state) s.node = t.tree.Root @@ -87,6 +88,8 @@ func (t *Template) evalList(s *state, node *parse.ListNode) (err error) { return nil } +var errVarNotSet = fmt.Errorf("variable not set (strict mode)") + func (t *Template) evalFunc(s *state, node *parse.FuncNode) error { var w = s.writer var buf bytes.Buffer @@ -106,8 +109,11 @@ func (t *Template) evalFunc(s *state, node *parse.FuncNode) error { s.writer = w s.node = node - v := s.mapper(node.Param) + v, exists := s.mapper(node.Param) + if node.Name == "" && !exists { + return fmt.Errorf("%w: %q", errVarNotSet, node.Param) + } fn := lookupFunc(node.Name, len(args)) _, err := io.WriteString(s.writer, fn(v, args...)) From 144217cfe5abdd38d29e3528479f25ac383ae7b9 Mon Sep 17 00:00:00 2001 From: Thomas Guettler Date: Mon, 27 Mar 2023 08:36:34 +0200 Subject: [PATCH 2/2] upgrade to go 1.19. --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 2b06213..66029f5 100644 --- a/.drone.yml +++ b/.drone.yml @@ -3,6 +3,6 @@ name: default steps: - name: build - image: golang:1.11 + image: golang:1.19 commands: - go test -v ./...