diff --git a/docs/pipelineruns.md b/docs/pipelineruns.md index f308dcced8a..2d4698ad6f8 100644 --- a/docs/pipelineruns.md +++ b/docs/pipelineruns.md @@ -32,6 +32,7 @@ weight: 204 - [PipelineRun status](#pipelinerun-status) - [The status field](#the-status-field) - [Monitoring execution status](#monitoring-execution-status) + - [Marking off user errors](#marking-off-user-errors) - [Cancelling a PipelineRun](#cancelling-a-pipelinerun) - [Gracefully cancelling a PipelineRun](#gracefully-cancelling-a-pipelinerun) - [Gracefully stopping a PipelineRun](#gracefully-stopping-a-pipelinerun) @@ -1538,6 +1539,30 @@ Some examples: | pipeline-run-0123456789-0123456789-0123456789-0123456789 | task2-0123456789-0123456789-0123456789-0123456789-0123456789 | pipeline-run-0123456789-012345607ad8c7aac5873cdfabe472a68996b5c | | pipeline-run | task4 (with 2x2 `Matrix`) | pipeline-run-task1-0, pipeline-run-task1-2, pipeline-run-task1-3, pipeline-run-task1-4 | +### Marking off user errors + +A user error in Tekton is any mistake made by user, such as a syntax error when specifying pipelines, tasks. User errors can occur in various stages of the Tekton pipeline, from authoring the pipeline configuration to executing the pipelines. They are currently explicitly labeled in the Run's conditions message, for example: + +```yaml +# Failed PipelineRun with "user error" labeled +apiVersion: tekton.dev/v1 # or tekton.dev/v1beta1 +kind: PipelineRun +metadata: + ... +spec: + ... +status: + ... + conditions: + - lastTransitionTime: "2022-06-02T19:02:58Z" + message: |- + PipelineRun default parameters is missing some parameters required by Pipeline pipelinerun-with-params's parameters: user error + pipelineRun missing parameters: [param foo] + reason: ParameterMissing + status: "False" + type: Succeeded +``` + ## Cancelling a `PipelineRun` To cancel a `PipelineRun` that's currently executing, update its definition diff --git a/pkg/apis/pipeline/errors/errors.go b/pkg/apis/pipeline/errors/errors.go new file mode 100644 index 00000000000..c8b3b970128 --- /dev/null +++ b/pkg/apis/pipeline/errors/errors.go @@ -0,0 +1,35 @@ +/* +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 + http://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. +*/ + +package errors + +import ( + "errors" +) + +var ( + // ErrUser is used to indicate that the user is responsible for an aerror. + // See github.com/tektoncd/pipeline/blob/main/docs/pipelineruns.md#marking-off-user-errors + // for more details. + ErrUser = errors.New("user error") +) + +// LabelUserError adds the "user error" mark of the error message +func LabelUserError(err error) error { + err = errors.Join(ErrUser, err) + return err +} + +func IsUserError(err error) bool { + return errors.Is(err, ErrUser) +} diff --git a/pkg/apis/pipeline/errors/errors_test.go b/pkg/apis/pipeline/errors/errors_test.go new file mode 100644 index 00000000000..5a9120e09db --- /dev/null +++ b/pkg/apis/pipeline/errors/errors_test.go @@ -0,0 +1,64 @@ +/* +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 + + http://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. +*/ + +package errors_test + +import ( + "errors" + "testing" + + pipelineErrors "github.com/tektoncd/pipeline/pkg/apis/pipeline/errors" +) + +type TestError struct{} + +var _ error = &TestError{} + +func (*TestError) Error() string { + return "test error" +} + +func TestUserErrorUnwrap(t *testing.T) { + originalError := &TestError{} + userError := pipelineErrors.LabelUserError(originalError) + + if !errors.Is(userError, pipelineErrors.ErrUser) { + t.Errorf("user error expected to unwrap successfully") + } +} + +func TestResolutionErrorMessage(t *testing.T) { + originalError := &TestError{} + expectedErrorMessage := pipelineErrors.ErrUser.Error() + "\n" + originalError.Error() + + userError := pipelineErrors.LabelUserError(originalError) + + if userError.Error() != expectedErrorMessage { + t.Errorf("user error message expected to equal to %s, got: %s", expectedErrorMessage, userError.Error()) + } +} + +func TestIsUserError(t *testing.T) { + originalError := &TestError{} + if pipelineErrors.IsUserError(originalError) { + t.Errorf("IsUserError fails to unwrap non-user error") + } + + userError := pipelineErrors.LabelUserError(originalError) + if !pipelineErrors.IsUserError(userError) { + t.Errorf("IsUserError fails to unwrap user error") + } +} diff --git a/pkg/reconciler/apiserver/apiserver.go b/pkg/reconciler/apiserver/apiserver.go index 75504f56537..df4d68a3f2f 100644 --- a/pkg/reconciler/apiserver/apiserver.go +++ b/pkg/reconciler/apiserver/apiserver.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/google/uuid" + pipelineErrors "github.com/tektoncd/pipeline/pkg/apis/pipeline/errors" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" @@ -76,7 +77,7 @@ func handleDryRunCreateErr(err error, objectName string) error { case apierrors.IsBadRequest(err): // Object rejected by validating webhook errType = ErrReferencedObjectValidationFailed case apierrors.IsInvalid(err), apierrors.IsMethodNotSupported(err): - errType = ErrCouldntValidateObjectPermanent + errType = pipelineErrors.LabelUserError(ErrCouldntValidateObjectPermanent) case apierrors.IsTimeout(err), apierrors.IsServerTimeout(err), apierrors.IsTooManyRequests(err): errType = ErrCouldntValidateObjectRetryable default: diff --git a/pkg/reconciler/pipelinerun/pipelinerun.go b/pkg/reconciler/pipelinerun/pipelinerun.go index d0bc0901695..47233f6adef 100644 --- a/pkg/reconciler/pipelinerun/pipelinerun.go +++ b/pkg/reconciler/pipelinerun/pipelinerun.go @@ -29,6 +29,7 @@ import ( "github.com/hashicorp/go-multierror" "github.com/tektoncd/pipeline/pkg/apis/config" "github.com/tektoncd/pipeline/pkg/apis/pipeline" + pipelineErrors "github.com/tektoncd/pipeline/pkg/apis/pipeline/errors" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" clientset "github.com/tektoncd/pipeline/pkg/client/clientset/versioned" @@ -443,7 +444,7 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1.PipelineRun, getPipel // This Run has failed, so we need to mark it as failed and stop reconciling it pr.Status.MarkFailed(v1.PipelineRunReasonInvalidGraph.String(), "PipelineRun %s/%s's Pipeline DAG is invalid: %s", - pr.Namespace, pr.Name, err) + pr.Namespace, pr.Name, pipelineErrors.LabelUserError(err)) return controller.NewPermanentError(err) } @@ -456,7 +457,7 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1.PipelineRun, getPipel // This Run has failed, so we need to mark it as failed and stop reconciling it pr.Status.MarkFailed(v1.PipelineRunReasonInvalidGraph.String(), "PipelineRun %s's Pipeline DAG is invalid for finally clause: %s", - pr.Namespace, pr.Name, err) + pr.Namespace, pr.Name, pipelineErrors.LabelUserError(err)) return controller.NewPermanentError(err) } @@ -492,7 +493,7 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1.PipelineRun, getPipel logger.Errorf("PipelineRun %q Param Enum validation failed: %v", pr.Name, err) pr.Status.MarkFailed(v1.PipelineRunReasonInvalidParamValue.String(), "PipelineRun %s/%s parameters have invalid value: %s", - pr.Namespace, pr.Name, err) + pr.Namespace, pr.Name, pipelineErrors.LabelUserError(err)) return controller.NewPermanentError(err) } } @@ -655,7 +656,8 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1.PipelineRun, getPipel err := rpt.EvaluateCEL() if err != nil { logger.Errorf("Error evaluating CEL %s: %v", pr.Name, err) - pr.Status.MarkFailed(string(v1.PipelineRunReasonCELEvaluationFailed), err.Error()) + pr.Status.MarkFailed(string(v1.PipelineRunReasonCELEvaluationFailed), + "Error evaluating CEL %s: %w", pr.Name, pipelineErrors.LabelUserError(err)) return controller.NewPermanentError(err) } } @@ -684,8 +686,9 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1.PipelineRun, getPipel } if err := resources.ValidateOptionalWorkspaces(pipelineSpec.Workspaces, pipelineRunFacts.State); err != nil { - logger.Errorf("Optional workspace not supported by task: %v", err) - pr.Status.MarkFailed(v1.PipelineRunReasonRequiredWorkspaceMarkedOptional.String(), err.Error()) + logger.Errorf("Optional workspace not supported by task: %w", err) + pr.Status.MarkFailed(v1.PipelineRunReasonRequiredWorkspaceMarkedOptional.String(), + "Optional workspace not supported by task: %w", pipelineErrors.LabelUserError(err)) return controller.NewPermanentError(err) } @@ -854,8 +857,9 @@ func (c *Reconciler) runNextSchedulableTask(ctx context.Context, pr *v1.Pipeline // Validate parameter types in matrix after apply substitutions from Task Results if rpt.PipelineTask.IsMatrixed() { if err := resources.ValidateParameterTypesInMatrix(pipelineRunFacts.State); err != nil { - logger.Errorf("Failed to validate matrix %q with error %v", pr.Name, err) - pr.Status.MarkFailed(v1.PipelineRunReasonInvalidMatrixParameterTypes.String(), err.Error()) + logger.Errorf("Failed to validate matrix %q with error %w", pr.Name, err) + pr.Status.MarkFailed(v1.PipelineRunReasonInvalidMatrixParameterTypes.String(), + "Failed to validate matrix %q with error %w", pipelineErrors.LabelUserError(err)) return controller.NewPermanentError(err) } } @@ -906,8 +910,9 @@ func (c *Reconciler) createTaskRuns(ctx context.Context, rpt *resources.Resolved } params = append(params, rpt.PipelineTask.Params...) if err := taskrun.ValidateEnumParam(ctx, params, rpt.ResolvedTask.TaskSpec.Params); err != nil { - err = fmt.Errorf("Invalid param value from PipelineTask \"%s\": %w", rpt.PipelineTask.Name, err) - pr.Status.MarkFailed(v1.PipelineRunReasonInvalidParamValue.String(), err.Error()) + pr.Status.MarkFailed(v1.PipelineRunReasonInvalidParamValue.String(), + "Invalid param value from PipelineTask \"%s\": %w", + rpt.PipelineTask.Name, pipelineErrors.LabelUserError(err)) return nil, controller.NewPermanentError(err) } } diff --git a/pkg/reconciler/pipelinerun/pipelinerun_test.go b/pkg/reconciler/pipelinerun/pipelinerun_test.go index 50de7d2b5a2..e8b9b66522c 100644 --- a/pkg/reconciler/pipelinerun/pipelinerun_test.go +++ b/pkg/reconciler/pipelinerun/pipelinerun_test.go @@ -879,7 +879,7 @@ spec: permanentError: true, wantEvents: []string{ "Normal Started", - "Warning Failed PipelineRun foo/pipeline-missing-object-param-keys parameters is missing object keys required by Pipeline foo/a-pipeline-with-object-params's parameters: pipelineRun missing object keys for parameters", + "Warning Failed PipelineRun foo/pipeline-missing-object-param-keys parameters is missing object keys required by Pipeline foo/a-pipeline-with-object-params's parameters: user error\npipelineRun missing object keys for parameters: map[some-param:[key2]]", }, }, { name: "invalid-pipeline-array-index-out-of-bound", @@ -900,7 +900,7 @@ spec: permanentError: true, wantEvents: []string{ "Normal Started", - "Warning Failed PipelineRun foo/pipeline-param-array-out-of-bound failed validation: failed to validate Pipeline foo/a-pipeline-with-array-indexing-params's parameter which has an invalid index while referring to an array: non-existent param references:[$(params.some-param[2]", + "Warning Failed PipelineRun foo/pipeline-param-array-out-of-bound failed validation: failed to validate Pipeline foo/a-pipeline-with-array-indexing-params's parameter which has an invalid index while referring to an array: user error\nnon-existent param references:[$(params.some-param[2]", }, }, { name: "invalid-embedded-pipeline-bad-name-shd-stop-reconciling", @@ -1190,7 +1190,7 @@ status: image: busybox script: 'exit 0' conditions: - - message: "Invalid task result reference: Could not find result with name result2 for task task1" + - message: "user error\nInvalid task result reference: Could not find result with name result2 for task task1" reason: InvalidTaskResultReference status: "False" type: Succeeded diff --git a/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go b/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go index e3c7652252f..018e5be104a 100644 --- a/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go +++ b/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go @@ -25,6 +25,7 @@ import ( "github.com/google/cel-go/cel" "github.com/tektoncd/pipeline/pkg/apis/config" "github.com/tektoncd/pipeline/pkg/apis/pipeline" + pipelineErrors "github.com/tektoncd/pipeline/pkg/apis/pipeline/errors" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" "github.com/tektoncd/pipeline/pkg/reconciler/taskrun/resources" @@ -507,7 +508,7 @@ func ValidateWorkspaceBindings(p *v1.PipelineSpec, pr *v1.PipelineRun) error { continue } if _, ok := pipelineRunWorkspaces[ws.Name]; !ok { - return fmt.Errorf("pipeline requires workspace with name %q be provided by pipelinerun", ws.Name) + return pipelineErrors.LabelUserError(fmt.Errorf("pipeline requires workspace with name %q be provided by pipelinerun", ws.Name)) } } return nil @@ -526,7 +527,7 @@ func ValidateTaskRunSpecs(p *v1.PipelineSpec, pr *v1.PipelineRun) error { for _, taskrunSpec := range pr.Spec.TaskRunSpecs { if _, ok := pipelineTasks[taskrunSpec.PipelineTaskName]; !ok { - return fmt.Errorf("pipelineRun's taskrunSpecs defined wrong taskName: %q, does not exist in Pipeline", taskrunSpec.PipelineTaskName) + return pipelineErrors.LabelUserError(fmt.Errorf("pipelineRun's taskrunSpecs defined wrong taskName: %q, does not exist in Pipeline", taskrunSpec.PipelineTaskName)) } } return nil diff --git a/pkg/reconciler/pipelinerun/resources/resultrefresolution.go b/pkg/reconciler/pipelinerun/resources/resultrefresolution.go index 103d587101c..22b1a46ba67 100644 --- a/pkg/reconciler/pipelinerun/resources/resultrefresolution.go +++ b/pkg/reconciler/pipelinerun/resources/resultrefresolution.go @@ -21,6 +21,7 @@ import ( "fmt" "sort" + pipelineErrors "github.com/tektoncd/pipeline/pkg/apis/pipeline/errors" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" ) @@ -28,7 +29,7 @@ import ( var ( // ErrInvalidTaskResultReference indicates that the reason for the failure status is that there // is an invalid task result reference - ErrInvalidTaskResultReference = errors.New("Invalid task result reference") + ErrInvalidTaskResultReference = pipelineErrors.LabelUserError(errors.New("Invalid task result reference")) ) // ResolvedResultRefs represents all of the ResolvedResultRef for a pipeline task diff --git a/pkg/reconciler/pipelinerun/resources/resultrefresolution_test.go b/pkg/reconciler/pipelinerun/resources/resultrefresolution_test.go index 657805c7e23..efc34447d72 100644 --- a/pkg/reconciler/pipelinerun/resources/resultrefresolution_test.go +++ b/pkg/reconciler/pipelinerun/resources/resultrefresolution_test.go @@ -672,14 +672,14 @@ func TestCheckMissingResultReferences(t *testing.T) { targets: PipelineRunState{ pipelineRunState[3], }, - wantErr: "Invalid task result reference: Could not find result with name missingResult for task aTask", + wantErr: "user error\nInvalid task result reference: Could not find result with name missingResult for task aTask", }, { name: "Test unsuccessful result references resolution - params", pipelineRunState: pipelineRunState, targets: PipelineRunState{ pipelineRunState[4], }, - wantErr: "Invalid task result reference: Could not find result with name missingResult for task aTask", + wantErr: "user error\nInvalid task result reference: Could not find result with name missingResult for task aTask", }, { name: "Valid: Test successful result references resolution - params - Run", pipelineRunState: pipelineRunState, @@ -716,14 +716,14 @@ func TestCheckMissingResultReferences(t *testing.T) { targets: PipelineRunState{ pipelineRunState[13], }, - wantErr: "Invalid task result reference: Could not find result with name iDoNotExist for task dTask", + wantErr: "user error\nInvalid task result reference: Could not find result with name iDoNotExist for task dTask", }, { name: "Invalid: Test result references resolution - matrix custom task - missing references to string replacements", pipelineRunState: pipelineRunState, targets: PipelineRunState{ pipelineRunState[14], }, - wantErr: "Invalid task result reference: Could not find result with name iDoNotExist for task aCustomPipelineTask", + wantErr: "user error\nInvalid task result reference: Could not find result with name iDoNotExist for task aCustomPipelineTask", }} { t.Run(tt.name, func(t *testing.T) { err := CheckMissingResultReferences(tt.pipelineRunState, tt.targets) diff --git a/pkg/reconciler/pipelinerun/resources/validate_dependencies.go b/pkg/reconciler/pipelinerun/resources/validate_dependencies.go index 2ec3a2b3a8a..c39e7e74e9e 100644 --- a/pkg/reconciler/pipelinerun/resources/validate_dependencies.go +++ b/pkg/reconciler/pipelinerun/resources/validate_dependencies.go @@ -19,6 +19,7 @@ package resources import ( "fmt" + pipelineErrors "github.com/tektoncd/pipeline/pkg/apis/pipeline/errors" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" "k8s.io/apimachinery/pkg/util/sets" ) @@ -32,7 +33,7 @@ func ValidatePipelineTaskResults(state PipelineRunState) error { for _, rpt := range state { for _, ref := range v1.PipelineTaskResultRefs(rpt.PipelineTask) { if err := validateResultRef(ref, ptMap); err != nil { - return fmt.Errorf("invalid result reference in pipeline task %q: %w", rpt.PipelineTask.Name, err) + return pipelineErrors.LabelUserError(fmt.Errorf("invalid result reference in pipeline task %q: %w", rpt.PipelineTask.Name, err)) } } } diff --git a/pkg/reconciler/pipelinerun/resources/validate_params.go b/pkg/reconciler/pipelinerun/resources/validate_params.go index e342cb912bf..ff97878f3c2 100644 --- a/pkg/reconciler/pipelinerun/resources/validate_params.go +++ b/pkg/reconciler/pipelinerun/resources/validate_params.go @@ -19,6 +19,7 @@ package resources import ( "fmt" + pipelineErrors "github.com/tektoncd/pipeline/pkg/apis/pipeline/errors" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" "github.com/tektoncd/pipeline/pkg/list" "github.com/tektoncd/pipeline/pkg/reconciler/taskrun" @@ -45,7 +46,7 @@ func ValidateParamTypesMatching(p *v1.PipelineSpec, pr *v1.PipelineRun) error { // Return an error with the misconfigured parameters' names, or return nil if there are none. if len(wrongTypeParamNames) != 0 { - return fmt.Errorf("parameters have inconsistent types : %s", wrongTypeParamNames) + return pipelineErrors.LabelUserError(fmt.Errorf("parameters have inconsistent types : %s", wrongTypeParamNames)) } return nil } @@ -71,7 +72,7 @@ func ValidateRequiredParametersProvided(pipelineParameters *v1.ParamSpecs, pipel // Return an error with the missing parameters' names, or return nil if there are none. if len(missingParams) != 0 { - return fmt.Errorf("pipelineRun missing parameters: %s", missingParams) + return pipelineErrors.LabelUserError(fmt.Errorf("pipelineRun missing parameters: %s", missingParams)) } return nil } @@ -80,7 +81,7 @@ func ValidateRequiredParametersProvided(pipelineParameters *v1.ParamSpecs, pipel func ValidateObjectParamRequiredKeys(pipelineParameters []v1.ParamSpec, pipelineRunParameters []v1.Param) error { missings := taskrun.MissingKeysObjectParamNames(pipelineParameters, pipelineRunParameters) if len(missings) != 0 { - return fmt.Errorf("pipelineRun missing object keys for parameters: %v", missings) + return pipelineErrors.LabelUserError(fmt.Errorf("pipelineRun missing object keys for parameters: %v", missings)) } return nil diff --git a/pkg/reconciler/pipelinerun/resources/validate_params_test.go b/pkg/reconciler/pipelinerun/resources/validate_params_test.go index 4b6e667d8fc..f463540276d 100644 --- a/pkg/reconciler/pipelinerun/resources/validate_params_test.go +++ b/pkg/reconciler/pipelinerun/resources/validate_params_test.go @@ -629,7 +629,7 @@ func TestValidateParamArrayIndex_invalid(t *testing.T) { }}, }, params: v1.Params{{Name: "second-param", Value: *v1.NewStructuredValues("second-value", "second-value-again")}}, - expected: fmt.Errorf("non-existent param references:[$(params.first-param[2]) $(params.second-param[2])]"), + expected: fmt.Errorf("user error\nnon-existent param references:[$(params.first-param[2]) $(params.second-param[2])]"), }, { name: "single parameter reference with when expression out of bound", original: v1.PipelineSpec{ @@ -646,7 +646,7 @@ func TestValidateParamArrayIndex_invalid(t *testing.T) { }}, }, params: v1.Params{{Name: "second-param", Value: *v1.NewStructuredValues("second-value", "second-value-again")}}, - expected: fmt.Errorf("non-existent param references:[$(params.first-param[2]) $(params.second-param[2])]"), + expected: fmt.Errorf("user error\nnon-existent param references:[$(params.first-param[2]) $(params.second-param[2])]"), }, { name: "pipeline parameter reference nested inside task parameter out of bound", original: v1.PipelineSpec{ @@ -662,7 +662,7 @@ func TestValidateParamArrayIndex_invalid(t *testing.T) { }}, }, params: nil, // no parameter values. - expected: fmt.Errorf("non-existent param references:[$(params.first-param[2]) $(params.second-param[2])]"), + expected: fmt.Errorf("user error\nnon-existent param references:[$(params.first-param[2]) $(params.second-param[2])]"), }, { name: "array parameter reference out of bound", original: v1.PipelineSpec{ @@ -680,7 +680,7 @@ func TestValidateParamArrayIndex_invalid(t *testing.T) { params: v1.Params{ {Name: "second-param", Value: *v1.NewStructuredValues("second-value", "array")}, }, - expected: fmt.Errorf("non-existent param references:[$(params.first-param[3]) $(params.second-param[4])]"), + expected: fmt.Errorf("user error\nnon-existent param references:[$(params.first-param[3]) $(params.second-param[4])]"), }, { name: "object parameter reference out of bound", original: v1.PipelineSpec{ @@ -700,7 +700,7 @@ func TestValidateParamArrayIndex_invalid(t *testing.T) { params: v1.Params{ {Name: "second-param", Value: *v1.NewStructuredValues("second-value", "array")}, }, - expected: fmt.Errorf("non-existent param references:[$(params.first-param[4]) $(params.second-param[4])]"), + expected: fmt.Errorf("user error\nnon-existent param references:[$(params.first-param[4]) $(params.second-param[4])]"), }, { name: "parameter evaluation with final tasks reference out of bound", original: v1.PipelineSpec{ @@ -721,7 +721,7 @@ func TestValidateParamArrayIndex_invalid(t *testing.T) { }}, }, params: v1.Params{{Name: "second-param", Value: *v1.NewStructuredValues("second-value", "second-value-again")}}, - expected: fmt.Errorf("non-existent param references:[$(params.first-param[2]) $(params.second-param[2])]"), + expected: fmt.Errorf("user error\nnon-existent param references:[$(params.first-param[2]) $(params.second-param[2])]"), }, { name: "parameter evaluation with both tasks and final tasks reference out of bound", original: v1.PipelineSpec{ @@ -753,7 +753,7 @@ func TestValidateParamArrayIndex_invalid(t *testing.T) { }}, }, params: v1.Params{{Name: "second-param", Value: *v1.NewStructuredValues("second-value", "second-value-again")}}, - expected: fmt.Errorf("non-existent param references:[$(params.first-param[2]) $(params.first-param[3]) $(params.first-param[4]) $(params.second-param[2]) $(params.second-param[3]) $(params.second-param[4])]"), + expected: fmt.Errorf("user error\nnon-existent param references:[$(params.first-param[2]) $(params.first-param[3]) $(params.first-param[4]) $(params.second-param[2]) $(params.second-param[3]) $(params.second-param[4])]"), }, { name: "parameter in matrix reference out of bound", original: v1.PipelineSpec{ @@ -771,7 +771,7 @@ func TestValidateParamArrayIndex_invalid(t *testing.T) { }}, }, params: v1.Params{{Name: "second-param", Value: *v1.NewStructuredValues("second-value", "second-value-again")}}, - expected: fmt.Errorf("non-existent param references:[$(params.first-param[2])]"), + expected: fmt.Errorf("user error\nnon-existent param references:[$(params.first-param[2])]"), }, { name: "parameter references with bracket notation and special characters reference out of bound", original: v1.PipelineSpec{ @@ -795,7 +795,7 @@ func TestValidateParamArrayIndex_invalid(t *testing.T) { {Name: "second/param", Value: *v1.NewStructuredValues("second-value", "second-value-again")}, {Name: "fourth/param", Value: *v1.NewStructuredValues("fourth-value", "fourth-value-again")}, }, - expected: fmt.Errorf("non-existent param references:[$(params[\"first.param\"][2]) $(params[\"second/param\"][2]) $(params['fourth/param'][2]) $(params['third.param'][2])]"), + expected: fmt.Errorf("user error\nnon-existent param references:[$(params[\"first.param\"][2]) $(params[\"second/param\"][2]) $(params['fourth/param'][2]) $(params['third.param'][2])]"), }, { name: "single parameter in workspace subpath reference out of bound", original: v1.PipelineSpec{ @@ -818,7 +818,7 @@ func TestValidateParamArrayIndex_invalid(t *testing.T) { }}, }, params: v1.Params{{Name: "second-param", Value: *v1.NewStructuredValues("second-value", "second-value-again")}}, - expected: fmt.Errorf("non-existent param references:[$(params.first-param[2]) $(params.second-param[3])]"), + expected: fmt.Errorf("user error\nnon-existent param references:[$(params.first-param[2]) $(params.second-param[3])]"), }, } { tt := tt // capture range variable diff --git a/pkg/reconciler/taskrun/resources/validate_params.go b/pkg/reconciler/taskrun/resources/validate_params.go index 7f6525cb22a..539e6e27582 100644 --- a/pkg/reconciler/taskrun/resources/validate_params.go +++ b/pkg/reconciler/taskrun/resources/validate_params.go @@ -3,6 +3,7 @@ package resources import ( "fmt" + pipelineErrors "github.com/tektoncd/pipeline/pkg/apis/pipeline/errors" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" "github.com/tektoncd/pipeline/pkg/substitution" "k8s.io/apimachinery/pkg/util/sets" @@ -41,7 +42,7 @@ func ValidateOutOfBoundArrayParams(declarations v1.ParamSpecs, params v1.Params, } } if outofBoundParams.Len() > 0 { - return fmt.Errorf("non-existent param references:%v", outofBoundParams.List()) + return pipelineErrors.LabelUserError(fmt.Errorf("non-existent param references:%v", outofBoundParams.List())) } return nil } diff --git a/pkg/reconciler/taskrun/resources/validate_params_test.go b/pkg/reconciler/taskrun/resources/validate_params_test.go index f3b5aca6156..449abc6f534 100644 --- a/pkg/reconciler/taskrun/resources/validate_params_test.go +++ b/pkg/reconciler/taskrun/resources/validate_params_test.go @@ -82,7 +82,7 @@ func TestValidateParamArrayIndex(t *testing.T) { }}, }}, }, - expectedError: fmt.Errorf("non-existent param references:[%v]", strings.Join(stepsInvalidReferences, " ")), + expectedError: fmt.Errorf("user error\nnon-existent param references:[%v]", strings.Join(stepsInvalidReferences, " ")), }, { name: "stepTemplate reference invalid", params: v1.Params{{ @@ -98,7 +98,7 @@ func TestValidateParamArrayIndex(t *testing.T) { Image: "$(params.array-params[3])", }, }, - expectedError: fmt.Errorf("non-existent param references:[%v]", "$(params.array-params[3])"), + expectedError: fmt.Errorf("user error\nnon-existent param references:[%v]", "$(params.array-params[3])"), }, { name: "volumes reference invalid", params: v1.Params{{ @@ -160,7 +160,7 @@ func TestValidateParamArrayIndex(t *testing.T) { }, }, }, - expectedError: fmt.Errorf("non-existent param references:[%v]", strings.Join(volumesInvalidReferences, " ")), + expectedError: fmt.Errorf("user error\nnon-existent param references:[%v]", strings.Join(volumesInvalidReferences, " ")), }, { name: "workspaces reference invalid", params: v1.Params{{ @@ -176,7 +176,7 @@ func TestValidateParamArrayIndex(t *testing.T) { MountPath: "$(params.array-params[3])", }}, }, - expectedError: fmt.Errorf("non-existent param references:[%v]", "$(params.array-params[3])"), + expectedError: fmt.Errorf("user error\nnon-existent param references:[%v]", "$(params.array-params[3])"), }, { name: "sidecar reference invalid", params: v1.Params{{ @@ -193,7 +193,7 @@ func TestValidateParamArrayIndex(t *testing.T) { }, }, }, - expectedError: fmt.Errorf("non-existent param references:[%v]", "$(params.array-params[3])"), + expectedError: fmt.Errorf("user error\nnon-existent param references:[%v]", "$(params.array-params[3])"), }, } for _, tc := range tcs {