Skip to content

Commit 22ef994

Browse files
[TEP-0144] Add enum API field
Part of [tektoncd#7270][tektoncd#7270]. In [TEP-0144][tep-0144] we proposed a new `enum` field to support built-in param input validation. This commit adds the `Enum` api field, validation and conversion logic. /kind feature [tektoncd#7270]: tektoncd#7270 [tep-0144]: https://github.com/tektoncd/community/blob/main/teps/0144-param-enum.md
1 parent f0c7ed5 commit 22ef994

27 files changed

+742
-222
lines changed

config/config-feature-flags.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,6 @@ data:
124124
# Setting this flag to "true" will enable the CEL evaluation in WhenExpression
125125
# This feature is in preview mode and not implemented yet. Please check #7244 for the updates.
126126
enable-cel-in-whenexpression: "false"
127+
# Setting this flag to "true" will enable the built-in param input validation via param enum.
128+
# NOTE (#7270): this feature is still under development and not yet functional.
129+
enable-param-enum: "false"

docs/pipeline-api.md

+26
Original file line numberDiff line numberDiff line change
@@ -1691,6 +1691,19 @@ default is set, a Task may be executed without a supplied value for the
16911691
parameter.</p>
16921692
</td>
16931693
</tr>
1694+
<tr>
1695+
<td>
1696+
<code>enum</code><br/>
1697+
<em>
1698+
[]string
1699+
</em>
1700+
</td>
1701+
<td>
1702+
<em>(Optional)</em>
1703+
<p>Enum declares a set of allowed param input values for tasks/pipelines that can be validated.
1704+
If Enum is not set, no input validation is performed for the param.</p>
1705+
</td>
1706+
</tr>
16941707
</tbody>
16951708
</table>
16961709
<h3 id="tekton.dev/v1.ParamSpecs">ParamSpecs
@@ -9668,6 +9681,19 @@ default is set, a Task may be executed without a supplied value for the
96689681
parameter.</p>
96699682
</td>
96709683
</tr>
9684+
<tr>
9685+
<td>
9686+
<code>enum</code><br/>
9687+
<em>
9688+
[]string
9689+
</em>
9690+
</td>
9691+
<td>
9692+
<em>(Optional)</em>
9693+
<p>Enum declares a set of allowed param input values for tasks/pipelines that can be validated.
9694+
If Enum is not set, no input validation is performed for the param.</p>
9695+
</td>
9696+
</tr>
96719697
</tbody>
96729698
</table>
96739699
<h3 id="tekton.dev/v1beta1.ParamSpecs">ParamSpecs

pkg/apis/config/feature_flags.go

+8
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ const (
9696
EnableCELInWhenExpression = "enable-cel-in-whenexpression"
9797
// DefaultEnableCELInWhenExpression is the default value for EnableCELInWhenExpression
9898
DefaultEnableCELInWhenExpression = false
99+
// EnableParamEnum is the flag to enabled enum in params
100+
EnableParamEnum = "enable-param-enum"
101+
// DefaultEnableParamEnum is the default value for EnableParamEnum
102+
DefaultEnableParamEnum = false
99103

100104
disableAffinityAssistantKey = "disable-affinity-assistant"
101105
disableCredsInitKey = "disable-creds-init"
@@ -145,6 +149,7 @@ type FeatureFlags struct {
145149
SetSecurityContext bool
146150
Coschedule string
147151
EnableCELInWhenExpression bool
152+
EnableParamEnum bool
148153
}
149154

150155
// GetFeatureFlagsConfigName returns the name of the configmap containing all
@@ -220,6 +225,9 @@ func NewFeatureFlagsFromMap(cfgMap map[string]string) (*FeatureFlags, error) {
220225
if err := setFeature(EnableCELInWhenExpression, DefaultEnableCELInWhenExpression, &tc.EnableCELInWhenExpression); err != nil {
221226
return nil, err
222227
}
228+
if err := setFeature(EnableParamEnum, DefaultEnableParamEnum, &tc.EnableParamEnum); err != nil {
229+
return nil, err
230+
}
223231
// Given that they are alpha features, Tekton Bundles and Custom Tasks should be switched on if
224232
// enable-api-fields is "alpha". If enable-api-fields is not "alpha" then fall back to the value of
225233
// each feature's individual flag.

pkg/apis/config/feature_flags_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ func TestNewFeatureFlagsFromConfigMap(t *testing.T) {
7474
SetSecurityContext: true,
7575
Coschedule: config.CoscheduleDisabled,
7676
EnableCELInWhenExpression: true,
77+
EnableParamEnum: true,
7778
},
7879
fileName: "feature-flags-all-flags-set",
7980
},
@@ -273,6 +274,9 @@ func TestNewFeatureFlagsConfigMapErrors(t *testing.T) {
273274
}, {
274275
fileName: "feature-flags-invalid-enable-cel-in-whenexpression",
275276
want: `failed parsing feature flags config "invalid": strconv.ParseBool: parsing "invalid": invalid syntax`,
277+
}, {
278+
fileName: "feature-flags-invalid-enable-param-enum",
279+
want: `failed parsing feature flags config "invalid": strconv.ParseBool: parsing "invalid": invalid syntax`,
276280
}} {
277281
t.Run(tc.fileName, func(t *testing.T) {
278282
cm := test.ConfigMapFromTestFile(t, tc.fileName)

pkg/apis/config/testdata/feature-flags-all-flags-set.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,4 @@ data:
3333
set-security-context: "true"
3434
keep-pod-on-cancel: "true"
3535
enable-cel-in-whenexpression: "true"
36+
enable-param-enum: "true"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright 2023 The Tekton Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
apiVersion: v1
16+
kind: ConfigMap
17+
metadata:
18+
name: feature-flags
19+
namespace: tekton-pipelines
20+
data:
21+
enable-param-enum: "invalid"

pkg/apis/pipeline/v1/openapi_generated.go

+15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/apis/pipeline/v1/param_types.go

+39-9
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"fmt"
2323
"strings"
2424

25+
"github.com/tektoncd/pipeline/pkg/apis/config"
2526
"github.com/tektoncd/pipeline/pkg/substitution"
2627
corev1 "k8s.io/api/core/v1"
2728
"k8s.io/apimachinery/pkg/util/sets"
@@ -53,6 +54,10 @@ type ParamSpec struct {
5354
// parameter.
5455
// +optional
5556
Default *ParamValue `json:"default,omitempty"`
57+
// Enum declares a set of allowed param input values for tasks/pipelines that can be validated.
58+
// If Enum is not set, no input validation is performed for the param.
59+
// +optional
60+
Enum []string `json:"enum,omitempty"`
5661
}
5762

5863
// ParamSpecs is a list of ParamSpec
@@ -132,22 +137,47 @@ func (ps ParamSpecs) sortByType() (ParamSpecs, ParamSpecs, ParamSpecs) {
132137

133138
// validateNoDuplicateNames returns an error if any of the params have the same name
134139
func (ps ParamSpecs) validateNoDuplicateNames() *apis.FieldError {
140+
var errs *apis.FieldError
135141
names := ps.getNames()
136-
seen := sets.String{}
137-
dups := sets.String{}
142+
for dup := range findDups(names) {
143+
errs = errs.Also(apis.ErrGeneric("parameter appears more than once", "").ViaFieldKey("params", dup))
144+
}
145+
return errs
146+
}
147+
148+
// validateParamEnum validates feature flag, duplication and allowed types for Param Enum
149+
func (ps ParamSpecs) validateParamEnums(ctx context.Context) *apis.FieldError {
138150
var errs *apis.FieldError
139-
for _, n := range names {
140-
if seen.Has(n) {
141-
dups.Insert(n)
151+
for _, p := range ps {
152+
if len(p.Enum) == 0 {
153+
continue
154+
}
155+
if !config.FromContextOrDefaults(ctx).FeatureFlags.EnableParamEnum {
156+
errs = errs.Also(errs, apis.ErrGeneric(fmt.Sprintf("feature flag %s should be set to true to use Enum", config.EnableParamEnum), "").ViaFieldKey("params", p.Name))
157+
}
158+
if p.Type != ParamTypeString {
159+
errs = errs.Also(apis.ErrGeneric("enum can only be set with string type param", "").ViaFieldKey("params", p.Name))
160+
}
161+
for dup := range findDups(p.Enum) {
162+
errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("parameter enum value %v appears more than once", dup), "").ViaFieldKey("params", p.Name))
142163
}
143-
seen.Insert(n)
144-
}
145-
for n := range dups {
146-
errs = errs.Also(apis.ErrGeneric("parameter appears more than once", "").ViaFieldKey("params", n))
147164
}
148165
return errs
149166
}
150167

168+
// findDups returns the duplicate element in the given slice
169+
func findDups(vals []string) sets.String {
170+
seen := sets.String{}
171+
dups := sets.String{}
172+
for _, val := range vals {
173+
if seen.Has(val) {
174+
dups.Insert(val)
175+
}
176+
seen.Insert(val)
177+
}
178+
return dups
179+
}
180+
151181
// Param declares an ParamValues to use for the parameter called name.
152182
type Param struct {
153183
Name string `json:"name"`

pkg/apis/pipeline/v1/pipeline_types_test.go

+30-17
Original file line numberDiff line numberDiff line change
@@ -508,10 +508,9 @@ func TestPipelineTask_ValidateCustomTask(t *testing.T) {
508508

509509
func TestPipelineTask_ValidateRegularTask_Success(t *testing.T) {
510510
tests := []struct {
511-
name string
512-
tasks PipelineTask
513-
enableAlphaAPIFields bool
514-
enableBetaAPIFields bool
511+
name string
512+
tasks PipelineTask
513+
configMap map[string]string
515514
}{{
516515
name: "pipeline task - valid taskRef name",
517516
tasks: PipelineTask{
@@ -524,37 +523,51 @@ func TestPipelineTask_ValidateRegularTask_Success(t *testing.T) {
524523
Name: "foo",
525524
TaskSpec: &EmbeddedTask{TaskSpec: getTaskSpec()},
526525
},
526+
}, {
527+
name: "pipeline task - valid taskSpec with param enum",
528+
tasks: PipelineTask{
529+
Name: "foo",
530+
TaskSpec: &EmbeddedTask{
531+
TaskSpec: TaskSpec{
532+
Steps: []Step{
533+
{
534+
Name: "foo",
535+
Image: "bar",
536+
},
537+
},
538+
Params: []ParamSpec{
539+
{
540+
Name: "param1",
541+
Type: ParamTypeString,
542+
Enum: []string{"v1", "v2"},
543+
},
544+
},
545+
},
546+
},
547+
},
548+
configMap: map[string]string{"enable-param-enum": "true"},
527549
}, {
528550
name: "pipeline task - use of resolver with the feature flag set",
529551
tasks: PipelineTask{
530552
TaskRef: &TaskRef{ResolverRef: ResolverRef{Resolver: "bar"}},
531553
},
532-
enableBetaAPIFields: true,
554+
configMap: map[string]string{"enable-api-field": "beta"},
533555
}, {
534556
name: "pipeline task - use of resolver with the feature flag set to alpha",
535557
tasks: PipelineTask{
536558
TaskRef: &TaskRef{ResolverRef: ResolverRef{Resolver: "bar"}},
537559
},
538-
enableAlphaAPIFields: true,
560+
configMap: map[string]string{"enable-api-field": "alpha"},
539561
}, {
540562
name: "pipeline task - use of resolver params with the feature flag set",
541563
tasks: PipelineTask{
542564
TaskRef: &TaskRef{ResolverRef: ResolverRef{Resolver: "bar", Params: Params{{}}}},
543565
},
544-
enableBetaAPIFields: true,
566+
configMap: map[string]string{"enable-api-field": "beta"},
545567
}}
546568
for _, tt := range tests {
547569
t.Run(tt.name, func(t *testing.T) {
548-
ctx := context.Background()
549-
cfg := &config.Config{
550-
FeatureFlags: &config.FeatureFlags{},
551-
}
552-
if tt.enableAlphaAPIFields {
553-
cfg.FeatureFlags.EnableAPIFields = config.AlphaAPIFields
554-
} else if tt.enableBetaAPIFields {
555-
cfg.FeatureFlags.EnableAPIFields = config.BetaAPIFields
556-
}
557-
ctx = config.ToContext(ctx, cfg)
570+
ctx := cfgtesting.SetFeatureFlags(context.Background(), t, tt.configMap)
558571
err := tt.tasks.validateTask(ctx)
559572
if err != nil {
560573
t.Errorf("PipelineTask.validateTask() returned error for valid pipeline task: %v", err)

pkg/apis/pipeline/v1/pipeline_validation.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -437,11 +437,12 @@ func validatePipelineTasksWorkspacesUsage(wss []PipelineWorkspaceDeclaration, pt
437437

438438
// ValidatePipelineParameterVariables validates parameters with those specified by each pipeline task,
439439
// (1) it validates the type of parameter is either string or array (2) parameter default value matches
440-
// with the type of that param
440+
// with the type of that param (3) no duplicateion, feature flag and allowed param type when using param enum
441441
func ValidatePipelineParameterVariables(ctx context.Context, tasks []PipelineTask, params ParamSpecs) (errs *apis.FieldError) {
442442
// validates all the types within a slice of ParamSpecs
443443
errs = errs.Also(ValidateParameterTypes(ctx, params).ViaField("params"))
444444
errs = errs.Also(params.validateNoDuplicateNames())
445+
errs = errs.Also(params.validateParamEnums(ctx))
445446
for i, task := range tasks {
446447
errs = errs.Also(task.Params.validateDuplicateParameters().ViaField("params").ViaIndex(i))
447448
}

0 commit comments

Comments
 (0)