From 4a629008b729be9b3dda7a6daa2f60beade6f3ef Mon Sep 17 00:00:00 2001 From: Romain Beuque Date: Mon, 23 Mar 2020 13:21:01 +0100 Subject: [PATCH] fix: templating: allow templating to render empty string as result Signed-off-by: Romain Beuque --- engine/engine_test.go | 21 +++++++++++++++++++++ engine/step/apply.go | 31 ++++++++++++++++++++----------- engine/templates_tests/input.yaml | 7 ++++++- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/engine/engine_test.go b/engine/engine_test.go index 99628d39..df5b2d0b 100644 --- a/engine/engine_test.go +++ b/engine/engine_test.go @@ -16,6 +16,7 @@ import ( "github.com/loopfz/gadgeto/zesty" "github.com/ovh/configstore" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/ovh/utask" "github.com/ovh/utask/db" @@ -625,6 +626,26 @@ func TestBaseOutput(t *testing.T) { assert.Equal(t, "bar", output["foo"]) } +func TestEmptyStringInput(t *testing.T) { + input := map[string]interface{}{ + "quantity": -2.3, + "foo": "", + } + res, err := createResolution("input.yaml", input, nil) + assert.NotNil(t, res) + assert.Nil(t, err) + + res, err = runResolution(res) + + require.Nilf(t, err, "got error %s", err) + require.NotNil(t, res) + assert.Equal(t, resolution.StateDone, res.State) + assert.Equal(t, step.StateDone, res.Steps["stepOne"].State) + + output := res.Steps["stepOne"].Output.(map[string]interface{}) + assert.Equal(t, "", output["foo"]) +} + func TestScriptPlugin(t *testing.T) { argv := "world" res, err := createResolution("execScript.yaml", map[string]interface{}{"argv": argv}, nil) diff --git a/engine/step/apply.go b/engine/step/apply.go index 3891c2d3..b330f003 100644 --- a/engine/step/apply.go +++ b/engine/step/apply.go @@ -3,11 +3,16 @@ package step import ( "bytes" "encoding/json" + "errors" "reflect" "github.com/ovh/utask/engine/values" ) +var ( + errNotTemplatable = errors.New("value given is not templatable") +) + func resolveObject(val *values.Values, objson json.RawMessage, item interface{}, stepName string) ([]byte, error) { obj, err := rawResolveObject(val, objson, item, stepName) if err != nil { @@ -61,12 +66,14 @@ func applyMap(val *values.Values, v reflect.Value, item interface{}, stepName st applySlice(val, mv, item, stepName) case reflect.String: newValue, err := applyString(val, mv, item, stepName) - if err != nil { - return err - } - - if newValue != "" { + switch err { + case nil: v.SetMapIndex(iter.Key(), reflect.ValueOf(newValue)) + case errNotTemplatable: + // current value Kind is string, but actual type could not be string (e.g. json.Number) + // in that case, we should keep the actual value as it will never be templated + default: + return err } } } @@ -90,12 +97,14 @@ func applySlice(val *values.Values, v reflect.Value, item interface{}, stepName applySlice(val, elem, item, stepName) case reflect.String: newValue, err := applyString(val, elem, item, stepName) - if err != nil { - return err - } - - if newValue != "" { + switch err { + case nil: mv.Set(reflect.ValueOf(newValue)) + case errNotTemplatable: + // current value Kind is string, but actual type could not be string (e.g. json.Number) + // in that case, we should keep the actual value as it will never be templated + default: + return err } } } @@ -107,7 +116,7 @@ func applyString(val *values.Values, v reflect.Value, item interface{}, stepName strval := v.Interface() str, ok := strval.(string) if !ok { - return "", nil + return "", errNotTemplatable } resolved, err := val.Apply(str, item, stepName) if err != nil { diff --git a/engine/templates_tests/input.yaml b/engine/templates_tests/input.yaml index 32a3a3e4..0961f33d 100644 --- a/engine/templates_tests/input.yaml +++ b/engine/templates_tests/input.yaml @@ -8,6 +8,11 @@ inputs: collection: false type: number optional: false + - name: foo + description: A string value + type: string + optional: true + default: "" steps: stepOne: description: first step @@ -15,4 +20,4 @@ steps: retry_pattern: seconds action: type: echo - configuration: {output: {value: "{{.input.quantity}}"}} + configuration: {output: {value: "{{.input.quantity}}", foo: "{{.input.foo}}"}}