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 c08dc62 commit d48d115
Show file tree
Hide file tree
Showing 17 changed files with 356 additions and 54 deletions.
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
33 changes: 27 additions & 6 deletions pkg/apis/pipeline/v1/when_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ limitations under the License.
package v1

import (
"context"
"fmt"
"strings"

_ "github.com/google/cel-go/cel"
"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 @@ -32,18 +34,37 @@ 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.ErrMultipleOneOf("cel", "values")
}
env, err := cel.NewEnv(cel.Declarations())
if err != nil {
return apis.ErrGeneric("err: %s", err.Error())
}
_, iss := env.Compile(we.CEL)
if iss.Err() != nil {
fmt.Println("CEL expression could not be parsed", iss.Err())
return apis.ErrGeneric("invalid cel expression: %s with err: %s", we.CEL, iss.Err().Error())
}
return nil
}

if equality.Semantic.DeepEqual(we, &WhenExpression{}) || we == nil {
return apis.ErrMissingField(apis.CurrentField)
}
Expand Down
Loading

0 comments on commit d48d115

Please sign in to comment.