diff --git a/config/config-feature-flags.yaml b/config/config-feature-flags.yaml
index af73d4a005a..50eaa68519b 100644
--- a/config/config-feature-flags.yaml
+++ b/config/config-feature-flags.yaml
@@ -121,3 +121,6 @@ data:
# Setting this flag to "true" will keep pod on cancellation
# allowing examination of the logs on the pods from cancelled taskruns
keep-pod-on-cancel: "false"
+ # Setting this flag to "true" will enable the CEL evaluation in WhenExpression
+ # This feature is in preview mode and not implemented yet. Please check #7244 for the updates.
+ enable-cel-in-whenexpression: "false"
diff --git a/docs/pipeline-api.md b/docs/pipeline-api.md
index 573745f4a61..9b73b9e7e40 100644
--- a/docs/pipeline-api.md
+++ b/docs/pipeline-api.md
@@ -5782,6 +5782,20 @@ k8s.io/apimachinery/pkg/selection.Operator
It must be non-empty
+
+
+cel
+
+string
+
+ |
+
+(Optional)
+ CEL is a string of Common Language Expression, which can be used to conditionally execute
+the task based on the result of the expression evaluation
+More info about CEL syntax: https://github.com/google/cel-spec/blob/master/doc/langdef.md
+ |
+
WhenExpressions
@@ -14549,6 +14563,20 @@ k8s.io/apimachinery/pkg/selection.Operator
It must be non-empty
+
+
+cel
+
+string
+
+ |
+
+(Optional)
+ CEL is a string of Common Language Expression, which can be used to conditionally execute
+the task based on the result of the expression evaluation
+More info about CEL syntax: https://github.com/google/cel-spec/blob/master/doc/langdef.md
+ |
+
WhenExpressions
diff --git a/pkg/apis/config/feature_flags.go b/pkg/apis/config/feature_flags.go
index 5ad99f228ee..cb165ddddaf 100644
--- a/pkg/apis/config/feature_flags.go
+++ b/pkg/apis/config/feature_flags.go
@@ -92,6 +92,10 @@ const (
KeepPodOnCancel = "keep-pod-on-cancel"
// DefaultEnableKeepPodOnCancel is the default value for "keep-pod-on-cancel"
DefaultEnableKeepPodOnCancel = false
+ // EnableCELInWhenExpression is the flag to enabled CEL in WhenExpression
+ EnableCELInWhenExpression = "enable-cel-in-whenexpression"
+ // DefaultEnableCELInWhenExpression is the default value for EnableCelInWhenExpression
+ DefaultEnableCELInWhenExpression = false
disableAffinityAssistantKey = "disable-affinity-assistant"
disableCredsInitKey = "disable-creds-init"
@@ -140,6 +144,7 @@ type FeatureFlags struct {
MaxResultSize int
SetSecurityContext bool
Coschedule string
+ EnableCELInWhenExpression bool
}
// GetFeatureFlagsConfigName returns the name of the configmap containing all
@@ -209,10 +214,12 @@ func NewFeatureFlagsFromMap(cfgMap map[string]string) (*FeatureFlags, error) {
if err := setFeature(setSecurityContextKey, DefaultSetSecurityContext, &tc.SetSecurityContext); err != nil {
return nil, err
}
-
if err := setCoschedule(cfgMap, DefaultCoschedule, tc.DisableAffinityAssistant, &tc.Coschedule); err != nil {
return nil, err
}
+ if err := setFeature(EnableCELInWhenExpression, DefaultEnableCELInWhenExpression, &tc.EnableCELInWhenExpression); err != nil {
+ return nil, err
+ }
// Given that they are alpha features, Tekton Bundles and Custom Tasks should be switched on if
// enable-api-fields is "alpha". If enable-api-fields is not "alpha" then fall back to the value of
// each feature's individual flag.
diff --git a/pkg/apis/config/feature_flags_test.go b/pkg/apis/config/feature_flags_test.go
index a6f4e60bb1a..65756040698 100644
--- a/pkg/apis/config/feature_flags_test.go
+++ b/pkg/apis/config/feature_flags_test.go
@@ -73,6 +73,7 @@ func TestNewFeatureFlagsFromConfigMap(t *testing.T) {
MaxResultSize: 4096,
SetSecurityContext: true,
Coschedule: config.CoscheduleDisabled,
+ EnableCELInWhenExpression: true,
},
fileName: "feature-flags-all-flags-set",
},
@@ -269,6 +270,9 @@ func TestNewFeatureFlagsConfigMapErrors(t *testing.T) {
}, {
fileName: "feature-flags-invalid-disable-affinity-assistant",
want: `failed parsing feature flags config "truee": strconv.ParseBool: parsing "truee": invalid syntax`,
+ }, {
+ fileName: "feature-flags-invalid-enable-cel-in-whenexpression",
+ want: `failed parsing feature flags config "invalid": strconv.ParseBool: parsing "invalid": invalid syntax`,
}} {
t.Run(tc.fileName, func(t *testing.T) {
cm := test.ConfigMapFromTestFile(t, tc.fileName)
diff --git a/pkg/apis/config/testdata/feature-flags-all-flags-set.yaml b/pkg/apis/config/testdata/feature-flags-all-flags-set.yaml
index 9e01abd1944..63015ec07c1 100644
--- a/pkg/apis/config/testdata/feature-flags-all-flags-set.yaml
+++ b/pkg/apis/config/testdata/feature-flags-all-flags-set.yaml
@@ -32,3 +32,4 @@ data:
enable-provenance-in-status: "false"
set-security-context: "true"
keep-pod-on-cancel: "true"
+ enable-cel-in-whenexpression: "true"
diff --git a/pkg/apis/config/testdata/feature-flags-invalid-enable-cel-in-whenexpression.yaml b/pkg/apis/config/testdata/feature-flags-invalid-enable-cel-in-whenexpression.yaml
new file mode 100644
index 00000000000..fca3049351f
--- /dev/null
+++ b/pkg/apis/config/testdata/feature-flags-invalid-enable-cel-in-whenexpression.yaml
@@ -0,0 +1,21 @@
+# Copyright 2023 The Tekton Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: feature-flags
+ namespace: tekton-pipelines
+data:
+ enable-cel-in-whenexpression: "invalid"
diff --git a/pkg/apis/pipeline/v1/openapi_generated.go b/pkg/apis/pipeline/v1/openapi_generated.go
index b5a14a692f1..6425ec1de3b 100644
--- a/pkg/apis/pipeline/v1/openapi_generated.go
+++ b/pkg/apis/pipeline/v1/openapi_generated.go
@@ -4289,7 +4289,6 @@ func schema_pkg_apis_pipeline_v1_WhenExpression(ref common.ReferenceCallback) co
"input": {
SchemaProps: spec.SchemaProps{
Description: "Input is the string for guard checking which can be a static input or an output from a parent Task",
- Default: "",
Type: []string{"string"},
Format: "",
},
@@ -4297,7 +4296,6 @@ func schema_pkg_apis_pipeline_v1_WhenExpression(ref common.ReferenceCallback) co
"operator": {
SchemaProps: spec.SchemaProps{
Description: "Operator that represents an Input's relationship to the values",
- Default: "",
Type: []string{"string"},
Format: "",
},
@@ -4322,8 +4320,14 @@ func schema_pkg_apis_pipeline_v1_WhenExpression(ref common.ReferenceCallback) co
},
},
},
+ "cel": {
+ SchemaProps: spec.SchemaProps{
+ Description: "CEL is a string of Common Language Expression, which can be used to conditionally execute the task based on the result of the expression evaluation More info about CEL syntax: https://github.com/google/cel-spec/blob/master/doc/langdef.md",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
},
- Required: []string{"input", "operator", "values"},
},
},
}
diff --git a/pkg/apis/pipeline/v1/pipeline_validation.go b/pkg/apis/pipeline/v1/pipeline_validation.go
index 7b2de9f77ff..f20247bc6b1 100644
--- a/pkg/apis/pipeline/v1/pipeline_validation.go
+++ b/pkg/apis/pipeline/v1/pipeline_validation.go
@@ -88,7 +88,7 @@ func (ps *PipelineSpec) Validate(ctx context.Context) (errs *apis.FieldError) {
errs = errs.Also(validatePipelineResults(ps.Results, ps.Tasks, ps.Finally))
errs = errs.Also(validateTasksAndFinallySection(ps))
errs = errs.Also(validateFinalTasks(ps.Tasks, ps.Finally))
- errs = errs.Also(validateWhenExpressions(ps.Tasks, ps.Finally))
+ errs = errs.Also(validateWhenExpressions(ctx, ps.Tasks, ps.Finally))
errs = errs.Also(validateMatrix(ctx, ps.Tasks).ViaField("tasks"))
errs = errs.Also(validateMatrix(ctx, ps.Finally).ViaField("finally"))
return errs
@@ -745,12 +745,12 @@ func validateResultsVariablesExpressionsInFinally(expressions []string, pipeline
return errs
}
-func validateWhenExpressions(tasks []PipelineTask, finalTasks []PipelineTask) (errs *apis.FieldError) {
+func validateWhenExpressions(ctx context.Context, tasks []PipelineTask, finalTasks []PipelineTask) (errs *apis.FieldError) {
for i, t := range tasks {
- errs = errs.Also(t.When.validate().ViaFieldIndex("tasks", i))
+ errs = errs.Also(t.When.validate(ctx).ViaFieldIndex("tasks", i))
}
for i, t := range finalTasks {
- errs = errs.Also(t.When.validate().ViaFieldIndex("finally", i))
+ errs = errs.Also(t.When.validate(ctx).ViaFieldIndex("finally", i))
}
return errs
}
diff --git a/pkg/apis/pipeline/v1/swagger.json b/pkg/apis/pipeline/v1/swagger.json
index bb031c72730..76d325bfff0 100644
--- a/pkg/apis/pipeline/v1/swagger.json
+++ b/pkg/apis/pipeline/v1/swagger.json
@@ -2220,21 +2220,18 @@
"v1.WhenExpression": {
"description": "WhenExpression allows a PipelineTask to declare expressions to be evaluated before the Task is run to determine whether the Task should be executed or skipped",
"type": "object",
- "required": [
- "input",
- "operator",
- "values"
- ],
"properties": {
+ "cel": {
+ "description": "CEL is a string of Common Language Expression, which can be used to conditionally execute the task based on the result of the expression evaluation More info about CEL syntax: https://github.com/google/cel-spec/blob/master/doc/langdef.md",
+ "type": "string"
+ },
"input": {
"description": "Input is the string for guard checking which can be a static input or an output from a parent Task",
- "type": "string",
- "default": ""
+ "type": "string"
},
"operator": {
"description": "Operator that represents an Input's relationship to the values",
- "type": "string",
- "default": ""
+ "type": "string"
},
"values": {
"description": "Values is an array of strings, which is compared against the input, for guard checking It must be non-empty",
diff --git a/pkg/apis/pipeline/v1/when_types.go b/pkg/apis/pipeline/v1/when_types.go
index 58af7273d2b..45a8bdbd98c 100644
--- a/pkg/apis/pipeline/v1/when_types.go
+++ b/pkg/apis/pipeline/v1/when_types.go
@@ -27,15 +27,21 @@ import (
// to determine whether the Task should be executed or skipped
type WhenExpression struct {
// Input is the string for guard checking which can be a static input or an output from a parent Task
- Input string `json:"input"`
+ Input string `json:"input,omitempty"`
// Operator that represents an Input's relationship to the values
- Operator selection.Operator `json:"operator"`
+ Operator selection.Operator `json:"operator,omitempty"`
// Values is an array of strings, which is compared against the input, for guard checking
// It must be non-empty
// +listType=atomic
- Values []string `json:"values"`
+ Values []string `json:"values,omitempty"`
+
+ // CEL is a string of Common Language Expression, which can be used to conditionally execute
+ // the task based on the result of the expression evaluation
+ // More info about CEL syntax: https://github.com/google/cel-spec/blob/master/doc/langdef.md
+ // +optional
+ CEL string `json:"cel,omitempty"`
}
func (we *WhenExpression) isInputInValues() bool {
diff --git a/pkg/apis/pipeline/v1/when_validation.go b/pkg/apis/pipeline/v1/when_validation.go
index f445d13ed6a..fb8a92487d9 100644
--- a/pkg/apis/pipeline/v1/when_validation.go
+++ b/pkg/apis/pipeline/v1/when_validation.go
@@ -17,11 +17,13 @@ limitations under the License.
package v1
import (
+ "context"
"fmt"
"strings"
// TODO(#7244): Pull the cel-go library for now, the following PR will use the library.
_ "github.com/google/cel-go/cel"
+ "github.com/tektoncd/pipeline/pkg/apis/config"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/apimachinery/pkg/util/sets"
@@ -33,18 +35,28 @@ var validWhenOperators = []string{
string(selection.NotIn),
}
-func (wes WhenExpressions) validate() *apis.FieldError {
- return wes.validateWhenExpressionsFields().ViaField("when")
+func (wes WhenExpressions) validate(ctx context.Context) *apis.FieldError {
+ return wes.validateWhenExpressionsFields(ctx).ViaField("when")
}
-func (wes WhenExpressions) validateWhenExpressionsFields() (errs *apis.FieldError) {
+func (wes WhenExpressions) validateWhenExpressionsFields(ctx context.Context) (errs *apis.FieldError) {
for idx, we := range wes {
- errs = errs.Also(we.validateWhenExpressionFields().ViaIndex(idx))
+ errs = errs.Also(we.validateWhenExpressionFields(ctx).ViaIndex(idx))
}
return errs
}
-func (we *WhenExpression) validateWhenExpressionFields() *apis.FieldError {
+func (we *WhenExpression) validateWhenExpressionFields(ctx context.Context) *apis.FieldError {
+ if we.CEL != "" {
+ if !config.FromContextOrDefaults(ctx).FeatureFlags.EnableCELInWhenExpression {
+ return apis.ErrGeneric("feature flag %s should be set to true to use CEL: %s in WhenExpression", config.EnableCELInWhenExpression, we.CEL)
+ }
+ if we.Input != "" || we.Operator != "" || len(we.Values) != 0 {
+ return apis.ErrGeneric(fmt.Sprintf("cel and input+operator+values cannot be set in one WhenExpression: %v", we))
+ }
+ return nil
+ }
+
if equality.Semantic.DeepEqual(we, &WhenExpression{}) || we == nil {
return apis.ErrMissingField(apis.CurrentField)
}
diff --git a/pkg/apis/pipeline/v1/when_validation_test.go b/pkg/apis/pipeline/v1/when_validation_test.go
index 8cb2a891356..c602e0c1c7d 100644
--- a/pkg/apis/pipeline/v1/when_validation_test.go
+++ b/pkg/apis/pipeline/v1/when_validation_test.go
@@ -17,8 +17,10 @@ limitations under the License.
package v1
import (
+ "context"
"testing"
+ "github.com/tektoncd/pipeline/pkg/apis/config"
"k8s.io/apimachinery/pkg/selection"
)
@@ -54,7 +56,7 @@ func TestWhenExpressions_Valid(t *testing.T) {
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- if err := tt.wes.validate(); err != nil {
+ if err := tt.wes.validate(context.Background()); err != nil {
t.Errorf("WhenExpressions.validate() returned an error for valid when expressions: %s", tt.wes)
}
})
@@ -97,9 +99,68 @@ func TestWhenExpressions_Invalid(t *testing.T) {
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- if err := tt.wes.validate(); err == nil {
+ if err := tt.wes.validate(context.Background()); err == nil {
t.Errorf("WhenExpressions.validate() did not return error for invalid when expressions: %s, %s", tt.wes, err)
}
})
}
}
+
+func TestCELinWhenExpressions_Valid(t *testing.T) {
+ ctx := config.ToContext(context.Background(), &config.Config{
+ FeatureFlags: &config.FeatureFlags{
+ EnableCELInWhenExpression: true,
+ },
+ })
+ tests := []struct {
+ name string
+ wes WhenExpressions
+ }{{
+ name: "valid cel",
+ wes: []WhenExpression{{
+ CEL: " 'foo' == 'foo' ",
+ }},
+ }}
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if err := tt.wes.validate(ctx); err != nil {
+ t.Errorf("WhenExpressions.validate() returned an error: %s for valid when expressions: %s", err, tt.wes)
+ }
+ })
+ }
+}
+
+func TestCELWhenExpressions_Invalid(t *testing.T) {
+ tests := []struct {
+ name string
+ wes WhenExpressions
+ enableFeatureFlag bool
+ }{{
+ name: "feature flag not set",
+ wes: []WhenExpression{{
+ CEL: " 'foo' == 'foo' ",
+ }},
+ enableFeatureFlag: false,
+ }, {
+ name: "CEL should not coexist with input+operator+values",
+ wes: []WhenExpression{{
+ CEL: "'foo' != 'foo'",
+ Input: "foo",
+ Operator: selection.In,
+ Values: []string{"foo"},
+ }},
+ enableFeatureFlag: true,
+ }}
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ctx := config.ToContext(context.Background(), &config.Config{
+ FeatureFlags: &config.FeatureFlags{
+ EnableCELInWhenExpression: tt.enableFeatureFlag,
+ },
+ })
+ if err := tt.wes.validate(ctx); err == nil {
+ t.Errorf("WhenExpressions.validate() did not return error for invalid when expressions: %s", tt.wes)
+ }
+ })
+ }
+}
diff --git a/pkg/apis/pipeline/v1beta1/openapi_generated.go b/pkg/apis/pipeline/v1beta1/openapi_generated.go
index 74a6be3b64f..eba5a872419 100644
--- a/pkg/apis/pipeline/v1beta1/openapi_generated.go
+++ b/pkg/apis/pipeline/v1beta1/openapi_generated.go
@@ -5693,7 +5693,6 @@ func schema_pkg_apis_pipeline_v1beta1_WhenExpression(ref common.ReferenceCallbac
"input": {
SchemaProps: spec.SchemaProps{
Description: "Input is the string for guard checking which can be a static input or an output from a parent Task",
- Default: "",
Type: []string{"string"},
Format: "",
},
@@ -5701,7 +5700,6 @@ func schema_pkg_apis_pipeline_v1beta1_WhenExpression(ref common.ReferenceCallbac
"operator": {
SchemaProps: spec.SchemaProps{
Description: "Operator that represents an Input's relationship to the values",
- Default: "",
Type: []string{"string"},
Format: "",
},
@@ -5726,8 +5724,14 @@ func schema_pkg_apis_pipeline_v1beta1_WhenExpression(ref common.ReferenceCallbac
},
},
},
+ "cel": {
+ SchemaProps: spec.SchemaProps{
+ Description: "CEL is a string of Common Language Expression, which can be used to conditionally execute the task based on the result of the expression evaluation More info about CEL syntax: https://github.com/google/cel-spec/blob/master/doc/langdef.md",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
},
- Required: []string{"input", "operator", "values"},
},
},
}
diff --git a/pkg/apis/pipeline/v1beta1/pipeline_conversion.go b/pkg/apis/pipeline/v1beta1/pipeline_conversion.go
index 9f5c75a839f..b80b2ad74b7 100644
--- a/pkg/apis/pipeline/v1beta1/pipeline_conversion.go
+++ b/pkg/apis/pipeline/v1beta1/pipeline_conversion.go
@@ -262,12 +262,14 @@ func (we WhenExpression) convertTo(ctx context.Context, sink *v1.WhenExpression)
sink.Input = we.Input
sink.Operator = we.Operator
sink.Values = we.Values
+ sink.CEL = we.CEL
}
func (we *WhenExpression) convertFrom(ctx context.Context, source v1.WhenExpression) {
we.Input = source.Input
we.Operator = source.Operator
we.Values = source.Values
+ we.CEL = source.CEL
}
func (m *Matrix) convertTo(ctx context.Context, sink *v1.Matrix) {
diff --git a/pkg/apis/pipeline/v1beta1/pipeline_conversion_test.go b/pkg/apis/pipeline/v1beta1/pipeline_conversion_test.go
index e9ba9cc9032..3b5939ccce3 100644
--- a/pkg/apis/pipeline/v1beta1/pipeline_conversion_test.go
+++ b/pkg/apis/pipeline/v1beta1/pipeline_conversion_test.go
@@ -62,6 +62,9 @@ func TestPipelineConversion(t *testing.T) {
Name: "foo",
OnError: v1beta1.PipelineTaskContinue,
TaskRef: &v1beta1.TaskRef{Name: "example.com/my-foo-task"},
+ WhenExpressions: v1beta1.WhenExpressions{{
+ CEL: "'$(params.param-1)'=='foo'",
+ }},
}},
Params: []v1beta1.ParamSpec{{
Name: "param-1",
diff --git a/pkg/apis/pipeline/v1beta1/pipeline_validation.go b/pkg/apis/pipeline/v1beta1/pipeline_validation.go
index 2a0cbd6d2b6..118fff45a15 100644
--- a/pkg/apis/pipeline/v1beta1/pipeline_validation.go
+++ b/pkg/apis/pipeline/v1beta1/pipeline_validation.go
@@ -86,7 +86,7 @@ func (ps *PipelineSpec) Validate(ctx context.Context) (errs *apis.FieldError) {
errs = errs.Also(validatePipelineResults(ps.Results, ps.Tasks, ps.Finally))
errs = errs.Also(validateTasksAndFinallySection(ps))
errs = errs.Also(validateFinalTasks(ps.Tasks, ps.Finally))
- errs = errs.Also(validateWhenExpressions(ps.Tasks, ps.Finally))
+ errs = errs.Also(validateWhenExpressions(ctx, ps.Tasks, ps.Finally))
errs = errs.Also(validateMatrix(ctx, ps.Tasks).ViaField("tasks"))
errs = errs.Also(validateMatrix(ctx, ps.Finally).ViaField("finally"))
return errs
@@ -707,12 +707,12 @@ func validateResultsVariablesExpressionsInFinally(expressions []string, pipeline
return errs
}
-func validateWhenExpressions(tasks []PipelineTask, finalTasks []PipelineTask) (errs *apis.FieldError) {
+func validateWhenExpressions(ctx context.Context, tasks []PipelineTask, finalTasks []PipelineTask) (errs *apis.FieldError) {
for i, t := range tasks {
- errs = errs.Also(t.WhenExpressions.validate().ViaFieldIndex("tasks", i))
+ errs = errs.Also(t.WhenExpressions.validate(ctx).ViaFieldIndex("tasks", i))
}
for i, t := range finalTasks {
- errs = errs.Also(t.WhenExpressions.validate().ViaFieldIndex("finally", i))
+ errs = errs.Also(t.WhenExpressions.validate(ctx).ViaFieldIndex("finally", i))
}
return errs
}
diff --git a/pkg/apis/pipeline/v1beta1/swagger.json b/pkg/apis/pipeline/v1beta1/swagger.json
index 97f789dbcbf..4ff7d3d43ab 100644
--- a/pkg/apis/pipeline/v1beta1/swagger.json
+++ b/pkg/apis/pipeline/v1beta1/swagger.json
@@ -3111,21 +3111,18 @@
"v1beta1.WhenExpression": {
"description": "WhenExpression allows a PipelineTask to declare expressions to be evaluated before the Task is run to determine whether the Task should be executed or skipped",
"type": "object",
- "required": [
- "input",
- "operator",
- "values"
- ],
"properties": {
+ "cel": {
+ "description": "CEL is a string of Common Language Expression, which can be used to conditionally execute the task based on the result of the expression evaluation More info about CEL syntax: https://github.com/google/cel-spec/blob/master/doc/langdef.md",
+ "type": "string"
+ },
"input": {
"description": "Input is the string for guard checking which can be a static input or an output from a parent Task",
- "type": "string",
- "default": ""
+ "type": "string"
},
"operator": {
"description": "Operator that represents an Input's relationship to the values",
- "type": "string",
- "default": ""
+ "type": "string"
},
"values": {
"description": "Values is an array of strings, which is compared against the input, for guard checking It must be non-empty",
diff --git a/pkg/apis/pipeline/v1beta1/when_types.go b/pkg/apis/pipeline/v1beta1/when_types.go
index e98eff147c7..76a78ea0ea1 100644
--- a/pkg/apis/pipeline/v1beta1/when_types.go
+++ b/pkg/apis/pipeline/v1beta1/when_types.go
@@ -27,15 +27,21 @@ import (
// to determine whether the Task should be executed or skipped
type WhenExpression struct {
// Input is the string for guard checking which can be a static input or an output from a parent Task
- Input string `json:"input"`
+ Input string `json:"input,omitempty"`
// Operator that represents an Input's relationship to the values
- Operator selection.Operator `json:"operator"`
+ Operator selection.Operator `json:"operator,omitempty"`
// Values is an array of strings, which is compared against the input, for guard checking
// It must be non-empty
// +listType=atomic
- Values []string `json:"values"`
+ Values []string `json:"values,omitempty"`
+
+ // CEL is a string of Common Language Expression, which can be used to conditionally execute
+ // the task based on the result of the expression evaluation
+ // More info about CEL syntax: https://github.com/google/cel-spec/blob/master/doc/langdef.md
+ // +optional
+ CEL string `json:"cel,omitempty"`
}
func (we *WhenExpression) isInputInValues() bool {
diff --git a/pkg/apis/pipeline/v1beta1/when_validation.go b/pkg/apis/pipeline/v1beta1/when_validation.go
index 17bb55c56cf..8279950afab 100644
--- a/pkg/apis/pipeline/v1beta1/when_validation.go
+++ b/pkg/apis/pipeline/v1beta1/when_validation.go
@@ -17,9 +17,11 @@ limitations under the License.
package v1beta1
import (
+ "context"
"fmt"
"strings"
+ "github.com/tektoncd/pipeline/pkg/apis/config"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/apimachinery/pkg/util/sets"
@@ -31,18 +33,28 @@ var validWhenOperators = []string{
string(selection.NotIn),
}
-func (wes WhenExpressions) validate() *apis.FieldError {
- return wes.validateWhenExpressionsFields().ViaField("when")
+func (wes WhenExpressions) validate(ctx context.Context) *apis.FieldError {
+ return wes.validateWhenExpressionsFields(ctx).ViaField("when")
}
-func (wes WhenExpressions) validateWhenExpressionsFields() (errs *apis.FieldError) {
+func (wes WhenExpressions) validateWhenExpressionsFields(ctx context.Context) (errs *apis.FieldError) {
for idx, we := range wes {
- errs = errs.Also(we.validateWhenExpressionFields().ViaIndex(idx))
+ errs = errs.Also(we.validateWhenExpressionFields(ctx).ViaIndex(idx))
}
return errs
}
-func (we *WhenExpression) validateWhenExpressionFields() *apis.FieldError {
+func (we *WhenExpression) validateWhenExpressionFields(ctx context.Context) *apis.FieldError {
+ if we.CEL != "" {
+ if !config.FromContextOrDefaults(ctx).FeatureFlags.EnableCELInWhenExpression {
+ return apis.ErrGeneric("feature flag %s should be set to true to use CEL: %s in WhenExpression", config.EnableCELInWhenExpression, we.CEL)
+ }
+ if we.Input != "" || we.Operator != "" || len(we.Values) != 0 {
+ return apis.ErrGeneric(fmt.Sprintf("cel and input+operator+values cannot be set in one WhenExpression: %v", we))
+ }
+ return nil
+ }
+
if equality.Semantic.DeepEqual(we, &WhenExpression{}) || we == nil {
return apis.ErrMissingField(apis.CurrentField)
}
diff --git a/pkg/apis/pipeline/v1beta1/when_validation_test.go b/pkg/apis/pipeline/v1beta1/when_validation_test.go
index ac23b41d402..e02f99a9b7a 100644
--- a/pkg/apis/pipeline/v1beta1/when_validation_test.go
+++ b/pkg/apis/pipeline/v1beta1/when_validation_test.go
@@ -17,8 +17,10 @@ limitations under the License.
package v1beta1
import (
+ "context"
"testing"
+ "github.com/tektoncd/pipeline/pkg/apis/config"
"k8s.io/apimachinery/pkg/selection"
)
@@ -54,7 +56,7 @@ func TestWhenExpressions_Valid(t *testing.T) {
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- if err := tt.wes.validate(); err != nil {
+ if err := tt.wes.validate(context.Background()); err != nil {
t.Errorf("WhenExpressions.validate() returned an error for valid when expressions: %s", tt.wes)
}
})
@@ -97,9 +99,68 @@ func TestWhenExpressions_Invalid(t *testing.T) {
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- if err := tt.wes.validate(); err == nil {
+ if err := tt.wes.validate(context.Background()); err == nil {
t.Errorf("WhenExpressions.validate() did not return error for invalid when expressions: %s, %s", tt.wes, err)
}
})
}
}
+
+func TestCELinWhenExpressions_Valid(t *testing.T) {
+ ctx := config.ToContext(context.Background(), &config.Config{
+ FeatureFlags: &config.FeatureFlags{
+ EnableCELInWhenExpression: true,
+ },
+ })
+ tests := []struct {
+ name string
+ wes WhenExpressions
+ }{{
+ name: "valid cel",
+ wes: []WhenExpression{{
+ CEL: " 'foo' == 'foo' ",
+ }},
+ }}
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if err := tt.wes.validate(ctx); err != nil {
+ t.Errorf("WhenExpressions.validate() returned an error: %s for valid when expressions: %s", err, tt.wes)
+ }
+ })
+ }
+}
+
+func TestCELWhenExpressions_Invalid(t *testing.T) {
+ tests := []struct {
+ name string
+ wes WhenExpressions
+ enableFeatureFlag bool
+ }{{
+ name: "feature flag not set",
+ wes: []WhenExpression{{
+ CEL: " 'foo' == 'foo' ",
+ }},
+ enableFeatureFlag: false,
+ }, {
+ name: "CEL should not coexist with input+operator+values",
+ wes: []WhenExpression{{
+ CEL: "'foo' != 'foo'",
+ Input: "foo",
+ Operator: selection.In,
+ Values: []string{"foo"},
+ }},
+ enableFeatureFlag: true,
+ }}
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ctx := config.ToContext(context.Background(), &config.Config{
+ FeatureFlags: &config.FeatureFlags{
+ EnableCELInWhenExpression: tt.enableFeatureFlag,
+ },
+ })
+ if err := tt.wes.validate(ctx); err == nil {
+ t.Errorf("WhenExpressions.validate() did not return error for invalid when expressions: %s", tt.wes)
+ }
+ })
+ }
+}