Skip to content

Commit a9ff6b5

Browse files
[TEP-0144] Validate PipelineRun for Param Enum
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 validation logic for PipelineRun against Param Enum /kind feature [tektoncd#7270]: tektoncd#7270 [tep-0144]: https://github.com/tektoncd/community/blob/main/teps/0144-param-enum.md add pr validation
1 parent 515c4a3 commit a9ff6b5

File tree

7 files changed

+372
-1
lines changed

7 files changed

+372
-1
lines changed

docs/pipelineruns.md

+14
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,20 @@ case is when your CI system autogenerates `PipelineRuns` and it has `Parameters`
271271
provide to all `PipelineRuns`. Because you can pass in extra `Parameters`, you don't have to
272272
go through the complexity of checking each `Pipeline` and providing only the required params.
273273

274+
#### Parameter Enums
275+
276+
> :seedling: **Specifying `enum` is an [alpha](additional-configs.md#alpha-features) feature.** The `enable-param-enum` feature flag must be set to `"true"` to enable this feature.
277+
278+
> :seedling: This feature is WIP and not yet supported/implemented. Documentation to be completed.
279+
280+
If a `Parameter` is guarded by `Enum` in the `Pipeline`, you can only provide `Parameter` values in the `PipelineRun` that are predefined in the `Param.Enum` in the `Pipeline`. The `PipelineRun` will fail with reason `InvalidParamValue` otherwise.
281+
282+
Tekton will also the validate the `param` values passed to any referenced `Tasks` (vis `taskRef`) if `Enum` is specified for the `Task`. The `PipelineRun` will fail with reason `InvalidParamValue` if `Enum` validation is failed for any of the `PipelineTask`.
283+
284+
You can also specify `Enum` for `PipelineRun` with an embedded `Pipeline`. The same param validation will be executed in this scenario.
285+
286+
See more details in [Param.Enum](./pipelines.md#param-enum).
287+
274288
#### Propagated Parameters
275289

276290
When using an inlined spec, parameters from the parent `PipelineRun` will be

docs/pipelines.md

+64-1
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,70 @@ spec:
276276

277277
> :seedling: This feature is WIP and not yet supported/implemented. Documentation to be completed.
278278

279-
Parameter declarations can include `enum` which is a predefine set of valid values that can be accepted by the `Pipeline`.
279+
Parameter declarations can include `enum` which is a predefine set of valid values that can be accepted by the `Pipeline` `Param`. For example, the valid/allowed values for `Param` "message" is bounded to `v1` and `v2`:
280+
281+
``` yaml
282+
apiVersion: tekton.dev/v1
283+
kind: Pipeline
284+
metadata:
285+
name: pipeline-param-enum
286+
spec:
287+
params:
288+
- name: message
289+
enum: ["v1", "v2"]
290+
default: "v1"
291+
tasks:
292+
- name: task1
293+
params:
294+
- name: message
295+
value: $(params.message)
296+
steps:
297+
- name: build
298+
image: bash:3.2
299+
script: |
300+
echo "$(params.message)"
301+
```
302+
303+
If the `Param` value passed in by `PipelineRun` is **NOT** in the predefined `enum` list, the `PipelineRun` will fail with reason `InvalidParamValue`.
304+
305+
If a `PipelineTask` references a `Task` with `enum`, Tekton validates the **intersection** of enum specified in the referenced `Task` and the enum specified in the Pipeline `spec.params`. In the example below, the referenced `Task` accepts `v1` and `v2` as valid values, and the `Pipeline` accepts `v2` and `v3` as valid values. Only passing `v3` in the `PipelineRun` will lead to a sucessful execution.
306+
307+
``` yaml
308+
apiVersion: tekton.dev/v1
309+
kind: Task
310+
metadata:
311+
name: param-enum-demo
312+
spec:
313+
params:
314+
- name: message
315+
type: string
316+
enum: ["v1", "v2"]
317+
steps:
318+
- name: build
319+
image: bash:latest
320+
script: |
321+
echo "$(params.message)"
322+
```
323+
324+
``` yaml
325+
apiVersion: tekton.dev/v1
326+
kind: Pipeline
327+
metadata:
328+
name: pipeline-param-enum
329+
spec:
330+
params:
331+
- name: message
332+
enum: ["v2", "v3"]
333+
tasks:
334+
- name: task1
335+
params:
336+
- name: message
337+
value: $(params.message)
338+
taskRef:
339+
name: param-enum-demo
340+
```
341+
342+
See usage in this [example](../examples/v1/pipelineruns/alpha/param-enum.yaml)
280343

281344
## Adding `Tasks` to the `Pipeline`
282345

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
apiVersion: tekton.dev/v1
2+
kind: Pipeline
3+
metadata:
4+
name: pipeline-param-enum
5+
spec:
6+
params:
7+
- name: message
8+
enum: ["v1", "v2", "v3"]
9+
default: "v1"
10+
tasks:
11+
- name: task1
12+
params:
13+
- name: message
14+
value: $(params.message)
15+
steps:
16+
- name: build
17+
image: bash:3.2
18+
script: |
19+
echo "$(params.message)"
20+
---
21+
apiVersion: tekton.dev/v1
22+
kind: PipelineRun
23+
metadata:
24+
name: pipelinerun-param-enum
25+
spec:
26+
pipelineRef:
27+
name: pipeline-param-enum
28+
params:
29+
- name: message
30+
value: "v2"

pkg/apis/pipeline/v1/pipelinerun_types.go

+2
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,8 @@ const (
409409
PipelineRunReasonCreateRunFailed PipelineRunReason = "CreateRunFailed"
410410
// ReasonCELEvaluationFailed indicates the pipeline fails the CEL evaluation
411411
PipelineRunReasonCELEvaluationFailed PipelineRunReason = "CELEvaluationFailed"
412+
// PipelineRunReasonInvalidParamValue indicates that the PipelineRun Param input value is not allowed.
413+
PipelineRunReasonInvalidParamValue PipelineRunReason = "InvalidParamValue"
412414
)
413415

414416
func (t PipelineRunReason) String() string {

pkg/reconciler/pipelinerun/pipelinerun.go

+22
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,18 @@ func (c *Reconciler) resolvePipelineState(
387387
return nil, controller.NewPermanentError(err)
388388
}
389389
}
390+
391+
cfg := config.FromContextOrDefaults(ctx)
392+
if cfg.FeatureFlags.EnableParamEnum {
393+
if len(resolvedTask.TaskRuns) > 0 && len(resolvedTask.TaskRuns[0].Status.Conditions) > 0 {
394+
cond := resolvedTask.TaskRuns[0].Status.Conditions[0]
395+
if cond.Status == corev1.ConditionFalse && cond.Reason == v1.TaskRunReasonInvalidParamValue {
396+
pr.Status.MarkFailed(v1.PipelineRunReasonInvalidParamValue.String(),
397+
"Invalid param value in the referenced Task from PipelineTask \"%s\": %s", resolvedTask.PipelineTask.Name, cond.Message)
398+
return nil, controller.NewPermanentError(err)
399+
}
400+
}
401+
}
390402
pst = append(pst, resolvedTask)
391403
}
392404
return pst, nil
@@ -487,6 +499,16 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1.PipelineRun, getPipel
487499
return controller.NewPermanentError(err)
488500
}
489501

502+
if config.FromContextOrDefaults(ctx).FeatureFlags.EnableParamEnum {
503+
if err := taskrun.ValidateEnumParam(ctx, pr.Spec.Params, pipelineSpec.Params); err != nil {
504+
logger.Errorf("PipelineRun %q Param Enum validation failed: %v", pr.Name, err)
505+
pr.Status.MarkFailed(v1.PipelineRunReasonInvalidParamValue.String(),
506+
"PipelineRun %s/%s parameters have invalid value: %s",
507+
pr.Namespace, pr.Name, err)
508+
return controller.NewPermanentError(err)
509+
}
510+
}
511+
490512
// Ensure that the keys of an object param declared in PipelineSpec are not missed in the PipelineRunSpec
491513
if err = resources.ValidateObjectParamRequiredKeys(pipelineSpec.Params, pr.Spec.Params); err != nil {
492514
// This Run has failed, so we need to mark it as failed and stop reconciling it

pkg/reconciler/pipelinerun/pipelinerun_test.go

+119
Original file line numberDiff line numberDiff line change
@@ -4278,6 +4278,125 @@ spec:
42784278
checkPipelineRunConditionStatusAndReason(t, pipelineRun, corev1.ConditionFalse, string(v1.PipelineRunReasonCELEvaluationFailed))
42794279
}
42804280

4281+
func TestReconcile_Pipeline_Level_Enum_Failed(t *testing.T) {
4282+
ps := []*v1.Pipeline{parse.MustParseV1Pipeline(t, `
4283+
metadata:
4284+
name: test-pipeline-level-enum
4285+
namespace: foo
4286+
spec:
4287+
params:
4288+
- name: version
4289+
type: string
4290+
enum: ["v1", "v2"]
4291+
tasks:
4292+
- name: a-task
4293+
taskSpec:
4294+
name: a-task
4295+
params:
4296+
- name: version
4297+
steps:
4298+
- name: s1
4299+
image: alpine
4300+
script: |
4301+
echo $(params.version)
4302+
`)}
4303+
prs := []*v1.PipelineRun{parse.MustParseV1PipelineRun(t, `
4304+
metadata:
4305+
name: test-pipeline-level-enum-run
4306+
namespace: foo
4307+
spec:
4308+
params:
4309+
- name: version
4310+
value: "v3"
4311+
pipelineRef:
4312+
name: test-pipeline-level-enum
4313+
`)}
4314+
cms := []*corev1.ConfigMap{
4315+
{
4316+
ObjectMeta: metav1.ObjectMeta{Name: config.GetFeatureFlagsConfigName(), Namespace: system.Namespace()},
4317+
Data: map[string]string{
4318+
"enable-param-enum": "true",
4319+
},
4320+
},
4321+
}
4322+
d := test.Data{
4323+
PipelineRuns: prs,
4324+
Pipelines: ps,
4325+
ConfigMaps: cms,
4326+
}
4327+
prt := newPipelineRunTest(t, d)
4328+
defer prt.Cancel()
4329+
pipelineRun, _ := prt.reconcileRun("foo", "test-pipeline-level-enum-run", []string{}, true)
4330+
checkPipelineRunConditionStatusAndReason(t, pipelineRun, corev1.ConditionFalse, string(v1.PipelineRunReasonInvalidParamValue))
4331+
}
4332+
4333+
func TestReconcile_PipelineTask_Level_Enum_Failed(t *testing.T) {
4334+
ps := []*v1.Pipeline{parse.MustParseV1Pipeline(t, `
4335+
metadata:
4336+
name: test-pipelineTask-level-enum
4337+
namespace: foo
4338+
spec:
4339+
params:
4340+
- name: version
4341+
type: string
4342+
tasks:
4343+
- name: a-task
4344+
params:
4345+
- name: version
4346+
value: $(params.version)
4347+
taskSpec:
4348+
name: a-task
4349+
params:
4350+
- name: version
4351+
enum: ["v1", "v2"]
4352+
steps:
4353+
- name: s1
4354+
image: alpine
4355+
script: |
4356+
echo $(params.version)
4357+
`)}
4358+
prs := []*v1.PipelineRun{parse.MustParseV1PipelineRun(t, `
4359+
metadata:
4360+
name: test-pipelineTask-level-enum-run
4361+
namespace: foo
4362+
spec:
4363+
params:
4364+
- name: version
4365+
value: "v3"
4366+
pipelineRef:
4367+
name: test-pipelineTask-level-enum
4368+
`)}
4369+
4370+
trs := []*v1.TaskRun{mustParseTaskRunWithObjectMeta(t,
4371+
taskRunObjectMeta("test-pipelineTask-level-enum-a-task", "foo",
4372+
"test-pipelineTask-level-enum-run", "test-pipelineTask-level-enum", "a-task", true),
4373+
`
4374+
status:
4375+
conditions:
4376+
- status: "False"
4377+
type: Succeeded
4378+
reason: "InvalidParamValue"
4379+
`)}
4380+
cms := []*corev1.ConfigMap{
4381+
{
4382+
ObjectMeta: metav1.ObjectMeta{Name: config.GetFeatureFlagsConfigName(), Namespace: system.Namespace()},
4383+
Data: map[string]string{
4384+
"enable-param-enum": "true",
4385+
},
4386+
},
4387+
}
4388+
d := test.Data{
4389+
PipelineRuns: prs,
4390+
Pipelines: ps,
4391+
ConfigMaps: cms,
4392+
TaskRuns: trs,
4393+
}
4394+
prt := newPipelineRunTest(t, d)
4395+
defer prt.Cancel()
4396+
pipelineRun, _ := prt.reconcileRun("foo", "test-pipelineTask-level-enum-run", []string{}, true)
4397+
checkPipelineRunConditionStatusAndReason(t, pipelineRun, corev1.ConditionFalse, string(v1.PipelineRunReasonInvalidParamValue))
4398+
}
4399+
42814400
// TestReconcileWithAffinityAssistantStatefulSet tests that given a pipelineRun with workspaces,
42824401
// an Affinity Assistant StatefulSet is created for each PVC workspace and
42834402
// that the Affinity Assistant names is propagated to TaskRuns.

0 commit comments

Comments
 (0)