diff --git a/docs-src/content/functions/test.yml b/docs-src/content/functions/test.yml index f2e210997..594dfba8b 100644 --- a/docs-src/content/functions/test.yml +++ b/docs-src/content/functions/test.yml @@ -37,3 +37,43 @@ funcs: template: :1:3: executing "" at : error calling fail: template generation failed $ gomplate -i '{{ test.Fail "something is wrong!" }}' template: :1:7: executing "" at : 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, non-zero, 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 < 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: :1:25: executing "" at : 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: :1:7: executing "" at <"config">: map has no entry for key "bogus" diff --git a/docs/content/functions/test.md b/docs/content/functions/test.md index 6cccbdc52..2a8f616f2 100644 --- a/docs/content/functions/test.md +++ b/docs/content/functions/test.md @@ -69,3 +69,57 @@ template: :1:3: executing "" at : error calling fail: template g $ gomplate -i '{{ test.Fail "something is wrong!" }}' template: :1:7: executing "" at : error calling Fail: template generation failed: something is wrong! ``` + +## `test.Required` + +**Alias:** `required` + +Passes through the given value, if it's non-empty, non-zero, 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 < 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: :1:25: executing "" at : 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: :1:7: executing "" at <"config">: map has no entry for key "bogus" +``` diff --git a/funcs/test.go b/funcs/test.go index b9d6376bd..daab68ba0 100644 --- a/funcs/test.go +++ b/funcs/test.go @@ -26,6 +26,7 @@ func AddTestFuncs(f map[string]interface{}) { f["assert"] = TestNS().Assert f["fail"] = TestNS().Fail + f["required"] = TestNS().Required } // TestFuncs - @@ -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 "", errors.Errorf("wrong number of args: want 1 or 2, got %d", len(args)) + } +} diff --git a/test/test.go b/test/test.go index c3c2a62c4..0c5674a75 100644 --- a/test/test.go +++ b/test/test.go @@ -2,6 +2,7 @@ package test import ( "github.com/pkg/errors" + "reflect" ) // Assert - @@ -22,3 +23,15 @@ 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 not was not set" + } + + if value == nil || reflect.DeepEqual(value, reflect.Zero(reflect.TypeOf(value)).Interface()) { + return nil, errors.New(message) + } + return value, nil +}