Skip to content

Commit c0b6634

Browse files
committed
envsubst: Add strict mode
Incorporate drone/envsubst#34 Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
1 parent e28dea2 commit c0b6634

File tree

3 files changed

+81
-11
lines changed

3 files changed

+81
-11
lines changed

envsubst/eval.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ package envsubst
2323
import "os"
2424

2525
// Eval replaces ${var} in the string based on the mapping function.
26-
func Eval(s string, mapping func(string) string) (string, error) {
26+
func Eval(s string, mapping func(string) (string, bool)) (string, error) {
2727
t, err := Parse(s)
2828
if err != nil {
2929
return s, err
@@ -34,6 +34,14 @@ func Eval(s string, mapping func(string) string) (string, error) {
3434
// EvalEnv replaces ${var} in the string according to the values of the
3535
// current environment variables. References to undefined variables are
3636
// replaced by the empty string.
37-
func EvalEnv(s string) (string, error) {
38-
return Eval(s, os.Getenv)
37+
func EvalEnv(s string, strict bool) (string, error) {
38+
mapping := Getenv
39+
if strict {
40+
mapping = os.LookupEnv
41+
}
42+
return Eval(s, mapping)
43+
}
44+
45+
func Getenv(s string) (string, bool) {
46+
return os.Getenv(s), true
3947
}

envsubst/eval_test.go

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ limitations under the License.
2020

2121
package envsubst
2222

23-
import "testing"
23+
import (
24+
"errors"
25+
"testing"
26+
)
2427

2528
// test cases sourced from tldp.org
2629
// http://www.tldp.org/LDP/abs/html/parameter-substitution.html
@@ -230,8 +233,8 @@ func TestExpand(t *testing.T) {
230233
for _, expr := range expressions {
231234
t.Run(expr.input, func(t *testing.T) {
232235
t.Logf(expr.input)
233-
output, err := Eval(expr.input, func(s string) string {
234-
return expr.params[s]
236+
output, err := Eval(expr.input, func(s string) (string, bool) {
237+
return expr.params[s], true
235238
})
236239
if err != nil {
237240
t.Errorf("Want %q expanded but got error %q", expr.input, err)
@@ -246,3 +249,56 @@ func TestExpand(t *testing.T) {
246249
})
247250
}
248251
}
252+
253+
func TestExpandStrict(t *testing.T) {
254+
var expressions = []struct {
255+
params map[string]string
256+
input string
257+
output string
258+
wantErr error
259+
}{
260+
// text-only
261+
{
262+
params: map[string]string{},
263+
input: "abcdEFGH28ij",
264+
output: "abcdEFGH28ij",
265+
wantErr: nil,
266+
},
267+
// existing
268+
{
269+
params: map[string]string{"foo": "bar"},
270+
input: "${foo}",
271+
output: "bar",
272+
wantErr: nil,
273+
},
274+
// missing
275+
{
276+
params: map[string]string{},
277+
input: "${missing}",
278+
output: "",
279+
wantErr: errVarNotSet,
280+
},
281+
}
282+
283+
for _, expr := range expressions {
284+
t.Run(expr.input, func(t *testing.T) {
285+
t.Logf(expr.input)
286+
output, err := Eval(expr.input, func(s string) (string, bool) {
287+
v, exists := expr.params[s]
288+
return v, exists
289+
})
290+
if expr.wantErr == nil && err != nil {
291+
t.Errorf("Want %q expanded but got error %q", expr.input, err)
292+
}
293+
if expr.wantErr != nil && !errors.Is(err, expr.wantErr) {
294+
t.Errorf("Want error %q but got error %q", expr.wantErr, err)
295+
}
296+
if output != expr.output {
297+
t.Errorf("Want %q expanded to %q, got %q",
298+
expr.input,
299+
expr.output,
300+
output)
301+
}
302+
})
303+
}
304+
}

envsubst/template.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ package envsubst
2222

2323
import (
2424
"bytes"
25+
"fmt"
2526
"io"
26-
"io/ioutil"
27+
"os"
2728

2829
"github.com/fluxcd/pkg/envsubst/parse"
2930
)
@@ -36,7 +37,7 @@ type state struct {
3637
node parse.Node // current node
3738

3839
// maps variable names to values
39-
mapper func(string) string
40+
mapper func(string) (value string, exists bool)
4041
}
4142

4243
// Template is the representation of a parsed shell format string.
@@ -58,15 +59,15 @@ func Parse(s string) (t *Template, err error) {
5859
// ParseFile creates a new shell format template and parses the template
5960
// definition from the named file.
6061
func ParseFile(path string) (*Template, error) {
61-
b, err := ioutil.ReadFile(path)
62+
b, err := os.ReadFile(path)
6263
if err != nil {
6364
return nil, err
6465
}
6566
return Parse(string(b))
6667
}
6768

6869
// Execute applies a parsed template to the specified data mapping.
69-
func (t *Template) Execute(mapping func(string) string) (str string, err error) {
70+
func (t *Template) Execute(mapping func(string) (string, bool)) (str string, err error) {
7071
b := new(bytes.Buffer)
7172
s := new(state)
7273
s.node = t.tree.Root
@@ -107,6 +108,8 @@ func (t *Template) evalList(s *state, node *parse.ListNode) (err error) {
107108
return nil
108109
}
109110

111+
var errVarNotSet = fmt.Errorf("variable not set (strict mode)")
112+
110113
func (t *Template) evalFunc(s *state, node *parse.FuncNode) error {
111114
var w = s.writer
112115
var buf bytes.Buffer
@@ -126,8 +129,11 @@ func (t *Template) evalFunc(s *state, node *parse.FuncNode) error {
126129
s.writer = w
127130
s.node = node
128131

129-
v := s.mapper(node.Param)
132+
v, exists := s.mapper(node.Param)
130133

134+
if node.Name == "" && !exists {
135+
return fmt.Errorf("%w: %q", errVarNotSet, node.Param)
136+
}
131137
fn := lookupFunc(node.Name, len(args))
132138

133139
_, err := io.WriteString(s.writer, fn(v, args...))

0 commit comments

Comments
 (0)