diff --git a/docs/pipelineruns.md b/docs/pipelineruns.md index f308dcced8a..d2e26ba64b3 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,36 @@ 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 reason, for example: + +```yaml +# Failed PipelineRun with reason labeled "User error" +apiVersion: tekton.dev/v1 +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: pipelineRun missing parameters: + [pl-param-x]' + reason: '[User error] ParameterMissing' + status: "False" + type: Succeeded +``` + +```console +~/pipeline$ tkn pr list +NAME STARTED DURATION STATUS +pipelinerun-with-params 5 seconds ago 0s Failed([User error] ParameterMissing) +``` + ## 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..4f55f4a373e --- /dev/null +++ b/pkg/apis/pipeline/errors/errors.go @@ -0,0 +1,61 @@ +/* +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 + +const userErrorLabel = "[User error] " + +type UserError struct { + Reason string + Original error +} + +var _ error = &UserError{} + +// Error returns the original error message. This implements the error.Error interface. +func (e *UserError) Error() string { + return e.Original.Error() +} + +// Unwrap returns the original error without the Reason annotation. This is +// intended to support usage of errors.Is and errors.As with Errors. +func (e *UserError) Unwrap() error { + return e.Original +} + +// newUserError returns a UserError with the given reason and underlying +// original error. +func newUserError(reason string, err error) *UserError { + return &UserError{ + Reason: reason, + Original: err, + } +} + +// WrapUserError wraps the original error with the user error label +func WrapUserError(err error) error { + return newUserError(userErrorLabel, err) +} + +// LabelsUserErrorReason labels the failure RunStatus Reason if any of its error messages has been +// wrapped as an UserError. It indicates that the user is responsible for an error. +// See github.com/tektoncd/pipeline/blob/main/docs/pipelineruns.md#marking-off-user-errors +// for more details. +func LabelsUserErrorReason(reason string, messageA []interface{}) string { + for _, message := range messageA { + if ue, ok := message.(*UserError); ok { + return ue.Reason + reason + } + } + return reason +} diff --git a/pkg/apis/pipeline/errors/errors_test.go b/pkg/apis/pipeline/errors/errors_test.go new file mode 100644 index 00000000000..12b2282c721 --- /dev/null +++ b/pkg/apis/pipeline/errors/errors_test.go @@ -0,0 +1,98 @@ +/* +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" + v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" +) + +type TestError struct{} + +var _ error = &TestError{} + +func (*TestError) Error() string { + return "test error" +} + +func TestUserErrorUnwrap(t *testing.T) { + originalError := &TestError{} + userError := pipelineErrors.WrapUserError(originalError) + + if !errors.Is(userError, &TestError{}) { + t.Errorf("user error expected to unwrap successfully") + } +} + +func TestResolutionErrorMessage(t *testing.T) { + originalError := &TestError{} + expectedErrorMessage := originalError.Error() + + userError := pipelineErrors.WrapUserError(originalError) + + if userError.Error() != expectedErrorMessage { + t.Errorf("user error message expected to equal to %s, got: %s", expectedErrorMessage, userError.Error()) + } +} + +func TestLabelsUserError(t *testing.T) { + const hasUserError = true + tcs := []struct { + description string + reason string + messages []interface{} + expected string + }{{ + description: "error messags with user error", + reason: v1.PipelineRunReasonInvalidGraph.String(), + messages: makeMessages(hasUserError), + expected: "[User error] " + v1.PipelineRunReasonInvalidGraph.String(), + }, { + description: "error messags without user error", + messages: makeMessages(!hasUserError), + reason: v1.PipelineRunReasonInvalidGraph.String(), + expected: v1.PipelineRunReasonInvalidGraph.String(), + }} + for _, tc := range tcs { + { + reason := pipelineErrors.LabelsUserErrorReason(tc.reason, tc.messages) + + if reason != tc.expected { + t.Errorf("failure reason expected: %s; but got %s", tc.expected, reason) + } + } + } +} + +func makeMessages(hasUserError bool) []interface{} { + msgs := []string{"foo error message", "bar error format"} + original := errors.New("orignal error") + + var messages []interface{} + messages = make([]interface{}, len(msgs)+1) + for i, v := range msgs { + messages[i] = v + } + + if hasUserError { + messages = append(messages, pipelineErrors.WrapUserError(original)) + } + return messages +} diff --git a/pkg/apis/pipeline/v1/pipelinerun_types.go b/pkg/apis/pipeline/v1/pipelinerun_types.go index c9db1bff010..0a443f0ca92 100644 --- a/pkg/apis/pipeline/v1/pipelinerun_types.go +++ b/pkg/apis/pipeline/v1/pipelinerun_types.go @@ -23,6 +23,7 @@ import ( "github.com/tektoncd/pipeline/pkg/apis/config" "github.com/tektoncd/pipeline/pkg/apis/pipeline" + pipelineErrors "github.com/tektoncd/pipeline/pkg/apis/pipeline/errors" pod "github.com/tektoncd/pipeline/pkg/apis/pipeline/pod" runv1beta1 "github.com/tektoncd/pipeline/pkg/apis/run/v1beta1" corev1 "k8s.io/api/core/v1" @@ -458,6 +459,7 @@ func (pr *PipelineRunStatus) MarkSucceeded(reason, messageFormat string, message // MarkFailed changes the Succeeded condition to False with the provided reason and message. func (pr *PipelineRunStatus) MarkFailed(reason, messageFormat string, messageA ...interface{}) { + reason = pipelineErrors.LabelsUserErrorReason(reason, messageA) pipelineRunCondSet.Manage(pr).MarkFalse(apis.ConditionSucceeded, reason, messageFormat, messageA...) succeeded := pr.GetCondition(apis.ConditionSucceeded) pr.CompletionTime = &succeeded.LastTransitionTime.Inner diff --git a/pkg/reconciler/apiserver/apiserver.go b/pkg/reconciler/apiserver/apiserver.go index 75504f56537..8489f6e12af 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.WrapUserError(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..6a00de162f9 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" @@ -375,7 +376,7 @@ func (c *Reconciler) resolvePipelineState( } else { pr.Status.MarkFailed(v1.PipelineRunReasonFailedValidation.String(), "PipelineRun %s/%s can't be Run; couldn't resolve all references: %s", - pipelineMeta.Namespace, pr.Name, err) + pipelineMeta.Namespace, pr.Name, pipelineErrors.WrapUserError(err)) } return nil, controller.NewPermanentError(err) } @@ -412,7 +413,10 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1.PipelineRun, getPipel pr.Status.MarkRunning(v1.PipelineRunReasonResolvingPipelineRef.String(), message) return nil case errors.Is(err, apiserver.ErrReferencedObjectValidationFailed), errors.Is(err, apiserver.ErrCouldntValidateObjectPermanent): - pr.Status.MarkFailed(v1.PipelineRunReasonFailedValidation.String(), err.Error()) + logger.Errorf("Failed dryRunValidation for PipelineRun %s: %w", pr.Name, err) + pr.Status.MarkFailed(v1.PipelineRunReasonFailedValidation.String(), + "Failed dryRunValidation for PipelineRun %s: %s", + pr.Name, pipelineErrors.WrapUserError(err)) return controller.NewPermanentError(err) case errors.Is(err, apiserver.ErrCouldntValidateObjectRetryable): return err @@ -443,7 +447,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.WrapUserError(err)) return controller.NewPermanentError(err) } @@ -456,7 +460,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.WrapUserError(err)) return controller.NewPermanentError(err) } @@ -464,7 +468,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.PipelineRunReasonFailedValidation.String(), "Pipeline %s/%s can't be Run; it has an invalid spec: %s", - pipelineMeta.Namespace, pipelineMeta.Name, err) + pipelineMeta.Namespace, pipelineMeta.Name, pipelineErrors.WrapUserError(err)) return controller.NewPermanentError(err) } @@ -492,7 +496,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.WrapUserError(err)) return controller.NewPermanentError(err) } } @@ -635,15 +639,19 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1.PipelineRun, getPipel if !rpt.IsCustomTask() { err := taskrun.ValidateResolvedTask(ctx, rpt.PipelineTask.Params, rpt.PipelineTask.Matrix, rpt.ResolvedTask) if err != nil { - logger.Errorf("Failed to validate pipelinerun %q with error %v", pr.Name, err) - pr.Status.MarkFailed(v1.PipelineRunReasonFailedValidation.String(), err.Error()) + logger.Errorf("Failed to validate pipelinerun %s with error %w", pr.Name, err) + pr.Status.MarkFailed(v1.PipelineRunReasonFailedValidation.String(), + "Failed to validate pipelinerun %s with error %s", + pr.Name, pipelineErrors.WrapUserError(err)) return controller.NewPermanentError(err) } if config.FromContextOrDefaults(ctx).FeatureFlags.EnableParamEnum { if err := resources.ValidateParamEnumSubset(originalTasks[i].Params, pipelineSpec.Params, rpt.ResolvedTask); err != nil { - logger.Errorf("Failed to validate pipelinerun %q with error %v", pr.Name, err) - pr.Status.MarkFailed(v1.PipelineRunReasonFailedValidation.String(), err.Error()) + logger.Errorf("Failed to validate pipelinerun %q with error %w", pr.Name, err) + pr.Status.MarkFailed(v1.PipelineRunReasonFailedValidation.String(), + "Failed to validate pipelinerun with error %s", + pipelineErrors.WrapUserError(err)) return controller.NewPermanentError(err) } } @@ -655,7 +663,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.WrapUserError(err)) return controller.NewPermanentError(err) } } @@ -684,8 +693,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.WrapUserError(err)) return controller.NewPermanentError(err) } @@ -854,8 +864,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.WrapUserError(err)) return controller.NewPermanentError(err) } } @@ -906,8 +917,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.WrapUserError(err)) return nil, controller.NewPermanentError(err) } } diff --git a/pkg/reconciler/pipelinerun/pipelinerun_test.go b/pkg/reconciler/pipelinerun/pipelinerun_test.go index 50de7d2b5a2..76fd159b16b 100644 --- a/pkg/reconciler/pipelinerun/pipelinerun_test.go +++ b/pkg/reconciler/pipelinerun/pipelinerun_test.go @@ -836,11 +836,11 @@ spec: pipelineRef: name: a-pipeline-without-params `), - reason: v1.PipelineRunReasonFailedValidation.String(), + reason: "[User error] " + v1.PipelineRunReasonFailedValidation.String(), permanentError: true, wantEvents: []string{ "Normal Started", - "Warning Failed invalid input params for task a-task-that-needs-params: missing values", + "Warning Failed Failed to validate pipelinerun pipeline-params-dont-exist with error invalid input params for task a-task-that-needs-params: missing values", }, }, { name: "invalid-pipeline-mismatching-parameter-types", @@ -855,7 +855,7 @@ spec: - name: some-param value: stringval `), - reason: v1.PipelineRunReasonParameterTypeMismatch.String(), + reason: "[User error] " + v1.PipelineRunReasonParameterTypeMismatch.String(), permanentError: true, wantEvents: []string{ "Normal Started", @@ -875,11 +875,11 @@ spec: value: key1: "a" `), - reason: v1.PipelineRunReasonObjectParameterMissKeys.String(), + reason: "[User error] " + v1.PipelineRunReasonObjectParameterMissKeys.String(), 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: pipelineRun missing object keys for parameters: map[some-param:[key2]]", }, }, { name: "invalid-pipeline-array-index-out-of-bound", @@ -896,7 +896,7 @@ spec: - "a" - "b" `), - reason: v1.PipelineRunReasonParamArrayIndexingInvalid.String(), + reason: "[User error] " + v1.PipelineRunReasonParamArrayIndexingInvalid.String(), permanentError: true, wantEvents: []string{ "Normal Started", @@ -915,7 +915,7 @@ spec: taskRef: name: b@d-t@$k `), - reason: v1.PipelineRunReasonFailedValidation.String(), + reason: "[User error] " + v1.PipelineRunReasonFailedValidation.String(), permanentError: true, wantEvents: []string{ "Normal Started", @@ -940,7 +940,7 @@ spec: - name: some-param value: stringval `, v1.ParamTypeArray)), - reason: v1.PipelineRunReasonParameterTypeMismatch.String(), + reason: "[User error] " + v1.PipelineRunReasonParameterTypeMismatch.String(), permanentError: true, wantEvents: []string{ "Normal Started", @@ -962,7 +962,7 @@ spec: taskRef: name: a-task-that-needs-params `, v1.ParamTypeString)), - reason: v1.PipelineRunReasonParameterMissing.String(), + reason: "[User error] " + v1.PipelineRunReasonParameterMissing.String(), permanentError: true, wantEvents: []string{ "Normal Started", @@ -982,7 +982,7 @@ spec: name: dag-task-1 runAfter: [ dag-task-1 ] `), - reason: v1.PipelineRunReasonInvalidGraph.String(), + reason: "[User error] " + v1.PipelineRunReasonInvalidGraph.String(), permanentError: true, wantEvents: []string{ "Normal Started", @@ -1008,7 +1008,7 @@ spec: taskRef: name: taskName `), - reason: v1.PipelineRunReasonInvalidGraph.String(), + reason: "[User error] " + v1.PipelineRunReasonInvalidGraph.String(), permanentError: true, wantEvents: []string{ "Normal Started", @@ -4526,7 +4526,7 @@ spec: prt := newPipelineRunTest(t, d) defer prt.Cancel() pipelineRun, _ := prt.reconcileRun("foo", "test-pipeline-run-different-service-accs", []string{}, true) - checkPipelineRunConditionStatusAndReason(t, pipelineRun, corev1.ConditionFalse, string(v1.PipelineRunReasonCELEvaluationFailed)) + checkPipelineRunConditionStatusAndReason(t, pipelineRun, corev1.ConditionFalse, "[User error] "+v1.PipelineRunReasonCELEvaluationFailed.String()) } func TestReconcile_Enum_With_Matrix_Pass(t *testing.T) { @@ -4677,7 +4677,7 @@ spec: prt := newPipelineRunTest(t, d) defer prt.Cancel() pipelineRun, _ := prt.reconcileRun("foo", "test-pipeline-level-enum-run", []string{}, true) - checkPipelineRunConditionStatusAndReason(t, pipelineRun, corev1.ConditionFalse, string(v1.PipelineRunReasonFailedValidation)) + checkPipelineRunConditionStatusAndReason(t, pipelineRun, corev1.ConditionFalse, "[User error] "+v1.PipelineRunReasonFailedValidation.String()) } func TestReconcile_PipelineTask_Level_Enum_Failed(t *testing.T) { @@ -4741,7 +4741,7 @@ spec: prt := newPipelineRunTest(t, d) defer prt.Cancel() pipelineRun, _ := prt.reconcileRun("foo", "test-pipelineTask-level-enum-run", []string{}, true) - checkPipelineRunConditionStatusAndReason(t, pipelineRun, corev1.ConditionFalse, string(v1.PipelineRunReasonInvalidParamValue)) + checkPipelineRunConditionStatusAndReason(t, pipelineRun, corev1.ConditionFalse, "[User error] "+v1.PipelineRunReasonInvalidParamValue.String()) } // TestReconcileWithAffinityAssistantStatefulSet tests that given a pipelineRun with workspaces, @@ -7557,7 +7557,7 @@ spec: t.Fatalf("Somehow had error getting reconciled run out of fake client: %s", err) } - if tc.wantFailed && reconciledRun.Status.GetCondition(apis.ConditionSucceeded).Reason != v1.PipelineRunReasonFailedValidation.String() { + if tc.wantFailed && reconciledRun.Status.GetCondition(apis.ConditionSucceeded).Reason != "[User error] "+v1.PipelineRunReasonFailedValidation.String() { t.Errorf("Expected PipelineRun to have reason FailedValidation, but condition reason is %s", reconciledRun.Status.GetCondition(apis.ConditionSucceeded)) } if !tc.wantFailed && reconciledRun.Status.GetCondition(apis.ConditionSucceeded).IsFalse() { @@ -7787,13 +7787,13 @@ spec: reason: v1.PipelineRunReasonInvalidTaskResultReference.String(), }, { name: "pipelinerun-with-optional-workspace-validation", - reason: v1.PipelineRunReasonRequiredWorkspaceMarkedOptional.String(), + reason: "[User error] " + v1.PipelineRunReasonRequiredWorkspaceMarkedOptional.String(), }, { name: "pipelinerun-matrix-param-invalid-type", - reason: v1.PipelineRunReasonInvalidMatrixParameterTypes.String(), + reason: "[User error] " + v1.PipelineRunReasonInvalidMatrixParameterTypes.String(), }, { name: "pipelinerun-with-invalid-taskrunspecs", - reason: v1.PipelineRunReasonInvalidTaskRunSpec.String(), + reason: "[User error] " + v1.PipelineRunReasonInvalidTaskRunSpec.String(), }, } for _, tc := range testCases { @@ -12737,7 +12737,7 @@ spec: defer prt.Cancel() pipelineRun, clients := prt.reconcileRun(pr.Namespace, pr.Name, wantEvents /* wantEvents*/, true /* permanentError*/) // Validate the PR failed due to out of bounds array index reference - checkPipelineRunConditionStatusAndReason(t, pipelineRun, corev1.ConditionFalse, "PipelineValidationFailed") + checkPipelineRunConditionStatusAndReason(t, pipelineRun, corev1.ConditionFalse, "[User error] PipelineValidationFailed") taskRuns := getTaskRunsForPipelineTask(prt.TestAssets.Ctx, t, clients, pr.Namespace, pr.Name, "echo-platforms") // Validate no TaskRuns were created validateTaskRunsCount(t, taskRuns, 0) diff --git a/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go b/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go index e3c7652252f..144c1d3efb3 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.WrapUserError(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.WrapUserError(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..f4a4d2533aa 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.WrapUserError(errors.New("Invalid task result reference")) ) // ResolvedResultRefs represents all of the ResolvedResultRef for a pipeline task diff --git a/pkg/reconciler/pipelinerun/resources/validate_dependencies.go b/pkg/reconciler/pipelinerun/resources/validate_dependencies.go index 2ec3a2b3a8a..81261da82bd 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.WrapUserError(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..4f800df8a99 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.WrapUserError(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.WrapUserError(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.WrapUserError(fmt.Errorf("pipelineRun missing object keys for parameters: %v", missings)) } return nil diff --git a/pkg/reconciler/taskrun/resources/validate_params.go b/pkg/reconciler/taskrun/resources/validate_params.go index 7f6525cb22a..965b9c76ef0 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.WrapUserError(fmt.Errorf("non-existent param references:%v", outofBoundParams.List())) } return nil }