From 484de0f414fb1dc1e7ecc58f40e8a06c5074a2e9 Mon Sep 17 00:00:00 2001 From: Dibyo Mukherjee Date: Mon, 2 Dec 2019 18:53:11 -0500 Subject: [PATCH] Support JSONPath in TriggerBindings This commit switches TriggerBinding params to use JSONPath instead of GJSON. To keep the commit small, removing existing GJSON will be part of a separate commit. Signed-off-by: Dibyo Mukherjee --- docs/triggerbindings.md | 52 +++++++++++++++++------------------ pkg/template/event.go | 55 ++++++++++++++++++++++++++++++++------ pkg/template/event_test.go | 34 +++++++++++------------ pkg/template/resource.go | 2 +- 4 files changed, 88 insertions(+), 55 deletions(-) diff --git a/docs/triggerbindings.md b/docs/triggerbindings.md index cef345c54f..6f1db00ac2 100644 --- a/docs/triggerbindings.md +++ b/docs/triggerbindings.md @@ -33,46 +33,44 @@ Each parameter has a `name` and a `value`. ## Event Variable Interpolation -In order to parse generic events as efficiently as possible, -[GJSON](https://github.com/tidwall/gjson) is used internally. As a result, the -binding [path syntax](https://github.com/tidwall/gjson#path-syntax) differs -slightly from standard JSON. As of now, the following patterns are supported -within `TriggerBinding` parameter value interpolation: - -`\$\(body(\.[[:alnum:]/_\-\.\\]+|\.#\([[:alnum:]=<>%!"\*_-]+\)#??)*\)` - -`\$\(header(\.[[:alnum:]_\-]+)?\)` +TriggerBindings can access values from the HTTP JSON body and the headers using +JSONPath expressions. The expressions have to be wrapped in `$()` but can omit +the curly braces `{}` and the leading `.`. + +These are all valid expressions: +```shell script +$(body.key1) +$(.body.key) +$({.body.key1}) +$({body.key) +``` + +These are invalid expressions: +```shell script +{.body.key1} # INVALID - Not wrapped in $() +$({body) # INVALID - Ending curly brace absent +``` + -### Body +### Examples -HTTP Post request body data can be referenced using variable interpolation. Text -in the form of `$(body.X.Y.Z)` is replaced by the body data at JSON path -`X.Y.Z`. +``` shell script `$(body)` is replaced by the entire body. -The following are some example variable interpolation replacements: -``` $(body) --> "{\"key1\": \"value1\", \"key2\": {\"key3\": \"value3\"}, \"key4\": -[\"value4\", \"value5\"]}" +$(body) -> "{"key1": "value1", "key2": {"key3": "value3"}, "key4": ["value4", "value5"]}" $(body.key1) -> "value1" -$(body.key2) -> "{\"key3\": \"value3\"}" +$(body.key2) -> "{"key3": "value3"}" $(body.key2.key3) -> "value3" -$(body.key4.0) -> "value4" -``` - -### Header +$(body.key4[0]) -> "value4" -HTTP Post request header data can be referenced using variable interpolation. -Text in the form of `$(header.X)` is replaced by the event's header named `X`. +# $(header) is replaced by all of the headers from the event. -`$(header)` is replaced by all of the headers from the event. - -The following are some example variable interpolation replacements: -``` -$(header) -> "{\"One\":[\"one\"], \"Two\":[\"one\",\"two\",\"three\"]}" +$(header) -> "{"One":["one"], "Two":["one","two","three"]}" $(header.One) -> "one" diff --git a/pkg/template/event.go b/pkg/template/event.go index 63bbe2887f..710029a27b 100644 --- a/pkg/template/event.go +++ b/pkg/template/event.go @@ -169,26 +169,65 @@ func getHeaderValue(header map[string][]string, headerName string) (string, erro func ResolveParams(bindings []*triggersv1.TriggerBinding, body []byte, header map[string][]string, params []pipelinev1.ParamSpec) ([]pipelinev1.Param, error) { out, err := MergeBindingParams(bindings) if err != nil { - return nil, xerrors.Errorf("error merging trigger params: %v", err) + return nil, xerrors.Errorf("error merging trigger params: %q", err) } - out, err = ApplyBodyToParams(body, out) + event, err := NewEvent(body, header) if err != nil { - return nil, xerrors.Errorf("error applying body to trigger params: %s", err) + return nil, xerrors.Errorf("failed to create Event: %q", err) } - out, err = ApplyHeaderToParams(header, out) + out, err = ApplyEventValuesToParams(event, out) if err != nil { - return nil, xerrors.Errorf("error applying header to trigger params: %s", err) + return nil, xerrors.Errorf("failed to ApplyEventValuesToParams: %q", err) } - return MergeInDefaultParams(out, params), nil } +// ResolveResources resolves a templated resource by replacing params with their values. func ResolveResources(template *triggersv1.TriggerTemplate, params []pipelinev1.Param) []json.RawMessage { resources := make([]json.RawMessage, len(template.Spec.ResourceTemplates)) - uid := UID() for i := range template.Spec.ResourceTemplates { resources[i] = ApplyParamsToResourceTemplate(params, template.Spec.ResourceTemplates[i].RawMessage) - resources[i] = ApplyUIDToResourceTemplate(resources[i], uid) + resources[i] = ApplyUIDToResourceTemplate(resources[i], UID()) } return resources } + + +// Event represents a HTTP event that Triggers processes +type Event struct { + Header map[string]string `json:"header"` + Body interface{} `json:"body"` +} + +// NewEvent returns a new Event from HTTP headers and body +func NewEvent(body []byte, headers map[string][]string) (Event, error) { + var data interface{} + if len(body) > 0 { + if err := json.Unmarshal(body, &data); err != nil { + return Event{}, xerrors.Errorf("failed to unmarshal request body: %q", err) + } + } + joinedHeaders := make(map[string]string, len(headers)) + for k, v := range headers { + joinedHeaders[k] = strings.Join(v, ",") + } + + return Event{ + Header: joinedHeaders, + Body: data, + }, nil +} + + +// ApplyEventValuesToParams returns a slice of Params with the JSONPath variables replaced +// with values from the event body and headers. +func ApplyEventValuesToParams(ec Event, params []pipelinev1.Param) ([]pipelinev1.Param, error) { + for idx, p := range params { + val, err := ParseJSONPath(ec, p.Value.StringVal) + if err != nil { + return nil, xerrors.Errorf("failed to replace JSONPath value for param %s: %s: %q", p.Name, p.Value, err) + } + params[idx].Value = pipelinev1.ArrayOrString{Type: pipelinev1.ParamTypeString, StringVal: val} + } + return params, nil +} diff --git a/pkg/template/event_test.go b/pkg/template/event_test.go index b7c35585e5..02ad47382f 100644 --- a/pkg/template/event_test.go +++ b/pkg/template/event_test.go @@ -661,17 +661,17 @@ func Test_NewResources(t *testing.T) { args args want []json.RawMessage }{{ - name: "empty", - args: args{ - body: json.RawMessage{}, - header: map[string][]string{}, - binding: ResolvedTrigger{ - TriggerTemplate: bldr.TriggerTemplate("tt", "namespace"), - TriggerBindings: []*triggersv1.TriggerBinding{bldr.TriggerBinding("tb", "namespace")}, + name: "empty", + args: args{ + body: []byte{}, + header: map[string][]string{}, + binding: ResolvedTrigger { + TriggerTemplate: bldr.TriggerTemplate("tt", "namespace"), + TriggerBindings: []*triggersv1.TriggerBinding{bldr.TriggerBinding("tb", "namespace")}, + }, }, - }, - want: []json.RawMessage{}, - }, { + want: []json.RawMessage{}, + }, { name: "one resource template", args: args{ body: json.RawMessage(`{"foo": "bar"}`), @@ -799,7 +799,7 @@ func Test_NewResources(t *testing.T) { }, want: []json.RawMessage{ json.RawMessage(`{"rt1": "bar-cbhtc", "cbhtc": "cbhtc"}`), - json.RawMessage(`{"rt2": "default2-cbhtc"}`), + json.RawMessage(`{"rt2": "default2-bsvjp"}`), json.RawMessage(`{"rt3": "rt3"}`), }, }, { @@ -866,8 +866,7 @@ func Test_NewResources_error(t *testing.T) { header map[string][]string elParams []pipelinev1.Param binding ResolvedTrigger - }{ - { + }{{ name: "bodypath not found in body", body: json.RawMessage(`{"foo": "bar"}`), binding: ResolvedTrigger{ @@ -884,8 +883,7 @@ func Test_NewResources_error(t *testing.T) { ), }, }, - }, - { + }, { name: "header not found in event", body: json.RawMessage(`{"foo": "bar"}`), header: map[string][]string{"One": {"one"}}, @@ -903,8 +901,7 @@ func Test_NewResources_error(t *testing.T) { ), }, }, - }, - { + }, { name: "merge params error", elParams: []pipelinev1.Param{ { @@ -926,8 +923,7 @@ func Test_NewResources_error(t *testing.T) { ), }, }, - }, - { + }, { name: "conflicting bindings", binding: ResolvedTrigger{ TriggerTemplate: bldr.TriggerTemplate("tt", "namespace", diff --git a/pkg/template/resource.go b/pkg/template/resource.go index c40f889a21..4a503da4fc 100644 --- a/pkg/template/resource.go +++ b/pkg/template/resource.go @@ -42,7 +42,7 @@ type ResolvedTrigger struct { type getTriggerBinding func(name string, options metav1.GetOptions) (*triggersv1.TriggerBinding, error) type getTriggerTemplate func(name string, options metav1.GetOptions) (*triggersv1.TriggerTemplate, error) -// ResolveBindings takes in a trigger containing object refs to bindings and +// ResolveTrigger takes in a trigger containing object refs to bindings and // templates and resolves them to their underlying values. func ResolveTrigger(trigger triggersv1.EventListenerTrigger, getTB getTriggerBinding, getTT getTriggerTemplate) (ResolvedTrigger, error) { tb := make([]*triggersv1.TriggerBinding, 0, len(trigger.Bindings))