Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for reserved default object param #164

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/pipeline-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2029,6 +2029,9 @@ misses some keys required for the object param declared in Pipeline spec.</p>
<td><p>ReasonParamArrayIndexingInvalid indicates that the use of param array indexing is not under correct api fields feature gate
or the array is out of bound.</p>
</td>
</tr><tr><td><p>&#34;ParamKeyNotExistent&#34;</p></td>
<td><p>PipelineRunReasonParamKeyNotExistent indicates that the default object param doesn&rsquo;t have the key which the param reference requires</p>
</td>
</tr><tr><td><p>&#34;ParameterMissing&#34;</p></td>
<td><p>ReasonParameterMissing indicates that the reason for the failure status is that the
associated PipelineRun didn&rsquo;t provide all the required parameters</p>
Expand Down Expand Up @@ -5150,6 +5153,9 @@ TaskRuns failed due to reconciler/validation error should not use this reason.</
</tr><tr><td><p>&#34;InvalidParamValue&#34;</p></td>
<td><p>TaskRunReasonInvalidParamValue indicates that the TaskRun Param input value is not allowed.</p>
</td>
</tr><tr><td><p>&#34;ParamKeyNotExistent&#34;</p></td>
<td><p>TaskRunReasonParamKeyNotExistent indicates that the default object param doesn&rsquo;t have the key which the param reference requires</p>
</td>
</tr><tr><td><p>&#34;ResourceVerificationFailed&#34;</p></td>
<td><p>TaskRunReasonResourceVerificationFailed indicates that the task fails the trusted resource verification,
it could be the content has changed, signature is invalid or public key is invalid</p>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
name: echo-message-pipeline
spec:
params:
- name: hello
properties:
project_id:
type: string
type: object
tasks:
- name: task1
params:
- name: foo
value:
project_id: $(params.hello.project_id)
taskSpec:
params:
- name: foo
type: object
properties:
project_id:
type: string
steps:
- name: echo
image: ubuntu
script: |
#!/bin/bash
echo $(params.provider.project_id)
echo $(params.foo.project_id)
---
kind: PipelineRun
apiVersion: tekton.dev/v1
metadata:
name: test-pipelinerun
spec:
params:
- name: provider
value:
project_id: foo
- name: hello
value:
project_id: foo
pipelineRef:
name: echo-message-pipeline
37 changes: 37 additions & 0 deletions examples/v1/taskruns/alpha/remote-task-ref-reserved-param.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
kind: Task
apiVersion: tekton.dev/v1
metadata:
name: example-task
spec:
params:
- name: foo
type: object
properties:
project_id:
type: string
digest:
type: string
steps:
- image: ubuntu
name: echo
script: |
echo -n $(params.foo.project_id)
echo -n $(params.provider.project_id)
---
kind: TaskRun
apiVersion: tekton.dev/v1
metadata:
name: example-tr
spec:
params:
- name: foo
value:
project_id: foo
digest: bar
- name: provider
value:
project_id: foo
digest: bar
taskRef:
name: example-task
kind: task
10 changes: 6 additions & 4 deletions pkg/apis/pipeline/v1/pipeline_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ var _ apis.Validatable = (*Pipeline)(nil)
var _ resourcesemantics.VerbLimited = (*Pipeline)(nil)

const (
taskRef = "taskRef"
taskSpec = "taskSpec"
pipelineRef = "pipelineRef"
pipelineSpec = "pipelineSpec"
taskRef = "taskRef"
taskSpec = "taskSpec"
pipelineRef = "pipelineRef"
pipelineSpec = "pipelineSpec"
ReservedParamName = "provider"
)

// SupportedVerbs returns the operations that validation should be called for
Expand Down Expand Up @@ -389,6 +390,7 @@ func (ps *PipelineSpec) validatePipelineParameterUsage(ctx context.Context) (err
// validatePipelineTaskParameterUsage validates that parameters referenced in the Pipeline Tasks are declared by the Pipeline
func validatePipelineTaskParameterUsage(tasks []PipelineTask, params ParamSpecs) (errs *apis.FieldError) {
allParamNames := sets.NewString(params.GetNames()...)
allParamNames.Insert(ReservedParamName)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be here we should check that the ReservedParamName is not already in the set. If it is then a user also declared it and it should throw an error since it is reserved?

_, arrayParams, objectParams := params.SortByType()
arrayParamNames := sets.NewString(arrayParams.GetNames()...)
objectParameterNameKeys := map[string][]string{}
Expand Down
19 changes: 19 additions & 0 deletions pkg/apis/pipeline/v1/pipeline_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package v1

import (
"context"
"fmt"
"testing"

"github.com/google/go-cmp/cmp"
Expand Down Expand Up @@ -114,6 +115,24 @@ func TestPipeline_Validate_Success(t *testing.T) {
},
},
},
}, {
name: "valid reserved param",
wc: cfgtesting.EnableAlphaAPIFields,
p: &Pipeline{
ObjectMeta: metav1.ObjectMeta{Name: "pipeline"},
Spec: PipelineSpec{
Tasks: []PipelineTask{{
Name: "pipeline-task-use-reserved-param",
TaskSpec: &EmbeddedTask{TaskSpec: TaskSpec{
Steps: []Step{{
Name: "some-step",
Image: "some-image",
Script: fmt.Sprintf("echo $(params.%s.foo)", ReservedParamName),
}},
}},
}},
},
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
2 changes: 2 additions & 0 deletions pkg/apis/pipeline/v1/pipelinerun_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,8 @@ const (
PipelineRunReasonCELEvaluationFailed PipelineRunReason = "CELEvaluationFailed"
// PipelineRunReasonInvalidParamValue indicates that the PipelineRun Param input value is not allowed.
PipelineRunReasonInvalidParamValue PipelineRunReason = "InvalidParamValue"
// PipelineRunReasonParamKeyNotExistent indicates that the default object param doesn't have the key which the param reference requires
PipelineRunReasonParamKeyNotExistent PipelineRunReason = "ParamKeyNotExistent"
)

// PipelineTaskOnErrorAnnotation is used to pass the failure strategy to TaskRun pods from PipelineTask OnError field
Expand Down
24 changes: 24 additions & 0 deletions pkg/apis/pipeline/v1/task_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ func ValidateUsageOfDeclaredParameters(ctx context.Context, steps []Step, params
var errs *apis.FieldError
_, _, objectParams := params.SortByType()
allParameterNames := sets.NewString(params.GetNames()...)
allParameterNames.Insert(ReservedParamName)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be here we should check that the ReservedParamName is not already in the set. If it is then a user also declared it and it should throw an error since it is reserved?

Copy link
Owner Author

@Yongxuanzhang Yongxuanzhang Dec 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this check is not done from our side, there will be additional validation out of tekton and we're sure that users cannot declare the param with reserved name. :D

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(and we cannot do it from out side for some reason...

errs = errs.Also(validateVariables(ctx, steps, "params", allParameterNames))
errs = errs.Also(validateObjectUsage(ctx, steps, objectParams))
errs = errs.Also(ValidateObjectParamsHaveProperties(ctx, params))
Expand Down Expand Up @@ -762,3 +763,26 @@ func ValidateStepResultsVariables(ctx context.Context, results []StepResult, scr
errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(script, "results", resultsNames).ViaField("script"))
return errs
}

// Reserved Param doesn't have properties, so we need to check all the references if they are using a non-existent key from the param
func ValidateReservedParamReferenceMissingKeys(ctx context.Context, steps []Step, params Params) error {
objectKeys := sets.NewString()
// collect all the existent keys from the object reserved param.
for _, p := range params {
if p.Name == ReservedParamName && p.Value.Type == ParamTypeObject {
for key := range p.Value.ObjectVal {
objectKeys.Insert(key)
}
}
}

// check if the object's key names are referenced correctly i.e. param.objectParam.key1
if len(objectKeys) > 0 {
err := validateVariables(ctx, steps, fmt.Sprintf("params\\.%s", ReservedParamName), objectKeys)
if err != nil {
return err
}
}

return nil
}
146 changes: 146 additions & 0 deletions pkg/apis/pipeline/v1/task_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ func TestTaskValidate(t *testing.T) {
}},
},
},
}, {
name: "valid reserved param",
wc: cfgtesting.EnableAlphaAPIFields,
t: &v1.Task{
ObjectMeta: metav1.ObjectMeta{Name: "task"},
Spec: v1.TaskSpec{
Steps: []v1.Step{{
Name: "some-step",
Image: "some-image",
Script: fmt.Sprintf("echo $(params.%s.foo)", v1.ReservedParamName),
}},
},
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -2726,3 +2739,136 @@ func TestTaskSpecValidate_StepResults_Error(t *testing.T) {
})
}
}

func TestValidateReservedParamReferenceMissingKeys_Success(t *testing.T) {
tests := []struct {
name string
steps []v1.Step
params []v1.Param
}{{
name: "reference default param in all step fields",
steps: []v1.Step{{
Name: fmt.Sprintf("echo $(params.%s.foo)", v1.ReservedParamName),
Image: fmt.Sprintf("$(params.%s.foo)", v1.ReservedParamName),
Args: []string{fmt.Sprintf("echo $(params.%s.foo)", v1.ReservedParamName)},
Command: []string{fmt.Sprintf("echo $(params.%s.foo)", v1.ReservedParamName)},
WorkingDir: fmt.Sprintf("echo $(params.%s.foo)", v1.ReservedParamName),
VolumeMounts: []corev1.VolumeMount{{
Name: fmt.Sprintf("echo $(params.%s.foo)", v1.ReservedParamName),
}},
Script: fmt.Sprintf("echo $(params.%s.foo)", v1.ReservedParamName),
}},
params: []v1.Param{{
Name: v1.ReservedParamName,
Value: *v1.NewObject(map[string]string{"foo": "bar"}),
}},
}, {
name: "don't check non-reserved param",
steps: []v1.Step{{
Name: fmt.Sprintf("echo $(params.%s.foo)", "non-reserved"),
Image: fmt.Sprintf("$(params.%s.foo)", "non-reserved"),
Args: []string{fmt.Sprintf("echo $(params.%s.foo)", "non-reserved")},
Command: []string{fmt.Sprintf("echo $(params.%s.foo)", "non-reserved")},
WorkingDir: fmt.Sprintf("echo $(params.%s.foo)", "non-reserved"),
VolumeMounts: []corev1.VolumeMount{{
Name: fmt.Sprintf("echo $(params.%s.foo)", "non-reserved"),
}},
Script: fmt.Sprintf("echo $(params.%s.foo)", "non-reserved"),
}},
params: []v1.Param{{
Name: v1.ReservedParamName,
Value: *v1.NewObject(map[string]string{"foo": "bar"}),
}},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
err := v1.ValidateReservedParamReferenceMissingKeys(ctx, tt.steps, tt.params)
if err != nil {
t.Errorf("ValidateReservedParamReferenceMissingKeys() = %v", err)
}
})
}
}

func TestValidateReservedParamReferenceMissingKeys_Error(t *testing.T) {
tests := []struct {
name string
steps []v1.Step
params []v1.Param
}{{
name: "reference non-existent key in name",
steps: []v1.Step{{
Name: fmt.Sprintf("echo $(params.%s.%s)", v1.ReservedParamName, "non-existent"),
}},
params: []v1.Param{{
Name: v1.ReservedParamName,
Value: *v1.NewObject(map[string]string{"foo": "bar"}),
}},
}, {
name: "reference non-existent key in image",
steps: []v1.Step{{
Image: fmt.Sprintf("$(params.%s.%s)", v1.ReservedParamName, "non-existent"),
}},
params: []v1.Param{{
Name: v1.ReservedParamName,
Value: *v1.NewObject(map[string]string{"foo": "bar"}),
}},
}, {
name: "reference non-existent key in args",
steps: []v1.Step{{
Args: []string{fmt.Sprintf("echo $(params.%s.%s)", v1.ReservedParamName, "non-existent")},
}},
params: []v1.Param{{
Name: v1.ReservedParamName,
Value: *v1.NewObject(map[string]string{"foo": "bar"}),
}},
}, {
name: "reference non-existent key in command",
steps: []v1.Step{{
Command: []string{fmt.Sprintf("echo $(params.%s.%s)", v1.ReservedParamName, "non-existent")},
}},
params: []v1.Param{{
Name: v1.ReservedParamName,
Value: *v1.NewObject(map[string]string{"foo": "bar"}),
}},
}, {
name: "reference non-existent key in workingdir",
steps: []v1.Step{{
WorkingDir: fmt.Sprintf("echo $(params.%s.%s)", v1.ReservedParamName, "non-existent"),
}},
params: []v1.Param{{
Name: v1.ReservedParamName,
Value: *v1.NewObject(map[string]string{"foo": "bar"}),
}},
}, {
name: "reference non-existent key in volume",
steps: []v1.Step{{
VolumeMounts: []corev1.VolumeMount{{
Name: fmt.Sprintf("echo $(params.%s.%s)", v1.ReservedParamName, "non-existent"),
}},
}},
params: []v1.Param{{
Name: v1.ReservedParamName,
Value: *v1.NewObject(map[string]string{"foo": "bar"}),
}},
}, {
name: "reference non-existent key in script",
steps: []v1.Step{{
Script: fmt.Sprintf("echo $(params.%s.%s)", v1.ReservedParamName, "non-existent"),
}},
params: []v1.Param{{
Name: v1.ReservedParamName,
Value: *v1.NewObject(map[string]string{"foo": "bar"}),
}},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
err := v1.ValidateReservedParamReferenceMissingKeys(ctx, tt.steps, tt.params)
if err == nil {
t.Errorf("expected error but got nil")
}
})
}
}
2 changes: 2 additions & 0 deletions pkg/apis/pipeline/v1/taskrun_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ const (
// TaskRunReasonFailureIgnored is the reason set when the Taskrun has failed due to pod execution error and the failure is ignored for the owning PipelineRun.
// TaskRuns failed due to reconciler/validation error should not use this reason.
TaskRunReasonFailureIgnored TaskRunReason = "FailureIgnored"
// TaskRunReasonParamKeyNotExistent indicates that the default object param doesn't have the key which the param reference requires
TaskRunReasonParamKeyNotExistent TaskRunReason = "ParamKeyNotExistent"
)

func (t TaskRunReason) String() string {
Expand Down
10 changes: 6 additions & 4 deletions pkg/apis/pipeline/v1beta1/pipeline_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ var _ apis.Validatable = (*Pipeline)(nil)
var _ resourcesemantics.VerbLimited = (*Pipeline)(nil)

const (
taskRef = "taskRef"
taskSpec = "taskSpec"
pipelineRef = "pipelineRef"
pipelineSpec = "pipelineSpec"
taskRef = "taskRef"
taskSpec = "taskSpec"
pipelineRef = "pipelineRef"
pipelineSpec = "pipelineSpec"
ReservedParamName = "provider"
)

// SupportedVerbs returns the operations that validation should be called for
Expand Down Expand Up @@ -408,6 +409,7 @@ func (ps *PipelineSpec) validatePipelineParameterUsage(ctx context.Context) (err
// validatePipelineTaskParameterUsage validates that parameters referenced in the Pipeline Tasks are declared by the Pipeline
func validatePipelineTaskParameterUsage(tasks []PipelineTask, params ParamSpecs) (errs *apis.FieldError) {
allParamNames := sets.NewString(params.getNames()...)
allParamNames.Insert(ReservedParamName)
_, arrayParams, objectParams := params.sortByType()
arrayParamNames := sets.NewString(arrayParams.getNames()...)
objectParameterNameKeys := map[string][]string{}
Expand Down
Loading
Loading