diff --git a/pkg/apis/pipeline/v1beta1/pipelinerun_types.go b/pkg/apis/pipeline/v1beta1/pipelinerun_types.go index 30efd134b96..d06a7f57006 100644 --- a/pkg/apis/pipeline/v1beta1/pipelinerun_types.go +++ b/pkg/apis/pipeline/v1beta1/pipelinerun_types.go @@ -422,6 +422,21 @@ type ChildStatusReference struct { WhenExpressions []WhenExpression `json:"whenExpressions,omitempty"` } +// GetConditionChecks returns a map representation of this ChildStatusReference's ConditionChecks, in the same form +// as PipelineRunTaskRunStatus.ConditionChecks. +func (cr ChildStatusReference) GetConditionChecks() map[string]*PipelineRunConditionCheckStatus { + if len(cr.ConditionChecks) == 0 { + return nil + } + ccMap := make(map[string]*PipelineRunConditionCheckStatus) + + for _, cc := range cr.ConditionChecks { + ccMap[cc.ConditionCheckName] = &cc.PipelineRunConditionCheckStatus + } + + return ccMap +} + // PipelineRunStatusFields holds the fields of PipelineRunStatus' status. // This is defined separately and inlined so that other types can readily // consume these fields via duck typing. diff --git a/pkg/reconciler/pipelinerun/pipelinerun.go b/pkg/reconciler/pipelinerun/pipelinerun.go index e1d2e6eefe5..8c1c947a9bc 100644 --- a/pkg/reconciler/pipelinerun/pipelinerun.go +++ b/pkg/reconciler/pipelinerun/pipelinerun.go @@ -1196,6 +1196,53 @@ func (c *Reconciler) updatePipelineRunStatusFromInformer(ctx context.Context, pr return nil } +// filterTaskRunsForPipelineRun returns TaskRuns owned by the PipelineRun and their condition checks +func filterTaskRunsForPipelineRun(logger *zap.SugaredLogger, pr *v1beta1.PipelineRun, trs []*v1beta1.TaskRun) ([]*v1beta1.TaskRun, map[string][]*v1beta1.TaskRun) { + var normalTaskRuns []*v1beta1.TaskRun + conditionTaskRuns := make(map[string][]*v1beta1.TaskRun) + + for _, tr := range trs { + // Only process TaskRuns that are owned by this PipelineRun. + // This skips TaskRuns that are indirectly created by the PipelineRun (e.g. by custom tasks). + if len(tr.OwnerReferences) < 1 || tr.OwnerReferences[0].UID != pr.ObjectMeta.UID { + logger.Debugf("Found a TaskRun %s that is not owned by this PipelineRun", tr.Name) + continue + } + lbls := tr.GetLabels() + pipelineTaskName := lbls[pipeline.PipelineTaskLabelKey] + if _, ok := lbls[pipeline.ConditionCheckKey]; ok { + // Save condition for looping over them after this + if _, ok := conditionTaskRuns[pipelineTaskName]; !ok { + // If it's the first condition taskrun, initialise the slice + conditionTaskRuns[pipelineTaskName] = []*v1beta1.TaskRun{} + } + conditionTaskRuns[pipelineTaskName] = append(conditionTaskRuns[pipelineTaskName], tr) + } else { + normalTaskRuns = append(normalTaskRuns, tr) + } + } + + return normalTaskRuns, conditionTaskRuns +} + +// filterRunsForPipelineRun filters the given slice of Runs, returning only those owned by the given PipelineRun. +func filterRunsForPipelineRun(logger *zap.SugaredLogger, pr *v1beta1.PipelineRun, runs []*v1alpha1.Run) []*v1alpha1.Run { + var runsToInclude []*v1alpha1.Run + + // Loop over all the Runs associated to Tasks + for _, run := range runs { + // Only process Runs that are owned by this PipelineRun. + // This skips Runs that are indirectly created by the PipelineRun (e.g. by custom tasks). + if len(run.OwnerReferences) < 1 || run.OwnerReferences[0].UID != pr.ObjectMeta.UID { + logger.Debugf("Found a Run %s that is not owned by this PipelineRun", run.Name) + continue + } + runsToInclude = append(runsToInclude, run) + } + + return runsToInclude +} + func updatePipelineRunStatusFromTaskRuns(logger *zap.SugaredLogger, pr *v1beta1.PipelineRun, trs []*v1beta1.TaskRun) { // If no TaskRun was found, nothing to be done. We never remove taskruns from the status if trs == nil || len(trs) == 0 { @@ -1286,6 +1333,38 @@ func updatePipelineRunStatusFromTaskRuns(logger *zap.SugaredLogger, pr *v1beta1. } } +// getNewConditionChecksForTaskRun returns a map of condition task name to condition check status for each condition TaskRun +// provided which isn't already present in the existing map of condition checks. +func getNewConditionChecksForTaskRun(logger *zap.SugaredLogger, existingChecks map[string]*v1beta1.PipelineRunConditionCheckStatus, + actualConditionTaskRuns []*v1beta1.TaskRun) map[string]*v1beta1.PipelineRunConditionCheckStatus { + // If we don't have any condition task runs to process, just return nil. + if len(actualConditionTaskRuns) == 0 { + return nil + } + + newChecks := make(map[string]*v1beta1.PipelineRunConditionCheckStatus) + + for i, foundTaskRun := range actualConditionTaskRuns { + lbls := foundTaskRun.GetLabels() + if _, ok := existingChecks[foundTaskRun.Name]; !ok { + // The condition check was not found, so we need to add it + // We only add the condition name, the status can now be gathered by the + // normal reconcile process + if conditionName, ok := lbls[pipeline.ConditionNameKey]; ok { + newChecks[foundTaskRun.Name] = &v1beta1.PipelineRunConditionCheckStatus{ + ConditionName: fmt.Sprintf("%s-%s", conditionName, strconv.Itoa(i)), + } + } else { + // The condition name label is missing, so we cannot recover this + logger.Warnf("found an orphaned condition taskrun %#v with missing %s label", + foundTaskRun, pipeline.ConditionNameKey) + } + } + } + + return newChecks +} + func updatePipelineRunStatusFromRuns(logger *zap.SugaredLogger, pr *v1beta1.PipelineRun, runs []*v1alpha1.Run) { // If no Run was found, nothing to be done. We never remove runs from the status if runs == nil || len(runs) == 0 { @@ -1313,3 +1392,96 @@ func updatePipelineRunStatusFromRuns(logger *zap.SugaredLogger, pr *v1beta1.Pipe } } } + +func updatePipelineRunStatusFromChildRefs(logger *zap.SugaredLogger, pr *v1beta1.PipelineRun, trs []*v1beta1.TaskRun, runs []*v1alpha1.Run) { + // If no TaskRun or Run was found, nothing to be done. We never remove child references from the status. + // We do still return an empty map of TaskRun/Run names keyed by PipelineTask name for later functions. + if len(trs) == 0 && len(runs) == 0 { + return + } + + // Map PipelineTask names to TaskRun child references that were already in the status + childRefByPipelineTask := make(map[string]*v1beta1.ChildStatusReference) + + for i := range pr.Status.ChildReferences { + childRefByPipelineTask[pr.Status.ChildReferences[i].PipelineTaskName] = &pr.Status.ChildReferences[i] + } + + taskRuns, conditionTaskRuns := filterTaskRunsForPipelineRun(logger, pr, trs) + + // Loop over all the TaskRuns associated to Tasks + for _, tr := range taskRuns { + lbls := tr.GetLabels() + pipelineTaskName := lbls[pipeline.PipelineTaskLabelKey] + + if _, ok := childRefByPipelineTask[pipelineTaskName]; !ok { + // This tr was missing from the status. + // Add it without conditions, which are handled in the next loop + logger.Infof("Found a TaskRun %s that was missing from the PipelineRun status", tr.Name) + + // Since this was recovered now, add it to the map, or it might be overwritten + childRefByPipelineTask[pipelineTaskName] = &v1beta1.ChildStatusReference{ + TypeMeta: runtime.TypeMeta{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: "TaskRun", + }, + Name: tr.Name, + PipelineTaskName: pipelineTaskName, + } + } + } + + // Loop over all the Runs associated to Tasks + for _, r := range filterRunsForPipelineRun(logger, pr, runs) { + lbls := r.GetLabels() + pipelineTaskName := lbls[pipeline.PipelineTaskLabelKey] + + if _, ok := childRefByPipelineTask[pipelineTaskName]; !ok { + // This run was missing from the status. + // Add it without conditions, which are handled in the next loop + logger.Infof("Found a Run %s that was missing from the PipelineRun status", r.Name) + + // Since this was recovered now, add it to the map, or it might be overwritten + childRefByPipelineTask[pipelineTaskName] = &v1beta1.ChildStatusReference{ + TypeMeta: runtime.TypeMeta{ + APIVersion: v1alpha1.SchemeGroupVersion.String(), + Kind: "Run", + }, + Name: r.Name, + PipelineTaskName: pipelineTaskName, + } + } + } + + // Then loop by pipelinetask name over all the TaskRuns associated to Conditions + for pipelineTaskName, actualConditionTaskRuns := range conditionTaskRuns { + // GetTaskRunName will look in first ChildReferences and then TaskRuns to see if there's already a TaskRun name + // for this pipelineTaskName, and if not, it will generate one. + taskRunName := resources.GetTaskRunName(pr.Status.TaskRuns, pr.Status.ChildReferences, pipelineTaskName, pr.Name) + if _, ok := childRefByPipelineTask[pipelineTaskName]; !ok { + childRefByPipelineTask[pipelineTaskName] = &v1beta1.ChildStatusReference{ + TypeMeta: runtime.TypeMeta{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: "TaskRun", + }, + Name: taskRunName, + PipelineTaskName: pipelineTaskName, + } + } + + for k, v := range getNewConditionChecksForTaskRun(logger, childRefByPipelineTask[pipelineTaskName].GetConditionChecks(), actualConditionTaskRuns) { + // Just append any new condition checks to the relevant ChildStatusReference.ConditionChecks. + childRefByPipelineTask[pipelineTaskName].ConditionChecks = append(childRefByPipelineTask[pipelineTaskName].ConditionChecks, + &v1beta1.PipelineRunChildConditionCheckStatus{ + PipelineRunConditionCheckStatus: *v, + ConditionCheckName: k, + }) + } + } + + var newChildRefs []v1beta1.ChildStatusReference + for k := range childRefByPipelineTask { + newChildRefs = append(newChildRefs, *childRefByPipelineTask[k]) + } + pr.Status.ChildReferences = newChildRefs +} diff --git a/pkg/reconciler/pipelinerun/pipelinerun_updatestatus_test.go b/pkg/reconciler/pipelinerun/pipelinerun_updatestatus_test.go index 20d126ad764..baab33471dd 100644 --- a/pkg/reconciler/pipelinerun/pipelinerun_updatestatus_test.go +++ b/pkg/reconciler/pipelinerun/pipelinerun_updatestatus_test.go @@ -17,16 +17,19 @@ limitations under the License. package pipelinerun import ( + "fmt" "regexp" + "sort" "testing" + "github.com/ghodss/yaml" "github.com/google/go-cmp/cmp" - "github.com/tektoncd/pipeline/pkg/apis/pipeline" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" "github.com/tektoncd/pipeline/test/diff" - corev1 "k8s.io/api/core/v1" + "github.com/tektoncd/pipeline/test/parse" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "knative.dev/pkg/apis" duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1" @@ -41,168 +44,119 @@ type updateStatusTaskRunsData struct { simple map[string]*v1beta1.PipelineRunTaskRunStatus } -func getUpdateStatusTaskRunsData() updateStatusTaskRunsData { - // PipelineRunConditionCheckStatus recovered by updatePipelineRunStatusFromTaskRuns - // It does not include the status, which is then retrieved via the regular reconcile - prccs2Recovered := map[string]*v1beta1.PipelineRunConditionCheckStatus{ - "pr-task-2-running-condition-check-xxyyy": { - ConditionName: "running-condition-0", - }, - } - prccs3Recovered := map[string]*v1beta1.PipelineRunConditionCheckStatus{ - "pr-task-3-successful-condition-check-xxyyy": { - ConditionName: "successful-condition-0", - }, +func getUpdateStatusTaskRunsData(t *testing.T) updateStatusTaskRunsData { + prTask1Yaml := ` +pipelineTaskName: task-1 +status: {} +` + prTask2Yaml := ` +conditionChecks: + pr-task-2-running-condition-check-xxyyy: + conditionName: running-condition-0 + status: + check: + running: {} + conditions: + - status: Unknown + type: Succeeded +pipelineTaskName: task-2 +` + prTask3Yaml := ` +conditionChecks: + pr-task-3-successful-condition-check-xxyyy: + conditionName: successful-condition-0 + status: + check: + terminated: + exitCode: 0 + conditions: + - status: "True" + type: Succeeded +pipelineTaskName: task-3 +status: {} +` + + prTask4Yaml := ` +conditionChecks: + pr-task-4-failed-condition-check-xxyyy: + conditionName: failed-condition-0 + status: + check: + terminated: + exitCode: 127 + conditions: + - status: "False" + type: Succeeded +pipelineTaskName: task-4 +` + + prTask3NoStatusYaml := ` +conditionChecks: + pr-task-3-successful-condition-check-xxyyy: + conditionName: successful-condition-0 +pipelineTaskName: task-3 +status: {} +` + + orphanedPRTask2Yaml := ` +conditionChecks: + pr-task-2-running-condition-check-xxyyy: + conditionName: running-condition-0 +pipelineTaskName: task-2 +` + + orphanedPRTask4Yaml := ` +conditionChecks: + pr-task-4-failed-condition-check-xxyyy: + conditionName: failed-condition-0 +pipelineTaskName: task-4 +` + + withConditions := map[string]*v1beta1.PipelineRunTaskRunStatus{ + "pr-task-1-xxyyy": mustParsePipelineRunTaskRunStatus(t, prTask1Yaml), + "pr-task-2-xxyyy": mustParsePipelineRunTaskRunStatus(t, prTask2Yaml), + "pr-task-3-xxyyy": mustParsePipelineRunTaskRunStatus(t, prTask3Yaml), + "pr-task-4-xxyyy": mustParsePipelineRunTaskRunStatus(t, prTask4Yaml), } - prccs4Recovered := map[string]*v1beta1.PipelineRunConditionCheckStatus{ - "pr-task-4-failed-condition-check-xxyyy": { - ConditionName: "failed-condition-0", - }, + + missingTaskRuns := map[string]*v1beta1.PipelineRunTaskRunStatus{ + "pr-task-1-xxyyy": mustParsePipelineRunTaskRunStatus(t, prTask1Yaml), + "pr-task-2-xxyyy": mustParsePipelineRunTaskRunStatus(t, prTask2Yaml), + "pr-task-4-xxyyy": mustParsePipelineRunTaskRunStatus(t, prTask4Yaml), } - // PipelineRunConditionCheckStatus full is used to test the behaviour of updatePipelineRunStatusFromTaskRuns - // when no orphan TaskRuns are found, to check we don't alter good ones - prccs2Full := map[string]*v1beta1.PipelineRunConditionCheckStatus{ - "pr-task-2-running-condition-check-xxyyy": { - ConditionName: "running-condition-0", - Status: &v1beta1.ConditionCheckStatus{ - ConditionCheckStatusFields: v1beta1.ConditionCheckStatusFields{ - Check: corev1.ContainerState{ - Running: &corev1.ContainerStateRunning{}, - }, - }, - Status: duckv1beta1.Status{ - Conditions: []apis.Condition{{Type: apis.ConditionSucceeded, Status: corev1.ConditionUnknown}}, - }, - }, - }, + foundTaskRun := map[string]*v1beta1.PipelineRunTaskRunStatus{ + "pr-task-1-xxyyy": mustParsePipelineRunTaskRunStatus(t, prTask1Yaml), + "pr-task-2-xxyyy": mustParsePipelineRunTaskRunStatus(t, prTask2Yaml), + "pr-task-3-xxyyy": mustParsePipelineRunTaskRunStatus(t, prTask3NoStatusYaml), + "pr-task-4-xxyyy": mustParsePipelineRunTaskRunStatus(t, prTask4Yaml), } - prccs3Full := map[string]*v1beta1.PipelineRunConditionCheckStatus{ - "pr-task-3-successful-condition-check-xxyyy": { - ConditionName: "successful-condition-0", - Status: &v1beta1.ConditionCheckStatus{ - ConditionCheckStatusFields: v1beta1.ConditionCheckStatusFields{ - Check: corev1.ContainerState{ - Terminated: &corev1.ContainerStateTerminated{ExitCode: 0}, - }, - }, - Status: duckv1beta1.Status{ - Conditions: []apis.Condition{{Type: apis.ConditionSucceeded, Status: corev1.ConditionTrue}}, - }, - }, - }, + + recovered := map[string]*v1beta1.PipelineRunTaskRunStatus{ + "orphaned-taskruns-pr-task-2-xxyyy": mustParsePipelineRunTaskRunStatus(t, orphanedPRTask2Yaml), + "orphaned-taskruns-pr-task-4-xxyyy": mustParsePipelineRunTaskRunStatus(t, orphanedPRTask4Yaml), + "pr-task-1-xxyyy": mustParsePipelineRunTaskRunStatus(t, prTask1Yaml), + "pr-task-3-xxyyy": mustParsePipelineRunTaskRunStatus(t, prTask3NoStatusYaml), } - prccs4Full := map[string]*v1beta1.PipelineRunConditionCheckStatus{ - "pr-task-4-failed-condition-check-xxyyy": { - ConditionName: "failed-condition-0", - Status: &v1beta1.ConditionCheckStatus{ - ConditionCheckStatusFields: v1beta1.ConditionCheckStatusFields{ - Check: corev1.ContainerState{ - Terminated: &corev1.ContainerStateTerminated{ExitCode: 127}, - }, - }, - Status: duckv1beta1.Status{ - Conditions: []apis.Condition{{Type: apis.ConditionSucceeded, Status: corev1.ConditionFalse}}, - }, - }, - }, + + simple := map[string]*v1beta1.PipelineRunTaskRunStatus{ + "pr-task-1-xxyyy": mustParsePipelineRunTaskRunStatus(t, prTask1Yaml), } return updateStatusTaskRunsData{ - withConditions: map[string]*v1beta1.PipelineRunTaskRunStatus{ - "pr-task-1-xxyyy": { - PipelineTaskName: "task-1", - Status: &v1beta1.TaskRunStatus{}, - }, - "pr-task-2-xxyyy": { - PipelineTaskName: "task-2", - Status: nil, - ConditionChecks: prccs2Full, - }, - "pr-task-3-xxyyy": { - PipelineTaskName: "task-3", - Status: &v1beta1.TaskRunStatus{}, - ConditionChecks: prccs3Full, - }, - "pr-task-4-xxyyy": { - PipelineTaskName: "task-4", - Status: nil, - ConditionChecks: prccs4Full, - }, - }, - missingTaskRun: map[string]*v1beta1.PipelineRunTaskRunStatus{ - "pr-task-1-xxyyy": { - PipelineTaskName: "task-1", - Status: &v1beta1.TaskRunStatus{}, - }, - "pr-task-2-xxyyy": { - PipelineTaskName: "task-2", - Status: nil, - ConditionChecks: prccs2Full, - }, - "pr-task-4-xxyyy": { - PipelineTaskName: "task-4", - Status: nil, - ConditionChecks: prccs4Full, - }, - }, - foundTaskRun: map[string]*v1beta1.PipelineRunTaskRunStatus{ - "pr-task-1-xxyyy": { - PipelineTaskName: "task-1", - Status: &v1beta1.TaskRunStatus{}, - }, - "pr-task-2-xxyyy": { - PipelineTaskName: "task-2", - Status: nil, - ConditionChecks: prccs2Full, - }, - "pr-task-3-xxyyy": { - PipelineTaskName: "task-3", - Status: &v1beta1.TaskRunStatus{}, - ConditionChecks: prccs3Recovered, - }, - "pr-task-4-xxyyy": { - PipelineTaskName: "task-4", - Status: nil, - ConditionChecks: prccs4Full, - }, - }, - recovered: map[string]*v1beta1.PipelineRunTaskRunStatus{ - "pr-task-1-xxyyy": { - PipelineTaskName: "task-1", - Status: &v1beta1.TaskRunStatus{}, - }, - "orphaned-taskruns-pr-task-2-xxyyy": { - PipelineTaskName: "task-2", - Status: nil, - ConditionChecks: prccs2Recovered, - }, - "pr-task-3-xxyyy": { - PipelineTaskName: "task-3", - Status: &v1beta1.TaskRunStatus{}, - ConditionChecks: prccs3Recovered, - }, - "orphaned-taskruns-pr-task-4-xxyyy": { - PipelineTaskName: "task-4", - Status: nil, - ConditionChecks: prccs4Recovered, - }, - }, - simple: map[string]*v1beta1.PipelineRunTaskRunStatus{ - "pr-task-1-xxyyy": { - PipelineTaskName: "task-1", - Status: &v1beta1.TaskRunStatus{}, - }, - }, + withConditions: withConditions, + missingTaskRun: missingTaskRuns, + foundTaskRun: foundTaskRun, + recovered: recovered, + simple: simple, } } func TestUpdatePipelineRunStatusFromTaskRuns(t *testing.T) { prUID := types.UID("11111111-1111-1111-1111-111111111111") - otherPrUID := types.UID("22222222-2222-2222-2222-222222222222") - taskRunsPRStatusData := getUpdateStatusTaskRunsData() + taskRunsPRStatusData := getUpdateStatusTaskRunsData(t) prRunningStatus := duckv1beta1.Status{ Conditions: []apis.Condition{ @@ -273,82 +227,7 @@ func TestUpdatePipelineRunStatusFromTaskRuns(t *testing.T) { }, } - allTaskRuns := []*v1beta1.TaskRun{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pr-task-1-xxyyy", - Labels: map[string]string{ - pipeline.PipelineTaskLabelKey: "task-1", - }, - OwnerReferences: []metav1.OwnerReference{{UID: prUID}}, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pr-task-2-running-condition-check-xxyyy", - Labels: map[string]string{ - pipeline.PipelineTaskLabelKey: "task-2", - pipeline.ConditionCheckKey: "pr-task-2-running-condition-check-xxyyy", - pipeline.ConditionNameKey: "running-condition", - }, - OwnerReferences: []metav1.OwnerReference{{UID: prUID}}, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pr-task-3-xxyyy", - Labels: map[string]string{ - pipeline.PipelineTaskLabelKey: "task-3", - }, - OwnerReferences: []metav1.OwnerReference{{UID: prUID}}, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pr-task-3-successful-condition-check-xxyyy", - Labels: map[string]string{ - pipeline.PipelineTaskLabelKey: "task-3", - pipeline.ConditionCheckKey: "pr-task-3-successful-condition-check-xxyyy", - pipeline.ConditionNameKey: "successful-condition", - }, - OwnerReferences: []metav1.OwnerReference{{UID: prUID}}, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pr-task-4-failed-condition-check-xxyyy", - Labels: map[string]string{ - pipeline.PipelineTaskLabelKey: "task-4", - pipeline.ConditionCheckKey: "pr-task-4-failed-condition-check-xxyyy", - pipeline.ConditionNameKey: "failed-condition", - }, - OwnerReferences: []metav1.OwnerReference{{UID: prUID}}, - }, - }, - } - - taskRunsFromAnotherPR := []*v1beta1.TaskRun{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pr-task-1-xxyyy", - Labels: map[string]string{ - pipeline.PipelineTaskLabelKey: "task-1", - }, - OwnerReferences: []metav1.OwnerReference{{UID: otherPrUID}}, - }, - }, - } - - taskRunsWithNoOwner := []*v1beta1.TaskRun{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pr-task-1-xxyyy", - Labels: map[string]string{ - pipeline.PipelineTaskLabelKey: "task-1", - }, - }, - }, - } + allTaskRuns, taskRunsFromAnotherPR, taskRunsWithNoOwner, _, _, _ := getTestTaskRunsAndRuns(t) tcs := []struct { prName string @@ -369,42 +248,37 @@ func TestUpdatePipelineRunStatusFromTaskRuns(t *testing.T) { }, { prName: "status-nil-taskruns", prStatus: prStatusWithEmptyTaskRuns, - trs: []*v1beta1.TaskRun{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pr-task-1-xxyyy", - Labels: map[string]string{ - pipeline.PipelineTaskLabelKey: "task-1", - }, - OwnerReferences: []metav1.OwnerReference{{UID: prUID}}, - }, - }, - }, + trs: []*v1beta1.TaskRun{parse.MustParseTaskRun(t, ` +metadata: + labels: + tekton.dev/pipelineTask: task-1 + name: pr-task-1-xxyyy + ownerReferences: + - uid: 11111111-1111-1111-1111-111111111111 +`)}, expectedPrStatus: prStatusRecoveredSimple, }, { prName: "status-missing-taskruns", prStatus: prStatusMissingTaskRun, trs: []*v1beta1.TaskRun{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pr-task-3-xxyyy", - Labels: map[string]string{ - pipeline.PipelineTaskLabelKey: "task-3", - }, - OwnerReferences: []metav1.OwnerReference{{UID: prUID}}, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pr-task-3-successful-condition-check-xxyyy", - Labels: map[string]string{ - pipeline.PipelineTaskLabelKey: "task-3", - pipeline.ConditionCheckKey: "pr-task-3-successful-condition-check-xxyyy", - pipeline.ConditionNameKey: "successful-condition", - }, - OwnerReferences: []metav1.OwnerReference{{UID: prUID}}, - }, - }, + parse.MustParseTaskRun(t, ` +metadata: + labels: + tekton.dev/pipelineTask: task-3 + name: pr-task-3-xxyyy + ownerReferences: + - uid: 11111111-1111-1111-1111-111111111111 +`), + parse.MustParseTaskRun(t, ` +metadata: + labels: + tekton.dev/conditionCheck: pr-task-3-successful-condition-check-xxyyy + tekton.dev/conditionName: successful-condition + tekton.dev/pipelineTask: task-3 + name: pr-task-3-successful-condition-check-xxyyy + ownerReferences: + - uid: 11111111-1111-1111-1111-111111111111 +`), }, expectedPrStatus: prStatusFoundTaskRun, }, { @@ -439,7 +313,6 @@ func TestUpdatePipelineRunStatusFromTaskRuns(t *testing.T) { Status: tc.prStatus, } - // TODO(abayer): Change function call when TEP-0100 impl is done. updatePipelineRunStatusFromTaskRuns(logger, pr, tc.trs) actualPrStatus := pr.Status @@ -467,7 +340,6 @@ func TestUpdatePipelineRunStatusFromTaskRuns(t *testing.T) { func TestUpdatePipelineRunStatusFromRuns(t *testing.T) { prUID := types.UID("11111111-1111-1111-1111-111111111111") - otherPrUID := types.UID("22222222-2222-2222-2222-222222222222") prRunningStatus := duckv1beta1.Status{ Conditions: []apis.Condition{ @@ -535,58 +407,7 @@ func TestUpdatePipelineRunStatusFromRuns(t *testing.T) { }, } - allRuns := []*v1alpha1.Run{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pr-run-1-xxyyy", - Labels: map[string]string{ - pipeline.PipelineTaskLabelKey: "run-1", - }, - OwnerReferences: []metav1.OwnerReference{{UID: prUID}}, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pr-run-2-xxyyy", - Labels: map[string]string{ - pipeline.PipelineTaskLabelKey: "run-2", - }, - OwnerReferences: []metav1.OwnerReference{{UID: prUID}}, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pr-run-3-xxyyy", - Labels: map[string]string{ - pipeline.PipelineTaskLabelKey: "run-3", - }, - OwnerReferences: []metav1.OwnerReference{{UID: prUID}}, - }, - }, - } - - runsFromAnotherPR := []*v1alpha1.Run{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pr-run-1-xxyyy", - Labels: map[string]string{ - pipeline.PipelineTaskLabelKey: "run-1", - }, - OwnerReferences: []metav1.OwnerReference{{UID: otherPrUID}}, - }, - }, - } - - runsWithNoOwner := []*v1alpha1.Run{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pr-run-1-xxyyy", - Labels: map[string]string{ - pipeline.PipelineTaskLabelKey: "run-1", - }, - }, - }, - } + _, _, _, allRuns, runsFromAnotherPR, runsWithNoOwner := getTestTaskRunsAndRuns(t) tcs := []struct { prName string @@ -607,30 +428,26 @@ func TestUpdatePipelineRunStatusFromRuns(t *testing.T) { }, { prName: "status-nil-runs", prStatus: prStatusWithEmptyRuns, - runs: []*v1alpha1.Run{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "pr-run-1-xxyyy", - Labels: map[string]string{ - pipeline.PipelineTaskLabelKey: "run-1", - }, - OwnerReferences: []metav1.OwnerReference{{UID: prUID}}, - }, - }, - }, + runs: []*v1alpha1.Run{parse.MustParseRun(t, ` +metadata: + labels: + tekton.dev/pipelineTask: run-1 + name: pr-run-1-xxyyy + ownerReferences: + - uid: 11111111-1111-1111-1111-111111111111 +`)}, expectedPrStatus: prStatusRecoveredSimple, }, { prName: "status-missing-runs", prStatus: prStatusWithSomeRuns, - runs: []*v1alpha1.Run{{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pr-run-3-xxyyy", - Labels: map[string]string{ - pipeline.PipelineTaskLabelKey: "run-3", - }, - OwnerReferences: []metav1.OwnerReference{{UID: prUID}}, - }, - }}, + runs: []*v1alpha1.Run{parse.MustParseRun(t, ` +metadata: + labels: + tekton.dev/pipelineTask: run-3 + name: pr-run-3-xxyyy + ownerReferences: + - uid: 11111111-1111-1111-1111-111111111111 +`)}, expectedPrStatus: prStatusWithAllRuns, }, { prName: "status-matching-runs-pr", @@ -668,3 +485,518 @@ func TestUpdatePipelineRunStatusFromRuns(t *testing.T) { }) } } + +type updateStatusChildRefsData struct { + withConditions []v1beta1.ChildStatusReference + missingTaskRun []v1beta1.ChildStatusReference + foundTaskRun []v1beta1.ChildStatusReference + missingRun []v1beta1.ChildStatusReference + recovered []v1beta1.ChildStatusReference + simple []v1beta1.ChildStatusReference + simpleRun []v1beta1.ChildStatusReference +} + +func getUpdateStatusChildRefsData(t *testing.T) updateStatusChildRefsData { + prTask1Yaml := ` +apiVersion: tekton.dev/v1beta1 +kind: TaskRun +name: pr-task-1-xxyyy +pipelineTaskName: task-1 +` + + prTask2Yaml := ` +apiVersion: tekton.dev/v1beta1 +conditionChecks: +- conditionCheckName: pr-task-2-running-condition-check-xxyyy + conditionName: running-condition-0 + status: + check: + running: {} + conditions: + - status: Unknown + type: Succeeded +kind: TaskRun +name: pr-task-2-xxyyy +pipelineTaskName: task-2 +` + + prTask3Yaml := ` +apiVersion: tekton.dev/v1beta1 +conditionChecks: +- conditionCheckName: pr-task-3-successful-condition-check-xxyyy + conditionName: successful-condition-0 + status: + check: + terminated: + exitCode: 0 + conditions: + - status: "True" + type: Succeeded +kind: TaskRun +name: pr-task-3-xxyyy +pipelineTaskName: task-3 +` + + prTask4Yaml := ` +apiVersion: tekton.dev/v1beta1 +conditionChecks: +- conditionCheckName: pr-task-4-failed-condition-check-xxyyy + conditionName: failed-condition-0 + status: + check: + terminated: + exitCode: 127 + conditions: + - status: "False" + type: Succeeded +kind: TaskRun +name: pr-task-4-xxyyy +pipelineTaskName: task-4 +` + + prTask6Yaml := ` +apiVersion: tekton.dev/v1alpha1 +kind: Run +name: pr-run-6-xxyyy +pipelineTaskName: task-6 +` + + prTask3NoStatusYaml := ` +apiVersion: tekton.dev/v1beta1 +conditionChecks: +- conditionCheckName: pr-task-3-successful-condition-check-xxyyy + conditionName: successful-condition-0 +kind: TaskRun +name: pr-task-3-xxyyy +pipelineTaskName: task-3 +` + + orphanedPRTask2Yaml := ` +apiVersion: tekton.dev/v1beta1 +conditionChecks: +- conditionCheckName: pr-task-2-running-condition-check-xxyyy + conditionName: running-condition-0 +kind: TaskRun +name: orphaned-taskruns-pr-task-2-xxyyy +pipelineTaskName: task-2 +` + + orphanedPRTask4Yaml := ` +apiVersion: tekton.dev/v1beta1 +conditionChecks: +- conditionCheckName: pr-task-4-failed-condition-check-xxyyy + conditionName: failed-condition-0 +kind: TaskRun +name: orphaned-taskruns-pr-task-4-xxyyy +pipelineTaskName: task-4 +` + + withConditions := []v1beta1.ChildStatusReference{ + mustParseChildStatusReference(t, prTask1Yaml), + mustParseChildStatusReference(t, prTask2Yaml), + mustParseChildStatusReference(t, prTask3Yaml), + mustParseChildStatusReference(t, prTask4Yaml), + mustParseChildStatusReference(t, prTask6Yaml), + } + + missingTaskRun := []v1beta1.ChildStatusReference{ + mustParseChildStatusReference(t, prTask1Yaml), + mustParseChildStatusReference(t, prTask2Yaml), + mustParseChildStatusReference(t, prTask4Yaml), + mustParseChildStatusReference(t, prTask6Yaml), + } + + foundTaskRun := []v1beta1.ChildStatusReference{ + mustParseChildStatusReference(t, prTask1Yaml), + mustParseChildStatusReference(t, prTask2Yaml), + mustParseChildStatusReference(t, prTask3NoStatusYaml), + mustParseChildStatusReference(t, prTask4Yaml), + mustParseChildStatusReference(t, prTask6Yaml), + } + + missingRun := []v1beta1.ChildStatusReference{ + mustParseChildStatusReference(t, prTask1Yaml), + mustParseChildStatusReference(t, prTask2Yaml), + mustParseChildStatusReference(t, prTask3Yaml), + mustParseChildStatusReference(t, prTask4Yaml), + } + + recovered := []v1beta1.ChildStatusReference{ + mustParseChildStatusReference(t, prTask1Yaml), + mustParseChildStatusReference(t, orphanedPRTask2Yaml), + mustParseChildStatusReference(t, prTask3NoStatusYaml), + mustParseChildStatusReference(t, orphanedPRTask4Yaml), + mustParseChildStatusReference(t, prTask6Yaml), + } + + simple := []v1beta1.ChildStatusReference{mustParseChildStatusReference(t, prTask1Yaml)} + + simpleRun := []v1beta1.ChildStatusReference{mustParseChildStatusReference(t, prTask6Yaml)} + + return updateStatusChildRefsData{ + withConditions: withConditions, + missingTaskRun: missingTaskRun, + foundTaskRun: foundTaskRun, + missingRun: missingRun, + recovered: recovered, + simple: simple, + simpleRun: simpleRun, + } +} + +func TestUpdatePipelineRunStatusFromChildRefs(t *testing.T) { + prUID := types.UID("11111111-1111-1111-1111-111111111111") + + childRefsPRStatusData := getUpdateStatusChildRefsData(t) + + prRunningStatus := duckv1beta1.Status{ + Conditions: []apis.Condition{ + { + Type: "Succeeded", + Status: "Unknown", + Reason: "Running", + Message: "Not all Tasks in the Pipeline have finished executing", + }, + }, + } + + prStatusWithCondition := v1beta1.PipelineRunStatus{ + Status: prRunningStatus, + PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ + ChildReferences: childRefsPRStatusData.withConditions, + }, + } + + prStatusMissingTaskRun := v1beta1.PipelineRunStatus{ + Status: prRunningStatus, + PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ + ChildReferences: childRefsPRStatusData.missingTaskRun, + }, + } + + prStatusFoundTaskRun := v1beta1.PipelineRunStatus{ + Status: prRunningStatus, + PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ + ChildReferences: childRefsPRStatusData.foundTaskRun, + }, + } + + prStatusMissingRun := v1beta1.PipelineRunStatus{ + Status: prRunningStatus, + PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ + ChildReferences: childRefsPRStatusData.missingRun, + }, + } + + prStatusWithEmptyChildRefs := v1beta1.PipelineRunStatus{ + Status: prRunningStatus, + PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{}, + } + + prStatusWithOrphans := v1beta1.PipelineRunStatus{ + Status: duckv1beta1.Status{ + Conditions: []apis.Condition{ + { + Type: "Succeeded", + Status: "Unknown", + Reason: "Running", + Message: "Not all Tasks in the Pipeline have finished executing", + }, + }, + }, + PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{}, + } + + prStatusRecovered := v1beta1.PipelineRunStatus{ + Status: prRunningStatus, + PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ + ChildReferences: childRefsPRStatusData.recovered, + }, + } + + prStatusRecoveredSimple := v1beta1.PipelineRunStatus{ + Status: prRunningStatus, + PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ + ChildReferences: childRefsPRStatusData.simple, + }, + } + + prStatusRecoveredSimpleWithRun := v1beta1.PipelineRunStatus{ + Status: prRunningStatus, + PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ + ChildReferences: []v1beta1.ChildStatusReference{{ + TypeMeta: runtime.TypeMeta{ + APIVersion: "tekton.dev/v1alpha1", + Kind: "Run", + }, + Name: "pr-run-6-xxyyy", + PipelineTaskName: "task-6", + }}, + }, + } + + allTaskRuns, taskRunsFromAnotherPR, taskRunsWithNoOwner, _, runsFromAnotherPR, runsWithNoOwner := getTestTaskRunsAndRuns(t) + + singleRun := []*v1alpha1.Run{parse.MustParseRun(t, ` +metadata: + labels: + tekton.dev/pipelineTask: task-6 + name: pr-run-6-xxyyy + ownerReferences: + - uid: 11111111-1111-1111-1111-111111111111 +`)} + + tcs := []struct { + prName string + prStatus v1beta1.PipelineRunStatus + trs []*v1beta1.TaskRun + runs []*v1alpha1.Run + expectedPrStatus v1beta1.PipelineRunStatus + }{ + { + prName: "no-status-no-taskruns-or-runs", + prStatus: v1beta1.PipelineRunStatus{}, + trs: nil, + runs: nil, + expectedPrStatus: v1beta1.PipelineRunStatus{}, + }, { + prName: "status-no-taskruns-or-runs", + prStatus: prStatusWithCondition, + trs: nil, + runs: nil, + expectedPrStatus: prStatusWithCondition, + }, { + prName: "status-nil-taskruns", + prStatus: prStatusWithEmptyChildRefs, + trs: []*v1beta1.TaskRun{parse.MustParseTaskRun(t, ` +metadata: + labels: + tekton.dev/pipelineTask: task-1 + name: pr-task-1-xxyyy + ownerReferences: + - uid: 11111111-1111-1111-1111-111111111111 +`)}, + expectedPrStatus: prStatusRecoveredSimple, + }, { + prName: "status-nil-runs", + prStatus: prStatusWithEmptyChildRefs, + runs: singleRun, + expectedPrStatus: prStatusRecoveredSimpleWithRun, + }, { + prName: "status-missing-taskruns", + prStatus: prStatusMissingTaskRun, + trs: []*v1beta1.TaskRun{ + parse.MustParseTaskRun(t, ` +metadata: + labels: + tekton.dev/pipelineTask: task-3 + name: pr-task-3-xxyyy + ownerReferences: + - uid: 11111111-1111-1111-1111-111111111111 +`), + parse.MustParseTaskRun(t, ` +metadata: + labels: + tekton.dev/conditionCheck: pr-task-3-successful-condition-check-xxyyy + tekton.dev/conditionName: successful-condition + tekton.dev/pipelineTask: task-3 + name: pr-task-3-successful-condition-check-xxyyy + ownerReferences: + - uid: 11111111-1111-1111-1111-111111111111 +`), + }, + expectedPrStatus: prStatusFoundTaskRun, + }, { + prName: "status-missing-runs", + prStatus: prStatusMissingRun, + runs: singleRun, + expectedPrStatus: prStatusWithCondition, + }, { + prName: "status-matching-taskruns-pr", + prStatus: prStatusWithCondition, + trs: allTaskRuns, + expectedPrStatus: prStatusWithCondition, + }, { + prName: "orphaned-taskruns-pr", + prStatus: prStatusWithOrphans, + trs: allTaskRuns, + runs: singleRun, + expectedPrStatus: prStatusRecovered, + }, { + prName: "tr-and-run-from-another-pr", + prStatus: prStatusWithEmptyChildRefs, + trs: taskRunsFromAnotherPR, + runs: runsFromAnotherPR, + expectedPrStatus: prStatusWithEmptyChildRefs, + }, { + prName: "tr-and-run-with-no-owner", + prStatus: prStatusWithEmptyChildRefs, + trs: taskRunsWithNoOwner, + runs: runsWithNoOwner, + expectedPrStatus: prStatusWithEmptyChildRefs, + }, + } + + for _, tc := range tcs { + t.Run(tc.prName, func(t *testing.T) { + logger := logtesting.TestLogger(t) + + pr := &v1beta1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{Name: tc.prName, UID: prUID}, + Status: tc.prStatus, + } + + updatePipelineRunStatusFromChildRefs(logger, pr, tc.trs, tc.runs) + + actualPrStatus := pr.Status + + actualChildRefs := actualPrStatus.ChildReferences + if len(actualChildRefs) != 0 { + var fixedChildRefs []v1beta1.ChildStatusReference + re := regexp.MustCompile(`^[a-z\-]*?-(task|run)-[0-9]`) + for _, cr := range actualChildRefs { + cr.Name = fmt.Sprintf("%s-xxyyy", re.FindString(cr.Name)) + fixedChildRefs = append(fixedChildRefs, cr) + } + actualPrStatus.ChildReferences = fixedChildRefs + } + + // Sort the ChildReferences to deal with annoying ordering issues. + sort.Slice(actualPrStatus.ChildReferences, func(i, j int) bool { + return actualPrStatus.ChildReferences[i].PipelineTaskName < actualPrStatus.ChildReferences[j].PipelineTaskName + }) + + if d := cmp.Diff(tc.expectedPrStatus, actualPrStatus); d != "" { + t.Errorf("expected the PipelineRun status to match %#v. Diff %s", tc.expectedPrStatus, diff.PrintWantGot(d)) + } + }) + } +} + +func getTestTaskRunsAndRuns(t *testing.T) ([]*v1beta1.TaskRun, []*v1beta1.TaskRun, []*v1beta1.TaskRun, []*v1alpha1.Run, []*v1alpha1.Run, []*v1alpha1.Run) { + allTaskRuns := []*v1beta1.TaskRun{ + parse.MustParseTaskRun(t, ` +metadata: + labels: + tekton.dev/pipelineTask: task-1 + name: pr-task-1-xxyyy + ownerReferences: + - uid: 11111111-1111-1111-1111-111111111111 +`), + parse.MustParseTaskRun(t, ` +metadata: + labels: + tekton.dev/conditionCheck: pr-task-2-running-condition-check-xxyyy + tekton.dev/conditionName: running-condition + tekton.dev/pipelineTask: task-2 + name: pr-task-2-running-condition-check-xxyyy + ownerReferences: + - uid: 11111111-1111-1111-1111-111111111111 +`), + parse.MustParseTaskRun(t, ` +metadata: + labels: + tekton.dev/pipelineTask: task-3 + name: pr-task-3-xxyyy + ownerReferences: + - uid: 11111111-1111-1111-1111-111111111111 +`), + parse.MustParseTaskRun(t, ` +metadata: + labels: + tekton.dev/conditionCheck: pr-task-3-successful-condition-check-xxyyy + tekton.dev/conditionName: successful-condition + tekton.dev/pipelineTask: task-3 + name: pr-task-3-successful-condition-check-xxyyy + ownerReferences: + - uid: 11111111-1111-1111-1111-111111111111 +`), + parse.MustParseTaskRun(t, ` +metadata: + labels: + tekton.dev/conditionCheck: pr-task-4-failed-condition-check-xxyyy + tekton.dev/conditionName: failed-condition + tekton.dev/pipelineTask: task-4 + name: pr-task-4-failed-condition-check-xxyyy + ownerReferences: + - uid: 11111111-1111-1111-1111-111111111111 +`), + } + + taskRunsFromAnotherPR := []*v1beta1.TaskRun{parse.MustParseTaskRun(t, ` +metadata: + labels: + tekton.dev/pipelineTask: task-1 + name: pr-task-1-xxyyy + ownerReferences: + - uid: 22222222-2222-2222-2222-222222222222 +`)} + + taskRunsWithNoOwner := []*v1beta1.TaskRun{parse.MustParseTaskRun(t, ` +metadata: + labels: + tekton.dev/pipelineTask: task-1 + name: pr-task-1-xxyyy +`)} + + allRuns := []*v1alpha1.Run{ + parse.MustParseRun(t, ` +metadata: + labels: + tekton.dev/pipelineTask: run-1 + name: pr-run-1-xxyyy + ownerReferences: + - uid: 11111111-1111-1111-1111-111111111111 +`), + parse.MustParseRun(t, ` +metadata: + labels: + tekton.dev/pipelineTask: run-2 + name: pr-run-2-xxyyy + ownerReferences: + - uid: 11111111-1111-1111-1111-111111111111 +`), + parse.MustParseRun(t, ` +metadata: + labels: + tekton.dev/pipelineTask: run-3 + name: pr-run-3-xxyyy + ownerReferences: + - uid: 11111111-1111-1111-1111-111111111111 +`), + } + + runsFromAnotherPR := []*v1alpha1.Run{parse.MustParseRun(t, ` +metadata: + labels: + tekton.dev/pipelineTask: run-1 + name: pr-run-1-xxyyy + ownerReferences: + - uid: 22222222-2222-2222-2222-222222222222 +`)} + + runsWithNoOwner := []*v1alpha1.Run{parse.MustParseRun(t, ` +metadata: + labels: + tekton.dev/pipelineTask: run-1 + name: pr-run-1-xxyyy +`)} + + return allTaskRuns, taskRunsFromAnotherPR, taskRunsWithNoOwner, allRuns, runsFromAnotherPR, runsWithNoOwner +} + +func mustParsePipelineRunTaskRunStatus(t *testing.T, yamlStr string) *v1beta1.PipelineRunTaskRunStatus { + var output v1beta1.PipelineRunTaskRunStatus + if err := yaml.Unmarshal([]byte(yamlStr), &output); err != nil { + t.Fatalf("parsing task run status %s: %v", yamlStr, err) + } + return &output +} + +func mustParseChildStatusReference(t *testing.T, yamlStr string) v1beta1.ChildStatusReference { + var output v1beta1.ChildStatusReference + if err := yaml.Unmarshal([]byte(yamlStr), &output); err != nil { + t.Fatalf("parsing task run status %s: %v", yamlStr, err) + } + return output +}