diff --git a/docs/pipelineruns.md b/docs/pipelineruns.md index f308dcced8a..f02702c82de 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 message, for example: + +```yaml +# Failed PipelineRun with message labeled "[User error]" +apiVersion: tekton.dev/v1 +kind: PipelineRun +metadata: + ... +spec: + ... +status: + ... + conditions: + - lastTransitionTime: "2022-06-02T19:02:58Z" + message: '[User error] PipelineRun default parameters is missing some parameters required by + Pipeline pipelinerun-with-params''s parameters: pipelineRun missing parameters: + [pl-param-x]' + reason: 'ParameterMissing' + status: "False" + type: Succeeded +``` + +```console +~/pipeline$ tkn pr list +NAME STARTED DURATION STATUS +pipelinerun-with-params 5 seconds ago 0s Failed(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..a7dd38dad98 --- /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) +} + +// LabelUserError labels the failure RunStatus message 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 LabelUserError(messageFormat string, messageA []interface{}) string { + for _, message := range messageA { + if ue, ok := message.(*UserError); ok { + return ue.Reason + messageFormat + } + } + return messageFormat +} diff --git a/pkg/apis/pipeline/errors/errors_test.go b/pkg/apis/pipeline/errors/errors_test.go new file mode 100644 index 00000000000..44ebdb51681 --- /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 + + makeMessages := func(hasUserError bool) []interface{} { + msgs := []string{"foo error message", "bar error format"} + original := errors.New("orignal error") + + messages := make([]interface{}, 0) + for _, msg := range msgs { + messages = append(messages, msg) + } + + if hasUserError { + messages = append(messages, pipelineErrors.WrapUserError(original)) + } + return messages + } + + tcs := []struct { + description string + messageFormat string + messages []interface{} + expected string + }{{ + description: "error messags with user error", + messageFormat: v1.PipelineRunReasonInvalidGraph.String(), + messages: makeMessages(hasUserError), + expected: "[User error] " + v1.PipelineRunReasonInvalidGraph.String(), + }, { + description: "error messags without user error", + messages: makeMessages(!hasUserError), + messageFormat: v1.PipelineRunReasonInvalidGraph.String(), + expected: v1.PipelineRunReasonInvalidGraph.String(), + }} + for _, tc := range tcs { + { + messageFormat := pipelineErrors.LabelUserError(tc.messageFormat, tc.messages) + + if messageFormat != tc.expected { + t.Errorf("failure messageFormat expected: %s; but got %s", tc.expected, messageFormat) + } + } + } +} diff --git a/pkg/apis/pipeline/v1/pipelinerun_types.go b/pkg/apis/pipeline/v1/pipelinerun_types.go index 34be57243ae..fa2c0bb0db5 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" @@ -465,6 +466,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{}) { + messageFormat = pipelineErrors.LabelUserError(messageFormat, messageA) pipelineRunCondSet.Manage(pr).MarkFalse(apis.ConditionSucceeded, reason, messageFormat, messageA...) succeeded := pr.GetCondition(apis.ConditionSucceeded) pr.CompletionTime = &succeeded.LastTransitionTime.Inner diff --git a/pkg/apis/pipeline/v1/pipelinerun_types_test.go b/pkg/apis/pipeline/v1/pipelinerun_types_test.go index b51a06df37b..3515d27e588 100644 --- a/pkg/apis/pipeline/v1/pipelinerun_types_test.go +++ b/pkg/apis/pipeline/v1/pipelinerun_types_test.go @@ -18,12 +18,14 @@ package v1_test import ( "context" + "errors" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/tektoncd/pipeline/pkg/apis/config" + pipelineErrors "github.com/tektoncd/pipeline/pkg/apis/pipeline/errors" "github.com/tektoncd/pipeline/pkg/apis/pipeline/pod" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" "github.com/tektoncd/pipeline/test/diff" @@ -699,3 +701,84 @@ func TestPipelineRun_GetTaskRunSpec(t *testing.T) { } } } + +func TestPipelineRunMarkFailedCondition(t *testing.T) { + failedRunReason := v1.PipelineRunReasonFailed + messageFormat := "error bar occurred %s" + + makeMessages := func(hasUserError bool) []interface{} { + errorMsg := "baz error message" + original := errors.New("orignal error") + + messages := make([]interface{}, 0) + if hasUserError { + messages = append(messages, pipelineErrors.WrapUserError(original)) + } else { + messages = append(messages, errorMsg) + } + + return messages + } + + tcs := []struct { + name string + hasUserError bool + prStatus v1.PipelineRunStatus + expectedConditions duckv1.Conditions + }{{ + name: "mark pipelinerun status failed with user error", + hasUserError: true, + prStatus: v1.PipelineRunStatus{ + PipelineRunStatusFields: v1.PipelineRunStatusFields{ + StartTime: &metav1.Time{Time: now}, + }, + Status: duckv1.Status{ + Conditions: duckv1.Conditions{}, + }, + }, + expectedConditions: duckv1.Conditions{ + apis.Condition{ + Type: "Succeeded", + Status: "False", + Reason: "Failed", + Message: "[User error] error bar occurred orignal error", + }, + }, + }, { + name: "mark pipelinerun status failed non user error", + hasUserError: false, + prStatus: v1.PipelineRunStatus{ + PipelineRunStatusFields: v1.PipelineRunStatusFields{ + StartTime: &metav1.Time{Time: now}, + }, + Status: duckv1.Status{ + Conditions: duckv1.Conditions{}, + }, + }, + expectedConditions: duckv1.Conditions{ + apis.Condition{ + Type: "Succeeded", + Status: "False", + Reason: "Failed", + Message: "error bar occurred baz error message", + }, + }, + }} + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + pr := &v1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "bar", + }, + Status: tc.prStatus, + } + pr.Status.MarkFailed(failedRunReason.String(), messageFormat, makeMessages(tc.hasUserError)...) + updatedCondition := pr.Status.Status.Conditions + + if d := cmp.Diff(tc.expectedConditions, updatedCondition, cmpopts.IgnoreFields(apis.Condition{}, "LastTransitionTime")); d != "" { + t.Error(diff.PrintWantGot(d)) + } + }) + } +} 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 32c3254e54c..cb7591e9bed 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) } @@ -415,7 +416,7 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1.PipelineRun, getPipel 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, err) + pr.Name, pipelineErrors.WrapUserError(err)) return controller.NewPermanentError(err) case errors.Is(err, apiserver.ErrCouldntValidateObjectRetryable): return err @@ -446,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) } @@ -458,8 +459,8 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1.PipelineRun, getPipel if err != nil { // 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) + "PipelineRun %s/%s's Pipeline DAG is invalid for finally clause: %s", + pr.Namespace, pr.Name, pipelineErrors.WrapUserError(err)) return controller.NewPermanentError(err) } @@ -467,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) } @@ -495,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) } } @@ -641,7 +642,7 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1.PipelineRun, getPipel logger.Errorf("Failed to validate pipelinerun %s with error %w", pr.Name, err) pr.Status.MarkFailed(v1.PipelineRunReasonFailedValidation.String(), "Validation failed for pipelinerun %s with error %s", - pr.Name, err) + pr.Name, pipelineErrors.WrapUserError(err)) return controller.NewPermanentError(err) } @@ -650,7 +651,7 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1.PipelineRun, getPipel logger.Errorf("Failed to validate pipelinerun %q with error %w", pr.Name, err) pr.Status.MarkFailed(v1.PipelineRunReasonFailedValidation.String(), "Validation failed for pipelinerun with error %s", - err) + pipelineErrors.WrapUserError(err)) return controller.NewPermanentError(err) } } @@ -662,8 +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(v1.PipelineRunReasonCELEvaluationFailed.String(), - "Error evaluating CEL %s: %w", pr.Name, err) + pr.Status.MarkFailed(string(v1.PipelineRunReasonCELEvaluationFailed), + "Error evaluating CEL %s: %w", pr.Name, pipelineErrors.WrapUserError(err)) return controller.NewPermanentError(err) } } @@ -694,7 +695,7 @@ 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: %w", err) pr.Status.MarkFailed(v1.PipelineRunReasonRequiredWorkspaceMarkedOptional.String(), - "Optional workspace not supported by task: %w", err) + "Optional workspace not supported by task: %w", pipelineErrors.WrapUserError(err)) return controller.NewPermanentError(err) } @@ -872,7 +873,7 @@ func (c *Reconciler) runNextSchedulableTask(ctx context.Context, pr *v1.Pipeline if err := resources.ValidateParameterTypesInMatrix(pipelineRunFacts.State); err != nil { 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", err) + "Failed to validate matrix %q with error %w", pipelineErrors.WrapUserError(err)) return controller.NewPermanentError(err) } } @@ -925,7 +926,7 @@ func (c *Reconciler) createTaskRuns(ctx context.Context, rpt *resources.Resolved if err := taskrun.ValidateEnumParam(ctx, params, rpt.ResolvedTask.TaskSpec.Params); err != nil { pr.Status.MarkFailed(v1.PipelineRunReasonInvalidParamValue.String(), "Invalid param value from PipelineTask \"%s\": %w", - rpt.PipelineTask.Name, err) + 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 d58c8b9dfe3..3ed6e6d2ce0 100644 --- a/pkg/reconciler/pipelinerun/pipelinerun_test.go +++ b/pkg/reconciler/pipelinerun/pipelinerun_test.go @@ -840,7 +840,7 @@ spec: permanentError: true, wantEvents: []string{ "Normal Started", - "Warning Failed Validation failed for pipelinerun pipeline-params-dont-exist with error invalid input params for task a-task-that-needs-params: missing values", + "Warning Failed [User error] Validation failed for pipelinerun pipeline-params-dont-exist with error invalid input params for task a-task-that-needs-params: missing values for these params which have no default values: [some-param]", }, }, { name: "invalid-pipeline-mismatching-parameter-types", @@ -859,7 +859,7 @@ spec: permanentError: true, wantEvents: []string{ "Normal Started", - "Warning Failed PipelineRun foo/pipeline-mismatching-param-type parameters have mismatching types", + "Warning Failed [User error] PipelineRun foo/pipeline-mismatching-param-type parameters have mismatching types with Pipeline foo/a-pipeline-with-array-params's parameters: parameters have inconsistent types : [some-param]", }, }, { name: "invalid-pipeline-missing-object-keys", @@ -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 [User error] 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", @@ -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 [User error] 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])]", }, }, { name: "invalid-embedded-pipeline-bad-name-shd-stop-reconciling", @@ -919,7 +919,9 @@ spec: permanentError: true, wantEvents: []string{ "Normal Started", - "Warning Failed Pipeline foo/embedded-pipeline-invalid can't be Run; it has an invalid spec", + `Warning Failed [User error] Pipeline foo/embedded-pipeline-invalid can't be Run; it has an invalid spec: invalid value "bad-t@$k": tasks[0].name +Pipeline Task name must be a valid DNS Label.For more info refer to https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names +invalid value: name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]'): tasks[0].taskRef.name`, }, }, { name: "invalid-embedded-pipeline-mismatching-parameter-types", @@ -944,7 +946,7 @@ spec: permanentError: true, wantEvents: []string{ "Normal Started", - "Warning Failed PipelineRun foo/embedded-pipeline-mismatching-param-type parameters have mismatching types", + "Warning Failed [User error] PipelineRun foo/embedded-pipeline-mismatching-param-type parameters have mismatching types with Pipeline foo/embedded-pipeline-mismatching-param-type's parameters: parameters have inconsistent types : [some-param]", }, }, { name: "invalid-pipeline-run-missing-params-shd-stop-reconciling", @@ -966,7 +968,7 @@ spec: permanentError: true, wantEvents: []string{ "Normal Started", - "Warning Failed PipelineRun foo parameters is missing some parameters required by Pipeline pipelinerun-missing-params", + "Warning Failed [User error] PipelineRun foo parameters is missing some parameters required by Pipeline pipelinerun-missing-params's parameters: pipelineRun missing parameters: [some-param]", }, }, { name: "invalid-pipeline-with-invalid-dag-graph", @@ -986,7 +988,7 @@ spec: permanentError: true, wantEvents: []string{ "Normal Started", - "Warning Failed PipelineRun foo/pipeline-invalid-dag-graph's Pipeline DAG is invalid", + `Warning Failed [User error] PipelineRun foo/pipeline-invalid-dag-graph's Pipeline DAG is invalid: cycle detected; task "dag-task-1" depends on "dag-task-1"`, }, }, { name: "invalid-pipeline-with-invalid-final-tasks-graph", @@ -1012,7 +1014,7 @@ spec: permanentError: true, wantEvents: []string{ "Normal Started", - "Warning Failed PipelineRun foo's Pipeline DAG is invalid for finally clause", + "Warning Failed [User error] PipelineRun foo/pipeline-invalid-final-graph's Pipeline DAG is invalid for finally clause: task final-task-1 is already present in Graph, can't add it again: duplicate pipeline task", }, }} { t.Run(tc.name, func(t *testing.T) { @@ -4596,7 +4598,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, v1.PipelineRunReasonCELEvaluationFailed.String()) } func TestReconcile_Enum_With_Matrix_Pass(t *testing.T) { @@ -4747,7 +4749,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, v1.PipelineRunReasonFailedValidation.String()) } func TestReconcile_PipelineTask_Level_Enum_Failed(t *testing.T) { @@ -4811,7 +4813,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, v1.PipelineRunReasonInvalidParamValue.String()) } // TestReconcileWithAffinityAssistantStatefulSet tests that given a pipelineRun with workspaces, @@ -12800,7 +12802,7 @@ spec: wantEvents := []string{ "Normal Started", - "Warning Failed PipelineRun foo/pr can't be Run; couldn't resolve all references: array Result Index 3 for Task pt-with-result Result platforms is out of bound of size 3", + "Warning Failed [User error] PipelineRun foo/pr can't be Run; couldn't resolve all references: array Result Index 3 for Task pt-with-result Result platforms is out of bound of size 3", "Warning InternalError 1 error occurred:", } prt := newPipelineRunTest(t, d) diff --git a/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go b/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go index 93a9899dbdd..6d5ad38bdc1 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" @@ -509,7 +510,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 @@ -528,7 +529,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 2172a8f8eae..73d7f9cf29a 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 }