Skip to content

Commit

Permalink
[TEP-0145] Add CEL field to WhenExpression, and feature flag to guard…
Browse files Browse the repository at this point in the history
… the field

add cel to the WhenExpression, a feature flag
enable-cel-in-whenexpression to guard thie new api field.

Signed-off-by: Yongxuan Zhang [email protected]
  • Loading branch information
Yongxuanzhang committed Oct 19, 2023
1 parent a2cf8d0 commit 0322e41
Show file tree
Hide file tree
Showing 20 changed files with 258 additions and 53 deletions.
3 changes: 3 additions & 0 deletions config/config-feature-flags.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
28 changes: 28 additions & 0 deletions docs/pipeline-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5782,6 +5782,20 @@ k8s.io/apimachinery/pkg/selection.Operator
It must be non-empty</p>
</td>
</tr>
<tr>
<td>
<code>cel</code><br/>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>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: <a href="https://github.com/google/cel-spec/blob/master/doc/langdef.md">https://github.com/google/cel-spec/blob/master/doc/langdef.md</a></p>
</td>
</tr>
</tbody>
</table>
<h3 id="tekton.dev/v1.WhenExpressions">WhenExpressions
Expand Down Expand Up @@ -14549,6 +14563,20 @@ k8s.io/apimachinery/pkg/selection.Operator
It must be non-empty</p>
</td>
</tr>
<tr>
<td>
<code>cel</code><br/>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>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: <a href="https://github.com/google/cel-spec/blob/master/doc/langdef.md">https://github.com/google/cel-spec/blob/master/doc/langdef.md</a></p>
</td>
</tr>
</tbody>
</table>
<h3 id="tekton.dev/v1beta1.WhenExpressions">WhenExpressions
Expand Down
9 changes: 8 additions & 1 deletion pkg/apis/config/feature_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -140,6 +144,7 @@ type FeatureFlags struct {
MaxResultSize int
SetSecurityContext bool
Coschedule string
EnableCelInWhenExpression bool
}

// GetFeatureFlagsConfigName returns the name of the configmap containing all
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/config/feature_flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func TestNewFeatureFlagsFromConfigMap(t *testing.T) {
MaxResultSize: 4096,
SetSecurityContext: true,
Coschedule: config.CoscheduleDisabled,
EnableCelInWhenExpression: true,
},
fileName: "feature-flags-all-flags-set",
},
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions pkg/apis/config/testdata/feature-flags-all-flags-set.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ data:
enable-provenance-in-status: "false"
set-security-context: "true"
keep-pod-on-cancel: "true"
enable-cel-in-whenexpression: "true"
Original file line number Diff line number Diff line change
@@ -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"
10 changes: 7 additions & 3 deletions pkg/apis/pipeline/v1/openapi_generated.go

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

8 changes: 4 additions & 4 deletions pkg/apis/pipeline/v1/pipeline_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
15 changes: 6 additions & 9 deletions pkg/apis/pipeline/v1/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 9 additions & 3 deletions pkg/apis/pipeline/v1/when_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
22 changes: 17 additions & 5 deletions pkg/apis/pipeline/v1/when_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
}
Expand Down
41 changes: 39 additions & 2 deletions pkg/apis/pipeline/v1/when_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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)
}
})
Expand Down Expand Up @@ -97,9 +99,44 @@ 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 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)
}
})
}
}
10 changes: 7 additions & 3 deletions pkg/apis/pipeline/v1beta1/openapi_generated.go

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

2 changes: 2 additions & 0 deletions pkg/apis/pipeline/v1beta1/pipeline_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit 0322e41

Please sign in to comment.