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 authored and tekton-robot committed Oct 20, 2023
1 parent 5066b40 commit 4026876
Show file tree
Hide file tree
Showing 22 changed files with 292 additions and 54 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"
1 change: 1 addition & 0 deletions docs/additional-configs.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ Features currently in "alpha" are:
| [Configure Default Resolver](./resolution.md#configuring-built-in-resolvers) | [TEP-0133](https://github.com/tektoncd/community/blob/main/teps/0133-configure-default-resolver.md) | N/A | |
| [Coschedule](./affinityassistants.md) | [TEP-0135](https://github.com/tektoncd/community/blob/main/teps/0135-coscheduling-pipelinerun-pods.md) | N/A |`coschedule` |
| [keep pod on cancel](./taskruns.md#cancelling-a-taskrun) | N/A | v0.52 | keep-pod-on-cancel |
| [CEL in WhenExpression](./taskruns.md#cancelling-a-taskrun) | [TEP-0145](https://github.com/tektoncd/community/blob/main/teps/0145-cel-in-whenexpression.md) | N/A | enable-cel-in-whenexpression |

### Beta Features

Expand Down
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
10 changes: 9 additions & 1 deletion docs/pipelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,7 @@ If the result is **NOT** initialized before failing, and there is a `PipelineTas
```

- If the consuming `PipelineTask` has `OnError:stopAndFail`, the `PipelineRun` will fail with `InvalidTaskResultReference`.
- If the consuming `PipelineTask` has `OnError:continue`, the consuming `PipelineTask` will be skipped with reason `Results were missing`,
- If the consuming `PipelineTask` has `OnError:continue`, the consuming `PipelineTask` will be skipped with reason `Results were missing`,
and the `PipelineRun` will continue to execute.

### Guard `Task` execution using `when` expressions
Expand Down Expand Up @@ -775,6 +775,14 @@ There are a lot of scenarios where `when` expressions can be really useful. Some
- Checking if the name of a CI job matches
- Checking if an optional Workspace has been provided

#### Use CEL expression in WhenExpression

> :seedling: **`CEL in WhenExpression` is an [alpha](install.md#alpha-features) feature.**
> The `enable-cel-in-whenexpression` feature flag must be set to `"true"` to enable the use of `CEL` in `WhenExpression`.
>
> :warning: This feature is in a preview mode.
> It is still in a very early stage of development and is not yet fully functional

#### Guarding a `Task` and its dependent `Tasks`

To guard a `Task` and its dependent Tasks:
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 @@ -761,12 +761,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
Loading

0 comments on commit 4026876

Please sign in to comment.