Skip to content

Commit

Permalink
feat: Support dataTemplate and contextTemplate for Trigger Parameters (
Browse files Browse the repository at this point in the history
…#543)

* add dataTemplate and contextTemplate options to trigger parameters

* fix comment in api doc, add some test cases, return error from template if evaluates to empty string or <no value>

* add example

Co-authored-by: Vaibhav <[email protected]>
  • Loading branch information
tinyzimmer and VaibhavPage authored Mar 21, 2020
1 parent 3f1f2b4 commit cad2834
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 8 deletions.
52 changes: 51 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,7 @@ required = [
[[constraint]]
name = "github.com/Azure/azure-event-hubs-go"
version = "v1.3.1"

[[constraint]]
name = "github.com/Masterminds/sprig"
version = "2.22.0"
69 changes: 69 additions & 0 deletions examples/sensors/trigger-with-template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
name: trigger-template
labels:
sensors.argoproj.io/sensor-controller-instanceid: argo-events
spec:
template:
spec:
containers:
- name: sensor
image: argoproj/sensor:v0.13.0-rc
imagePullPolicy: Always
serviceAccountName: argo-events-sa
dependencies:
- name: test-dep
gatewayName: webhook-gateway
eventName: example
subscription:
http:
port: 9300
triggers:
- template:
name: templated-workflow-trigger
k8s:
group: argoproj.io
version: v1alpha1
resource: workflows
operation: create
source:
resource:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: webhook-
spec:
entrypoint: whalesay
arguments:
parameters:
- name: message
templates:
- name: whalesay
serviceAccountName: argo-events-sa
inputs:
parameters:
- name: message
- name: subject
container:
image: docker/whalesay:latest
command: [cowsay]
args: ["{{inputs.parameters.message}} from {{inputs.parameters.subject}}"]
parameters:
# Retrieve the 'message' key from the payload
- src:
dependencyName: test-dep
dataTemplate: "{{ .Input.message }}"
dest: spec.arguments.parameters.0.value
# Title case the context subject
- src:
dependencyName: test-dep
contextTemplate: "{{ .Input.subject | title }}"
dest: spec.arguments.parameters.1.value
# You can also form arbitrary values that don't derive from the
# dependency input
- src:
dependencyName: test-dep
dataTemplate: "{{ now | unixEpoch }}-"
dest: metadata.generateName
operation: append
10 changes: 10 additions & 0 deletions pkg/apis/sensor/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,11 +544,21 @@ type TriggerParameterSource struct {
// To access an array value use the index as the key. The dot and wildcard characters can be escaped with '\\'.
// See https://github.com/tidwall/gjson#path-syntax for more information on how to use this.
ContextKey string `json:"contextKey,omitempty" protobuf:"bytes,2,opt,name=contextKey"`
// ContextTemplate is a go-template for extracting a string from the event's context.
// If a ContextTemplate is provided with a ContextKey, the template will be evaluated first and fallback to the ContextKey.
// The templating follows the standard go-template syntax as well as sprig's extra functions.
// See https://pkg.go.dev/text/template and https://masterminds.github.io/sprig/
ContextTemplate string `json:"contextTemplate,omitempty" protobuf:"bytes,3,opt,name=contextTemplate"`
// DataKey is the JSONPath of the event's (JSON decoded) data key
// DataKey is a series of keys separated by a dot. A key may contain wildcard characters '*' and '?'.
// To access an array value use the index as the key. The dot and wildcard characters can be escaped with '\\'.
// See https://github.com/tidwall/gjson#path-syntax for more information on how to use this.
DataKey string `json:"dataKey,omitempty" protobuf:"bytes,3,opt,name=dataKey"`
// DataTemplate is a go-template for extracting a string from the event's data.
// If a DataTemplate is provided with a DataKey, the template will be evaluated first and fallback to the DataKey.
// The templating follows the standard go-template syntax as well as sprig's extra functions.
// See https://pkg.go.dev/text/template and https://masterminds.github.io/sprig/
DataTemplate string `json:"dataTemplate,omitempty" protobuf:"bytes,3,opt,name=dataTemplate"`
// Value is the default literal value to use for this parameter source
// This is only used if the DataKey is invalid.
// If the DataKey is invalid and this is not defined, this param source will produce an error.
Expand Down
58 changes: 51 additions & 7 deletions sensors/triggers/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ limitations under the License.
package triggers

import (
"bytes"
"encoding/json"
"fmt"
"text/template"

"github.com/Masterminds/sprig"
"github.com/argoproj/argo-events/common"
snctrl "github.com/argoproj/argo-events/controllers/sensor"
apicommon "github.com/argoproj/argo-events/pkg/apis/common"
Expand Down Expand Up @@ -162,19 +165,22 @@ func ResolveParamValue(src *v1alpha1.TriggerParameterSource, events map[string]a
var err error
var value []byte
var key string
var template string
if event, ok := events[src.DependencyName]; ok {
// If context or data keys are not set, return the event payload as is
if src.ContextKey == "" && src.DataKey == "" {
if src.ContextKey == "" && src.DataKey == "" && src.DataTemplate == "" && src.ContextTemplate == "" {
value, err = json.Marshal(&event)
}
// Get the context bytes
if src.ContextKey != "" {
if src.ContextKey != "" || src.ContextTemplate != "" {
key = src.ContextKey
template = src.ContextTemplate
value, err = json.Marshal(&event.Context)
}
// Get the payload bytes
if src.DataKey != "" {
if src.DataKey != "" || src.DataTemplate != "" {
key = src.DataKey
template = src.DataTemplate
value, err = renderEventDataAsJSON(&event)
}
}
Expand All @@ -184,12 +190,19 @@ func ResolveParamValue(src *v1alpha1.TriggerParameterSource, events map[string]a
}
// Get the value corresponding to specified key within JSON object
if value != nil {
if template != "" {
out, err := getValueWithTemplate(value, template)
if err == nil {
return out, nil
}
fmt.Printf("failed to execute the src event template, falling back to key or value. err: %+v\n", err)
}
if key != "" {
res := gjson.GetBytes(value, key)
if res.Exists() {
return res.String(), nil
res, err := getValueByKey(value, key)
if err == nil {
return res, nil
}
fmt.Printf("key %s does not exist to in the event object\n", key)
fmt.Printf("Failed to get value by key: %+v\n", err)
}
if src.Value != nil {
return *src.Value, nil
Expand Down Expand Up @@ -217,3 +230,34 @@ func ExtractEvents(sensor *v1alpha1.Sensor, params []v1alpha1.TriggerParameter)
}
return events
}

// getValueWithTemplate will attempt to execute the provided template against
// the raw json bytes and then returns the result or any error
func getValueWithTemplate(value []byte, templString string) (string, error) {
res := gjson.ParseBytes(value)
tpl, err := template.New("param").Funcs(sprig.HermeticTxtFuncMap()).Parse(templString)
if err != nil {
return "", err
}
var buf bytes.Buffer
if err := tpl.Execute(&buf, map[string]interface{}{
"Input": res.Value(),
}); err != nil {
return "", err
}
out := buf.String()
if out == "" || out == "<no value>" {
return "", fmt.Errorf("Template evaluated to empty string or no value: %s", templString)
}
return out, nil
}

// getValueByKey will return the value in the raw json bytes at the provided key,
// or an error if it does not exist.
func getValueByKey(value []byte, key string) (string, error) {
res := gjson.GetBytes(value, key)
if res.Exists() {
return res.String(), nil
}
return "", fmt.Errorf("key %s does not exist to in the event object\n", key)
}
59 changes: 59 additions & 0 deletions sensors/triggers/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,65 @@ func TestResolveParamValue(t *testing.T) {
},
result: "fake",
},
{
name: "get first name with template",
source: &v1alpha1.TriggerParameterSource{
DependencyName: "fake-dependency",
DataTemplate: "{{ .Input.name.first }}",
},
result: "fake",
},
{
name: "get capitalized first name with template",
source: &v1alpha1.TriggerParameterSource{
DependencyName: "fake-dependency",
DataTemplate: "{{ upper .Input.name.first }}",
},
result: "FAKE",
},
{
name: "get subject with template",
source: &v1alpha1.TriggerParameterSource{
DependencyName: "fake-dependency",
ContextTemplate: "{{ .Input.subject }}",
},
result: "example-1",
},
{
name: "get formatted subject with template",
source: &v1alpha1.TriggerParameterSource{
DependencyName: "fake-dependency",
ContextTemplate: `{{ .Input.subject | replace "-" "_" }}`,
},
result: "example_1",
},
{
name: "data template has preference over context template",
source: &v1alpha1.TriggerParameterSource{
DependencyName: "fake-dependency",
ContextTemplate: "{{ .Input.subject }}",
DataTemplate: "{{ .Input.name.first }}",
},
result: "fake",
},
{
name: "data template fails over to data key",
source: &v1alpha1.TriggerParameterSource{
DependencyName: "fake-dependency",
DataTemplate: "{{ .Input.name.non_exist }}",
DataKey: "name.first",
},
result: "fake",
},
{
name: "invalid template fails over to data key",
source: &v1alpha1.TriggerParameterSource{
DependencyName: "fake-dependency",
DataTemplate: "{{ no }}",
DataKey: "name.first",
},
result: "fake",
},
}

for _, test := range tests {
Expand Down

0 comments on commit cad2834

Please sign in to comment.