diff --git a/README.md b/README.md index 5e4c0726..65de463f 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,7 @@ The following templating functions are available: | **`Golang`** | Builtin functions from Golang text template | [Doc](https://golang.org/pkg/text/template/#hdr-Actions) | | **`Sprig`** | Extended set of functions from the Sprig project | [Doc](https://masterminds.github.io/sprig/) | | **`field`** | Equivalent to the dot notation, for entries with forbidden characters | ``{{field `config` `foo.bar`}}`` | +| **`fieldFrom`** | Equivalent to the dot notation, for entries with forbidden characters. It takes the previous template expression as source for the templating values. Example: ``{{ `{"foo.foo":"bar"}` | fromJson | fieldFrom `foo.foo` }}`` | ``{{expr | fieldFrom `config` `foo.bar`}}`` | | **`eval`** | Evaluates the value of a template variable | ``{{eval `var1`}}`` | | **`evalCache`** | Evaluates the value of a template variable, and cache for future usage (to avoid further computation) | ``{{evalCache `var1`}}`` | | **`fromJson`** | Decodes a JSON document into a structure. If the input cannot be decoded as JSON, the function will return an empty string | ``{{fromJson `{"a":"b"}`}}`` | diff --git a/engine/values/values.go b/engine/values/values.go index d4f0f6fd..ed7cfb58 100644 --- a/engine/values/values.go +++ b/engine/values/values.go @@ -67,6 +67,7 @@ func NewValues() *Values { } v.funcMap = sprig.FuncMap() v.funcMap["field"] = v.fieldTmpl + v.funcMap["fieldFrom"] = fieldFromTmpl v.funcMap["eval"] = v.varEval v.funcMap["evalCache"] = v.varEvalCache v.funcMap["fromJson"] = v.fromJSON @@ -308,9 +309,13 @@ func (v *Values) fieldTmpl(key ...string) reflect.Value { var i interface{} i = map[string]interface{}(v.m) + return fieldFn(i, key) +} + +func fieldFn(i interface{}, keys []string) reflect.Value { var ok bool - for _, k := range key { + for _, k := range keys { switch i.(type) { case map[string]interface{}: i, ok = i.(map[string]interface{})[k] @@ -329,6 +334,33 @@ func (v *Values) fieldTmpl(key ...string) reflect.Value { return reflect.ValueOf(i) } +func fieldFromTmpl(params ...interface{}) (reflect.Value, error) { + if len(params) < 2 { + return zero, errors.New("invalid number of parameters given") + } + var i interface{} + var ok bool + i, ok = params[len(params)-1].(map[string]interface{}) + if !ok { + return zero, errors.New("unable to cast given data to a map[string]") + } + + keys := []string{} + for j := range params { + if j >= len(params)-1 { + break + } + + item, ok := params[j].(string) + if !ok { + return zero, errors.New("foo") + } + keys = append(keys, item) + } + + return fieldFn(i, keys), nil +} + func (v *Values) varEvalCache(varName string) (interface{}, error) { i, ok := v.GetVariables()[varName] if !ok { diff --git a/engine/values/values_test.go b/engine/values/values_test.go new file mode 100644 index 00000000..85e42d21 --- /dev/null +++ b/engine/values/values_test.go @@ -0,0 +1,39 @@ +package values_test + +import ( + "testing" + + "github.com/ghodss/yaml" + "github.com/maxatome/go-testdeep/td" + "github.com/ovh/utask/engine/values" +) + +func TestTmpl(t *testing.T) { + input := `"step": + "first": + "output": + "result": + "my-payload": "{\"common-name\":\"utask.example.org\",\"id\":32,\"foo\":{\"bar\":1}}"` + obj := map[string]map[string]map[string]map[string]interface{}{} + err := yaml.Unmarshal([]byte(input), &obj) + td.CmpNil(t, err) + + v := values.NewValues() + v.SetOutput("first", obj["step"]["first"]["output"]) + + output, err := v.Apply("{{ field `step` `first` `output` `result` `my-payload` }}", nil, "foo") + td.CmpNil(t, err) + td.Cmp(t, string(output), "{\"common-name\":\"utask.example.org\",\"id\":32,\"foo\":{\"bar\":1}}") + + output, err = v.Apply("{{ field `step` `first` `output` `result` `my-payload` | fromJson | fieldFrom `common-name` }}", nil, "foo") + td.CmpNil(t, err) + td.Cmp(t, string(output), "utask.example.org") + + output, err = v.Apply("{{ field `step` `first` `output` `result` `my-payload` | fromJson | fieldFrom `foo` `bar` }}", nil, "foo") + td.CmpNil(t, err) + td.Cmp(t, string(output), "1") + + output, err = v.Apply("{{ `{\"common-name\":\"utask.example.org\",\"id\":32}` | fromJson | fieldFrom `invalid` | default `example.org` }}", nil, "foo") + td.CmpNil(t, err) + td.Cmp(t, string(output), "example.org") +} diff --git a/go.mod b/go.mod index a72f0b00..be28c46f 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/loopfz/gadgeto v0.10.1 github.com/magiconair/properties v1.8.1 // indirect github.com/markusthoemmes/goautoneg v0.0.0-20190713162725-c6008fefa5b1 - github.com/maxatome/go-testdeep v1.6.0 + github.com/maxatome/go-testdeep v1.8.0 github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75 // indirect github.com/mitchellh/reflectwalk v1.0.1 // indirect github.com/ovh/configstore v0.3.2 diff --git a/go.sum b/go.sum index 13921d64..f39439ba 100644 --- a/go.sum +++ b/go.sum @@ -225,8 +225,8 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJK github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/maxatome/go-testdeep v1.6.0 h1:E75ovdjJakM2V620YTykpckTEINs+aZ6iYFBNzb1h0w= -github.com/maxatome/go-testdeep v1.6.0/go.mod h1:011SgQ6efzZYAen6fDn4BqQ+lUR72ysdyKe7Dyogw70= +github.com/maxatome/go-testdeep v1.8.0 h1:7QpuvRvyCUefU41jbEU1+k8N4DhuE6jbu/CvFeUUOhU= +github.com/maxatome/go-testdeep v1.8.0/go.mod h1:011SgQ6efzZYAen6fDn4BqQ+lUR72ysdyKe7Dyogw70= github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75 h1:cUVxyR+UfmdEAZGJ8IiKld1O0dbGotEnkMolG5hfMSY= github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75/go.mod h1:pBbZyGwC5i16IBkjVKoy/sznA8jPD/K9iedwe1ESE6w= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= @@ -366,8 +366,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=