Skip to content

Commit

Permalink
templating: adding fieldFrom helper function to retrieve a field from…
Browse files Browse the repository at this point in the history
… a previous expression (#210)

`field` templating helper function allow to retrieve a key into a
map[string], even if the key is not a valid templating keyword.
`fieldFrom` provides the same feature, expect that it doesn't use the
values map from the task/resolution/steps, but use the previous
templating pipeline as a templating source.

Closes #200

Signed-off-by: Romain Beuque <[email protected]>
  • Loading branch information
rbeuque74 committed Jan 13, 2021
1 parent 3fe8365 commit acc23c0
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 6 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"}`}}`` |
Expand Down
34 changes: 33 additions & 1 deletion engine/values/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand All @@ -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 {
Expand Down
39 changes: 39 additions & 0 deletions engine/values/values_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down

0 comments on commit acc23c0

Please sign in to comment.