diff --git a/docs/cel_expressions.md b/docs/cel_expressions.md
index 47b11a9db..d39c7ef9a 100644
--- a/docs/cel_expressions.md
+++ b/docs/cel_expressions.md
@@ -276,6 +276,20 @@ interceptor.
'{"testing":"value"}'.parseJSON().testing == "value"
+
+
+ parseYAML()
+ |
+
+ <string>.parseYAML() -> map<string, dyn>
+ |
+
+ This parses a string that contains a YAML body into a map which which can be subsequently used in other expressions.
+ |
+
+ 'key1: value1\nkey2: value2\n'.parseYAML().key1 == "value"
+ |
+
parseURL()
diff --git a/go.mod b/go.mod
index b3f7e4ebc..5240a2556 100644
--- a/go.mod
+++ b/go.mod
@@ -35,7 +35,7 @@ require (
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 // indirect
knative.dev/caching v0.0.0-20200228235451-13d271455c74
knative.dev/pkg v0.0.0-20200207155214-fef852970f43
- sigs.k8s.io/yaml v1.2.0 // indirect
+ sigs.k8s.io/yaml v1.2.0
)
// Knative deps (release-0.12)
diff --git a/pkg/interceptors/cel/cel_test.go b/pkg/interceptors/cel/cel_test.go
index f02f2ca5e..37529b047 100644
--- a/pkg/interceptors/cel/cel_test.go
+++ b/pkg/interceptors/cel/cel_test.go
@@ -329,6 +329,7 @@ func TestExpressionEvaluation(t *testing.T) {
},
"b64value": "ZXhhbXBsZQ==",
"json_body": `{"testing": "value"}`,
+ "yaml_body": "key1: value1\nkey2: value2\nkey3: value3\n",
"testURL": "https://user:password@site.example.com/path/to?query=search#first",
"multiURL": "https://user:password@site.example.com/path/to?query=search&query=results",
}
@@ -426,6 +427,11 @@ func TestExpressionEvaluation(t *testing.T) {
expr: "body.json_body.parseJSON().testing == 'value'",
want: types.Bool(true),
},
+ {
+ name: "parse YAML body in a string",
+ expr: "body.yaml_body.parseYAML().key1 == 'value1'",
+ want: types.Bool(true),
+ },
{
name: "parse URL",
expr: "body.testURL.parseURL().path == '/path/to'",
@@ -477,8 +483,10 @@ func TestExpressionEvaluation(t *testing.T) {
func TestExpressionEvaluation_Error(t *testing.T) {
testSHA := "ec26c3e57ca3a959ca5aad62de7213c562f8c821"
jsonMap := map[string]interface{}{
- "value": "testing",
- "sha": testSHA,
+ "value": "testing",
+ "sha": testSHA,
+ "valid_yaml": "key1: value1\nkey2: value2\n",
+ "invalid_yaml": "key1: value1key2: value2\n",
"pull_request": map[string]interface{}{
"commits": []string{},
},
@@ -547,6 +555,21 @@ func TestExpressionEvaluation_Error(t *testing.T) {
expr: "body.pull_request.parseJSON().test == 'test'",
want: "unexpected type 'map' passed to parseJSON",
},
+ {
+ name: "parseYAML decoding non-string",
+ expr: "body.pull_request.parseYAML().key1 == 'value1'",
+ want: "unexpected type 'map' passed to parseYAML",
+ },
+ {
+ name: "unknown key",
+ expr: "body.valid_yaml.parseYAML().key3 == 'value3'",
+ want: "no such key: key3",
+ },
+ {
+ name: "invalid YAML body",
+ expr: "body.invalid_yaml.parseYAML().key1 == 'value1'",
+ want: "failed to decode 'key1: value1key2: value2\n' in parseYAML:",
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(rt *testing.T) {
diff --git a/pkg/interceptors/cel/triggers.go b/pkg/interceptors/cel/triggers.go
index aa10d290d..4eaa85200 100644
--- a/pkg/interceptors/cel/triggers.go
+++ b/pkg/interceptors/cel/triggers.go
@@ -31,6 +31,7 @@ import (
"github.com/google/cel-go/interpreter/functions"
"github.com/tektoncd/triggers/pkg/interceptors"
"k8s.io/client-go/kubernetes"
+ "sigs.k8s.io/yaml"
triggersv1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
@@ -137,6 +138,16 @@ import (
// Examples:
//
// 'https://example.com/testing'.parseURL().host == 'example.com'
+//
+// parseYAML
+//
+// Parses a YAML string into a map of strings to dynamic values
+//
+// .parseYAML() -> map
+//
+// Examples:
+//
+// body.field.parseYAML().item
// Triggers creates and returns a new cel.Lib with the triggers extensions.
func Triggers(request *http.Request, ns string, k kubernetes.Interface) cel.EnvOption {
@@ -171,6 +182,9 @@ func (triggersLib) CompileOptions() []cel.EnvOption {
decls.NewFunction("parseJSON",
decls.NewInstanceOverload("parseJSON_string",
[]*exprpb.Type{decls.String}, mapStrDyn)),
+ decls.NewFunction("parseYAML",
+ decls.NewInstanceOverload("parseYAML_string",
+ []*exprpb.Type{decls.String}, mapStrDyn)),
decls.NewFunction("parseURL",
decls.NewInstanceOverload("parseURL_string",
[]*exprpb.Type{decls.String}, mapStrDyn)),
@@ -197,6 +211,9 @@ func (t triggersLib) ProgramOptions() []cel.ProgramOption {
&functions.Overload{
Operator: "parseJSON",
Unary: parseJSONString},
+ &functions.Overload{
+ Operator: "parseYAML",
+ Unary: parseYAMLString},
&functions.Overload{
Operator: "parseURL",
Unary: parseURLString},
@@ -322,6 +339,19 @@ func parseJSONString(val ref.Val) ref.Val {
return types.NewDynamicMap(types.NewRegistry(), decodedVal)
}
+func parseYAMLString(val ref.Val) ref.Val {
+ str, ok := val.(types.String)
+ if !ok {
+ return types.ValOrErr(str, "unexpected type '%v' passed to parseYAML", val.Type())
+ }
+ decodedVal := map[string]interface{}{}
+ err := yaml.Unmarshal([]byte(str), &decodedVal)
+ if err != nil {
+ return types.NewErr("failed to decode '%v' in parseYAML: %w", str, err)
+ }
+ return types.NewDynamicMap(types.NewRegistry(), decodedVal)
+}
+
func parseURLString(val ref.Val) ref.Val {
str, ok := val.(types.String)
if !ok {
|