Skip to content

Commit 3e6b9e3

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 08ee164 commit 3e6b9e3

27 files changed

+746
-227
lines changed

config/config-feature-flags.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,6 @@ data:
127127
# Setting this flag to "true" will enable the use of StepActions in Steps
128128
# This feature is in preview mode and not implemented yet. Please check #7259 for updates.
129129
enable-step-actions: "false"
130+
# Setting this flag to "true" will enable the built-in param input validation via param enum.
131+
# NOTE (#7270): this feature is still under development and not yet functional.
132+
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
@@ -9917,6 +9930,19 @@ default is set, a Task may be executed without a supplied value for the
99179930
parameter.</p>
99189931
</td>
99199932
</tr>
9933+
<tr>
9934+
<td>
9935+
<code>enum</code><br/>
9936+
<em>
9937+
[]string
9938+
</em>
9939+
</td>
9940+
<td>
9941+
<em>(Optional)</em>
9942+
<p>Enum declares a set of allowed param input values for tasks/pipelines that can be validated.
9943+
If Enum is not set, no input validation is performed for the param.</p>
9944+
</td>
9945+
</tr>
99209946
</tbody>
99219947
</table>
99229948
<h3 id="tekton.dev/v1beta1.ParamSpecs">ParamSpecs

pkg/apis/config/feature_flags.go

+8
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ const (
100100
EnableStepActions = "enable-step-actions"
101101
// DefaultEnableStepActions is the default value for EnableStepActions
102102
DefaultEnableStepActions = false
103+
// EnableParamEnum is the flag to enabled enum in params
104+
EnableParamEnum = "enable-param-enum"
105+
// DefaultEnableParamEnum is the default value for EnableParamEnum
106+
DefaultEnableParamEnum = false
103107

104108
disableAffinityAssistantKey = "disable-affinity-assistant"
105109
disableCredsInitKey = "disable-creds-init"
@@ -150,6 +154,7 @@ type FeatureFlags struct {
150154
Coschedule string
151155
EnableCELInWhenExpression bool
152156
EnableStepActions bool
157+
EnableParamEnum bool
153158
}
154159

155160
// GetFeatureFlagsConfigName returns the name of the configmap containing all
@@ -228,6 +233,9 @@ func NewFeatureFlagsFromMap(cfgMap map[string]string) (*FeatureFlags, error) {
228233
if err := setFeature(EnableStepActions, DefaultEnableStepActions, &tc.EnableStepActions); err != nil {
229234
return nil, err
230235
}
236+
if err := setFeature(EnableParamEnum, DefaultEnableParamEnum, &tc.EnableParamEnum); err != nil {
237+
return nil, err
238+
}
231239
// Given that they are alpha features, Tekton Bundles and Custom Tasks should be switched on if
232240
// enable-api-fields is "alpha". If enable-api-fields is not "alpha" then fall back to the value of
233241
// each feature's individual flag.

pkg/apis/config/feature_flags_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ func TestNewFeatureFlagsFromConfigMap(t *testing.T) {
7575
Coschedule: config.CoscheduleDisabled,
7676
EnableCELInWhenExpression: true,
7777
EnableStepActions: true,
78+
EnableParamEnum: true,
7879
},
7980
fileName: "feature-flags-all-flags-set",
8081
},
@@ -277,6 +278,9 @@ func TestNewFeatureFlagsConfigMapErrors(t *testing.T) {
277278
}, {
278279
fileName: "feature-flags-invalid-enable-step-actions",
279280
want: `failed parsing feature flags config "invalid": strconv.ParseBool: parsing "invalid": invalid syntax`,
281+
}, {
282+
fileName: "feature-flags-invalid-enable-param-enum",
283+
want: `failed parsing feature flags config "invalid": strconv.ParseBool: parsing "invalid": invalid syntax`,
280284
}} {
281285
t.Run(tc.fileName, func(t *testing.T) {
282286
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
@@ -34,3 +34,4 @@ data:
3434
keep-pod-on-cancel: "true"
3535
enable-cel-in-whenexpression: "true"
3636
enable-step-actions: "true"
37+
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
@@ -433,11 +433,12 @@ func validatePipelineTasksWorkspacesUsage(wss []PipelineWorkspaceDeclaration, pt
433433

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

0 commit comments

Comments
 (0)