diff --git a/pkg/reconciler/pipelinerun/resources/pipelinerunstate.go b/pkg/reconciler/pipelinerun/resources/pipelinerunstate.go index b2e4a09627c..4fe91ed0f5e 100644 --- a/pkg/reconciler/pipelinerun/resources/pipelinerunstate.go +++ b/pkg/reconciler/pipelinerun/resources/pipelinerunstate.go @@ -18,7 +18,6 @@ package resources import ( "fmt" - "reflect" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" "github.com/tektoncd/pipeline/pkg/reconciler/pipeline/dag" @@ -193,53 +192,30 @@ func (facts *PipelineRunFacts) GetPipelineConditionStatus(pr *v1beta1.PipelineRu } } - allTasks := []string{} - withStatusTasks := []string{} - skipTasks := []v1beta1.SkippedTask{} - failedTasks := int(0) - cancelledTasks := int(0) - reason := v1beta1.PipelineRunReasonSuccessful.String() - - // Check to see if all tasks are success or skipped - // - // The completion reason is also calculated here, but it will only be used - // if all tasks are completed. - // - // The pipeline run completion reason is set from the taskrun completion reason - // according to the following logic: - // - // - All successful: ReasonSucceeded - // - Some successful, some skipped: ReasonCompleted - // - Some cancelled, none failed: ReasonCancelled - // - At least one failed: ReasonFailed - for _, rprt := range facts.State { - allTasks = append(allTasks, rprt.PipelineTask.Name) - switch { - case rprt.IsSuccessful(): - withStatusTasks = append(withStatusTasks, rprt.PipelineTask.Name) - case rprt.Skip(facts): - withStatusTasks = append(withStatusTasks, rprt.PipelineTask.Name) - skipTasks = append(skipTasks, v1beta1.SkippedTask{Name: rprt.PipelineTask.Name}) - // At least one is skipped and no failure yet, mark as completed - if reason == v1beta1.PipelineRunReasonSuccessful.String() { - reason = v1beta1.PipelineRunReasonCompleted.String() - } - case rprt.IsCancelled(): - cancelledTasks++ - withStatusTasks = append(withStatusTasks, rprt.PipelineTask.Name) - if reason != v1beta1.PipelineRunReasonFailed.String() { - reason = v1beta1.PipelineRunReasonCancelled.String() - } - case rprt.IsFailure(): - withStatusTasks = append(withStatusTasks, rprt.PipelineTask.Name) - failedTasks++ - reason = v1beta1.PipelineRunReasonFailed.String() - } - } + // report the count in PipelineRun Status + // get the count of successful tasks, failed tasks, cancelled tasks, skipped task, and incomplete tasks + sTasks, fTasks, cTasks, skTasks, iTasks := facts.getPipelineTasksCount() + // completed task is a collection of successful, failed, cancelled tasks (skipped tasks are reported separately) + cmTasks := sTasks + fTasks + cTasks - if reflect.DeepEqual(allTasks, withStatusTasks) { + // The completion reason is set from the TaskRun completion reason + // by default, set it to ReasonRunning + reason := v1beta1.PipelineRunReasonRunning.String() + + if facts.checkAllTasksDone() { status := corev1.ConditionTrue - if failedTasks > 0 || cancelledTasks > 0 { + reason := v1beta1.PipelineRunReasonSuccessful.String() + // Set reason to ReasonCompleted - At least one is skipped + if skTasks > 0 { + reason = v1beta1.PipelineRunReasonCompleted.String() + } + // Set reason to ReasonFailed - At least one failed + if fTasks > 0 { + reason = v1beta1.PipelineRunReasonFailed.String() + status = corev1.ConditionFalse + // Set reason to ReasonCancelled - At least one is cancelled and no failure yet + } else if cTasks > 0 { + reason = v1beta1.PipelineRunReasonCancelled.String() status = corev1.ConditionFalse } logger.Infof("All TaskRuns have finished for PipelineRun %s so it has finished", pr.Name) @@ -248,7 +224,7 @@ func (facts *PipelineRunFacts) GetPipelineConditionStatus(pr *v1beta1.PipelineRu Status: status, Reason: reason, Message: fmt.Sprintf("Tasks Completed: %d (Failed: %d, Cancelled %d), Skipped: %d", - len(allTasks)-len(skipTasks), failedTasks, cancelledTasks, len(skipTasks)), + cmTasks, fTasks, cTasks, skTasks), } } @@ -256,20 +232,21 @@ func (facts *PipelineRunFacts) GetPipelineConditionStatus(pr *v1beta1.PipelineRu // transition pipeline into stopping state when one of the tasks(dag/final) cancelled or one of the dag tasks failed // for a pipeline with final tasks, single dag task failure does not transition to interim stopping state // pipeline stays in running state until all final tasks are done before transitioning to failed state - if cancelledTasks > 0 || (failedTasks > 0 && facts.CheckFinalTasksDone()) { + if cTasks > 0 || (fTasks > 0 && facts.CheckFinalTasksDone()) { reason = v1beta1.PipelineRunReasonStopping.String() - } else { - reason = v1beta1.PipelineRunReasonRunning.String() } + + // return the status return &apis.Condition{ Type: apis.ConditionSucceeded, Status: corev1.ConditionUnknown, Reason: reason, Message: fmt.Sprintf("Tasks Completed: %d (Failed: %d, Cancelled %d), Incomplete: %d, Skipped: %d", - len(withStatusTasks)-len(skipTasks), failedTasks, cancelledTasks, len(allTasks)-len(withStatusTasks), len(skipTasks)), + cmTasks, fTasks, cTasks, iTasks, skTasks), } } +// GetSkippedTasks constructs a list of SkippedTask struct to be included in the PipelineRun Status func (facts *PipelineRunFacts) GetSkippedTasks() []v1beta1.SkippedTask { skipped := []v1beta1.SkippedTask{} for _, rprt := range facts.State { @@ -329,6 +306,48 @@ func (state PipelineRunState) GetTaskRunsStatus(pr *v1beta1.PipelineRun) map[str return status } +// checkAllTasksDone returns true if all DAG and finally tasks have been visited and executed +// tasks could have either executed successfully or failed or cancelled or skipped +func (facts *PipelineRunFacts) checkAllTasksDone() bool { + for _, t := range facts.State { + if t.TaskRun == nil { + if t.Skip(facts) { + continue + } + return false + } + if !t.IsDone() && !t.IsCancelled() { + return false + } + } + return true +} + +// getPipelineTasksCount returns the count of successful tasks, failed tasks, cancelled tasks, skipped task, and incomplete tasks +func (facts *PipelineRunFacts) getPipelineTasksCount() (int, int, int, int, int) { + s, f, c, sk, i := 0, 0, 0, 0, 0 + for _, t := range facts.State { + switch { + // increment success counter since the task is successful + case t.IsSuccessful(): + s++ + // increment failure counter since the task has failed + case t.IsFailure(): + f++ + // increment cancelled counter since the task is cancelled + case t.IsCancelled(): + c++ + // increment skip counter since the task is skipped + case t.Skip(facts): + sk++ + // increment incomplete counter since the task is pending and not executed yet + default: + i++ + } + } + return s, f, c, sk, i +} + func (facts *PipelineRunFacts) isDAGTask(pipelineTaskName string) bool { if _, ok := facts.TasksGraph.Nodes[pipelineTaskName]; ok { return true