Skip to content

Commit

Permalink
Drop escaping of strings in the JSON.
Browse files Browse the repository at this point in the history
Add support for the old escaping mechanism through an annotation on the
TriggerTemptlate.

This removes the old replacement of " with \" in parameters, which was yielding
invalid JSON when the strings were already quoted.
  • Loading branch information
bigkevmcd committed Nov 11, 2020
1 parent 0c2c7bb commit 3e8c705
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 21 deletions.
25 changes: 25 additions & 0 deletions docs/triggertemplates.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,28 @@ As of Tekton Pipelines version
embed resource specs. It is a best practice to embed each resource specs in the
PipelineRun or TaskRun that uses the resource spec. Embedding the resource spec
avoids a race condition between creating and using resources.


## Quote-Escaping

TriggerTemplate parameter values were previously escaped by simply replacing
`"` with `\"` this could lead to problems when strings were already escaped, and
generating invalid resources from the TriggerTemplate.

This behaviour has been deprecated, if this breaks your templates, you can add
an annotation to the TriggerTemplate.

```yaml
apiVersion: triggers.tekton.dev/v1alpha1
kind: TriggerTemplate
metadata:
name: escaped-tt
annotations:
tekton.dev/old-escape-quotes: "true"
spec:
params:
- name: gitrevision
description: The git revision
```

This will retain the previous behaviour.
11 changes: 10 additions & 1 deletion pkg/template/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,15 @@ import (
"net/http"
"strings"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

triggersv1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1"
)

const (
OldEscapeAnnotation = "tekton.dev/old-escape-quotes"
)

// ResolveParams takes given triggerbindings and produces the resulting
// resource params.
func ResolveParams(rt ResolvedTrigger, body []byte, header http.Header) ([]triggersv1.Param, error) {
Expand All @@ -45,8 +51,11 @@ func ResolveParams(rt ResolvedTrigger, body []byte, header http.Header) ([]trigg
func ResolveResources(template *triggersv1.TriggerTemplate, params []triggersv1.Param) []json.RawMessage {
resources := make([]json.RawMessage, len(template.Spec.ResourceTemplates))
uid := UID()

oldEscape := metav1.HasAnnotation(template.ObjectMeta, OldEscapeAnnotation)

for i := range template.Spec.ResourceTemplates {
resources[i] = applyParamsToResourceTemplate(params, template.Spec.ResourceTemplates[i].RawExtension.Raw)
resources[i] = applyParamsToResourceTemplate(params, template.Spec.ResourceTemplates[i].RawExtension.Raw, oldEscape)
resources[i] = applyUIDToResourceTemplate(resources[i], uid)
}
return resources
Expand Down
9 changes: 8 additions & 1 deletion pkg/template/event_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,13 @@ func TestResolveParams_Error(t *testing.T) {
}
}

func addOldEscape(t *triggersv1.TriggerTemplate) *triggersv1.TriggerTemplate {
t.Annotations = map[string]string{
OldEscapeAnnotation: "yes",
}
return t
}

func TestResolveResources(t *testing.T) {
tests := []struct {
name string
Expand Down Expand Up @@ -498,7 +505,7 @@ func TestResolveResources(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
// Seeded for UID() to return "cbhtc"
utilrand.Seed(0)
got := ResolveResources(tt.template, tt.params)
got := ResolveResources(addOldEscape(tt.template), tt.params)
// Use toString so that it is easy to compare the json.RawMessage diffs
if diff := cmp.Diff(toString(tt.want), toString(got)); diff != "" {
t.Errorf("didn't get expected resource template -want + got: %s", diff)
Expand Down
14 changes: 8 additions & 6 deletions pkg/template/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,24 +121,26 @@ func resolveBindingsToParams(bindings []*triggersv1.TriggerSpecBinding, getTB ge

// applyParamsToResourceTemplate returns the TriggerResourceTemplate with the
// param values substituted for all matching param variables in the template
func applyParamsToResourceTemplate(params []triggersv1.Param, rt json.RawMessage) json.RawMessage {
func applyParamsToResourceTemplate(params []triggersv1.Param, rt json.RawMessage, oldEscape bool) json.RawMessage {
// Assume the params are valid
for _, param := range params {
rt = applyParamToResourceTemplate(param, rt)
rt = applyParamToResourceTemplate(param, rt, oldEscape)
}
return rt
}

// applyParamToResourceTemplate returns the TriggerResourceTemplate with the
// param value substituted for all matching param variables in the template
func applyParamToResourceTemplate(param triggersv1.Param, rt json.RawMessage) json.RawMessage {
func applyParamToResourceTemplate(param triggersv1.Param, rt json.RawMessage, oldEscape bool) json.RawMessage {
// Assume the param is valid
paramVariable := fmt.Sprintf("$(tt.params.%s)", param.Name)
// Escape quotes so that that JSON strings can be appended to regular strings.
// See #257 for discussion on this behavior.
paramValue := strings.Replace(param.Value, `"`, `\"`, -1)
rt = bytes.Replace(rt, []byte(paramVariable), []byte(paramValue), -1)
return rt
if oldEscape {
paramValue := strings.Replace(param.Value, `"`, `\"`, -1)
return bytes.Replace(rt, []byte(paramVariable), []byte(paramValue), -1)
}
return bytes.Replace(rt, []byte(paramVariable), []byte(param.Value), -1)
}

// UID generates a random string like the Kubernetes apiserver generateName metafield postfix.
Expand Down
91 changes: 78 additions & 13 deletions pkg/template/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,18 @@ func Test_applyParamToResourceTemplate(t *testing.T) {
wantRtOneParamVar = json.RawMessage(`{"foo": "bar-onevalue-bar"}`)
rtMultipleParamVars = json.RawMessage(`{"$(tt.params.oneid)": "bar-$(tt.params.oneid)-$(tt.params.oneid)$(tt.params.oneid)$(tt.params.oneid)-$(tt.params.oneid)-bar"}`)
wantRtMultipleParamVars = json.RawMessage(`{"onevalue": "bar-onevalue-onevalueonevalueonevalue-onevalue-bar"}`)
quotedString = `this is a \"quoted\" string`
quotedValue = `{"a": "this is a \"quoted\" string"}`
)
type args struct {
param triggersv1.Param
rt json.RawMessage
}
tests := []struct {
name string
args args
want json.RawMessage
name string
args args
want json.RawMessage
oldEscape bool
}{
{
name: "replace no param vars",
Expand Down Expand Up @@ -87,23 +90,73 @@ func Test_applyParamToResourceTemplate(t *testing.T) {
},
want: wantRtMultipleParamVars,
}, {
name: "espcae quotes in param val",
name: "escape quotes in param val",
args: args{
param: triggersv1.Param{
Name: "p1",
Value: `{"a":"b"}`,
},
rt: json.RawMessage(`{"foo": $(tt.params.p1)}`),
},
want: json.RawMessage(`{"foo": {"a":"b"}}`),
}, {
name: "escape quotes in param val - old escaping",
args: args{
param: triggersv1.Param{
Name: "p1",
Value: `{"a":"b"}`,
},
rt: json.RawMessage(`{"foo": "$(tt.params.p1)"}`),
},
want: json.RawMessage(`{"foo": "{\"a\":\"b\"}"}`),
want: json.RawMessage(`{"foo": "{\"a\":\"b\"}"}`),
oldEscape: true,
}, {
name: "escape string with quoted message inside",
args: args{
param: triggersv1.Param{
Name: "p1",
Value: quotedString,
},
rt: json.RawMessage(`{"foo": "$(tt.params.p1)"}`),
},
want: json.RawMessage(`{"foo": "this is a \"quoted\" string"}`),
}, {
name: "join string with quoted message",
args: args{
param: triggersv1.Param{
Name: "p1",
Value: quotedString,
},
rt: json.RawMessage(`{"foo": "bar-$(tt.params.p1)-bar"}`),
},
want: json.RawMessage(`{"foo": "bar-this is a \"quoted\" string-bar"}`),
}, {
name: "escape string with object with quoted string",
args: args{
param: triggersv1.Param{
Name: "p1",
Value: quotedValue,
},
rt: json.RawMessage(`{"foo": $(tt.params.p1)}`),
},
want: json.RawMessage(`{"foo": {"a": "this is a \"quoted\" string"}}`),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := applyParamToResourceTemplate(tt.args.param, tt.args.rt)
if diff := cmp.Diff(tt.want, got); diff != "" {
temp := map[string]interface{}{}
if err := json.Unmarshal(tt.want, &temp); err != nil {
t.Errorf("the wanted value is not valid JSON: %s", err)
}
got := applyParamToResourceTemplate(tt.args.param, tt.args.rt, tt.oldEscape)
if diff := cmp.Diff(string(tt.want), string(got)); diff != "" {
t.Errorf("applyParamToResourceTemplate(): -want +got: %s", diff)
}
if !tt.oldEscape {
if err := json.Unmarshal(got, &temp); err != nil {
t.Errorf("failed to parse result json %s: %s", got, err)
}
}
})
}
}
Expand All @@ -116,9 +169,10 @@ func Test_ApplyParamsToResourceTemplate(t *testing.T) {
rt json.RawMessage
}
tests := []struct {
name string
args args
want json.RawMessage
name string
args args
oldEscape bool
want json.RawMessage
}{
{
name: "no params",
Expand All @@ -138,6 +192,17 @@ func Test_ApplyParamsToResourceTemplate(t *testing.T) {
},
want: json.RawMessage(`{"oneparam": "onevalue", "twoparam": "$(tt.params.twoid)", "threeparam": "$(tt.params.threeid)"`),
},
{
name: "old escape behaviour",
args: args{
params: []triggersv1.Param{
{Name: "oneid", Value: "this \"is a value\""},
},
rt: rt,
},
want: json.RawMessage(`{"oneparam": "this \"is a value\"", "twoparam": "$(tt.params.twoid)", "threeparam": "$(tt.params.threeid)"`),
oldEscape: true,
},
{
name: "multiple params",
args: args{
Expand Down Expand Up @@ -165,9 +230,9 @@ func Test_ApplyParamsToResourceTemplate(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := applyParamsToResourceTemplate(tt.args.params, tt.args.rt)
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("applyParamsToResourceTemplate(): -want +got: %s", diff)
got := applyParamsToResourceTemplate(tt.args.params, tt.args.rt, tt.oldEscape)
if diff := cmp.Diff(string(tt.want), string(got)); diff != "" {
t.Errorf("applyParamsToResourceTemplate(): -want +got: %s\n%s\n", diff, string(got))
}
})
}
Expand Down
3 changes: 3 additions & 0 deletions test/eventlistener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ func TestEventListenerCreate(t *testing.T) {
// TriggerTemplate
tt, err := c.TriggersClient.TriggersV1alpha1().TriggerTemplates(namespace).Create(context.Background(),
bldr.TriggerTemplate("my-triggertemplate", "",
bldr.EventListenerMeta(
bldr.Label("tekton.dev/old-escape-quotes", "true"),
),
bldr.TriggerTemplateSpec(
bldr.TriggerTemplateParam("oneparam", "", ""),
bldr.TriggerTemplateParam("twoparamname", "", ""),
Expand Down

0 comments on commit 3e8c705

Please sign in to comment.