Skip to content

Commit

Permalink
Add test.Required function
Browse files Browse the repository at this point in the history
Signed-off-by: Dave Henderson <[email protected]>
  • Loading branch information
hairyhenderson committed Sep 8, 2018
1 parent 955fc69 commit 15cd750
Show file tree
Hide file tree
Showing 8 changed files with 320 additions and 3 deletions.
39 changes: 39 additions & 0 deletions docs-src/content/functions/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,42 @@ funcs:
template: <arg>:1:3: executing "<arg>" at <fail>: error calling fail: template generation failed
$ gomplate -i '{{ test.Fail "something is wrong!" }}'
template: <arg>:1:7: executing "<arg>" at <test.Fail>: error calling Fail: template generation failed: something is wrong!
- name: test.Required
alias: required
description: |
Passes through the given value, if it's non-empty, and non-`nil`. Otherwise,
exits and prints a given error message so the user can adjust as necessary.
This is particularly useful for cases where templates require user-provided
data (such as datasources or environment variables), and rendering can not
continue correctly.
This was inspired by [Helm's `required` function](https://github.com/kubernetes/helm/blob/master/docs/charts_tips_and_tricks.md#know-your-template-functions),
but has slightly different behaviour. Notably, gomplate will always fail in
cases where a referenced _key_ is missing, and this function will have no
effect.
pipeline: true
arguments:
- name: message
required: false
description: The optional message to provide when the required value is not provided
- name: value
required: true
description: The required value
examples:
- |
$ FOO=foobar gomplate -i '{{ getenv "FOO" | required "Missing FOO environment variable!" }}'
foobar
$ FOO= gomplate -i '{{ getenv "FOO" | required "Missing FOO environment variable!" }}'
error: Missing FOO environment variable!
- |
$ cat <<EOF> config.yaml
defined: a value
empty: ""
EOF
$ gomplate -d config=config.yaml -i '{{ (ds "config").defined | required "The `config` datasource must have a value defined for `defined`" }}'
a value
$ gomplate -d config=config.yaml -i '{{ (ds "config").empty | required "The `config` datasource must have a value defined for `empty`" }}'
template: <arg>:1:25: executing "<arg>" at <required "The `confi...>: error calling required: The `config` datasource must have a value defined for `empty`
$ gomplate -d config=config.yaml -i '{{ (ds "config").bogus | required "The `config` datasource must have a value defined for `bogus`" }}'
template: <arg>:1:7: executing "<arg>" at <"config">: map has no entry for key "bogus"
53 changes: 53 additions & 0 deletions docs/content/functions/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,56 @@ template: <arg>:1:3: executing "<arg>" at <fail>: error calling fail: template g
$ gomplate -i '{{ test.Fail "something is wrong!" }}'
template: <arg>:1:7: executing "<arg>" at <test.Fail>: error calling Fail: template generation failed: something is wrong!
```

## `test.Required`

**Alias:** `required`

Passes through the given value, if it's non-empty, and non-`nil`. Otherwise,
exits and prints a given error message so the user can adjust as necessary.

This is particularly useful for cases where templates require user-provided
data (such as datasources or environment variables), and rendering can not
continue correctly.

This was inspired by [Helm's `required` function](https://github.com/kubernetes/helm/blob/master/docs/charts_tips_and_tricks.md#know-your-template-functions),
but has slightly different behaviour. Notably, gomplate will always fail in
cases where a referenced _key_ is missing, and this function will have no
effect.

### Usage
```go
test.Required [message] value
```

```go
value | test.Required [message]
```

### Arguments

| name | description |
|------|-------------|
| `message` | _(optional)_ The optional message to provide when the required value is not provided |
| `value` | _(required)_ The required value |

### Examples

```console
$ FOO=foobar gomplate -i '{{ getenv "FOO" | required "Missing FOO environment variable!" }}'
foobar
$ FOO= gomplate -i '{{ getenv "FOO" | required "Missing FOO environment variable!" }}'
error: Missing FOO environment variable!
```
```console
$ cat <<EOF> config.yaml
defined: a value
empty: ""
EOF
$ gomplate -d config=config.yaml -i '{{ (ds "config").defined | required "The `config` datasource must have a value defined for `defined`" }}'
a value
$ gomplate -d config=config.yaml -i '{{ (ds "config").empty | required "The `config` datasource must have a value defined for `empty`" }}'
template: <arg>:1:25: executing "<arg>" at <required "The `confi...>: error calling required: The `config` datasource must have a value defined for `empty`
$ gomplate -d config=config.yaml -i '{{ (ds "config").bogus | required "The `config` datasource must have a value defined for `bogus`" }}'
template: <arg>:1:7: executing "<arg>" at <"config">: map has no entry for key "bogus"
```
17 changes: 17 additions & 0 deletions funcs/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func AddTestFuncs(f map[string]interface{}) {

f["assert"] = TestNS().Assert
f["fail"] = TestNS().Fail
f["required"] = TestNS().Required
}

// TestFuncs -
Expand Down Expand Up @@ -59,3 +60,19 @@ func (f *TestFuncs) Fail(args ...interface{}) (string, error) {
return "", errors.Errorf("wrong number of args: want 0 or 1, got %d", len(args))
}
}

// Required -
func (f *TestFuncs) Required(args ...interface{}) (interface{}, error) {
switch len(args) {
case 1:
return test.Required("", args[0])
case 2:
message, ok := args[0].(string)
if !ok {
return nil, errors.Errorf("at <1>: expected string; found %T", args[0])
}
return test.Required(message, args[1])
default:
return nil, errors.Errorf("wrong number of args: want 1 or 2, got %d", len(args))
}
}
65 changes: 65 additions & 0 deletions funcs/test_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package funcs

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestAssert(t *testing.T) {
f := TestNS()
_, err := f.Assert(false)
assert.Error(t, err)

_, err = f.Assert(true)
assert.NoError(t, err)

_, err = f.Assert("foo", true)
assert.NoError(t, err)

_, err = f.Assert("foo", "false")
assert.EqualError(t, err, "assertion failed: foo")
}

func TestRequired(t *testing.T) {
f := TestNS()
errMsg := "can not render template: a required value was not set"
v, err := f.Required("")
assert.Error(t, err)
assert.EqualError(t, err, errMsg)
assert.Nil(t, v)

v, err = f.Required(nil)
assert.Error(t, err)
assert.EqualError(t, err, errMsg)
assert.Nil(t, v)

errMsg = "hello world"
v, err = f.Required(errMsg, nil)
assert.Error(t, err)
assert.EqualError(t, err, errMsg)
assert.Nil(t, v)

v, err = f.Required(42, nil)
assert.Error(t, err)
assert.EqualError(t, err, "at <1>: expected string; found int")
assert.Nil(t, v)

v, err = f.Required()
assert.Error(t, err)
assert.EqualError(t, err, "wrong number of args: want 1 or 2, got 0")
assert.Nil(t, v)

v, err = f.Required("", 2, 3)
assert.Error(t, err)
assert.EqualError(t, err, "wrong number of args: want 1 or 2, got 3")
assert.Nil(t, v)

v, err = f.Required(0)
assert.NoError(t, err)
assert.Equal(t, v, 0)

v, err = f.Required("foo")
assert.NoError(t, err)
assert.Equal(t, v, "foo")
}
14 changes: 14 additions & 0 deletions test/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package test

import (
"github.com/pkg/errors"
// "reflect"
)

// Assert -
Expand All @@ -22,3 +23,16 @@ func Fail(message string) error {
}
return errors.New("template generation failed")
}

// Required -
func Required(message string, value interface{}) (interface{}, error) {
if message == "" {
message = "can not render template: a required value was not set"
}

if s, ok := value.(string); value == nil || (ok && s == "") {
return nil, errors.New(message)
}

return value, nil
}
27 changes: 27 additions & 0 deletions test/test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,30 @@ func TestFail(t *testing.T) {
err = Fail("msg")
assert.EqualError(t, err, "template generation failed: msg")
}

func TestRequired(t *testing.T) {
v, err := Required("", nil)
assert.Error(t, err)
assert.Nil(t, v)

v, err = Required("", "")
assert.Error(t, err)
assert.Nil(t, v)

v, err = Required("foo", "")
assert.Error(t, err)
assert.EqualError(t, err, "foo")
assert.Nil(t, v)

v, err = Required("", 0)
assert.NoError(t, err)
assert.Equal(t, v, 0)

v, err = Required("", false)
assert.NoError(t, err)
assert.Equal(t, v, false)

v, err = Required("", map[string]string{})
assert.NoError(t, err)
assert.NotNil(t, v)
}
3 changes: 0 additions & 3 deletions tests/integration/basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package integration

import (
"bytes"
"fmt"
"io/ioutil"
"os"

Expand Down Expand Up @@ -121,7 +120,6 @@ func (s *BasicSuite) TestRoutesInputsToProperOutputsWithChmod(c *C) {
cmd.Stdin = bytes.NewBufferString("hello world")
})
result.Assert(c, icmd.Success)
fmt.Println(result.Combined())

testdata := []struct {
path string
Expand Down Expand Up @@ -150,7 +148,6 @@ func (s *BasicSuite) TestOverridesOutputModeWithChmod(c *C) {
cmd.Stdin = bytes.NewBufferString("hello world")
})
result.Assert(c, icmd.Success)
fmt.Println(result.Combined())

testdata := []struct {
path string
Expand Down
105 changes: 105 additions & 0 deletions tests/integration/test_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//+build integration
//+build !windows

package integration

import (
"bytes"

. "gopkg.in/check.v1"

"github.com/gotestyourself/gotestyourself/icmd"
)

type TestSuite struct {
}

var _ = Suite(&TestSuite{})

func (s *TestSuite) SetUpTest(c *C) {
}

func (s *TestSuite) TearDownTest(c *C) {
}

func (s *TestSuite) TestFail(c *C) {
result := icmd.RunCommand(GomplateBin, "-i", "{{ fail }}")
result.Assert(c, icmd.Expected{ExitCode: 1, Err: `template generation failed`})

result = icmd.RunCommand(GomplateBin, "-i", "{{ fail `some message` }}")
result.Assert(c, icmd.Expected{ExitCode: 1, Err: `some message`})
}

func (s *TestSuite) TestRequired(c *C) {
result := icmd.RunCmd(icmd.Command(GomplateBin,
"-i", `{{getenv "FOO" | required "FOO missing" }}`))
result.Assert(c, icmd.Expected{
ExitCode: 1,
Err: "FOO missing",
})

result = icmd.RunCmd(icmd.Command(GomplateBin,
"-i", `{{getenv "FOO" | required "FOO missing" }}`),
func(c *icmd.Cmd) {
c.Env = []string{"FOO=bar"}
})
result.Assert(c, icmd.Expected{
ExitCode: 0,
Out: "bar",
})

result = icmd.RunCmd(icmd.Command(GomplateBin,
"-d", "in=stdin:///?type=application/yaml",
"-i", `{{ (ds "in").foo | required "foo should not be null" }}`),
func(c *icmd.Cmd) {
c.Stdin = bytes.NewBufferString(`foo: null`)
})
result.Assert(c, icmd.Expected{
ExitCode: 1,
Err: "foo should not be null",
})

result = icmd.RunCmd(icmd.Command(GomplateBin,
"-d", "in=stdin:///?type=application/yaml",
"-i", `{{ (ds "in").foo | required }}`),
func(c *icmd.Cmd) {
c.Stdin = bytes.NewBufferString(`foo: []`)
})
result.Assert(c, icmd.Expected{
ExitCode: 0,
Out: "[]",
})

result = icmd.RunCmd(icmd.Command(GomplateBin,
"-d", "in=stdin:///?type=application/yaml",
"-i", `{{ (ds "in").foo | required }}`),
func(c *icmd.Cmd) {
c.Stdin = bytes.NewBufferString(`foo: {}`)
})
result.Assert(c, icmd.Expected{
ExitCode: 0,
Out: "map[]",
})

result = icmd.RunCmd(icmd.Command(GomplateBin,
"-d", "in=stdin:///?type=application/yaml",
"-i", `{{ (ds "in").foo | required }}`),
func(c *icmd.Cmd) {
c.Stdin = bytes.NewBufferString(`foo: 0`)
})
result.Assert(c, icmd.Expected{
ExitCode: 0,
Out: "0",
})

result = icmd.RunCmd(icmd.Command(GomplateBin,
"-d", "in=stdin:///?type=application/yaml",
"-i", `{{ (ds "in").foo | required }}`),
func(c *icmd.Cmd) {
c.Stdin = bytes.NewBufferString(`foo: false`)
})
result.Assert(c, icmd.Expected{
ExitCode: 0,
Out: "false",
})
}

0 comments on commit 15cd750

Please sign in to comment.