diff --git a/docs/install.md b/docs/install.md index e3276036d59..2b0f8253aaf 100644 --- a/docs/install.md +++ b/docs/install.md @@ -393,7 +393,7 @@ features](#alpha-features) to be used. - `embedded-status`: set this flag to "full" to enable full embedding of `TaskRun` and `Run` statuses in the `PipelineRun` status. Set it to "minimal" to populate the `ChildReferences` field in the `PipelineRun` status with name, kind, and API version information for each `TaskRun` and `Run` in the `PipelineRun` instead. Set it to "both" to - do both. For more information, see [Configuring usage of `TaskRun` and `Run` embedded statuses](pipelineruns.md#configuring-usage-of-taskrun-and-run-embedded-statuses). **NOTE**: This functionality is not yet active. + do both. For more information, see [Configuring usage of `TaskRun` and `Run` embedded statuses](pipelineruns.md#configuring-usage-of-taskrun-and-run-embedded-statuses). For example: diff --git a/pkg/reconciler/pipelinerun/cancel.go b/pkg/reconciler/pipelinerun/cancel.go index 24539ed9a24..ef653a43e59 100644 --- a/pkg/reconciler/pipelinerun/cancel.go +++ b/pkg/reconciler/pipelinerun/cancel.go @@ -99,23 +99,48 @@ func cancelPipelineRun(ctx context.Context, logger *zap.SugaredLogger, pr *v1bet func cancelPipelineTaskRuns(ctx context.Context, logger *zap.SugaredLogger, pr *v1beta1.PipelineRun, clientSet clientset.Interface) []string { errs := []string{} - // Loop over the TaskRuns in the PipelineRun status. - // If a TaskRun is not in the status yet we should not cancel it anyways. - for taskRunName := range pr.Status.TaskRuns { - logger.Infof("cancelling TaskRun %s", taskRunName) - - if _, err := clientSet.TektonV1beta1().TaskRuns(pr.Namespace).Patch(ctx, taskRunName, types.JSONPatchType, cancelTaskRunPatchBytes, metav1.PatchOptions{}, ""); err != nil { - errs = append(errs, fmt.Errorf("Failed to patch TaskRun `%s` with cancellation: %s", taskRunName, err).Error()) - continue + // If pr.Status.ChildReferences is populated, use that as source of truth for TaskRun and Run names. + if len(pr.Status.ChildReferences) > 0 { + // Loop over the ChildReferences in the PipelineRun status. + for _, cr := range pr.Status.ChildReferences { + switch cr.Kind { + case "TaskRun": + logger.Infof("cancelling TaskRun %s", cr.Name) + + if _, err := clientSet.TektonV1beta1().TaskRuns(pr.Namespace).Patch(ctx, cr.Name, types.JSONPatchType, cancelTaskRunPatchBytes, metav1.PatchOptions{}, ""); err != nil { + errs = append(errs, fmt.Errorf("Failed to patch TaskRun `%s` with cancellation: %s", cr.Name, err).Error()) + continue + } + case "Run": + logger.Infof("cancelling Run %s", cr.Name) + + if err := cancelRun(ctx, cr.Name, pr.Namespace, clientSet); err != nil { + errs = append(errs, fmt.Errorf("Failed to patch Run `%s` with cancellation: %s", cr.Name, err).Error()) + continue + } + default: + errs = append(errs, fmt.Errorf("unknown or unsupported kind `%s` for cancellation", cr.Kind).Error()) + } } - } - // Loop over the Runs in the PipelineRun status. - for runName := range pr.Status.Runs { - logger.Infof("cancelling Run %s", runName) - - if err := cancelRun(ctx, runName, pr.Namespace, clientSet); err != nil { - errs = append(errs, fmt.Errorf("Failed to patch Run `%s` with cancellation: %s", runName, err).Error()) - continue + } else { + // Loop over the TaskRuns in the PipelineRun status. + // If a TaskRun is not in the status yet we should not cancel it anyways. + for taskRunName := range pr.Status.TaskRuns { + logger.Infof("cancelling TaskRun %s", taskRunName) + + if _, err := clientSet.TektonV1beta1().TaskRuns(pr.Namespace).Patch(ctx, taskRunName, types.JSONPatchType, cancelTaskRunPatchBytes, metav1.PatchOptions{}, ""); err != nil { + errs = append(errs, fmt.Errorf("Failed to patch TaskRun `%s` with cancellation: %s", taskRunName, err).Error()) + continue + } + } + // Loop over the Runs in the PipelineRun status. + for runName := range pr.Status.Runs { + logger.Infof("cancelling Run %s", runName) + + if err := cancelRun(ctx, runName, pr.Namespace, clientSet); err != nil { + errs = append(errs, fmt.Errorf("Failed to patch Run `%s` with cancellation: %s", runName, err).Error()) + continue + } } } diff --git a/pkg/reconciler/pipelinerun/cancel_test.go b/pkg/reconciler/pipelinerun/cancel_test.go index d8c3492749c..2f06d24cf15 100644 --- a/pkg/reconciler/pipelinerun/cancel_test.go +++ b/pkg/reconciler/pipelinerun/cancel_test.go @@ -18,8 +18,11 @@ package pipelinerun import ( "context" + "errors" "testing" + "k8s.io/apimachinery/pkg/runtime" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" _ "github.com/tektoncd/pipeline/pkg/pipelinerunmetrics/fake" // Make sure the pipelinerunmetrics are setup @@ -36,6 +39,7 @@ func TestCancelPipelineRun(t *testing.T) { pipelineRun *v1beta1.PipelineRun taskRuns []*v1beta1.TaskRun runs []*v1alpha1.Run + expectedErr error }{{ name: "no-resolved-taskrun", pipelineRun: &v1beta1.PipelineRun{ @@ -104,10 +108,67 @@ func TestCancelPipelineRun(t *testing.T) { Status: v1beta1.PipelineRunSpecStatusCancelledDeprecated, }, }, + }, { + name: "child-references", + pipelineRun: &v1beta1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{Name: "test-pipeline-run-cancelled"}, + Spec: v1beta1.PipelineRunSpec{ + Status: v1beta1.PipelineRunSpecStatusCancelled, + }, + Status: v1beta1.PipelineRunStatus{PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ + ChildReferences: []v1beta1.ChildStatusReference{ + { + TypeMeta: runtime.TypeMeta{Kind: "TaskRun"}, + Name: "t1", + PipelineTaskName: "task-1", + }, + { + TypeMeta: runtime.TypeMeta{Kind: "TaskRun"}, + Name: "t2", + PipelineTaskName: "task-2", + }, + { + TypeMeta: runtime.TypeMeta{Kind: "Run"}, + Name: "r1", + PipelineTaskName: "run-1", + }, + { + TypeMeta: runtime.TypeMeta{Kind: "Run"}, + Name: "r2", + PipelineTaskName: "run-2", + }, + }, + }}, + }, + taskRuns: []*v1beta1.TaskRun{ + {ObjectMeta: metav1.ObjectMeta{Name: "t1"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "t2"}}, + }, + runs: []*v1alpha1.Run{ + {ObjectMeta: metav1.ObjectMeta{Name: "r1"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "r2"}}, + }, + }, { + name: "unknown-kind-on-child-references", + pipelineRun: &v1beta1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{Name: "test-pipeline-run-cancelled"}, + Spec: v1beta1.PipelineRunSpec{ + Status: v1beta1.PipelineRunSpecStatusCancelled, + }, + Status: v1beta1.PipelineRunStatus{PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ + ChildReferences: []v1beta1.ChildStatusReference{{ + TypeMeta: runtime.TypeMeta{Kind: "InvalidKind"}, + Name: "t1", + PipelineTaskName: "task-1", + }}, + }}, + }, + expectedErr: errors.New("error(s) from cancelling TaskRun(s) from PipelineRun test-pipeline-run-cancelled: unknown or unsupported kind `InvalidKind` for cancellation"), }} for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { + d := test.Data{ PipelineRuns: []*v1beta1.PipelineRun{tc.pipelineRun}, TaskRuns: tc.taskRuns, @@ -117,33 +178,48 @@ func TestCancelPipelineRun(t *testing.T) { ctx, cancel := context.WithCancel(ctx) defer cancel() c, _ := test.SeedTestData(t, ctx, d) - if err := cancelPipelineRun(ctx, logtesting.TestLogger(t), tc.pipelineRun, c.Pipeline); err != nil { - t.Fatal(err) - } - // This PipelineRun should still be complete and false, and the status should reflect that - cond := tc.pipelineRun.Status.GetCondition(apis.ConditionSucceeded) - if cond.IsTrue() { - t.Errorf("Expected PipelineRun status to be complete and false, but was %v", cond) - } - if tc.taskRuns != nil { - l, err := c.Pipeline.TektonV1beta1().TaskRuns("").List(ctx, metav1.ListOptions{}) - if err != nil { - t.Fatal(err) + + err := cancelPipelineRun(ctx, logtesting.TestLogger(t), tc.pipelineRun, c.Pipeline) + if tc.expectedErr != nil { + if err == nil { + t.Fatalf("expected error '%s', but did not get an error", tc.expectedErr) } - for _, tr := range l.Items { - if tr.Spec.Status != v1beta1.TaskRunSpecStatusCancelled { - t.Errorf("expected task %q to be marked as cancelled, was %q", tr.Name, tr.Spec.Status) - } + if tc.expectedErr.Error() != err.Error() { + t.Errorf("expected error '%s', but got '%s'", tc.expectedErr, err) } - } - if tc.runs != nil { - l, err := c.Pipeline.TektonV1alpha1().Runs("").List(ctx, metav1.ListOptions{}) + } else { if err != nil { t.Fatal(err) } - for _, r := range l.Items { - if r.Spec.Status != v1alpha1.RunSpecStatusCancelled { - t.Errorf("expected Run %q to be marked as cancelled, was %q", r.Name, r.Spec.Status) + // This PipelineRun should still be complete and false, and the status should reflect that + cond := tc.pipelineRun.Status.GetCondition(apis.ConditionSucceeded) + if cond.IsTrue() { + t.Errorf("Expected PipelineRun status to be complete and false, but was %v", cond) + } + if tc.taskRuns != nil { + l, err := c.Pipeline.TektonV1beta1().TaskRuns("").List(ctx, metav1.ListOptions{ + LabelSelector: "shouldCancel=true", + }) + if err != nil { + t.Fatal(err) + } + for _, tr := range l.Items { + if tr.Spec.Status != v1beta1.TaskRunSpecStatusCancelled { + t.Errorf("expected task %q to be marked as cancelled, was %q", tr.Name, tr.Spec.Status) + } + } + } + if tc.runs != nil { + l, err := c.Pipeline.TektonV1alpha1().Runs("").List(ctx, metav1.ListOptions{ + LabelSelector: "shouldCancel=true", + }) + if err != nil { + t.Fatal(err) + } + for _, r := range l.Items { + if r.Spec.Status != v1alpha1.RunSpecStatusCancelled { + t.Errorf("expected Run %q to be marked as cancelled, was %q", r.Name, r.Spec.Status) + } } } } diff --git a/pkg/reconciler/pipelinerun/pipelinerun.go b/pkg/reconciler/pipelinerun/pipelinerun.go index 6fa8afb97b3..17edd363db8 100644 --- a/pkg/reconciler/pipelinerun/pipelinerun.go +++ b/pkg/reconciler/pipelinerun/pipelinerun.go @@ -562,11 +562,22 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1beta1.PipelineRun, get // Read the condition the way it was set by the Mark* helpers after = pr.Status.GetCondition(apis.ConditionSucceeded) pr.Status.StartTime = pipelineRunFacts.State.AdjustStartTime(pr.Status.StartTime) - pr.Status.TaskRuns = pipelineRunFacts.State.GetTaskRunsStatus(pr) - pr.Status.Runs = pipelineRunFacts.State.GetRunsStatus(pr) + taskRunStatuses := pipelineRunFacts.State.GetTaskRunsStatus(pr) + runStatuses := pipelineRunFacts.State.GetRunsStatus(pr) + + if cfg.FeatureFlags.EmbeddedStatus == config.FullEmbeddedStatus || cfg.FeatureFlags.EmbeddedStatus == config.BothEmbeddedStatus { + pr.Status.TaskRuns = taskRunStatuses + pr.Status.Runs = runStatuses + } + + if cfg.FeatureFlags.EmbeddedStatus == config.MinimalEmbeddedStatus || cfg.FeatureFlags.EmbeddedStatus == config.BothEmbeddedStatus { + pr.Status.ChildReferences = pipelineRunFacts.State.GetChildReferences(v1beta1.SchemeGroupVersion.String(), + v1alpha1.SchemeGroupVersion.String()) + } + pr.Status.SkippedTasks = pipelineRunFacts.GetSkippedTasks() if after.Status == corev1.ConditionTrue { - pr.Status.PipelineResults = resources.ApplyTaskResultsToPipelineResults(pipelineSpec.Results, pr.Status.TaskRuns, pr.Status.Runs) + pr.Status.PipelineResults = resources.ApplyTaskResultsToPipelineResults(pipelineSpec.Results, taskRunStatuses, runStatuses) } logger.Infof("PipelineRun %s status is being set to %s", pr.Name, after) @@ -685,6 +696,8 @@ func (c *Reconciler) runNextSchedulableTask(ctx context.Context, pr *v1beta1.Pip return nil } +// updateTaskRunsStatusDirectly is used with "full" or "both" set as the value for the "embedded-status" feature flag. +// When the "full" and "both" options are removed, updateTaskRunsStatusDirectly can be removed. func (c *Reconciler) updateTaskRunsStatusDirectly(pr *v1beta1.PipelineRun) error { for taskRunName := range pr.Status.TaskRuns { // TODO(dibyom): Add conditionCheck statuses here @@ -702,6 +715,8 @@ func (c *Reconciler) updateTaskRunsStatusDirectly(pr *v1beta1.PipelineRun) error return nil } +// updateRunsStatusDirectly is used with "full" or "both" set as the value for the "embedded-status" feature flag. +// When the "full" and "both" options are removed, updateRunsStatusDirectly can be removed. func (c *Reconciler) updateRunsStatusDirectly(pr *v1beta1.PipelineRun) error { for runName := range pr.Status.Runs { prRunStatus := pr.Status.Runs[runName] @@ -1171,34 +1186,50 @@ func (c *Reconciler) updatePipelineRunStatusFromInformer(ctx context.Context, pr logger.Errorf("could not list TaskRuns %#v", err) return err } - updatePipelineRunStatusFromTaskRuns(logger, pr, taskRuns) + updatePipelineRunStatusFromTaskRuns(ctx, logger, pr, taskRuns) runs, err := c.runLister.Runs(pr.Namespace).List(k8slabels.SelectorFromSet(pipelineRunLabels)) if err != nil { logger.Errorf("could not list Runs %#v", err) return err } - updatePipelineRunStatusFromRuns(logger, pr, runs) + updatePipelineRunStatusFromRuns(ctx, logger, pr, runs) return nil } -func updatePipelineRunStatusFromTaskRuns(logger *zap.SugaredLogger, pr *v1beta1.PipelineRun, trs []*v1beta1.TaskRun) { +func updatePipelineRunStatusFromTaskRuns(ctx context.Context, 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 { + if len(trs) == 0 { return } + + cfg := config.FromContextOrDefaults(ctx) + fullEmbedded := cfg.FeatureFlags.EmbeddedStatus == config.FullEmbeddedStatus || cfg.FeatureFlags.EmbeddedStatus == config.BothEmbeddedStatus + minimalEmbedded := cfg.FeatureFlags.EmbeddedStatus == config.MinimalEmbeddedStatus || cfg.FeatureFlags.EmbeddedStatus == config.BothEmbeddedStatus + // Store a list of Condition TaskRuns for each PipelineTask (by name) conditionTaskRuns := make(map[string][]*v1beta1.TaskRun) // Map PipelineTask names to TaskRun names that were already in the status taskRunByPipelineTask := make(map[string]string) - if pr.Status.TaskRuns != nil { - for taskRunName, pipelineRunTaskRunStatus := range pr.Status.TaskRuns { - taskRunByPipelineTask[pipelineRunTaskRunStatus.PipelineTaskName] = taskRunName + // Map PipelineTask names to TaskRun child references that were already in the status + childRefByPipelineTask := make(map[string]*v1beta1.ChildStatusReference) + + if fullEmbedded { + if pr.Status.TaskRuns != nil { + for taskRunName, pipelineRunTaskRunStatus := range pr.Status.TaskRuns { + taskRunByPipelineTask[pipelineRunTaskRunStatus.PipelineTaskName] = taskRunName + } + } else { + pr.Status.TaskRuns = make(map[string]*v1beta1.PipelineRunTaskRunStatus) } - } else { - pr.Status.TaskRuns = make(map[string]*v1beta1.PipelineRunTaskRunStatus) } + if minimalEmbedded { + for i := range pr.Status.ChildReferences { + childRefByPipelineTask[pr.Status.ChildReferences[i].PipelineTaskName] = &pr.Status.ChildReferences[i] + } + } + // Loop over all the TaskRuns associated to Tasks for _, taskrun := range trs { // Only process TaskRuns that are owned by this PipelineRun. @@ -1218,7 +1249,8 @@ func updatePipelineRunStatusFromTaskRuns(logger *zap.SugaredLogger, pr *v1beta1. conditionTaskRuns[pipelineTaskName] = append(conditionTaskRuns[pipelineTaskName], taskrun) continue } - if _, ok := pr.Status.TaskRuns[taskrun.Name]; !ok { + + if _, ok := pr.Status.TaskRuns[taskrun.Name]; fullEmbedded && !ok { // This taskrun 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", taskrun.Name) @@ -1230,57 +1262,146 @@ func updatePipelineRunStatusFromTaskRuns(logger *zap.SugaredLogger, pr *v1beta1. // Since this was recovered now, add it to the map, or it might be overwritten taskRunByPipelineTask[pipelineTaskName] = taskrun.Name } + if _, ok := childRefByPipelineTask[pipelineTaskName]; minimalEmbedded && !ok { + // This taskrun 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", taskrun.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: taskrun.Name, + PipelineTaskName: pipelineTaskName, + } + } } // Then loop by pipelinetask name over all the TaskRuns associated to Conditions for pipelineTaskName, actualConditionTaskRuns := range conditionTaskRuns { - taskRunName, ok := taskRunByPipelineTask[pipelineTaskName] - if !ok { + ok := false + // Default the taskRunName to be a generated one - this will be overridden if we already find pipelineTaskName + // in either childRefByPipelineTask or taskRunByPipelineTask. + taskRunName := "" + cr, inChildRef := childRefByPipelineTask[pipelineTaskName] + if inChildRef { + taskRunName = cr.Name + } else if taskRunName, ok = taskRunByPipelineTask[pipelineTaskName]; !ok { // The pipelineTask associated to the conditions was not found in the pipelinerun // status. This means that the conditions were orphaned, and never added to the // status. In this case we need to generate a new TaskRun name, that will be used // to run the TaskRun if the conditions are passed. - taskRunName = resources.GetTaskRunName(pr.Status.TaskRuns, pipelineTaskName, pr.Name) + taskRunName = resources.GetTaskRunName(pr.Status.TaskRuns, pr.Status.ChildReferences, pipelineTaskName, pr.Name) + } + + if _, ok := childRefByPipelineTask[pipelineTaskName]; !ok && minimalEmbedded { + childRefByPipelineTask[pipelineTaskName] = &v1beta1.ChildStatusReference{ + TypeMeta: runtime.TypeMeta{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: "TaskRun", + }, + Name: taskRunName, + PipelineTaskName: pipelineTaskName, + } + } + + if _, ok := taskRunByPipelineTask[pipelineTaskName]; !ok && fullEmbedded { pr.Status.TaskRuns[taskRunName] = &v1beta1.PipelineRunTaskRunStatus{ PipelineTaskName: pipelineTaskName, Status: nil, ConditionChecks: nil, } } - // Build the map of condition checks for the taskrun - // If there were no other condition, initialise the map - conditionChecks := pr.Status.TaskRuns[taskRunName].ConditionChecks - if conditionChecks == nil { - conditionChecks = make(map[string]*v1beta1.PipelineRunConditionCheckStatus) + + updateConditionChecksForTaskRun(logger, pr, childRefByPipelineTask, actualConditionTaskRuns, pipelineTaskName, taskRunName, fullEmbedded, minimalEmbedded) + } + if minimalEmbedded { + var newChildRefs []v1beta1.ChildStatusReference + for k := range childRefByPipelineTask { + newChildRefs = append(newChildRefs, *childRefByPipelineTask[k]) } - for i, foundTaskRun := range actualConditionTaskRuns { - lbls := foundTaskRun.GetLabels() - if _, ok := conditionChecks[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 { - conditionChecks[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) + pr.Status.ChildReferences = newChildRefs + } +} + +func updateConditionChecksForTaskRun(logger *zap.SugaredLogger, pr *v1beta1.PipelineRun, childRefByPipelineTask map[string]*v1beta1.ChildStatusReference, + actualConditionTaskRuns []*v1beta1.TaskRun, pipelineTaskName string, taskRunName string, fullEmbedded, minimalEmbedded bool) (map[string]*v1beta1.PipelineRunConditionCheckStatus, []*v1beta1.PipelineRunChildConditionCheckStatus) { + // Build the map of condition checks for the taskrun + // If there were no other condition, initialise the map + conditionChecks := make(map[string]*v1beta1.PipelineRunConditionCheckStatus) + // Instantiate the slice of condition checks for ChildReferences as well. + var conditionsSlice []*v1beta1.PipelineRunChildConditionCheckStatus + + if _, ok := childRefByPipelineTask[pipelineTaskName]; ok && minimalEmbedded { + for i := range childRefByPipelineTask[pipelineTaskName].ConditionChecks { + cc := childRefByPipelineTask[pipelineTaskName].ConditionChecks[i] + conditionChecks[cc.ConditionCheckName] = &cc.PipelineRunConditionCheckStatus + } + } else if fullEmbedded { + conditionChecks = pr.Status.TaskRuns[taskRunName].ConditionChecks + } + if conditionChecks == nil { + conditionChecks = make(map[string]*v1beta1.PipelineRunConditionCheckStatus) + } + + for i, foundTaskRun := range actualConditionTaskRuns { + lbls := foundTaskRun.GetLabels() + if _, ok := conditionChecks[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 { + conditionChecks[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) } } + } + + if _, ok := childRefByPipelineTask[pipelineTaskName]; ok && minimalEmbedded { + for k, v := range conditionChecks { + conditionsSlice = append(conditionsSlice, &v1beta1.PipelineRunChildConditionCheckStatus{ + PipelineRunConditionCheckStatus: *v, + ConditionCheckName: k, + }) + } + childRefByPipelineTask[pipelineTaskName].ConditionChecks = conditionsSlice + } + + if fullEmbedded { pr.Status.TaskRuns[taskRunName].ConditionChecks = conditionChecks } + return conditionChecks, conditionsSlice } -func updatePipelineRunStatusFromRuns(logger *zap.SugaredLogger, pr *v1beta1.PipelineRun, runs []*v1alpha1.Run) { +func updatePipelineRunStatusFromRuns(ctx context.Context, 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 { return } - if pr.Status.Runs == nil { - pr.Status.Runs = make(map[string]*v1beta1.PipelineRunRunStatus) + + cfg := config.FromContextOrDefaults(ctx) + fullEmbedded := cfg.FeatureFlags.EmbeddedStatus == config.FullEmbeddedStatus || cfg.FeatureFlags.EmbeddedStatus == config.BothEmbeddedStatus + minimalEmbedded := cfg.FeatureFlags.EmbeddedStatus == config.MinimalEmbeddedStatus || cfg.FeatureFlags.EmbeddedStatus == config.BothEmbeddedStatus + + childRefByPipelineTask := make(map[string]v1beta1.ChildStatusReference) + + if fullEmbedded { + if pr.Status.Runs == nil { + pr.Status.Runs = make(map[string]*v1beta1.PipelineRunRunStatus) + } + } + if minimalEmbedded { + for _, cr := range pr.Status.ChildReferences { + childRefByPipelineTask[cr.PipelineTaskName] = cr + } } + // Loop over all the Runs associated to Tasks for _, run := range runs { // Only process Runs that are owned by this PipelineRun. @@ -1291,12 +1412,32 @@ func updatePipelineRunStatusFromRuns(logger *zap.SugaredLogger, pr *v1beta1.Pipe } lbls := run.GetLabels() pipelineTaskName := lbls[pipeline.PipelineTaskLabelKey] - if _, ok := pr.Status.Runs[run.Name]; !ok { + if _, ok := pr.Status.Runs[run.Name]; !ok && fullEmbedded { // This run was missing from the status. pr.Status.Runs[run.Name] = &v1beta1.PipelineRunRunStatus{ PipelineTaskName: pipelineTaskName, Status: &run.Status, } } + + if _, ok := childRefByPipelineTask[run.Name]; !ok && minimalEmbedded { + // This run was missing from the status. + childRefByPipelineTask[pipelineTaskName] = v1beta1.ChildStatusReference{ + TypeMeta: runtime.TypeMeta{ + APIVersion: v1alpha1.SchemeGroupVersion.String(), + Kind: "Run", + }, + Name: run.Name, + PipelineTaskName: pipelineTaskName, + } + } + } + + if minimalEmbedded { + var newChildRefs []v1beta1.ChildStatusReference + for _, v := range childRefByPipelineTask { + newChildRefs = append(newChildRefs, v) + } + pr.Status.ChildReferences = newChildRefs } } diff --git a/pkg/reconciler/pipelinerun/pipelinerun_test.go b/pkg/reconciler/pipelinerun/pipelinerun_test.go index 5ce478550c8..f0f6d02e953 100644 --- a/pkg/reconciler/pipelinerun/pipelinerun_test.go +++ b/pkg/reconciler/pipelinerun/pipelinerun_test.go @@ -24,6 +24,7 @@ import ( "net/http/httptest" "net/url" "regexp" + "sort" "testing" "time" @@ -100,10 +101,18 @@ var ( now = time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC) testClock = clock.NewFakePassiveClock(now) + + valuesForEmbeddedStatus = []string{ + config.DefaultEmbeddedStatus, + config.FullEmbeddedStatus, + config.BothEmbeddedStatus, + config.MinimalEmbeddedStatus, + } ) const ( - apiFieldsFeatureFlag = "enable-api-fields" + apiFieldsFeatureFlag = "enable-api-fields" + embeddedStatusFeatureFlag = "embedded-status" ) type PipelineRunTest struct { @@ -237,458 +246,514 @@ func getPipelineRunUpdates(t *testing.T, actions []ktesting.Action) []*v1beta1.P } func TestReconcile(t *testing.T) { - // TestReconcile runs "Reconcile" on a PipelineRun with one Task that has not been started yet. - // It verifies that the TaskRun is created, it checks the resulting API actions, status and events. - names.TestingSeed() - const pipelineRunName = "test-pipeline-run-success" - prs := []*v1beta1.PipelineRun{{ - ObjectMeta: baseObjectMeta(pipelineRunName, "foo"), - Spec: v1beta1.PipelineRunSpec{ - PipelineRef: &v1beta1.PipelineRef{ - Name: "test-pipeline", - }, - ServiceAccountName: "test-sa", - Resources: []v1beta1.PipelineResourceBinding{ - { - Name: "git-repo", - ResourceRef: &v1beta1.PipelineResourceRef{ - Name: "some-repo", + testCases := []struct { + name string + embeddedStatusVal string + }{ + { + name: "default embedded status", + embeddedStatusVal: config.DefaultEmbeddedStatus, + }, + { + name: "full embedded status", + embeddedStatusVal: config.FullEmbeddedStatus, + }, + { + name: "both embedded status", + embeddedStatusVal: config.BothEmbeddedStatus, + }, + { + name: "minimal embedded status", + embeddedStatusVal: config.MinimalEmbeddedStatus, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // TestReconcile runs "Reconcile" on a PipelineRun with one Task that has not been started yet. + // It verifies that the TaskRun is created, it checks the resulting API actions, status and events. + names.TestingSeed() + const pipelineRunName = "test-pipeline-run-success" + prs := []*v1beta1.PipelineRun{{ + ObjectMeta: baseObjectMeta(pipelineRunName, "foo"), + Spec: v1beta1.PipelineRunSpec{ + PipelineRef: &v1beta1.PipelineRef{ + Name: "test-pipeline", }, - }, - { - Name: "best-image", - ResourceSpec: &resourcev1alpha1.PipelineResourceSpec{ - Type: resourcev1alpha1.PipelineResourceTypeImage, - Params: []resourcev1alpha1.ResourceParam{{ - Name: "url", - Value: "gcr.io/sven", - }}, + ServiceAccountName: "test-sa", + Resources: []v1beta1.PipelineResourceBinding{ + { + Name: "git-repo", + ResourceRef: &v1beta1.PipelineResourceRef{ + Name: "some-repo", + }, + }, + { + Name: "best-image", + ResourceSpec: &resourcev1alpha1.PipelineResourceSpec{ + Type: resourcev1alpha1.PipelineResourceTypeImage, + Params: []resourcev1alpha1.ResourceParam{{ + Name: "url", + Value: "gcr.io/sven", + }}, + }, + }, }, + Params: []v1beta1.Param{{ + Name: "bar", + Value: *v1beta1.NewArrayOrString("somethingmorefun"), + }}, }, - }, - Params: []v1beta1.Param{{ + }} + funParam := v1beta1.Param{ + Name: "foo", + Value: *v1beta1.NewArrayOrString("somethingfun"), + } + moreFunParam := v1beta1.Param{ Name: "bar", - Value: *v1beta1.NewArrayOrString("somethingmorefun"), - }}, - }, - }} - funParam := v1beta1.Param{ - Name: "foo", - Value: *v1beta1.NewArrayOrString("somethingfun"), - } - moreFunParam := v1beta1.Param{ - Name: "bar", - Value: *v1beta1.NewArrayOrString("$(params.bar)"), - } - templatedParam := v1beta1.Param{ - Name: "templatedparam", - Value: *v1beta1.NewArrayOrString("$(inputs.workspace.$(params.rev-param))"), - } - contextRunParam := v1beta1.Param{ - Name: "contextRunParam", - Value: *v1beta1.NewArrayOrString("$(context.pipelineRun.name)"), - } - contextPipelineParam := v1beta1.Param{ - Name: "contextPipelineParam", - Value: *v1beta1.NewArrayOrString("$(context.pipeline.name)"), - } - retriesParam := v1beta1.Param{ - Name: "contextRetriesParam", - Value: *v1beta1.NewArrayOrString("$(context.pipelineTask.retries)"), - } - const pipelineName = "test-pipeline" - ps := []*v1beta1.Pipeline{{ - ObjectMeta: baseObjectMeta(pipelineName, "foo"), - Spec: v1beta1.PipelineSpec{ - Resources: []v1beta1.PipelineDeclaredResource{ - { - Name: "git-repo", - Type: resourcev1alpha1.PipelineResourceTypeGit, - }, - { - Name: "best-image", - Type: resourcev1alpha1.PipelineResourceTypeImage, - }, - }, - Params: []v1beta1.ParamSpec{ - { - Name: "pipeline-param", - Type: v1beta1.ParamTypeString, - Default: v1beta1.NewArrayOrString("somethingdifferent"), - }, - { - Name: "rev-param", - Type: v1beta1.ParamTypeString, - Default: v1beta1.NewArrayOrString("revision"), - }, - { - Name: "bar", - Type: v1beta1.ParamTypeString, - }, - }, - Tasks: []v1beta1.PipelineTask{ - { - // unit-test-3 uses runAfter to indicate it should run last - Name: "unit-test-3", - TaskRef: &v1beta1.TaskRef{ - Name: "unit-test-task", + Value: *v1beta1.NewArrayOrString("$(params.bar)"), + } + templatedParam := v1beta1.Param{ + Name: "templatedparam", + Value: *v1beta1.NewArrayOrString("$(inputs.workspace.$(params.rev-param))"), + } + contextRunParam := v1beta1.Param{ + Name: "contextRunParam", + Value: *v1beta1.NewArrayOrString("$(context.pipelineRun.name)"), + } + contextPipelineParam := v1beta1.Param{ + Name: "contextPipelineParam", + Value: *v1beta1.NewArrayOrString("$(context.pipeline.name)"), + } + retriesParam := v1beta1.Param{ + Name: "contextRetriesParam", + Value: *v1beta1.NewArrayOrString("$(context.pipelineTask.retries)"), + } + const pipelineName = "test-pipeline" + ps := []*v1beta1.Pipeline{{ + ObjectMeta: baseObjectMeta(pipelineName, "foo"), + Spec: v1beta1.PipelineSpec{ + Resources: []v1beta1.PipelineDeclaredResource{ + { + Name: "git-repo", + Type: resourcev1alpha1.PipelineResourceTypeGit, + }, + { + Name: "best-image", + Type: resourcev1alpha1.PipelineResourceTypeImage, + }, }, - Params: []v1beta1.Param{funParam, moreFunParam, templatedParam, contextRunParam, contextPipelineParam, retriesParam}, - Resources: &v1beta1.PipelineTaskResources{ - Inputs: []v1beta1.PipelineTaskInputResource{{ - Name: "workspace", - Resource: "git-repo", - }}, - Outputs: []v1beta1.PipelineTaskOutputResource{ - { - Name: "image-to-use", - Resource: "best-image", + Params: []v1beta1.ParamSpec{ + { + Name: "pipeline-param", + Type: v1beta1.ParamTypeString, + Default: v1beta1.NewArrayOrString("somethingdifferent"), + }, + { + Name: "rev-param", + Type: v1beta1.ParamTypeString, + Default: v1beta1.NewArrayOrString("revision"), + }, + { + Name: "bar", + Type: v1beta1.ParamTypeString, + }, + }, + Tasks: []v1beta1.PipelineTask{ + { + // unit-test-3 uses runAfter to indicate it should run last + Name: "unit-test-3", + TaskRef: &v1beta1.TaskRef{ + Name: "unit-test-task", }, - { - Name: "workspace", - Resource: "git-repo", + Params: []v1beta1.Param{funParam, moreFunParam, templatedParam, contextRunParam, contextPipelineParam, retriesParam}, + Resources: &v1beta1.PipelineTaskResources{ + Inputs: []v1beta1.PipelineTaskInputResource{{ + Name: "workspace", + Resource: "git-repo", + }}, + Outputs: []v1beta1.PipelineTaskOutputResource{ + { + Name: "image-to-use", + Resource: "best-image", + }, + { + Name: "workspace", + Resource: "git-repo", + }, + }, + }, + RunAfter: []string{"unit-test-2"}, + }, + { + // unit-test-1 can run right away because it has no dependencies + Name: "unit-test-1", + TaskRef: &v1beta1.TaskRef{ + Name: "unit-test-task", + }, + Params: []v1beta1.Param{funParam, moreFunParam, templatedParam, contextRunParam, contextPipelineParam, retriesParam}, + Resources: &v1beta1.PipelineTaskResources{ + Inputs: []v1beta1.PipelineTaskInputResource{{ + Name: "workspace", + Resource: "git-repo", + }}, + Outputs: []v1beta1.PipelineTaskOutputResource{ + { + Name: "image-to-use", + Resource: "best-image", + }, + { + Name: "workspace", + Resource: "git-repo", + }, + }, + }, + Retries: 5, + }, + { + Name: "unit-test-2", + TaskRef: &v1beta1.TaskRef{ + Name: "unit-test-followup-task", + }, + Resources: &v1beta1.PipelineTaskResources{ + Inputs: []v1beta1.PipelineTaskInputResource{{ + Name: "workspace", + Resource: "git-repo", + From: []string{"unit-test-1"}, + }}, + }, + }, + { + Name: "unit-test-cluster-task", + TaskRef: &v1beta1.TaskRef{ + Name: "unit-test-cluster-task", + Kind: v1beta1.ClusterTaskKind, + }, + Params: []v1beta1.Param{funParam, moreFunParam, templatedParam, contextRunParam, contextPipelineParam}, + Resources: &v1beta1.PipelineTaskResources{ + Inputs: []v1beta1.PipelineTaskInputResource{{ + Name: "workspace", + Resource: "git-repo", + }}, + Outputs: []v1beta1.PipelineTaskOutputResource{ + { + Name: "image-to-use", + Resource: "best-image", + }, + { + Name: "workspace", + Resource: "git-repo", + }, + }, }, }, }, - RunAfter: []string{"unit-test-2"}, }, + }} + ts := []*v1beta1.Task{ { - // unit-test-1 can run right away because it has no dependencies - Name: "unit-test-1", - TaskRef: &v1beta1.TaskRef{ - Name: "unit-test-task", - }, - Params: []v1beta1.Param{funParam, moreFunParam, templatedParam, contextRunParam, contextPipelineParam, retriesParam}, - Resources: &v1beta1.PipelineTaskResources{ - Inputs: []v1beta1.PipelineTaskInputResource{{ - Name: "workspace", - Resource: "git-repo", - }}, - Outputs: []v1beta1.PipelineTaskOutputResource{ + ObjectMeta: baseObjectMeta("unit-test-task", "foo"), + Spec: v1beta1.TaskSpec{ + Params: []v1beta1.ParamSpec{ + { + Name: "foo", + Type: v1beta1.ParamTypeString, + }, + { + Name: "bar", + Type: v1beta1.ParamTypeString, + }, + { + Name: "templatedparam", + Type: v1beta1.ParamTypeString, + }, + { + Name: "contextRunParam", + Type: v1beta1.ParamTypeString, + }, { - Name: "image-to-use", - Resource: "best-image", + Name: "contextPipelineParam", + Type: v1beta1.ParamTypeString, }, { - Name: "workspace", - Resource: "git-repo", + Name: "contextRetriesParam", + Type: v1beta1.ParamTypeString, + }, + }, + Resources: &v1beta1.TaskResources{ + Inputs: []v1beta1.TaskResource{{ + ResourceDeclaration: v1beta1.ResourceDeclaration{ + Name: "workspace", + Type: v1beta1.PipelineResourceTypeGit, + }, + }}, + Outputs: []v1beta1.TaskResource{ + { + ResourceDeclaration: v1beta1.ResourceDeclaration{ + Name: "image-to-use", + Type: v1beta1.PipelineResourceTypeImage, + }, + }, + { + ResourceDeclaration: v1beta1.ResourceDeclaration{ + Name: "workspace", + Type: v1beta1.PipelineResourceTypeGit, + }, + }, }, }, }, - Retries: 5, }, { - Name: "unit-test-2", - TaskRef: &v1beta1.TaskRef{ - Name: "unit-test-followup-task", - }, - Resources: &v1beta1.PipelineTaskResources{ - Inputs: []v1beta1.PipelineTaskInputResource{{ - Name: "workspace", - Resource: "git-repo", - From: []string{"unit-test-1"}, - }}, + ObjectMeta: baseObjectMeta("unit-test-followup-task", "foo"), + Spec: v1beta1.TaskSpec{ + Resources: &v1beta1.TaskResources{ + Inputs: []v1beta1.TaskResource{{ + ResourceDeclaration: v1beta1.ResourceDeclaration{ + Name: "workspace", + Type: v1beta1.PipelineResourceTypeGit, + }, + }}, + }, }, }, + } + clusterTasks := []*v1beta1.ClusterTask{ { - Name: "unit-test-cluster-task", - TaskRef: &v1beta1.TaskRef{ - Name: "unit-test-cluster-task", - Kind: v1beta1.ClusterTaskKind, - }, - Params: []v1beta1.Param{funParam, moreFunParam, templatedParam, contextRunParam, contextPipelineParam}, - Resources: &v1beta1.PipelineTaskResources{ - Inputs: []v1beta1.PipelineTaskInputResource{{ - Name: "workspace", - Resource: "git-repo", - }}, - Outputs: []v1beta1.PipelineTaskOutputResource{ + ObjectMeta: metav1.ObjectMeta{Name: "unit-test-cluster-task"}, + Spec: v1beta1.TaskSpec{ + Params: []v1beta1.ParamSpec{ + { + Name: "foo", + Type: v1beta1.ParamTypeString, + }, + { + Name: "bar", + Type: v1beta1.ParamTypeString, + }, { - Name: "image-to-use", - Resource: "best-image", + Name: "templatedparam", + Type: v1beta1.ParamTypeString, }, { - Name: "workspace", - Resource: "git-repo", + Name: "contextRunParam", + Type: v1beta1.ParamTypeString, + }, + { + Name: "contextPipelineParam", + Type: v1beta1.ParamTypeString, + }, + }, + Resources: &v1beta1.TaskResources{ + Inputs: []v1beta1.TaskResource{{ + ResourceDeclaration: v1beta1.ResourceDeclaration{ + Name: "workspace", + Type: v1beta1.PipelineResourceTypeGit, + }, + }}, + Outputs: []v1beta1.TaskResource{ + { + ResourceDeclaration: v1beta1.ResourceDeclaration{ + Name: "image-to-use", + Type: v1beta1.PipelineResourceTypeImage, + }, + }, + { + ResourceDeclaration: v1beta1.ResourceDeclaration{ + Name: "workspace", + Type: v1beta1.PipelineResourceTypeGit, + }, + }, }, }, }, }, - }, - }, - }} - ts := []*v1beta1.Task{ - { - ObjectMeta: baseObjectMeta("unit-test-task", "foo"), - Spec: v1beta1.TaskSpec{ - Params: []v1beta1.ParamSpec{ - { - Name: "foo", - Type: v1beta1.ParamTypeString, - }, - { - Name: "bar", - Type: v1beta1.ParamTypeString, - }, - { - Name: "templatedparam", - Type: v1beta1.ParamTypeString, - }, - { - Name: "contextRunParam", - Type: v1beta1.ParamTypeString, - }, - { - Name: "contextPipelineParam", - Type: v1beta1.ParamTypeString, - }, - { - Name: "contextRetriesParam", - Type: v1beta1.ParamTypeString, + { + ObjectMeta: metav1.ObjectMeta{Name: "unit-test-followup-task"}, + Spec: v1beta1.TaskSpec{ + Resources: &v1beta1.TaskResources{ + Inputs: []v1beta1.TaskResource{{ + ResourceDeclaration: v1beta1.ResourceDeclaration{ + Name: "workspace", + Type: v1beta1.PipelineResourceTypeGit, + }, + }}, + }, }, }, - Resources: &v1beta1.TaskResources{ - Inputs: []v1beta1.TaskResource{{ - ResourceDeclaration: v1beta1.ResourceDeclaration{ - Name: "workspace", - Type: v1beta1.PipelineResourceTypeGit, - }, + } + rs := []*resourcev1alpha1.PipelineResource{{ + ObjectMeta: baseObjectMeta("some-repo", "foo"), + Spec: resourcev1alpha1.PipelineResourceSpec{ + Type: resourcev1alpha1.PipelineResourceTypeGit, + Params: []resourcev1alpha1.ResourceParam{{ + Name: "url", + Value: "https://github.com/kristoff/reindeer", }}, - Outputs: []v1beta1.TaskResource{ + }, + }} + + // When PipelineResources are created in the cluster, Kubernetes will add a SelfLink. We + // are using this to differentiate between Resources that we are referencing by Spec or by Ref + // after we have resolved them. + rs[0].SelfLink = "some/link" + + d := test.Data{ + PipelineRuns: prs, + Pipelines: ps, + Tasks: ts, + ClusterTasks: clusterTasks, + PipelineResources: rs, + ConfigMaps: getConfigMapsWithEmbeddedStatus(tc.embeddedStatusVal), + } + prt := newPipelineRunTest(d, t) + defer prt.Cancel() + + wantEvents := []string{ + "Normal Started", + "Normal Running Tasks Completed: 0", + } + reconciledRun, clients := prt.reconcileRun("foo", "test-pipeline-run-success", wantEvents, false) + + actions := clients.Pipeline.Actions() + if len(actions) < 2 { + t.Fatalf("Expected client to have at least two action implementation but it has %d", len(actions)) + } + + // Check that the expected TaskRun was created + actual := getTaskRunCreations(t, actions)[0] + expectedTaskRun := &v1beta1.TaskRun{ + ObjectMeta: taskRunObjectMeta("test-pipeline-run-success-unit-test-1", "foo", "test-pipeline-run-success", "test-pipeline", "unit-test-1", false), + Spec: v1beta1.TaskRunSpec{ + TaskRef: &v1beta1.TaskRef{ + Name: "unit-test-task", + }, + ServiceAccountName: "test-sa", + Params: []v1beta1.Param{ { - ResourceDeclaration: v1beta1.ResourceDeclaration{ - Name: "image-to-use", - Type: v1beta1.PipelineResourceTypeImage, - }, + Name: "foo", + Value: *v1beta1.NewArrayOrString("somethingfun"), }, { - ResourceDeclaration: v1beta1.ResourceDeclaration{ - Name: "workspace", - Type: v1beta1.PipelineResourceTypeGit, - }, + Name: "bar", + Value: *v1beta1.NewArrayOrString("somethingmorefun"), }, - }, - }, - }, - }, - { - ObjectMeta: baseObjectMeta("unit-test-followup-task", "foo"), - Spec: v1beta1.TaskSpec{ - Resources: &v1beta1.TaskResources{ - Inputs: []v1beta1.TaskResource{{ - ResourceDeclaration: v1beta1.ResourceDeclaration{ - Name: "workspace", - Type: v1beta1.PipelineResourceTypeGit, + { + Name: "templatedparam", + Value: *v1beta1.NewArrayOrString("$(inputs.workspace.revision)"), }, - }}, - }, - }, - }, - } - clusterTasks := []*v1beta1.ClusterTask{ - { - ObjectMeta: metav1.ObjectMeta{Name: "unit-test-cluster-task"}, - Spec: v1beta1.TaskSpec{ - Params: []v1beta1.ParamSpec{ - { - Name: "foo", - Type: v1beta1.ParamTypeString, - }, - { - Name: "bar", - Type: v1beta1.ParamTypeString, - }, - { - Name: "templatedparam", - Type: v1beta1.ParamTypeString, - }, - { - Name: "contextRunParam", - Type: v1beta1.ParamTypeString, - }, - { - Name: "contextPipelineParam", - Type: v1beta1.ParamTypeString, - }, - }, - Resources: &v1beta1.TaskResources{ - Inputs: []v1beta1.TaskResource{{ - ResourceDeclaration: v1beta1.ResourceDeclaration{ - Name: "workspace", - Type: v1beta1.PipelineResourceTypeGit, + { + Name: "contextRunParam", + Value: *v1beta1.NewArrayOrString(pipelineRunName), }, - }}, - Outputs: []v1beta1.TaskResource{ { - ResourceDeclaration: v1beta1.ResourceDeclaration{ - Name: "image-to-use", - Type: v1beta1.PipelineResourceTypeImage, - }, + Name: "contextPipelineParam", + Value: *v1beta1.NewArrayOrString(pipelineName), }, { - ResourceDeclaration: v1beta1.ResourceDeclaration{ + Name: "contextRetriesParam", + Value: *v1beta1.NewArrayOrString("5"), + }, + }, + Resources: &v1beta1.TaskRunResources{ + Inputs: []v1beta1.TaskResourceBinding{{ + PipelineResourceBinding: v1beta1.PipelineResourceBinding{ Name: "workspace", - Type: v1beta1.PipelineResourceTypeGit, + ResourceRef: &v1beta1.PipelineResourceRef{ + Name: "some-repo", + }, + }, + }}, + Outputs: []v1beta1.TaskResourceBinding{ + { + PipelineResourceBinding: v1beta1.PipelineResourceBinding{ + Name: "image-to-use", + ResourceSpec: &resourcev1alpha1.PipelineResourceSpec{ + Type: resourcev1alpha1.PipelineResourceTypeImage, + Params: []resourcev1alpha1.ResourceParam{{ + Name: "url", + Value: "gcr.io/sven", + }}, + }, + }, + Paths: []string{"/pvc/unit-test-1/image-to-use"}, + }, + { + PipelineResourceBinding: v1beta1.PipelineResourceBinding{ + Name: "workspace", + ResourceRef: &v1beta1.PipelineResourceRef{ + Name: "some-repo", + }, + }, + Paths: []string{"/pvc/unit-test-1/workspace"}, }, }, }, + Timeout: &metav1.Duration{Duration: config.DefaultTimeoutMinutes * time.Minute}, }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{Name: "unit-test-followup-task"}, - Spec: v1beta1.TaskSpec{ - Resources: &v1beta1.TaskResources{ - Inputs: []v1beta1.TaskResource{{ - ResourceDeclaration: v1beta1.ResourceDeclaration{ - Name: "workspace", - Type: v1beta1.PipelineResourceTypeGit, - }, - }}, - }, - }, - }, - } - rs := []*resourcev1alpha1.PipelineResource{{ - ObjectMeta: baseObjectMeta("some-repo", "foo"), - Spec: resourcev1alpha1.PipelineResourceSpec{ - Type: resourcev1alpha1.PipelineResourceTypeGit, - Params: []resourcev1alpha1.ResourceParam{{ - Name: "url", - Value: "https://github.com/kristoff/reindeer", - }}, - }, - }} + } - // When PipelineResources are created in the cluster, Kubernetes will add a SelfLink. We - // are using this to differentiate between Resources that we are referencing by Spec or by Ref - // after we have resolved them. - rs[0].SelfLink = "some/link" + // ignore IgnoreUnexported ignore both after and before steps fields + if d := cmp.Diff(expectedTaskRun, actual, cmpopts.SortSlices(func(x, y v1beta1.TaskResourceBinding) bool { return x.Name < y.Name })); d != "" { + t.Errorf("expected to see TaskRun %v created. Diff %s", expectedTaskRun, diff.PrintWantGot(d)) + } + // test taskrun is able to recreate correct pipeline-pvc-name + if expectedTaskRun.GetPipelineRunPVCName() != "test-pipeline-run-success-pvc" { + t.Errorf("expected to see TaskRun PVC name set to %q created but got %s", "test-pipeline-run-success-pvc", expectedTaskRun.GetPipelineRunPVCName()) + } - d := test.Data{ - PipelineRuns: prs, - Pipelines: ps, - Tasks: ts, - ClusterTasks: clusterTasks, - PipelineResources: rs, - } - prt := newPipelineRunTest(d, t) - defer prt.Cancel() + // This PipelineRun is in progress now and the status should reflect that + condition := reconciledRun.Status.GetCondition(apis.ConditionSucceeded) + if condition == nil || condition.Status != corev1.ConditionUnknown { + t.Errorf("Expected PipelineRun status to be in progress, but was %v", condition) + } + if condition != nil && condition.Reason != v1beta1.PipelineRunReasonRunning.String() { + t.Errorf("Expected reason %q but was %s", v1beta1.PipelineRunReasonRunning.String(), condition.Reason) + } - wantEvents := []string{ - "Normal Started", - "Normal Running Tasks Completed: 0", - } - reconciledRun, clients := prt.reconcileRun("foo", "test-pipeline-run-success", wantEvents, false) + tr1Name := "test-pipeline-run-success-unit-test-1" + tr2Name := "test-pipeline-run-success-unit-test-cluster-task" - actions := clients.Pipeline.Actions() - if len(actions) < 2 { - t.Fatalf("Expected client to have at least two action implementation but it has %d", len(actions)) - } + if shouldHaveFullEmbeddedStatus(tc.embeddedStatusVal) { + if len(reconciledRun.Status.TaskRuns) != 2 { + t.Errorf("Expected PipelineRun status to include both TaskRun status items that can run immediately: %v", reconciledRun.Status.TaskRuns) + } + if _, exists := reconciledRun.Status.TaskRuns[tr1Name]; !exists { + t.Errorf("Expected PipelineRun status to include TaskRun status but was %v", reconciledRun.Status.TaskRuns) + } + if _, exists := reconciledRun.Status.TaskRuns[tr2Name]; !exists { + t.Errorf("Expected PipelineRun status to include TaskRun status but was %v", reconciledRun.Status.TaskRuns) + } + } + if shouldHaveMinimalEmbeddedStatus(tc.embeddedStatusVal) { + if len(reconciledRun.Status.ChildReferences) != 2 { + t.Errorf("Expected PipelineRun status to include both ChildReferences status items that can run immediately: %v", reconciledRun.Status.ChildReferences) + } + foundTR1 := false + foundTR2 := false - // Check that the expected TaskRun was created - actual := getTaskRunCreations(t, actions)[0] - expectedTaskRun := &v1beta1.TaskRun{ - ObjectMeta: taskRunObjectMeta("test-pipeline-run-success-unit-test-1", "foo", "test-pipeline-run-success", "test-pipeline", "unit-test-1", false), - Spec: v1beta1.TaskRunSpec{ - TaskRef: &v1beta1.TaskRef{ - Name: "unit-test-task", - }, - ServiceAccountName: "test-sa", - Params: []v1beta1.Param{ - { - Name: "foo", - Value: *v1beta1.NewArrayOrString("somethingfun"), - }, - { - Name: "bar", - Value: *v1beta1.NewArrayOrString("somethingmorefun"), - }, - { - Name: "templatedparam", - Value: *v1beta1.NewArrayOrString("$(inputs.workspace.revision)"), - }, - { - Name: "contextRunParam", - Value: *v1beta1.NewArrayOrString(pipelineRunName), - }, - { - Name: "contextPipelineParam", - Value: *v1beta1.NewArrayOrString(pipelineName), - }, - { - Name: "contextRetriesParam", - Value: *v1beta1.NewArrayOrString("5"), - }, - }, - Resources: &v1beta1.TaskRunResources{ - Inputs: []v1beta1.TaskResourceBinding{{ - PipelineResourceBinding: v1beta1.PipelineResourceBinding{ - Name: "workspace", - ResourceRef: &v1beta1.PipelineResourceRef{ - Name: "some-repo", - }, - }, - }}, - Outputs: []v1beta1.TaskResourceBinding{ - { - PipelineResourceBinding: v1beta1.PipelineResourceBinding{ - Name: "image-to-use", - ResourceSpec: &resourcev1alpha1.PipelineResourceSpec{ - Type: resourcev1alpha1.PipelineResourceTypeImage, - Params: []resourcev1alpha1.ResourceParam{{ - Name: "url", - Value: "gcr.io/sven", - }}, - }, - }, - Paths: []string{"/pvc/unit-test-1/image-to-use"}, - }, - { - PipelineResourceBinding: v1beta1.PipelineResourceBinding{ - Name: "workspace", - ResourceRef: &v1beta1.PipelineResourceRef{ - Name: "some-repo", - }, - }, - Paths: []string{"/pvc/unit-test-1/workspace"}, - }, - }, - }, - Timeout: &metav1.Duration{Duration: config.DefaultTimeoutMinutes * time.Minute}, - }, - } + for _, cr := range reconciledRun.Status.ChildReferences { + if cr.Name == tr1Name { + foundTR1 = true + } + if cr.Name == tr2Name { + foundTR2 = true + } + } - // ignore IgnoreUnexported ignore both after and before steps fields - if d := cmp.Diff(expectedTaskRun, actual, cmpopts.SortSlices(func(x, y v1beta1.TaskResourceBinding) bool { return x.Name < y.Name })); d != "" { - t.Errorf("expected to see TaskRun %v created. Diff %s", expectedTaskRun, diff.PrintWantGot(d)) - } - // test taskrun is able to recreate correct pipeline-pvc-name - if expectedTaskRun.GetPipelineRunPVCName() != "test-pipeline-run-success-pvc" { - t.Errorf("expected to see TaskRun PVC name set to %q created but got %s", "test-pipeline-run-success-pvc", expectedTaskRun.GetPipelineRunPVCName()) - } + if !foundTR1 { + t.Errorf("Expected PipelineRun status to include ChildReferences status for %s but was %v", tr1Name, reconciledRun.Status.ChildReferences) + } + if !foundTR2 { + t.Errorf("Expected PipelineRun status to include ChildReferences status for %s but was %v", tr2Name, reconciledRun.Status.ChildReferences) + } + } - // This PipelineRun is in progress now and the status should reflect that - condition := reconciledRun.Status.GetCondition(apis.ConditionSucceeded) - if condition == nil || condition.Status != corev1.ConditionUnknown { - t.Errorf("Expected PipelineRun status to be in progress, but was %v", condition) - } - if condition != nil && condition.Reason != v1beta1.PipelineRunReasonRunning.String() { - t.Errorf("Expected reason %q but was %s", v1beta1.PipelineRunReasonRunning.String(), condition.Reason) - } + // A PVC should have been created to deal with output -> input linking + ensurePVCCreated(prt.TestAssets.Ctx, t, clients, expectedTaskRun.GetPipelineRunPVCName(), "foo") - if len(reconciledRun.Status.TaskRuns) != 2 { - t.Errorf("Expected PipelineRun status to include both TaskRun status items that can run immediately: %v", reconciledRun.Status.TaskRuns) - } - if _, exists := reconciledRun.Status.TaskRuns["test-pipeline-run-success-unit-test-1"]; !exists { - t.Errorf("Expected PipelineRun status to include TaskRun status but was %v", reconciledRun.Status.TaskRuns) - } - if _, exists := reconciledRun.Status.TaskRuns["test-pipeline-run-success-unit-test-cluster-task"]; !exists { - t.Errorf("Expected PipelineRun status to include TaskRun status but was %v", reconciledRun.Status.TaskRuns) + }) } - - // A PVC should have been created to deal with output -> input linking - ensurePVCCreated(prt.TestAssets.Ctx, t, clients, expectedTaskRun.GetPipelineRunPVCName(), "foo") } // TestReconcile_CustomTask runs "Reconcile" on a PipelineRun with one Custom @@ -699,6 +764,7 @@ func TestReconcile_CustomTask(t *testing.T) { const pipelineRunName = "test-pipelinerun" const pipelineTaskName = "custom-task" const namespace = "namespace" + tcs := []struct { name string pr *v1beta1.PipelineRun @@ -900,68 +966,104 @@ func TestReconcile_CustomTask(t *testing.T) { }, }} - cms := []*corev1.ConfigMap{ - { - ObjectMeta: metav1.ObjectMeta{Name: config.GetFeatureFlagsConfigName(), Namespace: system.Namespace()}, - Data: map[string]string{ - "enable-custom-tasks": "true", - }, - }, - } - for _, tc := range tcs { - t.Run(tc.name, func(t *testing.T) { + for _, embeddedVal := range valuesForEmbeddedStatus { + t.Run(fmt.Sprintf("%s with %s embedded status", tc.name, embeddedVal), func(t *testing.T) { + cms := []*corev1.ConfigMap{ + { + ObjectMeta: metav1.ObjectMeta{Name: config.GetFeatureFlagsConfigName(), Namespace: system.Namespace()}, + Data: map[string]string{ + "enable-custom-tasks": "true", + embeddedStatusFeatureFlag: embeddedVal, + }, + }, + } - d := test.Data{ - PipelineRuns: []*v1beta1.PipelineRun{tc.pr}, - ConfigMaps: cms, - } - prt := newPipelineRunTest(d, t) - defer prt.Cancel() + d := test.Data{ + PipelineRuns: []*v1beta1.PipelineRun{tc.pr}, + ConfigMaps: cms, + } + prt := newPipelineRunTest(d, t) + defer prt.Cancel() - wantEvents := []string{ - "Normal Started", - "Normal Running Tasks Completed: 0", - } - reconciledRun, clients := prt.reconcileRun(namespace, pipelineRunName, wantEvents, false) + wantEvents := []string{ + "Normal Started", + "Normal Running Tasks Completed: 0", + } + reconciledRun, clients := prt.reconcileRun(namespace, pipelineRunName, wantEvents, false) - actions := clients.Pipeline.Actions() - if len(actions) < 2 { - t.Fatalf("Expected client to have at least two action implementation but it has %d", len(actions)) - } + actions := clients.Pipeline.Actions() + if len(actions) < 2 { + t.Fatalf("Expected client to have at least two action implementation but it has %d", len(actions)) + } - // Check that the expected Run was created. - actual := actions[0].(ktesting.CreateAction).GetObject() - if d := cmp.Diff(tc.wantRun, actual); d != "" { - t.Errorf("expected to see Run created: %s", diff.PrintWantGot(d)) - } + // Check that the expected Run was created. + actual := actions[0].(ktesting.CreateAction).GetObject() + if d := cmp.Diff(tc.wantRun, actual); d != "" { + t.Errorf("expected to see Run created: %s", diff.PrintWantGot(d)) + } - // This PipelineRun is in progress now and the status should reflect that - condition := reconciledRun.Status.GetCondition(apis.ConditionSucceeded) - if condition == nil || condition.Status != corev1.ConditionUnknown { - t.Errorf("Expected PipelineRun status to be in progress, but was %v", condition) - } - if condition != nil && condition.Reason != v1beta1.PipelineRunReasonRunning.String() { - t.Errorf("Expected reason %q but was %s", v1beta1.PipelineRunReasonRunning.String(), condition.Reason) - } + // This PipelineRun is in progress now and the status should reflect that + condition := reconciledRun.Status.GetCondition(apis.ConditionSucceeded) + if condition == nil || condition.Status != corev1.ConditionUnknown { + t.Errorf("Expected PipelineRun status to be in progress, but was %v", condition) + } + if condition != nil && condition.Reason != v1beta1.PipelineRunReasonRunning.String() { + t.Errorf("Expected reason %q but was %s", v1beta1.PipelineRunReasonRunning.String(), condition.Reason) + } - if len(reconciledRun.Status.Runs) != 1 { - t.Errorf("Expected PipelineRun status to include one Run status, got %d", len(reconciledRun.Status.Runs)) - } - if _, exists := reconciledRun.Status.Runs[tc.wantRun.Name]; !exists { - t.Errorf("Expected PipelineRun status to include Run status but was %v", reconciledRun.Status.Runs) - } - }) + if shouldHaveFullEmbeddedStatus(embeddedVal) { + if len(reconciledRun.Status.Runs) != 1 { + t.Errorf("Expected PipelineRun status to include one Run status, got %d", len(reconciledRun.Status.Runs)) + } + if _, exists := reconciledRun.Status.Runs[tc.wantRun.Name]; !exists { + t.Errorf("Expected PipelineRun status to include Run status but was %v", reconciledRun.Status.Runs) + } + } + if shouldHaveMinimalEmbeddedStatus(embeddedVal) { + if len(reconciledRun.Status.ChildReferences) != 1 { + t.Errorf("Expected PipelineRun status ChildReferences to include one Run, got %d", len(reconciledRun.Status.ChildReferences)) + } + if reconciledRun.Status.ChildReferences[0].Name != tc.wantRun.Name { + t.Errorf("Expected PipelineRun status ChildReferences to include Run %s but was %v", tc.wantRun.Name, reconciledRun.Status.ChildReferences) + } + } + }) + } } } func TestReconcile_PipelineSpecTaskSpec(t *testing.T) { // TestReconcile_PipelineSpecTaskSpec runs "Reconcile" on a PipelineRun that has an embedded PipelineSpec that has an embedded TaskSpec. // It verifies that a TaskRun is created, it checks the resulting API actions, status and events. - names.TestingSeed() + testCases := []struct { + name string + embeddedStatusVal string + }{ + { + name: "default embedded status", + embeddedStatusVal: config.DefaultEmbeddedStatus, + }, + { + name: "full embedded status", + embeddedStatusVal: config.FullEmbeddedStatus, + }, + { + name: "both embedded status", + embeddedStatusVal: config.BothEmbeddedStatus, + }, + { + name: "minimal embedded status", + embeddedStatusVal: config.MinimalEmbeddedStatus, + }, + } - prs := []*v1beta1.PipelineRun{ - parse.MustParsePipelineRun(t, ` + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + names.TestingSeed() + + prs := []*v1beta1.PipelineRun{ + parse.MustParsePipelineRun(t, ` metadata: name: test-pipeline-run-success namespace: foo @@ -969,9 +1071,9 @@ spec: pipelineRef: name: test-pipeline `), - } - ps := []*v1beta1.Pipeline{ - parse.MustParsePipeline(t, ` + } + ps := []*v1beta1.Pipeline{ + parse.MustParsePipeline(t, ` metadata: name: test-pipeline namespace: foo @@ -983,29 +1085,30 @@ spec: - name: mystep image: myimage `), - } + } - d := test.Data{ - PipelineRuns: prs, - Pipelines: ps, - } - prt := newPipelineRunTest(d, t) - defer prt.Cancel() + d := test.Data{ + PipelineRuns: prs, + Pipelines: ps, + ConfigMaps: getConfigMapsWithEmbeddedStatus(tc.embeddedStatusVal), + } + prt := newPipelineRunTest(d, t) + defer prt.Cancel() - wantEvents := []string{ - "Normal Started", - "Normal Running Tasks Completed: 0", - } - reconciledRun, clients := prt.reconcileRun("foo", "test-pipeline-run-success", wantEvents, false) + wantEvents := []string{ + "Normal Started", + "Normal Running Tasks Completed: 0", + } + reconciledRun, clients := prt.reconcileRun("foo", "test-pipeline-run-success", wantEvents, false) - actions := clients.Pipeline.Actions() - if len(actions) < 2 { - t.Fatalf("Expected client to have at least two action implementation but it has %d", len(actions)) - } + actions := clients.Pipeline.Actions() + if len(actions) < 2 { + t.Fatalf("Expected client to have at least two action implementation but it has %d", len(actions)) + } - // Check that the expected TaskRun was created - actual := getTaskRunCreations(t, actions)[0] - expectedTaskRun := parse.MustParseTaskRun(t, fmt.Sprintf(` + // Check that the expected TaskRun was created + actual := getTaskRunCreations(t, actions)[0] + expectedTaskRun := parse.MustParseTaskRun(t, fmt.Sprintf(` spec: taskSpec: steps: @@ -1015,25 +1118,38 @@ spec: timeout: 1h0m0s `, config.DefaultServiceAccountValue)) - expectedTaskRun.ObjectMeta = taskRunObjectMeta("test-pipeline-run-success-unit-test-task-spec", "foo", "test-pipeline-run-success", "test-pipeline", "unit-test-task-spec", false) - expectedTaskRun.Spec.Resources = &v1beta1.TaskRunResources{} + expectedTaskRun.ObjectMeta = taskRunObjectMeta("test-pipeline-run-success-unit-test-task-spec", "foo", "test-pipeline-run-success", "test-pipeline", "unit-test-task-spec", false) + expectedTaskRun.Spec.Resources = &v1beta1.TaskRunResources{} - // ignore IgnoreUnexported ignore both after and before steps fields - if d := cmp.Diff(expectedTaskRun, actual, ignoreTypeMeta, cmpopts.SortSlices(func(x, y v1beta1.TaskSpec) bool { return len(x.Steps) == len(y.Steps) })); d != "" { - t.Errorf("expected to see TaskRun %v created. Diff %s", expectedTaskRun, diff.PrintWantGot(d)) - } + // ignore IgnoreUnexported ignore both after and before steps fields + if d := cmp.Diff(expectedTaskRun, actual, ignoreTypeMeta, cmpopts.SortSlices(func(x, y v1beta1.TaskSpec) bool { return len(x.Steps) == len(y.Steps) })); d != "" { + t.Errorf("expected to see TaskRun %v created. Diff %s", expectedTaskRun, diff.PrintWantGot(d)) + } - // test taskrun is able to recreate correct pipeline-pvc-name - if expectedTaskRun.GetPipelineRunPVCName() != "test-pipeline-run-success-pvc" { - t.Errorf("expected to see TaskRun PVC name set to %q created but got %s", "test-pipeline-run-success-pvc", expectedTaskRun.GetPipelineRunPVCName()) - } + // test taskrun is able to recreate correct pipeline-pvc-name + if expectedTaskRun.GetPipelineRunPVCName() != "test-pipeline-run-success-pvc" { + t.Errorf("expected to see TaskRun PVC name set to %q created but got %s", "test-pipeline-run-success-pvc", expectedTaskRun.GetPipelineRunPVCName()) + } - if len(reconciledRun.Status.TaskRuns) != 1 { - t.Errorf("Expected PipelineRun status to include both TaskRun status items that can run immediately: %v", reconciledRun.Status.TaskRuns) - } + if shouldHaveFullEmbeddedStatus(tc.embeddedStatusVal) { + if len(reconciledRun.Status.TaskRuns) != 1 { + t.Errorf("Expected PipelineRun status to include both TaskRun status items that can run immediately: %v", reconciledRun.Status.TaskRuns) + } + + if _, exists := reconciledRun.Status.TaskRuns["test-pipeline-run-success-unit-test-task-spec"]; !exists { + t.Errorf("Expected PipelineRun status to include TaskRun status but was %v", reconciledRun.Status.TaskRuns) + } + } + if shouldHaveMinimalEmbeddedStatus(tc.embeddedStatusVal) { + if len(reconciledRun.Status.ChildReferences) != 1 { + t.Errorf("Expected PipelineRun status ChildReferences to include both TaskRun status items that can run immediately: %v", reconciledRun.Status.ChildReferences) + } - if _, exists := reconciledRun.Status.TaskRuns["test-pipeline-run-success-unit-test-task-spec"]; !exists { - t.Errorf("Expected PipelineRun status to include TaskRun status but was %v", reconciledRun.Status.TaskRuns) + if reconciledRun.Status.ChildReferences[0].Name != "test-pipeline-run-success-unit-test-task-spec" { + t.Errorf("Expected PipelineRun status to include TaskRun status but was %v", reconciledRun.Status.ChildReferences) + } + } + }) } } @@ -1660,7 +1776,21 @@ func getConfigMapsWithEnabledAlphaAPIFields() []*corev1.ConfigMap { }} } -func TestReconcileOnCancelledPipelineRun(t *testing.T) { +func getConfigMapsWithEmbeddedStatus(flagVal string) []*corev1.ConfigMap { + return []*corev1.ConfigMap{{ + ObjectMeta: metav1.ObjectMeta{Name: config.GetFeatureFlagsConfigName(), Namespace: system.Namespace()}, + Data: map[string]string{embeddedStatusFeatureFlag: flagVal}, + }} +} + +func getConfigMapsWithEmbeddedStatusAndAlphaAPI(flagVal string) []*corev1.ConfigMap { + cm := getConfigMapsWithEnabledAlphaAPIFields() + cm[0].Data[embeddedStatusFeatureFlag] = flagVal + + return cm +} + +func TestReconcileOnCancelledPipelineRunFullEmbeddedStatus(t *testing.T) { // TestReconcileOnCancelledPipelineRun runs "Reconcile" on a PipelineRun that has been cancelled. // It verifies that reconcile is successful, the pipeline status updated and events generated. prs := []*v1beta1.PipelineRun{createCancelledPipelineRun(t, "test-pipeline-run-cancelled", v1beta1.PipelineRunSpecStatusCancelled)} @@ -1668,7 +1798,42 @@ func TestReconcileOnCancelledPipelineRun(t *testing.T) { ts := []*v1beta1.Task{simpleHelloWorldTask} trs := []*v1beta1.TaskRun{createHelloWorldTaskRun(t, "test-pipeline-run-cancelled-hello-world", "foo", "test-pipeline-run-cancelled", "test-pipeline")} - cms := getConfigMapsWithEnabledAlphaAPIFields() + cms := getConfigMapsWithEmbeddedStatusAndAlphaAPI(config.FullEmbeddedStatus) + + d := test.Data{ + PipelineRuns: prs, + Pipelines: ps, + Tasks: ts, + TaskRuns: trs, + ConfigMaps: cms, + } + prt := newPipelineRunTest(d, t) + defer prt.Cancel() + + wantEvents := []string{ + "Warning Failed PipelineRun \"test-pipeline-run-cancelled\" was cancelled", + } + reconciledRun, _ := prt.reconcileRun("foo", "test-pipeline-run-cancelled", wantEvents, false) + + if reconciledRun.Status.CompletionTime == nil { + t.Errorf("Expected a CompletionTime on invalid PipelineRun but was nil") + } + + // This PipelineRun should still be complete and false, and the status should reflect that + if !reconciledRun.Status.GetCondition(apis.ConditionSucceeded).IsFalse() { + t.Errorf("Expected PipelineRun status to be complete and false, but was %v", reconciledRun.Status.GetCondition(apis.ConditionSucceeded)) + } +} + +func TestReconcileOnCancelledPipelineRunMinimalEmbeddedStatus(t *testing.T) { + // TestReconcileOnCancelledPipelineRun runs "Reconcile" on a PipelineRun that has been cancelled. + // It verifies that reconcile is successful, the pipeline status updated and events generated. + prs := []*v1beta1.PipelineRun{createCancelledPipelineRun(t, "test-pipeline-run-cancelled", v1beta1.PipelineRunSpecStatusCancelled)} + ps := []*v1beta1.Pipeline{simpleHelloWorldPipeline} + ts := []*v1beta1.Task{simpleHelloWorldTask} + trs := []*v1beta1.TaskRun{createHelloWorldTaskRun(t, "test-pipeline-run-cancelled-hello-world", "foo", + "test-pipeline-run-cancelled", "test-pipeline")} + cms := getConfigMapsWithEmbeddedStatusAndAlphaAPI(config.MinimalEmbeddedStatus) d := test.Data{ PipelineRuns: prs, @@ -1930,11 +2095,35 @@ func TestReconcileForCustomTaskWithPipelineRunTimedOut(t *testing.T) { } func TestReconcileOnCancelledRunFinallyPipelineRun(t *testing.T) { - // TestReconcileOnCancelledRunFinallyPipelineRun runs "Reconcile" on a PipelineRun that has been gracefully cancelled. - // It verifies that reconcile is successful, the pipeline status updated and events generated. - prs := []*v1beta1.PipelineRun{createCancelledPipelineRun(t, "test-pipeline-run-cancelled-run-finally", v1beta1.PipelineRunSpecStatusCancelledRunFinally)} - ps := []*v1beta1.Pipeline{ - parse.MustParsePipeline(t, ` + testCases := []struct { + name string + embeddedVal string + }{ + { + name: "default embedded status", + embeddedVal: config.DefaultEmbeddedStatus, + }, + { + name: "full embedded status", + embeddedVal: config.FullEmbeddedStatus, + }, + { + name: "both embedded status", + embeddedVal: config.BothEmbeddedStatus, + }, + { + name: "minimal embedded status", + embeddedVal: config.MinimalEmbeddedStatus, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // TestReconcileOnCancelledRunFinallyPipelineRun runs "Reconcile" on a PipelineRun that has been gracefully cancelled. + // It verifies that reconcile is successful, the pipeline status updated and events generated. + prs := []*v1beta1.PipelineRun{createCancelledPipelineRun(t, "test-pipeline-run-cancelled-run-finally", v1beta1.PipelineRunSpecStatusCancelledRunFinally)} + ps := []*v1beta1.Pipeline{ + parse.MustParsePipeline(t, ` metadata: name: test-pipeline namespace: foo @@ -1949,56 +2138,84 @@ spec: runAfter: - hello-world-1 `), - } - ts := []*v1beta1.Task{simpleHelloWorldTask} - cms := getConfigMapsWithEnabledAlphaAPIFields() + } + ts := []*v1beta1.Task{simpleHelloWorldTask} + cms := getConfigMapsWithEnabledAlphaAPIFields() - d := test.Data{ - PipelineRuns: prs, - Pipelines: ps, - Tasks: ts, - ConfigMaps: cms, - } - prt := newPipelineRunTest(d, t) - defer prt.Cancel() + d := test.Data{ + PipelineRuns: prs, + Pipelines: ps, + Tasks: ts, + ConfigMaps: cms, + } + prt := newPipelineRunTest(d, t) + defer prt.Cancel() - wantEvents := []string{ - "Warning Failed PipelineRun \"test-pipeline-run-cancelled-run-finally\" was cancelled", - } - reconciledRun, _ := prt.reconcileRun("foo", "test-pipeline-run-cancelled-run-finally", wantEvents, false) + wantEvents := []string{ + "Warning Failed PipelineRun \"test-pipeline-run-cancelled-run-finally\" was cancelled", + } + reconciledRun, _ := prt.reconcileRun("foo", "test-pipeline-run-cancelled-run-finally", wantEvents, false) - if reconciledRun.Status.CompletionTime == nil { - t.Errorf("Expected a CompletionTime on invalid PipelineRun but was nil") - } + if reconciledRun.Status.CompletionTime == nil { + t.Errorf("Expected a CompletionTime on invalid PipelineRun but was nil") + } - // This PipelineRun should still be complete and false, and the status should reflect that - if !reconciledRun.Status.GetCondition(apis.ConditionSucceeded).IsFalse() { - t.Errorf("Expected PipelineRun status to be complete and false, but was %v", reconciledRun.Status.GetCondition(apis.ConditionSucceeded)) - } + // This PipelineRun should still be complete and false, and the status should reflect that + if !reconciledRun.Status.GetCondition(apis.ConditionSucceeded).IsFalse() { + t.Errorf("Expected PipelineRun status to be complete and false, but was %v", reconciledRun.Status.GetCondition(apis.ConditionSucceeded)) + } - // There should be no task runs triggered for the pipeline tasks - if len(reconciledRun.Status.TaskRuns) != 0 { - t.Errorf("Expected PipelineRun status to have exactly no task runs, but was %v", len(reconciledRun.Status.TaskRuns)) - } + // There should be no task runs triggered for the pipeline tasks + if shouldHaveFullEmbeddedStatus(tc.embeddedVal) && len(reconciledRun.Status.TaskRuns) != 0 { + t.Errorf("Expected PipelineRun status to have exactly no task runs, but was %v", len(reconciledRun.Status.TaskRuns)) + } + if shouldHaveMinimalEmbeddedStatus(tc.embeddedVal) && len(reconciledRun.Status.ChildReferences) != 0 { + t.Errorf("Expected PipelineRun status ChildReferences to have exactly no task runs, but was %v", len(reconciledRun.Status.ChildReferences)) + } - expectedSkippedTasks := []v1beta1.SkippedTask{{ - Name: "hello-world-1", - }, { - Name: "hello-world-2", - }} + expectedSkippedTasks := []v1beta1.SkippedTask{{ + Name: "hello-world-1", + }, { + Name: "hello-world-2", + }} - if d := cmp.Diff(expectedSkippedTasks, reconciledRun.Status.SkippedTasks); d != "" { - t.Fatalf("Didn't get the expected list of skipped tasks. Diff: %s", diff.PrintWantGot(d)) + if d := cmp.Diff(expectedSkippedTasks, reconciledRun.Status.SkippedTasks); d != "" { + t.Fatalf("Didn't get the expected list of skipped tasks. Diff: %s", diff.PrintWantGot(d)) + } + }) } - } func TestReconcileOnCancelledRunFinallyPipelineRunWithFinalTask(t *testing.T) { - // TestReconcileOnCancelledRunFinallyPipelineRunWithFinalTask runs "Reconcile" on a PipelineRun that has been gracefully cancelled. - // It verifies that reconcile is successful, final tasks run, the pipeline status updated and events generated. - prs := []*v1beta1.PipelineRun{createCancelledPipelineRun(t, "test-pipeline-run-cancelled-run-finally", v1beta1.PipelineRunSpecStatusCancelledRunFinally)} - ps := []*v1beta1.Pipeline{ - parse.MustParsePipeline(t, ` + testCases := []struct { + name string + embeddedStatusVal string + }{ + { + name: "default embedded status", + embeddedStatusVal: config.DefaultEmbeddedStatus, + }, + { + name: "full embedded status", + embeddedStatusVal: config.FullEmbeddedStatus, + }, + { + name: "both embedded status", + embeddedStatusVal: config.BothEmbeddedStatus, + }, + { + name: "minimal embedded status", + embeddedStatusVal: config.MinimalEmbeddedStatus, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // TestReconcileOnCancelledRunFinallyPipelineRunWithFinalTask runs "Reconcile" on a PipelineRun that has been gracefully cancelled. + // It verifies that reconcile is successful, final tasks run, the pipeline status updated and events generated. + prs := []*v1beta1.PipelineRun{createCancelledPipelineRun(t, "test-pipeline-run-cancelled-run-finally", v1beta1.PipelineRunSpecStatusCancelledRunFinally)} + ps := []*v1beta1.Pipeline{ + parse.MustParsePipeline(t, ` metadata: name: test-pipeline namespace: foo @@ -2017,49 +2234,62 @@ spec: taskRef: name: some-task `), - } - ts := []*v1beta1.Task{ - simpleHelloWorldTask, - simpleSomeTask, - } - cms := getConfigMapsWithEnabledAlphaAPIFields() + } + ts := []*v1beta1.Task{ + simpleHelloWorldTask, + simpleSomeTask, + } + cms := getConfigMapsWithEmbeddedStatusAndAlphaAPI(tc.embeddedStatusVal) - d := test.Data{ - PipelineRuns: prs, - Pipelines: ps, - Tasks: ts, - ConfigMaps: cms, - } - prt := newPipelineRunTest(d, t) - defer prt.Cancel() + d := test.Data{ + PipelineRuns: prs, + Pipelines: ps, + Tasks: ts, + ConfigMaps: cms, + } + prt := newPipelineRunTest(d, t) + defer prt.Cancel() - wantEvents := []string{ - "Normal Started", - } - reconciledRun, _ := prt.reconcileRun("foo", "test-pipeline-run-cancelled-run-finally", wantEvents, false) + wantEvents := []string{ + "Normal Started", + } + reconciledRun, _ := prt.reconcileRun("foo", "test-pipeline-run-cancelled-run-finally", wantEvents, false) - if reconciledRun.Status.CompletionTime != nil { - t.Errorf("Expected a CompletionTime to be nil on incomplete PipelineRun but was %v", reconciledRun.Status.CompletionTime) - } + if reconciledRun.Status.CompletionTime != nil { + t.Errorf("Expected a CompletionTime to be nil on incomplete PipelineRun but was %v", reconciledRun.Status.CompletionTime) + } - // This PipelineRun should still be complete and false, and the status should reflect that - if !reconciledRun.Status.GetCondition(apis.ConditionSucceeded).IsUnknown() { - t.Errorf("Expected PipelineRun status to be complete and unknown, but was %v", reconciledRun.Status.GetCondition(apis.ConditionSucceeded)) - } + // This PipelineRun should still be complete and false, and the status should reflect that + if !reconciledRun.Status.GetCondition(apis.ConditionSucceeded).IsUnknown() { + t.Errorf("Expected PipelineRun status to be complete and unknown, but was %v", reconciledRun.Status.GetCondition(apis.ConditionSucceeded)) + } - // There should be exactly one task run triggered for the "final-task-1" final task - if len(reconciledRun.Status.TaskRuns) != 1 { - t.Errorf("Expected PipelineRun status to have exactly one task run, but was %v", len(reconciledRun.Status.TaskRuns)) - } + // There should be exactly one task run triggered for the "final-task-1" final task + if shouldHaveFullEmbeddedStatus(tc.embeddedStatusVal) { + if len(reconciledRun.Status.TaskRuns) != 1 { + t.Errorf("Expected PipelineRun status to have exactly one task run, but was %v", len(reconciledRun.Status.TaskRuns)) + } - expectedTaskRunsStatus := &v1beta1.PipelineRunTaskRunStatus{ - PipelineTaskName: "final-task-1", - Status: &v1beta1.TaskRunStatus{}, - } - for _, taskRun := range reconciledRun.Status.TaskRuns { - if d := cmp.Diff(taskRun, expectedTaskRunsStatus); d != "" { - t.Fatalf("Expected PipelineRun status to match TaskRun(s) status, but got a mismatch %s", diff.PrintWantGot(d)) - } + expectedTaskRunsStatus := &v1beta1.PipelineRunTaskRunStatus{ + PipelineTaskName: "final-task-1", + Status: &v1beta1.TaskRunStatus{}, + } + for _, taskRun := range reconciledRun.Status.TaskRuns { + if d := cmp.Diff(taskRun, expectedTaskRunsStatus); d != "" { + t.Fatalf("Expected PipelineRun status to match TaskRun(s) status, but got a mismatch %s", diff.PrintWantGot(d)) + } + } + } + if shouldHaveMinimalEmbeddedStatus(tc.embeddedStatusVal) { + if len(reconciledRun.Status.ChildReferences) != 1 { + t.Errorf("Expected PipelineRun status ChildReferences to have exactly one task run, but was %v", len(reconciledRun.Status.ChildReferences)) + } + + if reconciledRun.Status.ChildReferences[0].PipelineTaskName != "final-task-1" { + t.Errorf("Expected PipelineRun status to include TaskRun status but was %v", reconciledRun.Status.ChildReferences) + } + } + }) } } @@ -2174,94 +2404,122 @@ func TestReconcileOnCancelledRunFinallyPipelineRunWithFinalTaskAndRetries(t *tes // TestReconcileOnCancelledRunFinallyPipelineRunWithFinalTaskAndRetries runs "Reconcile" on a PipelineRun that has // been gracefully cancelled. It verifies that reconcile is successful, the pipeline status updated and events generated. - // Pipeline has a DAG task "hello-world-1" and Finally task "hello-world-2" - ps := []*v1beta1.Pipeline{{ - ObjectMeta: baseObjectMeta("test-pipeline", "foo"), - Spec: v1beta1.PipelineSpec{ - Tasks: []v1beta1.PipelineTask{{ - Name: "hello-world-1", - TaskRef: &v1beta1.TaskRef{ - Name: "hello-world", - }, - Retries: 2, - }}, - Finally: []v1beta1.PipelineTask{{ - Name: "hello-world-2", - TaskRef: &v1beta1.TaskRef{ - Name: "hello-world", - }, - }}, + testCases := []struct { + name string + embeddedStatusVal string + }{ + { + name: "default embedded status", + embeddedStatusVal: config.DefaultEmbeddedStatus, }, - }} - - // PipelineRun has been gracefully cancelled, and it has a TaskRun for DAG task "hello-world-1" that has failed - // with reason of cancellation - prs := []*v1beta1.PipelineRun{{ - ObjectMeta: baseObjectMeta("test-pipeline-run-cancelled-run-finally", "foo"), - Spec: v1beta1.PipelineRunSpec{ - PipelineRef: &v1beta1.PipelineRef{Name: "test-pipeline"}, - ServiceAccountName: "test-sa", - Status: v1beta1.PipelineRunSpecStatusCancelledRunFinally, + { + name: "full embedded status", + embeddedStatusVal: config.FullEmbeddedStatus, }, - Status: v1beta1.PipelineRunStatus{ - PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ - TaskRuns: map[string]*v1beta1.PipelineRunTaskRunStatus{ - "test-pipeline-run-cancelled-run-finally-hello-world": { - PipelineTaskName: "hello-world-1", - Status: &v1beta1.TaskRunStatus{ - Status: duckv1beta1.Status{ - Conditions: []apis.Condition{{ - Type: apis.ConditionSucceeded, - Status: corev1.ConditionFalse, - Reason: v1beta1.TaskRunReasonCancelled.String(), - }}, + { + name: "both embedded status", + embeddedStatusVal: config.BothEmbeddedStatus, + }, + { + name: "minimal embedded status", + embeddedStatusVal: config.MinimalEmbeddedStatus, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Pipeline has a DAG task "hello-world-1" and Finally task "hello-world-2" + ps := []*v1beta1.Pipeline{{ + ObjectMeta: baseObjectMeta("test-pipeline", "foo"), + Spec: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{{ + Name: "hello-world-1", + TaskRef: &v1beta1.TaskRef{ + Name: "hello-world", + }, + Retries: 2, + }}, + Finally: []v1beta1.PipelineTask{{ + Name: "hello-world-2", + TaskRef: &v1beta1.TaskRef{ + Name: "hello-world", + }, + }}, + }, + }} + + // PipelineRun has been gracefully cancelled, and it has a TaskRun for DAG task "hello-world-1" that has failed + // with reason of cancellation + prs := []*v1beta1.PipelineRun{{ + ObjectMeta: baseObjectMeta("test-pipeline-run-cancelled-run-finally", "foo"), + Spec: v1beta1.PipelineRunSpec{ + PipelineRef: &v1beta1.PipelineRef{Name: "test-pipeline"}, + ServiceAccountName: "test-sa", + Status: v1beta1.PipelineRunSpecStatusCancelledRunFinally, + }, + Status: v1beta1.PipelineRunStatus{ + PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ + TaskRuns: map[string]*v1beta1.PipelineRunTaskRunStatus{ + "test-pipeline-run-cancelled-run-finally-hello-world": { + PipelineTaskName: "hello-world-1", + Status: &v1beta1.TaskRunStatus{ + Status: duckv1beta1.Status{ + Conditions: []apis.Condition{{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionFalse, + Reason: v1beta1.TaskRunReasonCancelled.String(), + }}, + }, + }, }, }, }, }, - }, - }, - }} + }} - // TaskRun exists for DAG task "hello-world-1" that has failed with reason of cancellation - trs := []*v1beta1.TaskRun{createHelloWorldTaskRunWithStatus(t, "test-pipeline-run-cancelled-run-finally-hello-world", "foo", - "test-pipeline-run-cancelled-run-finally", "test-pipeline", "my-pod-name", - apis.Condition{ - Type: apis.ConditionSucceeded, - Status: corev1.ConditionFalse, - Reason: v1beta1.TaskRunSpecStatusCancelled, - })} + // TaskRun exists for DAG task "hello-world-1" that has failed with reason of cancellation + trs := []*v1beta1.TaskRun{createHelloWorldTaskRunWithStatus(t, "test-pipeline-run-cancelled-run-finally-hello-world", "foo", + "test-pipeline-run-cancelled-run-finally", "test-pipeline", "my-pod-name", + apis.Condition{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionFalse, + Reason: v1beta1.TaskRunSpecStatusCancelled, + })} - ts := []*v1beta1.Task{simpleHelloWorldTask} - cms := getConfigMapsWithEnabledAlphaAPIFields() + ts := []*v1beta1.Task{simpleHelloWorldTask} + cms := getConfigMapsWithEmbeddedStatusAndAlphaAPI(tc.embeddedStatusVal) - d := test.Data{ - PipelineRuns: prs, - Pipelines: ps, - TaskRuns: trs, - Tasks: ts, - ConfigMaps: cms, - } - prt := newPipelineRunTest(d, t) - defer prt.Cancel() + d := test.Data{ + PipelineRuns: prs, + Pipelines: ps, + TaskRuns: trs, + Tasks: ts, + ConfigMaps: cms, + } + prt := newPipelineRunTest(d, t) + defer prt.Cancel() - wantEvents := []string{ - "Normal Started", - "Normal CancelledRunningFinally Tasks Completed: 1 \\(Failed: 0, Cancelled 1\\), Incomplete: 1, Skipped: 0", - } - reconciledRun, _ := prt.reconcileRun("foo", "test-pipeline-run-cancelled-run-finally", wantEvents, false) + wantEvents := []string{ + "Normal Started", + "Normal CancelledRunningFinally Tasks Completed: 1 \\(Failed: 0, Cancelled 1\\), Incomplete: 1, Skipped: 0", + } + reconciledRun, _ := prt.reconcileRun("foo", "test-pipeline-run-cancelled-run-finally", wantEvents, false) - // This PipelineRun should still be running to execute the finally task, and the status should reflect that - if !reconciledRun.Status.GetCondition(apis.ConditionSucceeded).IsUnknown() { - t.Errorf("Expected PipelineRun status to be running to execute the finally task, but was %v", - reconciledRun.Status.GetCondition(apis.ConditionSucceeded)) - } + // This PipelineRun should still be running to execute the finally task, and the status should reflect that + if !reconciledRun.Status.GetCondition(apis.ConditionSucceeded).IsUnknown() { + t.Errorf("Expected PipelineRun status to be running to execute the finally task, but was %v", + reconciledRun.Status.GetCondition(apis.ConditionSucceeded)) + } - // There should be two task runs (failed dag task and one triggered for the finally task) - if len(reconciledRun.Status.TaskRuns) != 2 { - t.Errorf("Expected PipelineRun status to have exactly two task runs, but was %v", len(reconciledRun.Status.TaskRuns)) + // There should be two task runs (failed dag task and one triggered for the finally task) + if shouldHaveFullEmbeddedStatus(tc.embeddedStatusVal) && len(reconciledRun.Status.TaskRuns) != 2 { + t.Errorf("Expected PipelineRun status to have exactly two task runs, but was %v", len(reconciledRun.Status.TaskRuns)) + } + if shouldHaveMinimalEmbeddedStatus(tc.embeddedStatusVal) && len(reconciledRun.Status.ChildReferences) != 2 { + t.Errorf("Expected PipelineRun status ChildReferences to have exactly two task runs, but was %v", len(reconciledRun.Status.ChildReferences)) + } + }) } - } func TestReconcileCancelledRunFinallyFailsTaskRunCancellation(t *testing.T) { @@ -4566,315 +4824,395 @@ func ensurePVCCreated(ctx context.Context, t *testing.T, clients test.Clients, n } func TestReconcileWithWhenExpressionsWithParameters(t *testing.T) { - names.TestingSeed() - prName := "test-pipeline-run" - ps := []*v1beta1.Pipeline{{ - ObjectMeta: baseObjectMeta("test-pipeline", "foo"), - Spec: v1beta1.PipelineSpec{ - Params: []v1beta1.ParamSpec{{ - Name: "run", - Type: v1beta1.ParamTypeString, - }}, - Tasks: []v1beta1.PipelineTask{ - { - Name: "hello-world-1", - TaskRef: &v1beta1.TaskRef{Name: "hello-world-1"}, - WhenExpressions: []v1beta1.WhenExpression{ + testCases := []struct { + name string + embeddedStatusVal string + }{ + { + name: "default embedded status", + embeddedStatusVal: config.DefaultEmbeddedStatus, + }, + { + name: "full embedded status", + embeddedStatusVal: config.FullEmbeddedStatus, + }, + { + name: "both embedded status", + embeddedStatusVal: config.BothEmbeddedStatus, + }, + { + name: "minimal embedded status", + embeddedStatusVal: config.MinimalEmbeddedStatus, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + names.TestingSeed() + prName := "test-pipeline-run" + ps := []*v1beta1.Pipeline{{ + ObjectMeta: baseObjectMeta("test-pipeline", "foo"), + Spec: v1beta1.PipelineSpec{ + Params: []v1beta1.ParamSpec{{ + Name: "run", + Type: v1beta1.ParamTypeString, + }}, + Tasks: []v1beta1.PipelineTask{ { - Input: "foo", - Operator: selection.NotIn, - Values: []string{"bar"}, + Name: "hello-world-1", + TaskRef: &v1beta1.TaskRef{Name: "hello-world-1"}, + WhenExpressions: []v1beta1.WhenExpression{ + { + Input: "foo", + Operator: selection.NotIn, + Values: []string{"bar"}, + }, + { + Input: "$(params.run)", + Operator: selection.In, + Values: []string{"yes"}, + }, + }, }, { - Input: "$(params.run)", - Operator: selection.In, - Values: []string{"yes"}, + Name: "hello-world-2", + TaskRef: &v1beta1.TaskRef{Name: "hello-world-2"}, + WhenExpressions: []v1beta1.WhenExpression{{ + Input: "$(params.run)", + Operator: selection.NotIn, + Values: []string{"yes"}, + }}, }, }, }, - { - Name: "hello-world-2", - TaskRef: &v1beta1.TaskRef{Name: "hello-world-2"}, - WhenExpressions: []v1beta1.WhenExpression{{ - Input: "$(params.run)", - Operator: selection.NotIn, - Values: []string{"yes"}, + }} + prs := []*v1beta1.PipelineRun{{ + ObjectMeta: metav1.ObjectMeta{ + Name: prName, + Namespace: "foo", + Annotations: map[string]string{"PipelineRunAnnotation": "PipelineRunValue"}, + }, + Spec: v1beta1.PipelineRunSpec{ + PipelineRef: &v1beta1.PipelineRef{Name: "test-pipeline"}, + ServiceAccountName: "test-sa", + Params: []v1beta1.Param{{ + Name: "run", + Value: *v1beta1.NewArrayOrString("yes"), }}, }, - }, - }, - }} - prs := []*v1beta1.PipelineRun{{ - ObjectMeta: metav1.ObjectMeta{ - Name: prName, - Namespace: "foo", - Annotations: map[string]string{"PipelineRunAnnotation": "PipelineRunValue"}, - }, - Spec: v1beta1.PipelineRunSpec{ - PipelineRef: &v1beta1.PipelineRef{Name: "test-pipeline"}, - ServiceAccountName: "test-sa", - Params: []v1beta1.Param{{ - Name: "run", - Value: *v1beta1.NewArrayOrString("yes"), - }}, - }, - }} - ts := []*v1beta1.Task{ - {ObjectMeta: baseObjectMeta("hello-world-1", "foo")}, - {ObjectMeta: baseObjectMeta("hello-world-2", "foo")}, - } + }} + ts := []*v1beta1.Task{ + {ObjectMeta: baseObjectMeta("hello-world-1", "foo")}, + {ObjectMeta: baseObjectMeta("hello-world-2", "foo")}, + } - d := test.Data{ - PipelineRuns: prs, - Pipelines: ps, - Tasks: ts, - } - prt := newPipelineRunTest(d, t) - defer prt.Cancel() + d := test.Data{ + PipelineRuns: prs, + Pipelines: ps, + Tasks: ts, + ConfigMaps: getConfigMapsWithEmbeddedStatus(tc.embeddedStatusVal), + } + prt := newPipelineRunTest(d, t) + defer prt.Cancel() - wantEvents := []string{ - "Normal Started", - "Normal Running Tasks Completed: 0 \\(Failed: 0, Cancelled 0\\), Incomplete: 1, Skipped: 1", - } - pipelineRun, clients := prt.reconcileRun("foo", prName, wantEvents, false) + wantEvents := []string{ + "Normal Started", + "Normal Running Tasks Completed: 0 \\(Failed: 0, Cancelled 0\\), Incomplete: 1, Skipped: 1", + } + pipelineRun, clients := prt.reconcileRun("foo", prName, wantEvents, false) - // Check that the expected TaskRun was created - actual, err := clients.Pipeline.TektonV1beta1().TaskRuns("foo").List(prt.TestAssets.Ctx, metav1.ListOptions{ - LabelSelector: "tekton.dev/pipelineTask=hello-world-1,tekton.dev/pipelineRun=test-pipeline-run", - Limit: 1, - }) - if err != nil { - t.Fatalf("Failure to list TaskRun's %s", err) - } - if len(actual.Items) != 1 { - t.Fatalf("Expected 1 TaskRun got %d", len(actual.Items)) - } - expectedTaskRunName := "test-pipeline-run-hello-world-1" - expectedTaskRunObjectMeta := taskRunObjectMeta(expectedTaskRunName, "foo", "test-pipeline-run", "test-pipeline", "hello-world-1", false) - expectedTaskRunObjectMeta.Annotations["PipelineRunAnnotation"] = "PipelineRunValue" - expectedTaskRun := &v1beta1.TaskRun{ - ObjectMeta: expectedTaskRunObjectMeta, - Spec: v1beta1.TaskRunSpec{ - TaskRef: &v1beta1.TaskRef{Name: "hello-world-1"}, - ServiceAccountName: "test-sa", - Resources: &v1beta1.TaskRunResources{}, - Timeout: &metav1.Duration{Duration: config.DefaultTimeoutMinutes * time.Minute}, - }, - } - actualTaskRun := actual.Items[0] - if d := cmp.Diff(&actualTaskRun, expectedTaskRun, ignoreResourceVersion); d != "" { - t.Errorf("expected to see TaskRun %v created. Diff %s", expectedTaskRunName, diff.PrintWantGot(d)) - } + // Check that the expected TaskRun was created + actual, err := clients.Pipeline.TektonV1beta1().TaskRuns("foo").List(prt.TestAssets.Ctx, metav1.ListOptions{ + LabelSelector: "tekton.dev/pipelineTask=hello-world-1,tekton.dev/pipelineRun=test-pipeline-run", + Limit: 1, + }) + if err != nil { + t.Fatalf("Failure to list TaskRun's %s", err) + } + if len(actual.Items) != 1 { + t.Fatalf("Expected 1 TaskRun got %d", len(actual.Items)) + } + expectedTaskRunName := "test-pipeline-run-hello-world-1" + expectedTaskRunObjectMeta := taskRunObjectMeta(expectedTaskRunName, "foo", "test-pipeline-run", "test-pipeline", "hello-world-1", false) + expectedTaskRunObjectMeta.Annotations["PipelineRunAnnotation"] = "PipelineRunValue" + expectedTaskRun := &v1beta1.TaskRun{ + ObjectMeta: expectedTaskRunObjectMeta, + Spec: v1beta1.TaskRunSpec{ + TaskRef: &v1beta1.TaskRef{Name: "hello-world-1"}, + ServiceAccountName: "test-sa", + Resources: &v1beta1.TaskRunResources{}, + Timeout: &metav1.Duration{Duration: config.DefaultTimeoutMinutes * time.Minute}, + }, + } + actualTaskRun := actual.Items[0] + if d := cmp.Diff(&actualTaskRun, expectedTaskRun, ignoreResourceVersion); d != "" { + t.Errorf("expected to see TaskRun %v created. Diff %s", expectedTaskRunName, diff.PrintWantGot(d)) + } - actualWhenExpressionsInTaskRun := pipelineRun.Status.PipelineRunStatusFields.TaskRuns[expectedTaskRunName].WhenExpressions - expectedWhenExpressionsInTaskRun := []v1beta1.WhenExpression{{ - Input: "foo", - Operator: "notin", - Values: []string{"bar"}, - }, { - Input: "yes", - Operator: "in", - Values: []string{"yes"}, - }} - if d := cmp.Diff(expectedWhenExpressionsInTaskRun, actualWhenExpressionsInTaskRun); d != "" { - t.Errorf("expected to see When Expressions %v created. Diff %s", expectedTaskRunName, diff.PrintWantGot(d)) - } + expectedWhenExpressionsInTaskRun := []v1beta1.WhenExpression{{ + Input: "foo", + Operator: "notin", + Values: []string{"bar"}, + }, { + Input: "yes", + Operator: "in", + Values: []string{"yes"}, + }} + if shouldHaveFullEmbeddedStatus(tc.embeddedStatusVal) { + actualWhenExpressionsInTaskRun := pipelineRun.Status.PipelineRunStatusFields.TaskRuns[expectedTaskRunName].WhenExpressions + if d := cmp.Diff(expectedWhenExpressionsInTaskRun, actualWhenExpressionsInTaskRun); d != "" { + t.Errorf("expected to see When Expressions %v created. Diff %s", expectedTaskRunName, diff.PrintWantGot(d)) + } + } + if shouldHaveMinimalEmbeddedStatus(tc.embeddedStatusVal) { + var actualWhenExpressionsInTaskRun []v1beta1.WhenExpression + for _, cr := range pipelineRun.Status.ChildReferences { + if cr.Name == expectedTaskRunName { + actualWhenExpressionsInTaskRun = append(actualWhenExpressionsInTaskRun, cr.WhenExpressions...) + } + } + if d := cmp.Diff(expectedWhenExpressionsInTaskRun, actualWhenExpressionsInTaskRun); d != "" { + t.Errorf("expected to see When Expressions %v created. Diff %s", expectedTaskRunName, diff.PrintWantGot(d)) + } + } - actualSkippedTasks := pipelineRun.Status.SkippedTasks - expectedSkippedTasks := []v1beta1.SkippedTask{{ - Name: "hello-world-2", - WhenExpressions: v1beta1.WhenExpressions{{ - Input: "yes", - Operator: "notin", - Values: []string{"yes"}, - }}, - }} - if d := cmp.Diff(actualSkippedTasks, expectedSkippedTasks); d != "" { - t.Errorf("expected to find Skipped Tasks %v. Diff %s", expectedSkippedTasks, diff.PrintWantGot(d)) - } + actualSkippedTasks := pipelineRun.Status.SkippedTasks + expectedSkippedTasks := []v1beta1.SkippedTask{{ + Name: "hello-world-2", + WhenExpressions: v1beta1.WhenExpressions{{ + Input: "yes", + Operator: "notin", + Values: []string{"yes"}, + }}, + }} + if d := cmp.Diff(actualSkippedTasks, expectedSkippedTasks); d != "" { + t.Errorf("expected to find Skipped Tasks %v. Diff %s", expectedSkippedTasks, diff.PrintWantGot(d)) + } - skippedTasks := []string{"hello-world-2"} - for _, skippedTask := range skippedTasks { - labelSelector := fmt.Sprintf("tekton.dev/pipelineTask=%s,tekton.dev/pipelineRun=test-pipeline-run-different-service-accs", skippedTask) - actualSkippedTask, err := clients.Pipeline.TektonV1beta1().TaskRuns("foo").List(prt.TestAssets.Ctx, metav1.ListOptions{ - LabelSelector: labelSelector, - Limit: 1, + skippedTasks := []string{"hello-world-2"} + for _, skippedTask := range skippedTasks { + labelSelector := fmt.Sprintf("tekton.dev/pipelineTask=%s,tekton.dev/pipelineRun=test-pipeline-run-different-service-accs", skippedTask) + actualSkippedTask, err := clients.Pipeline.TektonV1beta1().TaskRuns("foo").List(prt.TestAssets.Ctx, metav1.ListOptions{ + LabelSelector: labelSelector, + Limit: 1, + }) + if err != nil { + t.Fatalf("Failure to list TaskRun's %s", err) + } + if len(actualSkippedTask.Items) != 0 { + t.Fatalf("Expected 0 TaskRuns got %d", len(actualSkippedTask.Items)) + } + } }) - if err != nil { - t.Fatalf("Failure to list TaskRun's %s", err) - } - if len(actualSkippedTask.Items) != 0 { - t.Fatalf("Expected 0 TaskRuns got %d", len(actualSkippedTask.Items)) - } } } func TestReconcileWithWhenExpressionsWithTaskResults(t *testing.T) { - names.TestingSeed() - ps := []*v1beta1.Pipeline{{ - ObjectMeta: baseObjectMeta("test-pipeline", "foo"), - Spec: v1beta1.PipelineSpec{ - Tasks: []v1beta1.PipelineTask{ - { - Name: "a-task", - TaskRef: &v1beta1.TaskRef{Name: "a-task"}, - }, - { - Name: "b-task", - TaskRef: &v1beta1.TaskRef{Name: "b-task"}, - WhenExpressions: []v1beta1.WhenExpression{ + testCases := []struct { + name string + embeddedStatusVal string + }{ + { + name: "default embedded status", + embeddedStatusVal: config.DefaultEmbeddedStatus, + }, + { + name: "full embedded status", + embeddedStatusVal: config.FullEmbeddedStatus, + }, + { + name: "both embedded status", + embeddedStatusVal: config.BothEmbeddedStatus, + }, + { + name: "minimal embedded status", + embeddedStatusVal: config.MinimalEmbeddedStatus, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + names.TestingSeed() + ps := []*v1beta1.Pipeline{{ + ObjectMeta: baseObjectMeta("test-pipeline", "foo"), + Spec: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{ + { + Name: "a-task", + TaskRef: &v1beta1.TaskRef{Name: "a-task"}, + }, + { + Name: "b-task", + TaskRef: &v1beta1.TaskRef{Name: "b-task"}, + WhenExpressions: []v1beta1.WhenExpression{ + { + Input: "$(tasks.a-task.results.aResult)", + Operator: selection.In, + Values: []string{"aResultValue"}, + }, + { + Input: "aResultValue", + Operator: selection.In, + Values: []string{"$(tasks.a-task.results.aResult)"}, + }, + }, + }, { - Input: "$(tasks.a-task.results.aResult)", - Operator: selection.In, - Values: []string{"aResultValue"}, + Name: "c-task", + TaskRef: &v1beta1.TaskRef{Name: "c-task"}, + WhenExpressions: []v1beta1.WhenExpression{{ + Input: "$(tasks.a-task.results.aResult)", + Operator: selection.In, + Values: []string{"missing"}, + }}, }, { - Input: "aResultValue", - Operator: selection.In, - Values: []string{"$(tasks.a-task.results.aResult)"}, + Name: "d-task", + TaskRef: &v1beta1.TaskRef{Name: "d-task"}, + RunAfter: []string{"c-task"}, }, }, }, - { - Name: "c-task", - TaskRef: &v1beta1.TaskRef{Name: "c-task"}, - WhenExpressions: []v1beta1.WhenExpression{{ - Input: "$(tasks.a-task.results.aResult)", - Operator: selection.In, - Values: []string{"missing"}, - }}, + }} + prs := []*v1beta1.PipelineRun{{ + ObjectMeta: baseObjectMeta("test-pipeline-run-different-service-accs", "foo"), + Spec: v1beta1.PipelineRunSpec{ + PipelineRef: &v1beta1.PipelineRef{Name: "test-pipeline"}, + ServiceAccountName: "test-sa-0", }, - { - Name: "d-task", - TaskRef: &v1beta1.TaskRef{Name: "d-task"}, - RunAfter: []string{"c-task"}, + }} + ts := []*v1beta1.Task{ + {ObjectMeta: baseObjectMeta("a-task", "foo")}, + {ObjectMeta: baseObjectMeta("b-task", "foo")}, + {ObjectMeta: baseObjectMeta("c-task", "foo")}, + {ObjectMeta: baseObjectMeta("d-task", "foo")}, + } + trs := []*v1beta1.TaskRun{{ + ObjectMeta: taskRunObjectMeta("test-pipeline-run-different-service-accs-a-task-xxyyy", "foo", + "test-pipeline-run-different-service-accs", "test-pipeline", "a-task", + true), + Spec: v1beta1.TaskRunSpec{ + TaskRef: &v1beta1.TaskRef{Name: "hello-world"}, + ServiceAccountName: "test-sa", + Resources: &v1beta1.TaskRunResources{}, + Timeout: &metav1.Duration{Duration: config.DefaultTimeoutMinutes * time.Minute}, }, - }, - }, - }} - prs := []*v1beta1.PipelineRun{{ - ObjectMeta: baseObjectMeta("test-pipeline-run-different-service-accs", "foo"), - Spec: v1beta1.PipelineRunSpec{ - PipelineRef: &v1beta1.PipelineRef{Name: "test-pipeline"}, - ServiceAccountName: "test-sa-0", - }, - }} - ts := []*v1beta1.Task{ - {ObjectMeta: baseObjectMeta("a-task", "foo")}, - {ObjectMeta: baseObjectMeta("b-task", "foo")}, - {ObjectMeta: baseObjectMeta("c-task", "foo")}, - {ObjectMeta: baseObjectMeta("d-task", "foo")}, - } - trs := []*v1beta1.TaskRun{{ - ObjectMeta: taskRunObjectMeta("test-pipeline-run-different-service-accs-a-task-xxyyy", "foo", - "test-pipeline-run-different-service-accs", "test-pipeline", "a-task", - true), - Spec: v1beta1.TaskRunSpec{ - TaskRef: &v1beta1.TaskRef{Name: "hello-world"}, - ServiceAccountName: "test-sa", - Resources: &v1beta1.TaskRunResources{}, - Timeout: &metav1.Duration{Duration: config.DefaultTimeoutMinutes * time.Minute}, - }, - Status: v1beta1.TaskRunStatus{ - Status: duckv1beta1.Status{ - Conditions: duckv1beta1.Conditions{ - apis.Condition{ - Type: apis.ConditionSucceeded, - Status: corev1.ConditionTrue, + Status: v1beta1.TaskRunStatus{ + Status: duckv1beta1.Status{ + Conditions: duckv1beta1.Conditions{ + apis.Condition{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionTrue, + }, + }, + }, + TaskRunStatusFields: v1beta1.TaskRunStatusFields{ + TaskRunResults: []v1beta1.TaskRunResult{{ + Name: "aResult", + Value: "aResultValue", + }}, }, }, - }, - TaskRunStatusFields: v1beta1.TaskRunStatusFields{ - TaskRunResults: []v1beta1.TaskRunResult{{ - Name: "aResult", - Value: "aResultValue", - }}, - }, - }, - }} + }} - d := test.Data{ - PipelineRuns: prs, - Pipelines: ps, - Tasks: ts, - TaskRuns: trs, - } - prt := newPipelineRunTest(d, t) - defer prt.Cancel() + d := test.Data{ + PipelineRuns: prs, + Pipelines: ps, + Tasks: ts, + TaskRuns: trs, + ConfigMaps: getConfigMapsWithEmbeddedStatus(tc.embeddedStatusVal), + } + prt := newPipelineRunTest(d, t) + defer prt.Cancel() - wantEvents := []string{ - "Normal Started", - "Normal Running Tasks Completed: 1 \\(Failed: 0, Cancelled 0\\), Incomplete: 2, Skipped: 1", - } - pipelineRun, clients := prt.reconcileRun("foo", "test-pipeline-run-different-service-accs", wantEvents, false) + wantEvents := []string{ + "Normal Started", + "Normal Running Tasks Completed: 1 \\(Failed: 0, Cancelled 0\\), Incomplete: 2, Skipped: 1", + } + pipelineRun, clients := prt.reconcileRun("foo", "test-pipeline-run-different-service-accs", wantEvents, false) - expectedTaskRunName := "test-pipeline-run-different-service-accs-b-task" - expectedTaskRun := &v1beta1.TaskRun{ - ObjectMeta: taskRunObjectMeta(expectedTaskRunName, "foo", "test-pipeline-run-different-service-accs", "test-pipeline", "b-task", false), - Spec: v1beta1.TaskRunSpec{ - TaskRef: &v1beta1.TaskRef{Name: "b-task"}, - ServiceAccountName: "test-sa-0", - Resources: &v1beta1.TaskRunResources{}, - Timeout: &metav1.Duration{Duration: config.DefaultTimeoutMinutes * time.Minute}, - }, - } - // Check that the expected TaskRun was created - actual, err := clients.Pipeline.TektonV1beta1().TaskRuns("foo").List(prt.TestAssets.Ctx, metav1.ListOptions{ - LabelSelector: "tekton.dev/pipelineTask=b-task,tekton.dev/pipelineRun=test-pipeline-run-different-service-accs", - Limit: 1, - }) + expectedTaskRunName := "test-pipeline-run-different-service-accs-b-task" + expectedTaskRun := &v1beta1.TaskRun{ + ObjectMeta: taskRunObjectMeta(expectedTaskRunName, "foo", "test-pipeline-run-different-service-accs", "test-pipeline", "b-task", false), + Spec: v1beta1.TaskRunSpec{ + TaskRef: &v1beta1.TaskRef{Name: "b-task"}, + ServiceAccountName: "test-sa-0", + Resources: &v1beta1.TaskRunResources{}, + Timeout: &metav1.Duration{Duration: config.DefaultTimeoutMinutes * time.Minute}, + }, + } + // Check that the expected TaskRun was created + actual, err := clients.Pipeline.TektonV1beta1().TaskRuns("foo").List(prt.TestAssets.Ctx, metav1.ListOptions{ + LabelSelector: "tekton.dev/pipelineTask=b-task,tekton.dev/pipelineRun=test-pipeline-run-different-service-accs", + Limit: 1, + }) - if err != nil { - t.Fatalf("Failure to list TaskRun's %s", err) - } - if len(actual.Items) != 1 { - t.Fatalf("Expected 1 TaskRuns got %d", len(actual.Items)) - } - actualTaskRun := actual.Items[0] - if d := cmp.Diff(&actualTaskRun, expectedTaskRun, ignoreResourceVersion); d != "" { - t.Errorf("expected to see TaskRun %v created. Diff %s", expectedTaskRunName, diff.PrintWantGot(d)) - } + if err != nil { + t.Fatalf("Failure to list TaskRun's %s", err) + } + if len(actual.Items) != 1 { + t.Fatalf("Expected 1 TaskRuns got %d", len(actual.Items)) + } + actualTaskRun := actual.Items[0] + if d := cmp.Diff(&actualTaskRun, expectedTaskRun, ignoreResourceVersion); d != "" { + t.Errorf("expected to see TaskRun %v created. Diff %s", expectedTaskRunName, diff.PrintWantGot(d)) + } - actualWhenExpressionsInTaskRun := pipelineRun.Status.PipelineRunStatusFields.TaskRuns[expectedTaskRunName].WhenExpressions - expectedWhenExpressionsInTaskRun := []v1beta1.WhenExpression{{ - Input: "aResultValue", - Operator: "in", - Values: []string{"aResultValue"}, - }, { - Input: "aResultValue", - Operator: "in", - Values: []string{"aResultValue"}, - }} - if d := cmp.Diff(expectedWhenExpressionsInTaskRun, actualWhenExpressionsInTaskRun); d != "" { - t.Errorf("expected to see When Expressions %v created. Diff %s", expectedTaskRunName, diff.PrintWantGot(d)) - } + expectedWhenExpressionsInTaskRun := []v1beta1.WhenExpression{{ + Input: "aResultValue", + Operator: "in", + Values: []string{"aResultValue"}, + }, { + Input: "aResultValue", + Operator: "in", + Values: []string{"aResultValue"}, + }} + if shouldHaveFullEmbeddedStatus(tc.embeddedStatusVal) { + actualWhenExpressionsInTaskRun := pipelineRun.Status.PipelineRunStatusFields.TaskRuns[expectedTaskRunName].WhenExpressions + if d := cmp.Diff(expectedWhenExpressionsInTaskRun, actualWhenExpressionsInTaskRun); d != "" { + t.Errorf("expected to see When Expressions %v created. Diff %s", expectedTaskRunName, diff.PrintWantGot(d)) + } + } + if shouldHaveMinimalEmbeddedStatus(tc.embeddedStatusVal) { + var actualWhenExpressionsInTaskRun []v1beta1.WhenExpression + for _, cr := range pipelineRun.Status.ChildReferences { + if cr.Name == expectedTaskRunName { + actualWhenExpressionsInTaskRun = append(actualWhenExpressionsInTaskRun, cr.WhenExpressions...) + } + } + if d := cmp.Diff(expectedWhenExpressionsInTaskRun, actualWhenExpressionsInTaskRun); d != "" { + t.Errorf("expected to see When Expressions %v created. Diff %s", expectedTaskRunName, diff.PrintWantGot(d)) + } + } - actualSkippedTasks := pipelineRun.Status.SkippedTasks - expectedSkippedTasks := []v1beta1.SkippedTask{{ - Name: "c-task", - WhenExpressions: v1beta1.WhenExpressions{{ - Input: "aResultValue", - Operator: "in", - Values: []string{"missing"}, - }}, - }} - if d := cmp.Diff(actualSkippedTasks, expectedSkippedTasks); d != "" { - t.Errorf("expected to find Skipped Tasks %v. Diff %s", expectedSkippedTasks, diff.PrintWantGot(d)) - } + actualSkippedTasks := pipelineRun.Status.SkippedTasks + expectedSkippedTasks := []v1beta1.SkippedTask{{ + Name: "c-task", + WhenExpressions: v1beta1.WhenExpressions{{ + Input: "aResultValue", + Operator: "in", + Values: []string{"missing"}, + }}, + }} + if d := cmp.Diff(actualSkippedTasks, expectedSkippedTasks); d != "" { + t.Errorf("expected to find Skipped Tasks %v. Diff %s", expectedSkippedTasks, diff.PrintWantGot(d)) + } - skippedTasks := []string{"c-task"} - for _, skippedTask := range skippedTasks { - labelSelector := fmt.Sprintf("tekton.dev/pipelineTask=%s,tekton.dev/pipelineRun=test-pipeline-run-different-service-accs", skippedTask) - actualSkippedTask, err := clients.Pipeline.TektonV1beta1().TaskRuns("foo").List(prt.TestAssets.Ctx, metav1.ListOptions{ - LabelSelector: labelSelector, - Limit: 1, + skippedTasks := []string{"c-task"} + for _, skippedTask := range skippedTasks { + labelSelector := fmt.Sprintf("tekton.dev/pipelineTask=%s,tekton.dev/pipelineRun=test-pipeline-run-different-service-accs", skippedTask) + actualSkippedTask, err := clients.Pipeline.TektonV1beta1().TaskRuns("foo").List(prt.TestAssets.Ctx, metav1.ListOptions{ + LabelSelector: labelSelector, + Limit: 1, + }) + if err != nil { + t.Fatalf("Failure to list TaskRun's %s", err) + } + if len(actualSkippedTask.Items) != 0 { + t.Fatalf("Expected 0 TaskRuns got %d", len(actualSkippedTask.Items)) + } + } }) - if err != nil { - t.Fatalf("Failure to list TaskRun's %s", err) - } - if len(actualSkippedTask.Items) != 0 { - t.Fatalf("Expected 0 TaskRuns got %d", len(actualSkippedTask.Items)) - } } } @@ -5804,182 +6142,37 @@ func TestReconcileWithTaskResultsEmbeddedNoneStarted(t *testing.T) { Name: "foo", Type: v1beta1.ParamTypeString, }}, - Tasks: []v1beta1.PipelineTask{ - { - Name: "a-task", - TaskRef: &v1beta1.TaskRef{Name: "a-task"}, - }, - { - Name: "b-task", - TaskRef: &v1beta1.TaskRef{Name: "b-task"}, - Params: []v1beta1.Param{{ - Name: "bParam", - Value: *v1beta1.NewArrayOrString("$(params.foo)/baz@$(tasks.a-task.results.A_RESULT)"), - }}, - }, - }, - }, - Params: []v1beta1.Param{{ - Name: "foo", - Value: *v1beta1.NewArrayOrString("bar"), - }}, - ServiceAccountName: "test-sa-0", - }, - }} - ts := []*v1beta1.Task{ - { - ObjectMeta: baseObjectMeta("a-task", "foo"), - Spec: v1beta1.TaskSpec{ - Results: []v1beta1.TaskResult{{ - Name: "A_RESULT", - }}, - }, - }, - { - ObjectMeta: baseObjectMeta("b-task", "foo"), - Spec: v1beta1.TaskSpec{ - Params: []v1beta1.ParamSpec{{ - Name: "bParam", - Type: v1beta1.ParamTypeString, - }}, - }, - }, - } - - d := test.Data{ - PipelineRuns: prs, - Tasks: ts, - } - prt := newPipelineRunTest(d, t) - defer prt.Cancel() - - reconciledRun, clients := prt.reconcileRun("foo", "test-pipeline-run-different-service-accs", []string{}, false) - - if !reconciledRun.Status.GetCondition(apis.ConditionSucceeded).IsUnknown() { - t.Errorf("Expected PipelineRun to be running, but condition status is %s", reconciledRun.Status.GetCondition(apis.ConditionSucceeded)) - } - - // Since b-task is dependent on a-task, via the results, only a-task should run - expectedTaskRunName := "test-pipeline-run-different-service-accs-a-task" - expectedTaskRun := &v1beta1.TaskRun{ - ObjectMeta: taskRunObjectMeta(expectedTaskRunName, "foo", "test-pipeline-run-different-service-accs", - "test-pipeline-run-different-service-accs", "a-task", false), - Spec: v1beta1.TaskRunSpec{ - TaskRef: &v1beta1.TaskRef{ - Name: "a-task", - Kind: v1beta1.NamespacedTaskKind, - }, - ServiceAccountName: "test-sa-0", - Resources: &v1beta1.TaskRunResources{}, - Timeout: &metav1.Duration{Duration: config.DefaultTimeoutMinutes * time.Minute}, - }, - } - // Check that the expected TaskRun was created (only) - actual, err := clients.Pipeline.TektonV1beta1().TaskRuns("foo").List(prt.TestAssets.Ctx, metav1.ListOptions{}) - if err != nil { - t.Fatalf("Failure to list TaskRun's %s", err) - } - if len(actual.Items) != 1 { - t.Fatalf("Expected 1 TaskRuns got %d", len(actual.Items)) - } - actualTaskRun := actual.Items[0] - if d := cmp.Diff(expectedTaskRun, &actualTaskRun, ignoreResourceVersion); d != "" { - t.Errorf("expected to see TaskRun %v created. Diff %s", expectedTaskRun, diff.PrintWantGot(d)) - } -} - -func TestReconcileWithPipelineResults(t *testing.T) { - names.TestingSeed() - ps := []*v1beta1.Pipeline{{ - ObjectMeta: baseObjectMeta("test-pipeline", "foo"), - Spec: v1beta1.PipelineSpec{ - Tasks: []v1beta1.PipelineTask{ - { - Name: "a-task", - TaskRef: &v1beta1.TaskRef{ - Name: "a-task", - }, - }, - { - Name: "b-task", - TaskRef: &v1beta1.TaskRef{ - Name: "b-task", - }, - Params: []v1beta1.Param{{ - Name: "bParam", - Value: *v1beta1.NewArrayOrString("$(tasks.a-task.results.aResult)"), - }}, - }, - }, - Results: []v1beta1.PipelineResult{{ - Name: "result", - Value: "$(tasks.a-task.results.aResult)", - Description: "pipeline result", - }}, - }, - }} - trs := []*v1beta1.TaskRun{{ - ObjectMeta: taskRunObjectMeta("test-pipeline-run-different-service-accs-a-task", "foo", - "test-pipeline-run-different-service-accs", "test-pipeline", "a-task", - true), - Spec: v1beta1.TaskRunSpec{ - TaskRef: &v1beta1.TaskRef{Name: "hello-world"}, - ServiceAccountName: "test-sa", - Resources: &v1beta1.TaskRunResources{}, - Timeout: &metav1.Duration{Duration: config.DefaultTimeoutMinutes * time.Minute}, - }, - Status: v1beta1.TaskRunStatus{ - Status: duckv1beta1.Status{ - Conditions: duckv1beta1.Conditions{ - apis.Condition{ - Type: apis.ConditionSucceeded, - Status: corev1.ConditionTrue, - }, - }, - }, - TaskRunStatusFields: v1beta1.TaskRunStatusFields{ - TaskRunResults: []v1beta1.TaskRunResult{{ - Name: "aResult", - Value: "aResultValue", - }}, - }, - }, - }} - prs := []*v1beta1.PipelineRun{{ - ObjectMeta: baseObjectMeta("test-pipeline-run-different-service-accs", "foo"), - Spec: v1beta1.PipelineRunSpec{ - PipelineRef: &v1beta1.PipelineRef{Name: "test-pipeline"}, - ServiceAccountName: "test-sa-0", - }, - Status: v1beta1.PipelineRunStatus{ - Status: duckv1beta1.Status{ - Conditions: duckv1beta1.Conditions{ - apis.Condition{ - Type: apis.ConditionSucceeded, - Status: corev1.ConditionTrue, - Reason: v1beta1.PipelineRunReasonSuccessful.String(), - Message: "All Tasks have completed executing", - }, - }, - }, - PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ - PipelineResults: []v1beta1.PipelineRunResult{{ - Name: "result", - Value: "aResultValue", - }}, - TaskRuns: map[string]*v1beta1.PipelineRunTaskRunStatus{ - trs[0].Name: { - PipelineTaskName: "a-task", - Status: &trs[0].Status, + Tasks: []v1beta1.PipelineTask{ + { + Name: "a-task", + TaskRef: &v1beta1.TaskRef{Name: "a-task"}, + }, + { + Name: "b-task", + TaskRef: &v1beta1.TaskRef{Name: "b-task"}, + Params: []v1beta1.Param{{ + Name: "bParam", + Value: *v1beta1.NewArrayOrString("$(params.foo)/baz@$(tasks.a-task.results.A_RESULT)"), + }}, }, }, - StartTime: &metav1.Time{Time: now.AddDate(0, 0, -1)}, - CompletionTime: &metav1.Time{Time: now}, }, + Params: []v1beta1.Param{{ + Name: "foo", + Value: *v1beta1.NewArrayOrString("bar"), + }}, + ServiceAccountName: "test-sa-0", }, }} ts := []*v1beta1.Task{ - {ObjectMeta: baseObjectMeta("a-task", "foo")}, + { + ObjectMeta: baseObjectMeta("a-task", "foo"), + Spec: v1beta1.TaskSpec{ + Results: []v1beta1.TaskResult{{ + Name: "A_RESULT", + }}, + }, + }, { ObjectMeta: baseObjectMeta("b-task", "foo"), Spec: v1beta1.TaskSpec{ @@ -5993,17 +6186,189 @@ func TestReconcileWithPipelineResults(t *testing.T) { d := test.Data{ PipelineRuns: prs, - Pipelines: ps, Tasks: ts, - TaskRuns: trs, } prt := newPipelineRunTest(d, t) defer prt.Cancel() - reconciledRun, _ := prt.reconcileRun("foo", "test-pipeline-run-different-service-accs", []string{}, false) + reconciledRun, clients := prt.reconcileRun("foo", "test-pipeline-run-different-service-accs", []string{}, false) + + if !reconciledRun.Status.GetCondition(apis.ConditionSucceeded).IsUnknown() { + t.Errorf("Expected PipelineRun to be running, but condition status is %s", reconciledRun.Status.GetCondition(apis.ConditionSucceeded)) + } + + // Since b-task is dependent on a-task, via the results, only a-task should run + expectedTaskRunName := "test-pipeline-run-different-service-accs-a-task" + expectedTaskRun := &v1beta1.TaskRun{ + ObjectMeta: taskRunObjectMeta(expectedTaskRunName, "foo", "test-pipeline-run-different-service-accs", + "test-pipeline-run-different-service-accs", "a-task", false), + Spec: v1beta1.TaskRunSpec{ + TaskRef: &v1beta1.TaskRef{ + Name: "a-task", + Kind: v1beta1.NamespacedTaskKind, + }, + ServiceAccountName: "test-sa-0", + Resources: &v1beta1.TaskRunResources{}, + Timeout: &metav1.Duration{Duration: config.DefaultTimeoutMinutes * time.Minute}, + }, + } + // Check that the expected TaskRun was created (only) + actual, err := clients.Pipeline.TektonV1beta1().TaskRuns("foo").List(prt.TestAssets.Ctx, metav1.ListOptions{}) + if err != nil { + t.Fatalf("Failure to list TaskRun's %s", err) + } + if len(actual.Items) != 1 { + t.Fatalf("Expected 1 TaskRuns got %d", len(actual.Items)) + } + actualTaskRun := actual.Items[0] + if d := cmp.Diff(expectedTaskRun, &actualTaskRun, ignoreResourceVersion); d != "" { + t.Errorf("expected to see TaskRun %v created. Diff %s", expectedTaskRun, diff.PrintWantGot(d)) + } +} + +func TestReconcileWithPipelineResults(t *testing.T) { + testCases := []struct { + name string + embeddedVal string + }{ + { + name: "default embedded status", + embeddedVal: config.DefaultEmbeddedStatus, + }, + { + name: "full embedded status", + embeddedVal: config.FullEmbeddedStatus, + }, + { + name: "both embedded status", + embeddedVal: config.BothEmbeddedStatus, + }, + { + name: "minimal embedded status", + embeddedVal: config.MinimalEmbeddedStatus, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + names.TestingSeed() + ps := []*v1beta1.Pipeline{{ + ObjectMeta: baseObjectMeta("test-pipeline", "foo"), + Spec: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{ + { + Name: "a-task", + TaskRef: &v1beta1.TaskRef{ + Name: "a-task", + }, + }, + { + Name: "b-task", + TaskRef: &v1beta1.TaskRef{ + Name: "b-task", + }, + Params: []v1beta1.Param{{ + Name: "bParam", + Value: *v1beta1.NewArrayOrString("$(tasks.a-task.results.aResult)"), + }}, + }, + }, + Results: []v1beta1.PipelineResult{{ + Name: "result", + Value: "$(tasks.a-task.results.aResult)", + Description: "pipeline result", + }}, + }, + }} + trs := []*v1beta1.TaskRun{{ + ObjectMeta: taskRunObjectMeta("test-pipeline-run-different-service-accs-a-task", "foo", + "test-pipeline-run-different-service-accs", "test-pipeline", "a-task", + true), + Spec: v1beta1.TaskRunSpec{ + TaskRef: &v1beta1.TaskRef{Name: "hello-world"}, + ServiceAccountName: "test-sa", + Resources: &v1beta1.TaskRunResources{}, + Timeout: &metav1.Duration{Duration: config.DefaultTimeoutMinutes * time.Minute}, + }, + Status: v1beta1.TaskRunStatus{ + Status: duckv1beta1.Status{ + Conditions: duckv1beta1.Conditions{ + apis.Condition{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionTrue, + }, + }, + }, + TaskRunStatusFields: v1beta1.TaskRunStatusFields{ + TaskRunResults: []v1beta1.TaskRunResult{{ + Name: "aResult", + Value: "aResultValue", + }}, + }, + }, + }} + prs := []*v1beta1.PipelineRun{{ + ObjectMeta: baseObjectMeta("test-pipeline-run-different-service-accs", "foo"), + Spec: v1beta1.PipelineRunSpec{ + PipelineRef: &v1beta1.PipelineRef{Name: "test-pipeline"}, + ServiceAccountName: "test-sa-0", + }, + Status: v1beta1.PipelineRunStatus{ + Status: duckv1beta1.Status{ + Conditions: duckv1beta1.Conditions{ + apis.Condition{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionTrue, + Reason: v1beta1.PipelineRunReasonSuccessful.String(), + Message: "All Tasks have completed executing", + }, + }, + }, + PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ + PipelineResults: []v1beta1.PipelineRunResult{{ + Name: "result", + Value: "aResultValue", + }}, + TaskRuns: map[string]*v1beta1.PipelineRunTaskRunStatus{ + trs[0].Name: { + PipelineTaskName: "a-task", + Status: &trs[0].Status, + }, + }, + StartTime: &metav1.Time{Time: now.AddDate(0, 0, -1)}, + CompletionTime: &metav1.Time{Time: now}, + }, + }, + }} + ts := []*v1beta1.Task{ + {ObjectMeta: baseObjectMeta("a-task", "foo")}, + { + ObjectMeta: baseObjectMeta("b-task", "foo"), + Spec: v1beta1.TaskSpec{ + Params: []v1beta1.ParamSpec{{ + Name: "bParam", + Type: v1beta1.ParamTypeString, + }}, + }, + }, + } + + d := test.Data{ + PipelineRuns: prs, + Pipelines: ps, + Tasks: ts, + TaskRuns: trs, + ConfigMaps: getConfigMapsWithEmbeddedStatus(tc.embeddedVal), + } + prt := newPipelineRunTest(d, t) + defer prt.Cancel() + + reconciledRun, _ := prt.reconcileRun("foo", "test-pipeline-run-different-service-accs", []string{}, false) - if d := cmp.Diff(&reconciledRun, &prs[0], ignoreResourceVersion); d != "" { - t.Errorf("expected to see pipeline run results created. Diff %s", diff.PrintWantGot(d)) + if d := cmp.Diff(&reconciledRun, &prs[0], ignoreResourceVersion); d != "" { + t.Errorf("expected to see pipeline run results created. Diff %s", diff.PrintWantGot(d)) + } + }) } } @@ -6075,245 +6440,276 @@ func TestReconcileOutOfSyncPipelineRun(t *testing.T) { // the reconciler is able to coverge back to a consistent state with the orphaned // TaskRuns back in the PipelineRun status. // For more details, see https://github.com/tektoncd/pipeline/issues/2558 - prOutOfSyncName := "test-pipeline-run-out-of-sync" - helloWorldTask := simpleHelloWorldTask - - // Condition checks for the third task - prccs3 := make(map[string]*v1beta1.PipelineRunConditionCheckStatus) - conditionCheckName3 := prOutOfSyncName + "-hello-world-3-always-true" - prccs3[conditionCheckName3] = &v1beta1.PipelineRunConditionCheckStatus{ - ConditionName: "always-true-0", - Status: &v1beta1.ConditionCheckStatus{ - Status: duckv1beta1.Status{ - Conditions: duckv1beta1.Conditions{ - apis.Condition{ - Type: apis.ConditionSucceeded, - Status: corev1.ConditionUnknown, - }, - }, - }, + + testCases := []struct { + name string + embeddedStatusVal string + }{ + { + name: "default embedded status", + embeddedStatusVal: config.DefaultEmbeddedStatus, }, - } - // Condition checks for the fourth task - prccs4 := make(map[string]*v1beta1.PipelineRunConditionCheckStatus) - conditionCheckName4 := prOutOfSyncName + "-hello-world-4-always-true" - prccs4[conditionCheckName4] = &v1beta1.PipelineRunConditionCheckStatus{ - ConditionName: "always-true-0", - Status: &v1beta1.ConditionCheckStatus{ - Status: duckv1beta1.Status{ - Conditions: duckv1beta1.Conditions{ - apis.Condition{ - Type: apis.ConditionSucceeded, - Status: corev1.ConditionUnknown, - }, - }, - }, + { + name: "full embedded status", + embeddedStatusVal: config.FullEmbeddedStatus, + }, + { + name: "both embedded status", + embeddedStatusVal: config.BothEmbeddedStatus, + }, + { + name: "minimal embedded status", + embeddedStatusVal: config.MinimalEmbeddedStatus, }, } - testPipeline := &v1beta1.Pipeline{ - ObjectMeta: baseObjectMeta("test-pipeline", "foo"), - Spec: v1beta1.PipelineSpec{ - Tasks: []v1beta1.PipelineTask{ - { - Name: "hello-world-1", - TaskRef: &v1beta1.TaskRef{ - Name: helloWorldTask.Name, + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + + prOutOfSyncName := "test-pipeline-run-out-of-sync" + helloWorldTask := simpleHelloWorldTask + + // Condition checks for the third task + prccs3 := make(map[string]*v1beta1.PipelineRunConditionCheckStatus) + conditionCheckName3 := prOutOfSyncName + "-hello-world-3-always-true" + prccs3[conditionCheckName3] = &v1beta1.PipelineRunConditionCheckStatus{ + ConditionName: "always-true-0", + Status: &v1beta1.ConditionCheckStatus{ + Status: duckv1beta1.Status{ + Conditions: duckv1beta1.Conditions{ + apis.Condition{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionUnknown, + }, + }, }, }, - { - Name: "hello-world-2", - TaskRef: &v1beta1.TaskRef{ - Name: helloWorldTask.Name, + } + // Condition checks for the fourth task + prccs4 := make(map[string]*v1beta1.PipelineRunConditionCheckStatus) + conditionCheckName4 := prOutOfSyncName + "-hello-world-4-always-true" + prccs4[conditionCheckName4] = &v1beta1.PipelineRunConditionCheckStatus{ + ConditionName: "always-true-0", + Status: &v1beta1.ConditionCheckStatus{ + Status: duckv1beta1.Status{ + Conditions: duckv1beta1.Conditions{ + apis.Condition{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionUnknown, + }, + }, }, }, - { - Name: "hello-world-3", - TaskRef: &v1beta1.TaskRef{ - Name: helloWorldTask.Name, + } + testPipeline := &v1beta1.Pipeline{ + ObjectMeta: baseObjectMeta("test-pipeline", "foo"), + Spec: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{ + { + Name: "hello-world-1", + TaskRef: &v1beta1.TaskRef{ + Name: helloWorldTask.Name, + }, + }, + { + Name: "hello-world-2", + TaskRef: &v1beta1.TaskRef{ + Name: helloWorldTask.Name, + }, + }, + { + Name: "hello-world-3", + TaskRef: &v1beta1.TaskRef{ + Name: helloWorldTask.Name, + }, + Conditions: []v1beta1.PipelineTaskCondition{{ + ConditionRef: "always-true", + }}, + }, + { + Name: "hello-world-4", + TaskRef: &v1beta1.TaskRef{ + Name: helloWorldTask.Name, + }, + Conditions: []v1beta1.PipelineTaskCondition{{ + ConditionRef: "always-true", + }}, + }, + { + Name: "hello-world-5", + TaskRef: &v1beta1.TaskRef{APIVersion: "example.dev/v0", Kind: "Example"}, + }, }, - Conditions: []v1beta1.PipelineTaskCondition{{ - ConditionRef: "always-true", - }}, }, - { - Name: "hello-world-4", + } + + // This taskrun is in the pipelinerun status. It completed successfully. + taskRunDone := &v1beta1.TaskRun{ + ObjectMeta: taskRunObjectMeta("test-pipeline-run-out-of-sync-hello-world-1", "foo", prOutOfSyncName, testPipeline.Name, "hello-world-1", false), + Spec: v1beta1.TaskRunSpec{ TaskRef: &v1beta1.TaskRef{ - Name: helloWorldTask.Name, + Name: "hello-world", }, - Conditions: []v1beta1.PipelineTaskCondition{{ - ConditionRef: "always-true", - }}, }, - { - Name: "hello-world-5", - TaskRef: &v1beta1.TaskRef{APIVersion: "example.dev/v0", Kind: "Example"}, + Status: v1beta1.TaskRunStatus{ + Status: duckv1beta1.Status{ + Conditions: duckv1beta1.Conditions{ + apis.Condition{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionTrue, + }, + }, + }, }, - }, - }, - } + } - // This taskrun is in the pipelinerun status. It completed successfully. - taskRunDone := &v1beta1.TaskRun{ - ObjectMeta: taskRunObjectMeta("test-pipeline-run-out-of-sync-hello-world-1", "foo", prOutOfSyncName, testPipeline.Name, "hello-world-1", false), - Spec: v1beta1.TaskRunSpec{ - TaskRef: &v1beta1.TaskRef{ - Name: "hello-world", - }, - }, - Status: v1beta1.TaskRunStatus{ - Status: duckv1beta1.Status{ - Conditions: duckv1beta1.Conditions{ - apis.Condition{ - Type: apis.ConditionSucceeded, - Status: corev1.ConditionTrue, + // This taskrun is *not* in the pipelinerun status. It's still running. + taskRunOrphaned := &v1beta1.TaskRun{ + ObjectMeta: taskRunObjectMeta("test-pipeline-run-out-of-sync-hello-world-2", "foo", prOutOfSyncName, testPipeline.Name, "hello-world-2", false), + Spec: v1beta1.TaskRunSpec{ + TaskRef: &v1beta1.TaskRef{ + Name: "hello-world", + }, + }, + Status: v1beta1.TaskRunStatus{ + Status: duckv1beta1.Status{ + Conditions: duckv1beta1.Conditions{ + apis.Condition{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionUnknown, + }, + }, }, }, - }, - }, - } + } - // This taskrun is *not* in the pipelinerun status. It's still running. - taskRunOrphaned := &v1beta1.TaskRun{ - ObjectMeta: taskRunObjectMeta("test-pipeline-run-out-of-sync-hello-world-2", "foo", prOutOfSyncName, testPipeline.Name, "hello-world-2", false), - Spec: v1beta1.TaskRunSpec{ - TaskRef: &v1beta1.TaskRef{ - Name: "hello-world", - }, - }, - Status: v1beta1.TaskRunStatus{ - Status: duckv1beta1.Status{ - Conditions: duckv1beta1.Conditions{ - apis.Condition{ - Type: apis.ConditionSucceeded, - Status: corev1.ConditionUnknown, + // This taskrun has a condition attached. The condition is in the pipelinerun, but the taskrun + // itself is *not* in the pipelinerun status. It's still running. + taskRunWithCondition := &v1beta1.TaskRun{ + ObjectMeta: taskRunObjectMeta("test-pipeline-run-out-of-sync-hello-world-3", "foo", prOutOfSyncName, testPipeline.Name, "hello-world-3", false), + Spec: v1beta1.TaskRunSpec{ + TaskRef: &v1beta1.TaskRef{ + Name: "hello-world", }, }, - }, - }, - } - - // This taskrun has a condition attached. The condition is in the pipelinerun, but the taskrun - // itself is *not* in the pipelinerun status. It's still running. - taskRunWithCondition := &v1beta1.TaskRun{ - ObjectMeta: taskRunObjectMeta("test-pipeline-run-out-of-sync-hello-world-3", "foo", prOutOfSyncName, testPipeline.Name, "hello-world-3", false), - Spec: v1beta1.TaskRunSpec{ - TaskRef: &v1beta1.TaskRef{ - Name: "hello-world", - }, - }, - Status: v1beta1.TaskRunStatus{ - Status: duckv1beta1.Status{ - Conditions: duckv1beta1.Conditions{ - apis.Condition{ - Type: apis.ConditionSucceeded, - Status: corev1.ConditionUnknown, + Status: v1beta1.TaskRunStatus{ + Status: duckv1beta1.Status{ + Conditions: duckv1beta1.Conditions{ + apis.Condition{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionUnknown, + }, + }, }, }, - }, - }, - } + } - taskRunForConditionOfOrphanedTaskRunObjectMeta := taskRunObjectMeta(conditionCheckName3, "foo", prOutOfSyncName, testPipeline.Name, "hello-world-3", false) - taskRunForConditionOfOrphanedTaskRunObjectMeta.Labels[pipeline.ConditionCheckKey] = conditionCheckName3 - taskRunForConditionOfOrphanedTaskRunObjectMeta.Labels[pipeline.ConditionNameKey] = "always-true" + taskRunForConditionOfOrphanedTaskRunObjectMeta := taskRunObjectMeta(conditionCheckName3, "foo", prOutOfSyncName, testPipeline.Name, "hello-world-3", false) + taskRunForConditionOfOrphanedTaskRunObjectMeta.Labels[pipeline.ConditionCheckKey] = conditionCheckName3 + taskRunForConditionOfOrphanedTaskRunObjectMeta.Labels[pipeline.ConditionNameKey] = "always-true" - taskRunForConditionOfOrphanedTaskRun := &v1beta1.TaskRun{ - ObjectMeta: taskRunForConditionOfOrphanedTaskRunObjectMeta, - Spec: v1beta1.TaskRunSpec{ - TaskRef: &v1beta1.TaskRef{ - Name: "always-true-0", - }, - }, - Status: v1beta1.TaskRunStatus{ - Status: duckv1beta1.Status{ - Conditions: duckv1beta1.Conditions{ - apis.Condition{ - Type: apis.ConditionSucceeded, - Status: corev1.ConditionUnknown, + taskRunForConditionOfOrphanedTaskRun := &v1beta1.TaskRun{ + ObjectMeta: taskRunForConditionOfOrphanedTaskRunObjectMeta, + Spec: v1beta1.TaskRunSpec{ + TaskRef: &v1beta1.TaskRef{ + Name: "always-true-0", }, }, - }, - }, - } + Status: v1beta1.TaskRunStatus{ + Status: duckv1beta1.Status{ + Conditions: duckv1beta1.Conditions{ + apis.Condition{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionUnknown, + }, + }, + }, + }, + } - // This taskrun has a condition attached. The condition is *not* the in pipelinerun, and it's still - // running. The taskrun itself was not created yet. - taskRunWithOrphanedConditionName := "test-pipeline-run-out-of-sync-hello-world-4" - taskRunForOrphanedConditionObjectMeta := taskRunObjectMeta(conditionCheckName4, "foo", prOutOfSyncName, testPipeline.Name, "hello-world-4", false) - taskRunForOrphanedConditionObjectMeta.Labels[pipeline.ConditionCheckKey] = conditionCheckName4 - taskRunForOrphanedConditionObjectMeta.Labels[pipeline.ConditionNameKey] = "always-true" + // This taskrun has a condition attached. The condition is *not* the in pipelinerun, and it's still + // running. The taskrun itself was not created yet. + taskRunWithOrphanedConditionName := "test-pipeline-run-out-of-sync-hello-world-4" + taskRunForOrphanedConditionObjectMeta := taskRunObjectMeta(conditionCheckName4, "foo", prOutOfSyncName, testPipeline.Name, "hello-world-4", false) + taskRunForOrphanedConditionObjectMeta.Labels[pipeline.ConditionCheckKey] = conditionCheckName4 + taskRunForOrphanedConditionObjectMeta.Labels[pipeline.ConditionNameKey] = "always-true" - taskRunForOrphanedCondition := &v1beta1.TaskRun{ - ObjectMeta: taskRunForOrphanedConditionObjectMeta, - Spec: v1beta1.TaskRunSpec{ - TaskRef: &v1beta1.TaskRef{ - Name: "always-true-0", - }, - }, - Status: v1beta1.TaskRunStatus{ - Status: duckv1beta1.Status{ - Conditions: duckv1beta1.Conditions{ - apis.Condition{ - Type: apis.ConditionSucceeded, - Status: corev1.ConditionUnknown, + taskRunForOrphanedCondition := &v1beta1.TaskRun{ + ObjectMeta: taskRunForOrphanedConditionObjectMeta, + Spec: v1beta1.TaskRunSpec{ + TaskRef: &v1beta1.TaskRef{ + Name: "always-true-0", }, }, - }, - }, - } + Status: v1beta1.TaskRunStatus{ + Status: duckv1beta1.Status{ + Conditions: duckv1beta1.Conditions{ + apis.Condition{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionUnknown, + }, + }, + }, + }, + } - orphanedRun := &v1alpha1.Run{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-pipeline-run-out-of-sync-hello-world-5", - Namespace: "foo", - OwnerReferences: []metav1.OwnerReference{{ - Kind: "PipelineRun", - Name: prOutOfSyncName, - }}, - Labels: map[string]string{ - pipeline.PipelineLabelKey: testPipeline.Name, - pipeline.PipelineRunLabelKey: prOutOfSyncName, - pipeline.PipelineTaskLabelKey: "hello-world-5", - }, - Annotations: map[string]string{}, - }, - Spec: v1alpha1.RunSpec{ - Ref: &v1beta1.TaskRef{ - APIVersion: "example.dev/v0", - Kind: "Example", - }, - }, - Status: v1alpha1.RunStatus{ - Status: duckv1.Status{ - Conditions: []apis.Condition{ - { - Type: apis.ConditionSucceeded, - Status: corev1.ConditionUnknown, + orphanedRun := &v1alpha1.Run{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pipeline-run-out-of-sync-hello-world-5", + Namespace: "foo", + OwnerReferences: []metav1.OwnerReference{{ + Kind: "PipelineRun", + Name: prOutOfSyncName, + }}, + Labels: map[string]string{ + pipeline.PipelineLabelKey: testPipeline.Name, + pipeline.PipelineRunLabelKey: prOutOfSyncName, + pipeline.PipelineTaskLabelKey: "hello-world-5", }, + Annotations: map[string]string{}, }, - }, - }, - } + Spec: v1alpha1.RunSpec{ + Ref: &v1beta1.TaskRef{ + APIVersion: "example.dev/v0", + Kind: "Example", + }, + }, + Status: v1alpha1.RunStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + { + Type: apis.ConditionSucceeded, + Status: corev1.ConditionUnknown, + }, + }, + }, + }, + } - prOutOfSync := &v1beta1.PipelineRun{ - ObjectMeta: baseObjectMeta(prOutOfSyncName, "foo"), - Spec: v1beta1.PipelineRunSpec{ - PipelineRef: &v1beta1.PipelineRef{Name: testPipeline.Name}, - ServiceAccountName: "test-sa", - }, - Status: v1beta1.PipelineRunStatus{ - Status: duckv1beta1.Status{ - Conditions: duckv1beta1.Conditions{ - apis.Condition{ - Type: apis.ConditionSucceeded, - Status: corev1.ConditionUnknown, - Reason: "", - Message: "", + prOutOfSync := &v1beta1.PipelineRun{ + ObjectMeta: baseObjectMeta(prOutOfSyncName, "foo"), + Spec: v1beta1.PipelineRunSpec{ + PipelineRef: &v1beta1.PipelineRef{Name: testPipeline.Name}, + ServiceAccountName: "test-sa", + }, + Status: v1beta1.PipelineRunStatus{ + Status: duckv1beta1.Status{ + Conditions: duckv1beta1.Conditions{ + apis.Condition{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionUnknown, + Reason: "", + Message: "", + }, + }, }, + PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{}, }, - }, - PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ - TaskRuns: map[string]*v1beta1.PipelineRunTaskRunStatus{ + } + + if shouldHaveFullEmbeddedStatus(tc.embeddedStatusVal) { + prOutOfSync.Status.TaskRuns = map[string]*v1beta1.PipelineRunTaskRunStatus{ taskRunDone.Name: { PipelineTaskName: "hello-world-1", Status: &v1beta1.TaskRunStatus{}, @@ -6323,239 +6719,360 @@ func TestReconcileOutOfSyncPipelineRun(t *testing.T) { Status: nil, ConditionChecks: prccs3, }, + } + } + if shouldHaveMinimalEmbeddedStatus(tc.embeddedStatusVal) { + prOutOfSync.Status.ChildReferences = []v1beta1.ChildStatusReference{ + { + TypeMeta: runtime.TypeMeta{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: "TaskRun", + }, + Name: taskRunDone.Name, + PipelineTaskName: "hello-world-1", + }, + { + TypeMeta: runtime.TypeMeta{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: "TaskRun", + }, + Name: taskRunWithCondition.Name, + PipelineTaskName: "hello-world-3", + ConditionChecks: []*v1beta1.PipelineRunChildConditionCheckStatus{{ + PipelineRunConditionCheckStatus: *prccs3[conditionCheckName3], + ConditionCheckName: conditionCheckName3, + }}, + }, + } + } + + prs := []*v1beta1.PipelineRun{prOutOfSync} + ps := []*v1beta1.Pipeline{testPipeline} + ts := []*v1beta1.Task{helloWorldTask} + trs := []*v1beta1.TaskRun{taskRunDone, taskRunOrphaned, taskRunWithCondition, + taskRunForOrphanedCondition, taskRunForConditionOfOrphanedTaskRun} + runs := []*v1alpha1.Run{orphanedRun} + cs := []*v1alpha1.Condition{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "always-true", + Namespace: "foo", + }, + Spec: v1alpha1.ConditionSpec{ + Check: v1alpha1.Step{ + Container: corev1.Container{ + Image: "foo", + Args: []string{"bar"}, + }, + }, }, - }, - }, - } - prs := []*v1beta1.PipelineRun{prOutOfSync} - ps := []*v1beta1.Pipeline{testPipeline} - ts := []*v1beta1.Task{helloWorldTask} - trs := []*v1beta1.TaskRun{taskRunDone, taskRunOrphaned, taskRunWithCondition, - taskRunForOrphanedCondition, taskRunForConditionOfOrphanedTaskRun} - runs := []*v1alpha1.Run{orphanedRun} - cs := []*v1alpha1.Condition{{ - ObjectMeta: metav1.ObjectMeta{ - Name: "always-true", - Namespace: "foo", - }, - Spec: v1alpha1.ConditionSpec{ - Check: v1alpha1.Step{ - Container: corev1.Container{ - Image: "foo", - Args: []string{"bar"}, - }, - }, - }, - }} + }} - cms := []*corev1.ConfigMap{ - { - ObjectMeta: metav1.ObjectMeta{Name: config.GetFeatureFlagsConfigName(), Namespace: system.Namespace()}, - Data: map[string]string{ - "enable-custom-tasks": "true", - }, - }, - } + cms := []*corev1.ConfigMap{ + { + ObjectMeta: metav1.ObjectMeta{Name: config.GetFeatureFlagsConfigName(), Namespace: system.Namespace()}, + Data: map[string]string{ + "enable-custom-tasks": "true", + embeddedStatusFeatureFlag: tc.embeddedStatusVal, + }, + }, + } - d := test.Data{ - PipelineRuns: prs, - Pipelines: ps, - Tasks: ts, - TaskRuns: trs, - Conditions: cs, - Runs: runs, - ConfigMaps: cms, - } - prt := newPipelineRunTest(d, t) - defer prt.Cancel() + d := test.Data{ + PipelineRuns: prs, + Pipelines: ps, + Tasks: ts, + TaskRuns: trs, + Conditions: cs, + Runs: runs, + ConfigMaps: cms, + } + prt := newPipelineRunTest(d, t) + defer prt.Cancel() - reconciledRun, clients := prt.reconcileRun("foo", prOutOfSync.Name, []string{}, false) + reconciledRun, clients := prt.reconcileRun("foo", prOutOfSync.Name, []string{}, false) - actions := clients.Pipeline.Actions() - if len(actions) < 3 { - t.Fatalf("Expected client to have at least three action implementation but it has %d", len(actions)) - } + actions := clients.Pipeline.Actions() + if len(actions) < 3 { + t.Fatalf("Expected client to have at least three action implementation but it has %d", len(actions)) + } - _ = getPipelineRunUpdates(t, actions) - pipelineUpdates := 0 - for _, action := range actions { - if action != nil { - switch { - case action.Matches("create", "taskruns"): - t.Errorf("Expected client to not have created a TaskRun, but it did") - case action.Matches("create", "runs"): - t.Errorf("Expected client to not have created a Run, but it did") - case action.Matches("update", "pipelineruns"): - pipelineUpdates++ - case action.Matches("patch", "pipelineruns"): - pipelineUpdates++ - default: - continue + _ = getPipelineRunUpdates(t, actions) + pipelineUpdates := 0 + for _, action := range actions { + if action != nil { + switch { + case action.Matches("create", "taskruns"): + t.Errorf("Expected client to not have created a TaskRun, but it did") + case action.Matches("create", "runs"): + t.Errorf("Expected client to not have created a Run, but it did") + case action.Matches("update", "pipelineruns"): + pipelineUpdates++ + case action.Matches("patch", "pipelineruns"): + pipelineUpdates++ + default: + continue + } + } } - } - } - // We actually expect three update calls because the first status update fails due to - // optimistic concurrency (due to the label update) and is retried after reloading via - // the client. - if got, want := pipelineUpdates, 3; got != want { - // If only the pipelinerun status changed, we expect one update - t.Fatalf("Expected client to have updated the pipelinerun %d times, but it did %d times", want, got) - } + // We actually expect three update calls because the first status update fails due to + // optimistic concurrency (due to the label update) and is retried after reloading via + // the client. + if got, want := pipelineUpdates, 3; got != want { + // If only the pipelinerun status changed, we expect one update + t.Fatalf("Expected client to have updated the pipelinerun %d times, but it did %d times", want, got) + } - // This PipelineRun should still be running and the status should reflect that - if !reconciledRun.Status.GetCondition(apis.ConditionSucceeded).IsUnknown() { - t.Errorf("Expected PipelineRun status to be running, but was %v", reconciledRun.Status.GetCondition(apis.ConditionSucceeded)) - } + // This PipelineRun should still be running and the status should reflect that + if !reconciledRun.Status.GetCondition(apis.ConditionSucceeded).IsUnknown() { + t.Errorf("Expected PipelineRun status to be running, but was %v", reconciledRun.Status.GetCondition(apis.ConditionSucceeded)) + } - expectedTaskRunsStatus := make(map[string]*v1beta1.PipelineRunTaskRunStatus) - expectedRunsStatus := make(map[string]*v1beta1.PipelineRunRunStatus) - // taskRunDone did not change - expectedTaskRunsStatus[taskRunDone.Name] = &v1beta1.PipelineRunTaskRunStatus{ - PipelineTaskName: "hello-world-1", - Status: &v1beta1.TaskRunStatus{ - Status: duckv1beta1.Status{ - Conditions: []apis.Condition{ - { - Type: apis.ConditionSucceeded, - Status: corev1.ConditionTrue, + expectedTaskRunsStatus := make(map[string]*v1beta1.PipelineRunTaskRunStatus) + expectedRunsStatus := make(map[string]*v1beta1.PipelineRunRunStatus) + // taskRunDone did not change + expectedTaskRunsStatus[taskRunDone.Name] = &v1beta1.PipelineRunTaskRunStatus{ + PipelineTaskName: "hello-world-1", + Status: &v1beta1.TaskRunStatus{ + Status: duckv1beta1.Status{ + Conditions: []apis.Condition{ + { + Type: apis.ConditionSucceeded, + Status: corev1.ConditionTrue, + }, + }, }, }, - }, - }, - } - // taskRunOrphaned was recovered into the status - expectedTaskRunsStatus[taskRunOrphaned.Name] = &v1beta1.PipelineRunTaskRunStatus{ - PipelineTaskName: "hello-world-2", - Status: &v1beta1.TaskRunStatus{ - Status: duckv1beta1.Status{ - Conditions: []apis.Condition{ - { - Type: apis.ConditionSucceeded, - Status: corev1.ConditionUnknown, + } + // taskRunOrphaned was recovered into the status + expectedTaskRunsStatus[taskRunOrphaned.Name] = &v1beta1.PipelineRunTaskRunStatus{ + PipelineTaskName: "hello-world-2", + Status: &v1beta1.TaskRunStatus{ + Status: duckv1beta1.Status{ + Conditions: []apis.Condition{ + { + Type: apis.ConditionSucceeded, + Status: corev1.ConditionUnknown, + }, + }, }, }, - }, - }, - } - // taskRunWithCondition was recovered into the status. The condition did not change. - expectedTaskRunsStatus[taskRunWithCondition.Name] = &v1beta1.PipelineRunTaskRunStatus{ - PipelineTaskName: "hello-world-3", - Status: &v1beta1.TaskRunStatus{ - Status: duckv1beta1.Status{ - Conditions: []apis.Condition{ - { - Type: apis.ConditionSucceeded, - Status: corev1.ConditionUnknown, + } + // taskRunWithCondition was recovered into the status. The condition did not change. + expectedTaskRunsStatus[taskRunWithCondition.Name] = &v1beta1.PipelineRunTaskRunStatus{ + PipelineTaskName: "hello-world-3", + Status: &v1beta1.TaskRunStatus{ + Status: duckv1beta1.Status{ + Conditions: []apis.Condition{ + { + Type: apis.ConditionSucceeded, + Status: corev1.ConditionUnknown, + }, + }, }, }, - }, - }, - ConditionChecks: prccs3, - } - // taskRunWithOrphanedConditionName had the condition recovered into the status. No taskrun. - expectedTaskRunsStatus[taskRunWithOrphanedConditionName] = &v1beta1.PipelineRunTaskRunStatus{ - PipelineTaskName: "hello-world-4", - ConditionChecks: prccs4, - } - // orphanedRun was recovered into the status - expectedRunsStatus[orphanedRun.Name] = &v1beta1.PipelineRunRunStatus{ - PipelineTaskName: "hello-world-5", - Status: &v1alpha1.RunStatus{ - Status: duckv1.Status{ - Conditions: []apis.Condition{ - { - Type: apis.ConditionSucceeded, - Status: corev1.ConditionUnknown, + ConditionChecks: prccs3, + } + // taskRunWithOrphanedConditionName had the condition recovered into the status. No taskrun. + expectedTaskRunsStatus[taskRunWithOrphanedConditionName] = &v1beta1.PipelineRunTaskRunStatus{ + PipelineTaskName: "hello-world-4", + ConditionChecks: prccs4, + } + // orphanedRun was recovered into the status + expectedRunsStatus[orphanedRun.Name] = &v1beta1.PipelineRunRunStatus{ + PipelineTaskName: "hello-world-5", + Status: &v1alpha1.RunStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + { + Type: apis.ConditionSucceeded, + Status: corev1.ConditionUnknown, + }, + }, }, }, - }, - }, - } + } - if d := cmp.Diff(reconciledRun.Status.TaskRuns, expectedTaskRunsStatus); d != "" { - t.Fatalf("Expected PipelineRun status to match TaskRun(s) status, but got a mismatch: %s", d) - } - if d := cmp.Diff(reconciledRun.Status.Runs, expectedRunsStatus); d != "" { - t.Fatalf("Expected PipelineRun status to match Run(s) status, but got a mismatch: %s", d) + if shouldHaveFullEmbeddedStatus(tc.embeddedStatusVal) { + if d := cmp.Diff(expectedTaskRunsStatus, reconciledRun.Status.TaskRuns); d != "" { + t.Fatalf("Expected PipelineRun status to match TaskRun(s) status, but got a mismatch: %s", d) + } + if d := cmp.Diff(expectedRunsStatus, reconciledRun.Status.Runs); d != "" { + t.Fatalf("Expected PipelineRun status to match Run(s) status, but got a mismatch: %s", d) + } + } + if shouldHaveMinimalEmbeddedStatus(tc.embeddedStatusVal) { + taskRunsStatus := make(map[string]*v1beta1.PipelineRunTaskRunStatus) + runsStatus := make(map[string]*v1beta1.PipelineRunRunStatus) + + for _, cr := range reconciledRun.Status.ChildReferences { + if cr.Kind == "TaskRun" { + trStatusForPipelineRun := &v1beta1.PipelineRunTaskRunStatus{ + PipelineTaskName: cr.PipelineTaskName, + WhenExpressions: cr.WhenExpressions, + } + + for _, cc := range cr.ConditionChecks { + if trStatusForPipelineRun.ConditionChecks == nil { + trStatusForPipelineRun.ConditionChecks = make(map[string]*v1beta1.PipelineRunConditionCheckStatus) + } + trStatusForPipelineRun.ConditionChecks[cc.ConditionCheckName] = &v1beta1.PipelineRunConditionCheckStatus{ + ConditionName: cc.ConditionName, + Status: cc.Status, + } + } + + tr, _ := clients.Pipeline.TektonV1beta1().TaskRuns("foo").Get(ctx, cr.Name, metav1.GetOptions{}) + if tr != nil { + trStatusForPipelineRun.Status = &tr.Status + } + + taskRunsStatus[cr.Name] = trStatusForPipelineRun + } else if cr.Kind == "Run" { + rStatusForPipelineRun := &v1beta1.PipelineRunRunStatus{ + PipelineTaskName: cr.PipelineTaskName, + WhenExpressions: cr.WhenExpressions, + } + + r, _ := clients.Pipeline.TektonV1alpha1().Runs("foo").Get(ctx, cr.Name, metav1.GetOptions{}) + if r != nil { + rStatusForPipelineRun.Status = &r.Status + } + + runsStatus[cr.Name] = rStatusForPipelineRun + } + } + if d := cmp.Diff(expectedTaskRunsStatus, taskRunsStatus); d != "" { + t.Fatalf("Expected PipelineRun status to match TaskRun(s) status, but got a mismatch: %s", d) + } + if d := cmp.Diff(expectedRunsStatus, runsStatus); d != "" { + t.Fatalf("Expected PipelineRun status to match Run(s) status, but got a mismatch: %s", d) + } + } + }) } } func TestUpdatePipelineRunStatusFromInformer(t *testing.T) { - names.TestingSeed() - - pr := &v1beta1.PipelineRun{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-pipeline-run", - Namespace: "foo", - Labels: map[string]string{"mylabel": "myvale"}, + testCases := []struct { + name string + embeddedStatusVal string + }{ + { + name: "default embedded status", + embeddedStatusVal: config.DefaultEmbeddedStatus, }, - Spec: v1beta1.PipelineRunSpec{ - PipelineSpec: &v1beta1.PipelineSpec{ - Tasks: []v1beta1.PipelineTask{{ - Name: "unit-test-task-spec", - TaskSpec: &v1beta1.EmbeddedTask{ - TaskSpec: v1beta1.TaskSpec{ - Steps: []v1beta1.Step{{Container: corev1.Container{ - Name: "mystep", - Image: "myimage"}}}, - }, - }, - }}, - }, + { + name: "full embedded status", + embeddedStatusVal: config.FullEmbeddedStatus, + }, + { + name: "both embedded status", + embeddedStatusVal: config.BothEmbeddedStatus, + }, + { + name: "minimal embedded status", + embeddedStatusVal: config.MinimalEmbeddedStatus, }, } - d := test.Data{ - PipelineRuns: []*v1beta1.PipelineRun{pr}, - } - prt := newPipelineRunTest(d, t) - defer prt.Cancel() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + names.TestingSeed() - wantEvents := []string{ - "Normal Started", - "Normal Running Tasks Completed: 0", - } + pr := &v1beta1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pipeline-run", + Namespace: "foo", + Labels: map[string]string{"mylabel": "myvale"}, + }, + Spec: v1beta1.PipelineRunSpec{ + PipelineSpec: &v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{{ + Name: "unit-test-task-spec", + TaskSpec: &v1beta1.EmbeddedTask{ + TaskSpec: v1beta1.TaskSpec{ + Steps: []v1beta1.Step{{Container: corev1.Container{ + Name: "mystep", + Image: "myimage"}}}, + }, + }, + }}, + }, + }, + } - // Reconcile the PipelineRun. This creates a Taskrun. - reconciledRun, clients := prt.reconcileRun("foo", "test-pipeline-run", wantEvents, false) + d := test.Data{ + PipelineRuns: []*v1beta1.PipelineRun{pr}, + ConfigMaps: getConfigMapsWithEmbeddedStatus(tc.embeddedStatusVal), + } + prt := newPipelineRunTest(d, t) + defer prt.Cancel() - // Save the name of the TaskRun that was created. - taskRunName := "" - if len(reconciledRun.Status.TaskRuns) != 1 { - t.Fatalf("Expected 1 TaskRun but got %d", len(reconciledRun.Status.TaskRuns)) - } - for k := range reconciledRun.Status.TaskRuns { - taskRunName = k - break - } + wantEvents := []string{ + "Normal Started", + "Normal Running Tasks Completed: 0", + } - // Add a label to the PipelineRun. This tests a scenario in issue 3126 which could prevent the reconciler - // from finding TaskRuns that are missing from the status. - reconciledRun.ObjectMeta.Labels["bah"] = "humbug" - reconciledRun, err := clients.Pipeline.TektonV1beta1().PipelineRuns("foo").Update(prt.TestAssets.Ctx, reconciledRun, metav1.UpdateOptions{}) - if err != nil { - t.Fatalf("unexpected error when updating status: %v", err) - } + // Reconcile the PipelineRun. This creates a Taskrun. + reconciledRun, clients := prt.reconcileRun("foo", "test-pipeline-run", wantEvents, false) - // The label update triggers another reconcile. Depending on timing, the PipelineRun passed to the reconcile may or may not - // have the updated status with the name of the created TaskRun. Clear the status because we want to test the case where the - // status does not have the TaskRun. - reconciledRun.Status = v1beta1.PipelineRunStatus{} - if _, err := clients.Pipeline.TektonV1beta1().PipelineRuns("foo").UpdateStatus(prt.TestAssets.Ctx, reconciledRun, metav1.UpdateOptions{}); err != nil { - t.Fatalf("unexpected error when updating status: %v", err) - } + // Save the name of the TaskRun that was created. + taskRunName := "" + if shouldHaveFullEmbeddedStatus(tc.embeddedStatusVal) { + if len(reconciledRun.Status.TaskRuns) != 1 { + t.Fatalf("Expected 1 TaskRun but got %d", len(reconciledRun.Status.TaskRuns)) + } + for k := range reconciledRun.Status.TaskRuns { + taskRunName = k + break + } + } + if shouldHaveMinimalEmbeddedStatus(tc.embeddedStatusVal) { + if len(reconciledRun.Status.ChildReferences) != 1 { + t.Fatalf("Expected 1 TaskRun but got %d", len(reconciledRun.Status.ChildReferences)) + } + taskRunName = reconciledRun.Status.ChildReferences[0].Name + } + + // Add a label to the PipelineRun. This tests a scenario in issue 3126 which could prevent the reconciler + // from finding TaskRuns that are missing from the status. + reconciledRun.ObjectMeta.Labels["bah"] = "humbug" + reconciledRun, err := clients.Pipeline.TektonV1beta1().PipelineRuns("foo").Update(prt.TestAssets.Ctx, reconciledRun, metav1.UpdateOptions{}) + if err != nil { + t.Fatalf("unexpected error when updating status: %v", err) + } - reconciledRun, _ = prt.reconcileRun("foo", "test-pipeline-run", wantEvents, false) + // The label update triggers another reconcile. Depending on timing, the PipelineRun passed to the reconcile may or may not + // have the updated status with the name of the created TaskRun. Clear the status because we want to test the case where the + // status does not have the TaskRun. + reconciledRun.Status = v1beta1.PipelineRunStatus{} + if _, err := clients.Pipeline.TektonV1beta1().PipelineRuns("foo").UpdateStatus(prt.TestAssets.Ctx, reconciledRun, metav1.UpdateOptions{}); err != nil { + t.Fatalf("unexpected error when updating status: %v", err) + } - // Verify that the reconciler found the existing TaskRun instead of creating a new one. - if len(reconciledRun.Status.TaskRuns) != 1 { - t.Fatalf("Expected 1 TaskRun after label change but got %d", len(reconciledRun.Status.TaskRuns)) - } - for k := range reconciledRun.Status.TaskRuns { - if k != taskRunName { - t.Fatalf("Status has unexpected taskrun %s", k) - } + reconciledRun, _ = prt.reconcileRun("foo", "test-pipeline-run", wantEvents, false) + + // Verify that the reconciler found the existing TaskRun instead of creating a new one. + if shouldHaveFullEmbeddedStatus(tc.embeddedStatusVal) { + if len(reconciledRun.Status.TaskRuns) != 1 { + t.Fatalf("Expected 1 TaskRun after label change but got %d", len(reconciledRun.Status.TaskRuns)) + } + for k := range reconciledRun.Status.TaskRuns { + if k != taskRunName { + t.Fatalf("Status has unexpected taskrun %s", k) + } + } + } + if shouldHaveMinimalEmbeddedStatus(tc.embeddedStatusVal) { + if len(reconciledRun.Status.ChildReferences) != 1 { + t.Fatalf("Expected 1 TaskRun after label change but got %d", len(reconciledRun.Status.ChildReferences)) + } + if reconciledRun.Status.ChildReferences[0].Name != taskRunName { + t.Fatalf("Status has unexpected taskrun %s", reconciledRun.Status.ChildReferences[0].Name) + } + } + }) } } @@ -6872,34 +7389,57 @@ func TestUpdatePipelineRunStatusFromTaskRuns(t *testing.T) { } for _, tc := range tcs { - t.Run(tc.prName, func(t *testing.T) { - logger := logtesting.TestLogger(t) + for _, embeddedVal := range valuesForEmbeddedStatus { + t.Run(fmt.Sprintf("%s-with-%s-embedded-status", tc.prName, embeddedVal), func(t *testing.T) { + logger := logtesting.TestLogger(t) - pr := &v1beta1.PipelineRun{ - ObjectMeta: metav1.ObjectMeta{Name: tc.prName, UID: prUID}, - Status: tc.prStatus, - } - - updatePipelineRunStatusFromTaskRuns(logger, pr, tc.trs) - actualPrStatus := pr.Status - - // The TaskRun keys for recovered taskruns will contain a new random key, appended to the - // base name that we expect. Replace the random part so we can diff the whole structure - actualTaskRuns := actualPrStatus.PipelineRunStatusFields.TaskRuns - if actualTaskRuns != nil { - fixedTaskRuns := make(map[string]*v1beta1.PipelineRunTaskRunStatus) - re := regexp.MustCompile(`^[a-z\-]*?-task-[0-9]`) - for k, v := range actualTaskRuns { - newK := re.FindString(k) - fixedTaskRuns[newK+"-xxyyy"] = v + ctx := config.ToContext(context.Background(), &config.Config{ + FeatureFlags: &config.FeatureFlags{EmbeddedStatus: embeddedVal}, + }) + + pr := &v1beta1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{Name: tc.prName, UID: prUID}, + Status: prStatusForEmbeddedStatus(tc.prStatus, embeddedVal), } - actualPrStatus.PipelineRunStatusFields.TaskRuns = fixedTaskRuns - } - if d := cmp.Diff(tc.expectedPrStatus, actualPrStatus); d != "" { - t.Errorf("expected the PipelineRun status to match %#v. Diff %s", tc.expectedPrStatus, diff.PrintWantGot(d)) - } - }) + updatePipelineRunStatusFromTaskRuns(ctx, logger, pr, tc.trs) + actualPrStatus := pr.Status + + expectedPRStatus := prStatusForEmbeddedStatus(tc.expectedPrStatus, embeddedVal) + + // The TaskRun keys for recovered taskruns will contain a new random key, appended to the + // base name that we expect. Replace the random part so we can diff the whole structure + actualTaskRuns := actualPrStatus.PipelineRunStatusFields.TaskRuns + if actualTaskRuns != nil { + fixedTaskRuns := make(map[string]*v1beta1.PipelineRunTaskRunStatus) + re := regexp.MustCompile(`^[a-z\-]*?-task-[0-9]`) + for k, v := range actualTaskRuns { + newK := re.FindString(k) + fixedTaskRuns[newK+"-xxyyy"] = v + } + actualPrStatus.PipelineRunStatusFields.TaskRuns = fixedTaskRuns + } + actualChildRefs := actualPrStatus.ChildReferences + if len(actualChildRefs) != 0 { + var fixedChildRefs []v1beta1.ChildStatusReference + re := regexp.MustCompile(`^[a-z\-]*?-task-[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(expectedPRStatus, actualPrStatus); d != "" { + t.Errorf("expected the PipelineRun status to match %#v. Diff %s", expectedPRStatus, diff.PrintWantGot(d)) + } + }) + } } } @@ -7438,8 +7978,15 @@ spec: t.Errorf("Expected reason %q but was %s", v1beta1.PipelineRunReasonRunning.String(), condition.Reason) } - if len(reconciledRun.Status.TaskRuns) != 1 { - t.Errorf("Expected PipelineRun status to include the TaskRun status items that can run immediately: %v", reconciledRun.Status.TaskRuns) + if shouldHaveFullEmbeddedStatus(cms[0].Data[embeddedStatusFeatureFlag]) { + if len(reconciledRun.Status.TaskRuns) != 1 { + t.Errorf("Expected PipelineRun status to include the TaskRun status items that can run immediately: %v", reconciledRun.Status.TaskRuns) + } + } + if shouldHaveMinimalEmbeddedStatus(cms[0].Data[embeddedStatusFeatureFlag]) { + if len(reconciledRun.Status.ChildReferences) != 1 { + t.Errorf("Expected PipelineRun status to include the ChildReferences status items that can run immediately: %v", reconciledRun.Status.ChildReferences) + } } wantCloudEvents := []string{ @@ -7455,10 +8002,34 @@ spec: // this test validates taskSpec metadata is embedded into task run func TestReconcilePipeline_TaskSpecMetadata(t *testing.T) { - names.TestingSeed() + testCases := []struct { + name string + embeddedStatusVal string + }{ + { + name: "default embedded status", + embeddedStatusVal: config.DefaultEmbeddedStatus, + }, + { + name: "full embedded status", + embeddedStatusVal: config.FullEmbeddedStatus, + }, + { + name: "both embedded status", + embeddedStatusVal: config.BothEmbeddedStatus, + }, + { + name: "minimal embedded status", + embeddedStatusVal: config.MinimalEmbeddedStatus, + }, + } - prs := []*v1beta1.PipelineRun{ - parse.MustParsePipelineRun(t, ` + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + names.TestingSeed() + + prs := []*v1beta1.PipelineRun{ + parse.MustParsePipelineRun(t, ` metadata: name: test-pipeline-run-success namespace: foo @@ -7466,10 +8037,10 @@ spec: pipelineRef: name: test-pipeline `), - } + } - ps := []*v1beta1.Pipeline{ - parse.MustParsePipeline(t, ` + ps := []*v1beta1.Pipeline{ + parse.MustParsePipeline(t, ` metadata: name: test-pipeline namespace: foo @@ -7493,60 +8064,70 @@ spec: annotation1: value1 annotation2: value2 `), - } - - d := test.Data{ - PipelineRuns: prs, - Pipelines: ps, - } - prt := newPipelineRunTest(d, t) - defer prt.Cancel() - - reconciledRun, clients := prt.reconcileRun("foo", "test-pipeline-run-success", []string{}, false) + } - actions := clients.Pipeline.Actions() - if len(actions) == 0 { - t.Fatalf("Expected client to have been used to create a TaskRun but it wasn't") - } + d := test.Data{ + PipelineRuns: prs, + Pipelines: ps, + ConfigMaps: getConfigMapsWithEmbeddedStatus(tc.embeddedStatusVal), + } + prt := newPipelineRunTest(d, t) + defer prt.Cancel() - actualTaskRun := make(map[string]*v1beta1.TaskRun) - for _, a := range actions { - if a.GetResource().Resource == "taskruns" { - t := a.(ktesting.CreateAction).GetObject().(*v1beta1.TaskRun) - actualTaskRun[t.Name] = t - } - } + reconciledRun, clients := prt.reconcileRun("foo", "test-pipeline-run-success", []string{}, false) - // Check that the expected TaskRun was created - if len(actualTaskRun) != 2 { - t.Errorf("Expected two TaskRuns to be created, but found %d TaskRuns.", len(actualTaskRun)) - } + actions := clients.Pipeline.Actions() + if len(actions) == 0 { + t.Fatalf("Expected client to have been used to create a TaskRun but it wasn't") + } - expectedTaskRun := make(map[string]*v1beta1.TaskRun) - expectedTaskRun["test-pipeline-run-success-task-with-metadata"] = getTaskRunWithTaskSpec( - "test-pipeline-run-success-task-with-metadata", - "test-pipeline-run-success", - "test-pipeline", - "task-with-metadata", - map[string]string{"label1": "labelvalue1", "label2": "labelvalue2"}, - map[string]string{"annotation1": "value1", "annotation2": "value2"}, - ) + actualTaskRun := make(map[string]*v1beta1.TaskRun) + for _, a := range actions { + if a.GetResource().Resource == "taskruns" { + t := a.(ktesting.CreateAction).GetObject().(*v1beta1.TaskRun) + actualTaskRun[t.Name] = t + } + } - expectedTaskRun["test-pipeline-run-success-task-without-metadata"] = getTaskRunWithTaskSpec( - "test-pipeline-run-success-task-without-metadata", - "test-pipeline-run-success", - "test-pipeline", - "task-without-metadata", - map[string]string{}, - map[string]string{}, - ) + // Check that the expected TaskRun was created + if len(actualTaskRun) != 2 { + t.Errorf("Expected two TaskRuns to be created, but found %d TaskRuns.", len(actualTaskRun)) + } - if d := cmp.Diff(actualTaskRun, expectedTaskRun); d != "" { - t.Fatalf("Expected TaskRuns to match, but got a mismatch: %s", d) - } + expectedTaskRun := make(map[string]*v1beta1.TaskRun) + expectedTaskRun["test-pipeline-run-success-task-with-metadata"] = getTaskRunWithTaskSpec( + "test-pipeline-run-success-task-with-metadata", + "test-pipeline-run-success", + "test-pipeline", + "task-with-metadata", + map[string]string{"label1": "labelvalue1", "label2": "labelvalue2"}, + map[string]string{"annotation1": "value1", "annotation2": "value2"}, + ) + + expectedTaskRun["test-pipeline-run-success-task-without-metadata"] = getTaskRunWithTaskSpec( + "test-pipeline-run-success-task-without-metadata", + "test-pipeline-run-success", + "test-pipeline", + "task-without-metadata", + map[string]string{}, + map[string]string{}, + ) + + if d := cmp.Diff(actualTaskRun, expectedTaskRun); d != "" { + t.Fatalf("Expected TaskRuns to match, but got a mismatch: %s", d) + } - if len(reconciledRun.Status.TaskRuns) != 2 { - t.Errorf("Expected PipelineRun status to include both TaskRun status items that can run immediately: %v", reconciledRun.Status.TaskRuns) + if shouldHaveFullEmbeddedStatus(tc.embeddedStatusVal) { + if len(reconciledRun.Status.TaskRuns) != 2 { + t.Errorf("Expected PipelineRun status to include both TaskRun status items that can run immediately: %v", reconciledRun.Status.TaskRuns) + } + } + if shouldHaveMinimalEmbeddedStatus(tc.embeddedStatusVal) { + if len(reconciledRun.Status.ChildReferences) != 2 { + t.Errorf("Expected PipelineRun status ChildReferences to include both TaskRun status items that can run immediately: %v", reconciledRun.Status.ChildReferences) + } + } + }) } } @@ -7977,143 +8558,181 @@ func (prt PipelineRunTest) reconcileRun(namespace, pipelineRunName string, wantE } func TestReconcile_RemotePipelineRef(t *testing.T) { - names.TestingSeed() - - ctx := context.Background() - cfg := config.NewStore(logtesting.TestLogger(t)) - cfg.OnConfigChanged(&corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{Name: config.GetFeatureFlagsConfigName()}, - Data: map[string]string{ - "enable-tekton-oci-bundles": "true", - }, - }) - ctx = cfg.ToContext(ctx) - - // Set up a fake registry to push an image to. - s := httptest.NewServer(registry.New()) - defer s.Close() - u, err := url.Parse(s.URL) - if err != nil { - t.Fatal(err) - } - - ref := u.Host + "/testreconcile_remotepipelineref" - - prs := []*v1beta1.PipelineRun{{ - ObjectMeta: metav1.ObjectMeta{Name: "test-pipeline-run-success", Namespace: "foo"}, - Spec: v1beta1.PipelineRunSpec{ - ServiceAccountName: "test-sa", - PipelineRef: &v1beta1.PipelineRef{Name: "test-pipeline", Bundle: ref}, - Timeout: &metav1.Duration{Duration: config.DefaultTimeoutMinutes * time.Minute}, + testCases := []struct { + name string + embeddedStatusVal string + }{ + { + name: "default embedded status", + embeddedStatusVal: config.DefaultEmbeddedStatus, }, - }} - ps := &v1beta1.Pipeline{ - TypeMeta: metav1.TypeMeta{APIVersion: "tekton.dev/v1beta1", Kind: "Pipeline"}, - ObjectMeta: metav1.ObjectMeta{Name: "test-pipeline", Namespace: "foo"}, - Spec: v1beta1.PipelineSpec{ - Tasks: []v1beta1.PipelineTask{{ - Name: "unit-test-1", - TaskRef: &v1beta1.TaskRef{Name: "unit-test-task", Bundle: ref}}, - }, + { + name: "full embedded status", + embeddedStatusVal: config.FullEmbeddedStatus, }, - } - cms := []*corev1.ConfigMap{ { - ObjectMeta: metav1.ObjectMeta{Name: config.GetFeatureFlagsConfigName(), Namespace: system.Namespace()}, - Data: map[string]string{ - "enable-tekton-oci-bundles": "true", - }, + name: "both embedded status", + embeddedStatusVal: config.BothEmbeddedStatus, + }, + { + name: "minimal embedded status", + embeddedStatusVal: config.MinimalEmbeddedStatus, }, } - // This task will be uploaded along with the pipeline definition. - remoteTask := &v1beta1.Task{ - ObjectMeta: baseObjectMeta("unit-test-task", "foo"), - TypeMeta: metav1.TypeMeta{ - APIVersion: "tekton.dev/v1beta1", - Kind: "Task", - }, - Spec: v1beta1.TaskSpec{}, - } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + names.TestingSeed() + + ctx := context.Background() + cfg := config.NewStore(logtesting.TestLogger(t)) + cfg.OnConfigChanged(&corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: config.GetFeatureFlagsConfigName()}, + Data: map[string]string{ + "enable-tekton-oci-bundles": "true", + }, + }) + ctx = cfg.ToContext(ctx) + + // Set up a fake registry to push an image to. + s := httptest.NewServer(registry.New()) + defer s.Close() + u, err := url.Parse(s.URL) + if err != nil { + t.Fatal(err) + } - // Create a bundle from our pipeline and tasks. - if _, err := test.CreateImage(ref, ps, remoteTask); err != nil { - t.Fatalf("failed to create image in pipeline renconcile: %s", err.Error()) - } + ref := u.Host + "/testreconcile_remotepipelineref" - // Unlike the tests above, we do *not* locally define our pipeline or unit-test task. - d := test.Data{ - PipelineRuns: prs, - ServiceAccounts: []*corev1.ServiceAccount{{ - ObjectMeta: metav1.ObjectMeta{Name: prs[0].Spec.ServiceAccountName, Namespace: "foo"}, - }}, - ConfigMaps: cms, - } + prs := []*v1beta1.PipelineRun{{ + ObjectMeta: metav1.ObjectMeta{Name: "test-pipeline-run-success", Namespace: "foo"}, + Spec: v1beta1.PipelineRunSpec{ + ServiceAccountName: "test-sa", + PipelineRef: &v1beta1.PipelineRef{Name: "test-pipeline", Bundle: ref}, + Timeout: &metav1.Duration{Duration: config.DefaultTimeoutMinutes * time.Minute}, + }, + }} + ps := &v1beta1.Pipeline{ + TypeMeta: metav1.TypeMeta{APIVersion: "tekton.dev/v1beta1", Kind: "Pipeline"}, + ObjectMeta: metav1.ObjectMeta{Name: "test-pipeline", Namespace: "foo"}, + Spec: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{{ + Name: "unit-test-1", + TaskRef: &v1beta1.TaskRef{Name: "unit-test-task", Bundle: ref}}, + }, + }, + } + cms := []*corev1.ConfigMap{ + { + ObjectMeta: metav1.ObjectMeta{Name: config.GetFeatureFlagsConfigName(), Namespace: system.Namespace()}, + Data: map[string]string{ + "enable-tekton-oci-bundles": "true", + embeddedStatusFeatureFlag: tc.embeddedStatusVal, + }, + }, + } - prt := newPipelineRunTest(d, t) - defer prt.Cancel() + // This task will be uploaded along with the pipeline definition. + remoteTask := &v1beta1.Task{ + ObjectMeta: baseObjectMeta("unit-test-task", "foo"), + TypeMeta: metav1.TypeMeta{ + APIVersion: "tekton.dev/v1beta1", + Kind: "Task", + }, + Spec: v1beta1.TaskSpec{}, + } - wantEvents := []string{ - "Normal Started", - "Normal Running Tasks Completed: 0", - } - reconciledRun, clients := prt.reconcileRun("foo", "test-pipeline-run-success", wantEvents, false) + // Create a bundle from our pipeline and tasks. + if _, err := test.CreateImage(ref, ps, remoteTask); err != nil { + t.Fatalf("failed to create image in pipeline renconcile: %s", err.Error()) + } - if len(clients.Pipeline.Actions()) == 0 { - t.Fatalf("Expected client to have been used to create a TaskRun but it wasn't") - } + // Unlike the tests above, we do *not* locally define our pipeline or unit-test task. + d := test.Data{ + PipelineRuns: prs, + ServiceAccounts: []*corev1.ServiceAccount{{ + ObjectMeta: metav1.ObjectMeta{Name: prs[0].Spec.ServiceAccountName, Namespace: "foo"}, + }}, + ConfigMaps: cms, + } - // Check that the expected TaskRun was created - actual := getTaskRunCreations(t, clients.Pipeline.Actions())[0] - expectedTaskRun := &v1beta1.TaskRun{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-pipeline-run-success-unit-test-1", - Namespace: "foo", - Annotations: map[string]string{}, - Labels: map[string]string{ - "tekton.dev/pipeline": "test-pipeline", - "tekton.dev/pipelineRun": "test-pipeline-run-success", - pipeline.PipelineTaskLabelKey: "unit-test-1", - pipeline.MemberOfLabelKey: v1beta1.PipelineTasks, - }, - OwnerReferences: []metav1.OwnerReference{{ - APIVersion: "tekton.dev/v1beta1", - Kind: "PipelineRun", - Name: "test-pipeline-run-success", - Controller: &trueb, - BlockOwnerDeletion: &trueb, - }}, - }, - Spec: v1beta1.TaskRunSpec{ - ServiceAccountName: "test-sa", - Resources: &v1beta1.TaskRunResources{}, - Timeout: &metav1.Duration{Duration: config.DefaultTimeoutMinutes * time.Minute}, - TaskRef: &v1beta1.TaskRef{ - Kind: "Task", - Name: "unit-test-task", - Bundle: ref, - }, - }, - } + prt := newPipelineRunTest(d, t) + defer prt.Cancel() - if d := cmp.Diff(expectedTaskRun, actual, cmpopts.SortSlices(func(x, y v1beta1.TaskResourceBinding) bool { return x.Name < y.Name })); d != "" { - t.Errorf("expected to see TaskRun %v created. Diff %s", expectedTaskRun, diff.PrintWantGot(d)) - } + wantEvents := []string{ + "Normal Started", + "Normal Running Tasks Completed: 0", + } + reconciledRun, clients := prt.reconcileRun("foo", "test-pipeline-run-success", wantEvents, false) - // This PipelineRun is in progress now and the status should reflect that - condition := reconciledRun.Status.GetCondition(apis.ConditionSucceeded) - if condition == nil || condition.Status != corev1.ConditionUnknown { - t.Errorf("Expected PipelineRun status to be in progress, but was %v", condition) - } - if condition != nil && condition.Reason != v1beta1.PipelineRunReasonRunning.String() { - t.Errorf("Expected reason %q but was %s", v1beta1.PipelineRunReasonRunning.String(), condition.Reason) - } + if len(clients.Pipeline.Actions()) == 0 { + t.Fatalf("Expected client to have been used to create a TaskRun but it wasn't") + } - if len(reconciledRun.Status.TaskRuns) != 1 { - t.Errorf("Expected PipelineRun status to include the TaskRun status item that ran immediately: %v", reconciledRun.Status.TaskRuns) - } - if _, exists := reconciledRun.Status.TaskRuns["test-pipeline-run-success-unit-test-1"]; !exists { - t.Errorf("Expected PipelineRun status to include TaskRun status but was %v", reconciledRun.Status.TaskRuns) + // Check that the expected TaskRun was created + actual := getTaskRunCreations(t, clients.Pipeline.Actions())[0] + expectedTaskRun := &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pipeline-run-success-unit-test-1", + Namespace: "foo", + Annotations: map[string]string{}, + Labels: map[string]string{ + "tekton.dev/pipeline": "test-pipeline", + "tekton.dev/pipelineRun": "test-pipeline-run-success", + pipeline.PipelineTaskLabelKey: "unit-test-1", + pipeline.MemberOfLabelKey: v1beta1.PipelineTasks, + }, + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: "tekton.dev/v1beta1", + Kind: "PipelineRun", + Name: "test-pipeline-run-success", + Controller: &trueb, + BlockOwnerDeletion: &trueb, + }}, + }, + Spec: v1beta1.TaskRunSpec{ + ServiceAccountName: "test-sa", + Resources: &v1beta1.TaskRunResources{}, + Timeout: &metav1.Duration{Duration: config.DefaultTimeoutMinutes * time.Minute}, + TaskRef: &v1beta1.TaskRef{ + Kind: "Task", + Name: "unit-test-task", + Bundle: ref, + }, + }, + } + + if d := cmp.Diff(expectedTaskRun, actual, cmpopts.SortSlices(func(x, y v1beta1.TaskResourceBinding) bool { return x.Name < y.Name })); d != "" { + t.Errorf("expected to see TaskRun %v created. Diff %s", expectedTaskRun, diff.PrintWantGot(d)) + } + + // This PipelineRun is in progress now and the status should reflect that + condition := reconciledRun.Status.GetCondition(apis.ConditionSucceeded) + if condition == nil || condition.Status != corev1.ConditionUnknown { + t.Errorf("Expected PipelineRun status to be in progress, but was %v", condition) + } + if condition != nil && condition.Reason != v1beta1.PipelineRunReasonRunning.String() { + t.Errorf("Expected reason %q but was %s", v1beta1.PipelineRunReasonRunning.String(), condition.Reason) + } + + if shouldHaveFullEmbeddedStatus(tc.embeddedStatusVal) { + if len(reconciledRun.Status.TaskRuns) != 1 { + t.Errorf("Expected PipelineRun status to include the TaskRun status item that ran immediately: %v", reconciledRun.Status.TaskRuns) + } + if _, exists := reconciledRun.Status.TaskRuns["test-pipeline-run-success-unit-test-1"]; !exists { + t.Errorf("Expected PipelineRun status to include TaskRun status but was %v", reconciledRun.Status.TaskRuns) + } + } + + if shouldHaveMinimalEmbeddedStatus(tc.embeddedStatusVal) { + if len(reconciledRun.Status.ChildReferences) != 1 { + t.Errorf("Expected PipelineRun status ChildReferences to include the TaskRun status item that ran immediately: %v", reconciledRun.Status.ChildReferences) + } + if reconciledRun.Status.ChildReferences[0].Name != "test-pipeline-run-success-unit-test-1" { + t.Errorf("Expected PipelineRun status to include TaskRun status but was %v", reconciledRun.Status.ChildReferences) + } + } + }) } } @@ -8121,24 +8740,106 @@ func TestReconcile_RemotePipelineRef(t *testing.T) { // a Task and a Pipeline can be omitted by a PipelineRun and the run will still start // successfully without an error. func TestReconcile_OptionalWorkspacesOmitted(t *testing.T) { - names.TestingSeed() + testCases := []struct { + name string + embeddedStatusVal string + }{ + { + name: "default embedded status", + embeddedStatusVal: config.DefaultEmbeddedStatus, + }, + { + name: "full embedded status", + embeddedStatusVal: config.FullEmbeddedStatus, + }, + { + name: "both embedded status", + embeddedStatusVal: config.BothEmbeddedStatus, + }, + { + name: "minimal embedded status", + embeddedStatusVal: config.MinimalEmbeddedStatus, + }, + } - ctx := context.Background() - cfg := config.NewStore(logtesting.TestLogger(t)) - ctx = cfg.ToContext(ctx) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + names.TestingSeed() - prs := []*v1beta1.PipelineRun{{ - ObjectMeta: metav1.ObjectMeta{Name: "test-pipeline-run-success", Namespace: "foo"}, - Spec: v1beta1.PipelineRunSpec{ - ServiceAccountName: "test-sa", - PipelineSpec: &v1beta1.PipelineSpec{ - Workspaces: []v1beta1.PipelineWorkspaceDeclaration{{ - Name: "optional-workspace", - Optional: true, + ctx := context.Background() + cfg := config.NewStore(logtesting.TestLogger(t)) + ctx = cfg.ToContext(ctx) + + prs := []*v1beta1.PipelineRun{{ + ObjectMeta: metav1.ObjectMeta{Name: "test-pipeline-run-success", Namespace: "foo"}, + Spec: v1beta1.PipelineRunSpec{ + ServiceAccountName: "test-sa", + PipelineSpec: &v1beta1.PipelineSpec{ + Workspaces: []v1beta1.PipelineWorkspaceDeclaration{{ + Name: "optional-workspace", + Optional: true, + }}, + Tasks: []v1beta1.PipelineTask{{ + Name: "unit-test-1", + TaskSpec: &v1beta1.EmbeddedTask{TaskSpec: v1beta1.TaskSpec{ + Workspaces: []v1beta1.WorkspaceDeclaration{{ + Name: "ws", + Optional: true, + }}, + Steps: []v1beta1.Step{{ + Container: corev1.Container{ + Image: "foo:latest", + }, + }}, + }}, + Workspaces: []v1beta1.WorkspacePipelineTaskBinding{{ + Name: "ws", + Workspace: "optional-workspace", + }}, + }}, + }, + }, + }} + + // Unlike the tests above, we do *not* locally define our pipeline or unit-test task. + d := test.Data{ + PipelineRuns: prs, + ServiceAccounts: []*corev1.ServiceAccount{{ + ObjectMeta: metav1.ObjectMeta{Name: prs[0].Spec.ServiceAccountName, Namespace: "foo"}, }}, - Tasks: []v1beta1.PipelineTask{{ - Name: "unit-test-1", - TaskSpec: &v1beta1.EmbeddedTask{TaskSpec: v1beta1.TaskSpec{ + ConfigMaps: getConfigMapsWithEmbeddedStatus(tc.embeddedStatusVal), + } + + prt := newPipelineRunTest(d, t) + defer prt.Cancel() + + reconciledRun, clients := prt.reconcileRun("foo", "test-pipeline-run-success", nil, false) + + // Check that the expected TaskRun was created + actual := getTaskRunCreations(t, clients.Pipeline.Actions())[0] + expectedTaskRun := &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pipeline-run-success-unit-test-1", + Namespace: "foo", + Annotations: map[string]string{}, + Labels: map[string]string{ + "tekton.dev/pipeline": "test-pipeline-run-success", + "tekton.dev/pipelineRun": "test-pipeline-run-success", + pipeline.PipelineTaskLabelKey: "unit-test-1", + pipeline.MemberOfLabelKey: v1beta1.PipelineTasks, + }, + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: "tekton.dev/v1beta1", + Kind: "PipelineRun", + Name: "test-pipeline-run-success", Controller: &trueb, + BlockOwnerDeletion: &trueb, + }}, + }, + Spec: v1beta1.TaskRunSpec{ + ServiceAccountName: "test-sa", + Resources: &v1beta1.TaskRunResources{}, + Timeout: &metav1.Duration{Duration: config.DefaultTimeoutMinutes * time.Minute}, + TaskSpec: &v1beta1.TaskSpec{ Workspaces: []v1beta1.WorkspaceDeclaration{{ Name: "ws", Optional: true, @@ -8148,85 +8849,41 @@ func TestReconcile_OptionalWorkspacesOmitted(t *testing.T) { Image: "foo:latest", }, }}, - }}, - Workspaces: []v1beta1.WorkspacePipelineTaskBinding{{ - Name: "ws", - Workspace: "optional-workspace", - }}, - }}, - }, - }, - }} - - // Unlike the tests above, we do *not* locally define our pipeline or unit-test task. - d := test.Data{ - PipelineRuns: prs, - ServiceAccounts: []*corev1.ServiceAccount{{ - ObjectMeta: metav1.ObjectMeta{Name: prs[0].Spec.ServiceAccountName, Namespace: "foo"}, - }}, - } - - prt := newPipelineRunTest(d, t) - defer prt.Cancel() - - reconciledRun, clients := prt.reconcileRun("foo", "test-pipeline-run-success", nil, false) - - // Check that the expected TaskRun was created - actual := getTaskRunCreations(t, clients.Pipeline.Actions())[0] - expectedTaskRun := &v1beta1.TaskRun{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-pipeline-run-success-unit-test-1", - Namespace: "foo", - Annotations: map[string]string{}, - Labels: map[string]string{ - "tekton.dev/pipeline": "test-pipeline-run-success", - "tekton.dev/pipelineRun": "test-pipeline-run-success", - pipeline.PipelineTaskLabelKey: "unit-test-1", - pipeline.MemberOfLabelKey: v1beta1.PipelineTasks, - }, - OwnerReferences: []metav1.OwnerReference{{ - APIVersion: "tekton.dev/v1beta1", - Kind: "PipelineRun", - Name: "test-pipeline-run-success", Controller: &trueb, - BlockOwnerDeletion: &trueb, - }}, - }, - Spec: v1beta1.TaskRunSpec{ - ServiceAccountName: "test-sa", - Resources: &v1beta1.TaskRunResources{}, - Timeout: &metav1.Duration{Duration: config.DefaultTimeoutMinutes * time.Minute}, - TaskSpec: &v1beta1.TaskSpec{ - Workspaces: []v1beta1.WorkspaceDeclaration{{ - Name: "ws", - Optional: true, - }}, - Steps: []v1beta1.Step{{ - Container: corev1.Container{ - Image: "foo:latest", }, - }}, - }, - }, - } + }, + } - if d := cmp.Diff(expectedTaskRun, actual, cmpopts.SortSlices(func(x, y v1beta1.TaskResourceBinding) bool { return x.Name < y.Name })); d != "" { - t.Errorf("expected to see TaskRun %v created. Diff %s", expectedTaskRun, diff.PrintWantGot(d)) - } + if d := cmp.Diff(expectedTaskRun, actual, cmpopts.SortSlices(func(x, y v1beta1.TaskResourceBinding) bool { return x.Name < y.Name })); d != "" { + t.Errorf("expected to see TaskRun %v created. Diff %s", expectedTaskRun, diff.PrintWantGot(d)) + } - // This PipelineRun is in progress now and the status should reflect that - condition := reconciledRun.Status.GetCondition(apis.ConditionSucceeded) - if condition == nil || condition.Status != corev1.ConditionUnknown { - t.Errorf("Expected PipelineRun status to be in progress, but was %v", condition) - } - if condition != nil && condition.Reason != v1beta1.PipelineRunReasonRunning.String() { - t.Errorf("Expected reason %q but was %s", v1beta1.PipelineRunReasonRunning.String(), condition.Reason) - } + // This PipelineRun is in progress now and the status should reflect that + condition := reconciledRun.Status.GetCondition(apis.ConditionSucceeded) + if condition == nil || condition.Status != corev1.ConditionUnknown { + t.Errorf("Expected PipelineRun status to be in progress, but was %v", condition) + } + if condition != nil && condition.Reason != v1beta1.PipelineRunReasonRunning.String() { + t.Errorf("Expected reason %q but was %s", v1beta1.PipelineRunReasonRunning.String(), condition.Reason) + } - if len(reconciledRun.Status.TaskRuns) != 1 { - t.Errorf("Expected PipelineRun status to include the TaskRun status item that ran immediately: %v", reconciledRun.Status.TaskRuns) - } - if _, exists := reconciledRun.Status.TaskRuns["test-pipeline-run-success-unit-test-1"]; !exists { - t.Errorf("Expected PipelineRun status to include TaskRun status but was %v", reconciledRun.Status.TaskRuns) + if shouldHaveFullEmbeddedStatus(tc.embeddedStatusVal) { + if len(reconciledRun.Status.TaskRuns) != 1 { + t.Errorf("Expected PipelineRun status to include the TaskRun status item that ran immediately: %v", reconciledRun.Status.TaskRuns) + } + if _, exists := reconciledRun.Status.TaskRuns["test-pipeline-run-success-unit-test-1"]; !exists { + t.Errorf("Expected PipelineRun status to include TaskRun status but was %v", reconciledRun.Status.TaskRuns) + } + } + + if shouldHaveMinimalEmbeddedStatus(tc.embeddedStatusVal) { + if len(reconciledRun.Status.ChildReferences) != 1 { + t.Errorf("Expected PipelineRun status ChildReferences to include the TaskRun status item that ran immediately: %v", reconciledRun.Status.ChildReferences) + } + if reconciledRun.Status.ChildReferences[0].Name != "test-pipeline-run-success-unit-test-1" { + t.Errorf("Expected PipelineRun status to include TaskRun status but was %v", reconciledRun.Status.ChildReferences) + } + } + }) } } @@ -8491,3 +9148,46 @@ spec: status: startTime: %s`, prName, specStatus, now.Format(time.RFC3339))) } + +func shouldHaveFullEmbeddedStatus(embeddedVal string) bool { + return embeddedVal == config.FullEmbeddedStatus || embeddedVal == config.BothEmbeddedStatus +} + +func shouldHaveMinimalEmbeddedStatus(embeddedVal string) bool { + return embeddedVal == config.MinimalEmbeddedStatus || embeddedVal == config.BothEmbeddedStatus +} + +func prStatusForEmbeddedStatus(input v1beta1.PipelineRunStatus, embeddedVal string) v1beta1.PipelineRunStatus { + output := input + if shouldHaveMinimalEmbeddedStatus(embeddedVal) { + for k, v := range input.TaskRuns { + cr := v1beta1.ChildStatusReference{ + TypeMeta: runtime.TypeMeta{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: "TaskRun", + }, + Name: k, + PipelineTaskName: v.PipelineTaskName, + WhenExpressions: v.WhenExpressions, + } + + for ccName, ccVal := range v.ConditionChecks { + cr.ConditionChecks = append(cr.ConditionChecks, &v1beta1.PipelineRunChildConditionCheckStatus{ + PipelineRunConditionCheckStatus: *ccVal, + ConditionCheckName: ccName, + }) + } + output.ChildReferences = append(output.ChildReferences, cr) + } + } + if !shouldHaveFullEmbeddedStatus(embeddedVal) { + output.TaskRuns = nil + } + + // Sort the ChildReferences to deal with annoying ordering issues. + sort.Slice(output.ChildReferences, func(i, j int) bool { + return output.ChildReferences[i].PipelineTaskName < output.ChildReferences[j].PipelineTaskName + }) + + return output +} diff --git a/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go b/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go index 414341e20ad..50d1809c73e 100644 --- a/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go +++ b/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go @@ -502,14 +502,14 @@ func ResolvePipelineRunTask( } rprt.CustomTask = isCustomTask(ctx, rprt) if rprt.IsCustomTask() { - rprt.RunName = getRunName(pipelineRun.Status.Runs, task.Name, pipelineRun.Name) + rprt.RunName = getRunName(pipelineRun.Status.Runs, pipelineRun.Status.ChildReferences, task.Name, pipelineRun.Name) run, err := getRun(rprt.RunName) if err != nil && !errors.IsNotFound(err) { return nil, fmt.Errorf("error retrieving Run %s: %w", rprt.RunName, err) } rprt.Run = run } else { - rprt.TaskRunName = GetTaskRunName(pipelineRun.Status.TaskRuns, task.Name, pipelineRun.Name) + rprt.TaskRunName = GetTaskRunName(pipelineRun.Status.TaskRuns, pipelineRun.Status.ChildReferences, task.Name, pipelineRun.Name) // Find the Task that this PipelineTask is using var ( @@ -560,7 +560,7 @@ func ResolvePipelineRunTask( // Get all conditions that this pipelineTask will be using, if any if len(task.Conditions) > 0 { - rcc, err := resolveConditionChecks(&task, pipelineRun.Status.TaskRuns, rprt.TaskRunName, getTaskRun, getCondition, providedResources) + rcc, err := resolveConditionChecks(&task, pipelineRun.Status.TaskRuns, pipelineRun.Status.ChildReferences, rprt.TaskRunName, getTaskRun, getCondition, providedResources) if err != nil { return nil, err } @@ -571,7 +571,7 @@ func ResolvePipelineRunTask( } // getConditionCheckName should return a unique name for a `ConditionCheck` if one has not already been defined, and the existing one otherwise. -func getConditionCheckName(taskRunStatus map[string]*v1beta1.PipelineRunTaskRunStatus, trName, conditionRegisterName string) string { +func getConditionCheckName(taskRunStatus map[string]*v1beta1.PipelineRunTaskRunStatus, childRefs []v1beta1.ChildStatusReference, trName, conditionRegisterName string) string { trStatus, ok := taskRunStatus[trName] if ok && trStatus.ConditionChecks != nil { for k, v := range trStatus.ConditionChecks { @@ -581,11 +581,26 @@ func getConditionCheckName(taskRunStatus map[string]*v1beta1.PipelineRunTaskRunS } } } + for _, cr := range childRefs { + if cr.Name == trName { + for _, cc := range cr.ConditionChecks { + if cc.ConditionName == conditionRegisterName { + return cc.ConditionCheckName + } + } + } + } return kmeta.ChildName(trName, fmt.Sprintf("-%s", conditionRegisterName)) } // GetTaskRunName should return a unique name for a `TaskRun` if one has not already been defined, and the existing one otherwise. -func GetTaskRunName(taskRunsStatus map[string]*v1beta1.PipelineRunTaskRunStatus, ptName, prName string) string { +func GetTaskRunName(taskRunsStatus map[string]*v1beta1.PipelineRunTaskRunStatus, childRefs []v1beta1.ChildStatusReference, ptName, prName string) string { + for _, cr := range childRefs { + if cr.Kind == "TaskRun" && cr.PipelineTaskName == ptName { + return cr.Name + } + } + for k, v := range taskRunsStatus { if v.PipelineTaskName == ptName { return k @@ -597,16 +612,23 @@ func GetTaskRunName(taskRunsStatus map[string]*v1beta1.PipelineRunTaskRunStatus, // getRunName should return a unique name for a `Run` if one has not already // been defined, and the existing one otherwise. -func getRunName(runsStatus map[string]*v1beta1.PipelineRunRunStatus, ptName, prName string) string { +func getRunName(runsStatus map[string]*v1beta1.PipelineRunRunStatus, childRefs []v1beta1.ChildStatusReference, ptName, prName string) string { + for _, cr := range childRefs { + if cr.Kind == "Run" && cr.PipelineTaskName == ptName { + return cr.Name + } + } + for k, v := range runsStatus { if v.PipelineTaskName == ptName { return k } } + return kmeta.ChildName(prName, fmt.Sprintf("-%s", ptName)) } -func resolveConditionChecks(pt *v1beta1.PipelineTask, taskRunStatus map[string]*v1beta1.PipelineRunTaskRunStatus, taskRunName string, getTaskRun resources.GetTaskRun, getCondition GetCondition, providedResources map[string]*resourcev1alpha1.PipelineResource) ([]*ResolvedConditionCheck, error) { +func resolveConditionChecks(pt *v1beta1.PipelineTask, taskRunStatus map[string]*v1beta1.PipelineRunTaskRunStatus, childRefs []v1beta1.ChildStatusReference, taskRunName string, getTaskRun resources.GetTaskRun, getCondition GetCondition, providedResources map[string]*resourcev1alpha1.PipelineResource) ([]*ResolvedConditionCheck, error) { rccs := []*ResolvedConditionCheck{} for i := range pt.Conditions { ptc := pt.Conditions[i] @@ -619,7 +641,7 @@ func resolveConditionChecks(pt *v1beta1.PipelineTask, taskRunStatus map[string]* Msg: err.Error(), } } - conditionCheckName := getConditionCheckName(taskRunStatus, taskRunName, crName) + conditionCheckName := getConditionCheckName(taskRunStatus, childRefs, taskRunName, crName) // TODO(#3133): Also handle Custom Task Runs (getRun here) cctr, err := getTaskRun(conditionCheckName) if err != nil { diff --git a/pkg/reconciler/pipelinerun/resources/pipelinerunresolution_test.go b/pkg/reconciler/pipelinerun/resources/pipelinerunresolution_test.go index 20146f9d750..c1d883fe9a1 100644 --- a/pkg/reconciler/pipelinerun/resources/pipelinerunresolution_test.go +++ b/pkg/reconciler/pipelinerun/resources/pipelinerunresolution_test.go @@ -3596,6 +3596,11 @@ func TestGetTaskRunName(t *testing.T) { PipelineTaskName: "task1", }, } + childRefs := []v1beta1.ChildStatusReference{{ + TypeMeta: runtime.TypeMeta{Kind: "TaskRun"}, + Name: "taskrun-for-task1", + PipelineTaskName: "task1", + }} for _, tc := range []struct { name string @@ -3630,8 +3635,12 @@ func TestGetTaskRunName(t *testing.T) { if tc.prName != "" { testPrName = tc.prName } - gotTrName := GetTaskRunName(taskRunsStatus, tc.ptName, testPrName) - if d := cmp.Diff(tc.wantTrName, gotTrName); d != "" { + trNameFromTRStatus := GetTaskRunName(taskRunsStatus, nil, tc.ptName, testPrName) + if d := cmp.Diff(tc.wantTrName, trNameFromTRStatus); d != "" { + t.Errorf("GetTaskRunName: %s", diff.PrintWantGot(d)) + } + trNameFromChildRefs := GetTaskRunName(nil, childRefs, tc.ptName, testPrName) + if d := cmp.Diff(tc.wantTrName, trNameFromChildRefs); d != "" { t.Errorf("GetTaskRunName: %s", diff.PrintWantGot(d)) } }) @@ -3645,6 +3654,11 @@ func TestGetRunName(t *testing.T) { PipelineTaskName: "task1", }, } + childRefs := []v1beta1.ChildStatusReference{{ + TypeMeta: runtime.TypeMeta{Kind: "Run"}, + Name: "run-for-task1", + PipelineTaskName: "task1", + }} for _, tc := range []struct { name string @@ -3679,8 +3693,12 @@ func TestGetRunName(t *testing.T) { if tc.prName != "" { testPrName = tc.prName } - gotTrName := getRunName(runsStatus, tc.ptName, testPrName) - if d := cmp.Diff(tc.wantTrName, gotTrName); d != "" { + rnFromRunsStatus := getRunName(runsStatus, nil, tc.ptName, testPrName) + if d := cmp.Diff(tc.wantTrName, rnFromRunsStatus); d != "" { + t.Errorf("GetTaskRunName: %s", diff.PrintWantGot(d)) + } + rnFromChildRefs := getRunName(nil, childRefs, tc.ptName, testPrName) + if d := cmp.Diff(tc.wantTrName, rnFromChildRefs); d != "" { t.Errorf("GetTaskRunName: %s", diff.PrintWantGot(d)) } }) diff --git a/pkg/reconciler/pipelinerun/resources/pipelinerunstate.go b/pkg/reconciler/pipelinerun/resources/pipelinerunstate.go index 914fcd9a535..972c3550966 100644 --- a/pkg/reconciler/pipelinerun/resources/pipelinerunstate.go +++ b/pkg/reconciler/pipelinerun/resources/pipelinerunstate.go @@ -26,6 +26,7 @@ import ( "go.uber.org/zap" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/clock" "k8s.io/apimachinery/pkg/util/sets" "knative.dev/pkg/apis" @@ -223,6 +224,71 @@ func (state PipelineRunState) GetRunsStatus(pr *v1beta1.PipelineRun) map[string] return status } +// GetChildReferences returns a slice of references, including version, kind, name, and pipeline task name, for all +// TaskRuns and Runs in the state. +func (state PipelineRunState) GetChildReferences(taskRunVersion string, runVersion string) []v1beta1.ChildStatusReference { + var childRefs []v1beta1.ChildStatusReference + + for _, rprt := range state { + if rprt.ResolvedConditionChecks == nil && ((rprt.CustomTask && rprt.Run == nil) || (!rprt.CustomTask && rprt.TaskRun == nil)) { + continue + } + + var childAPIVersion string + var childTaskKind string + var childName string + var childConditions []*v1beta1.PipelineRunChildConditionCheckStatus + + if rprt.CustomTask { + childName = rprt.RunName + childTaskKind = "Run" + + if rprt.Run != nil { + childAPIVersion = rprt.Run.APIVersion + } else { + childAPIVersion = runVersion + } + } else { + childName = rprt.TaskRunName + childTaskKind = "TaskRun" + + if rprt.TaskRun != nil { + childAPIVersion = rprt.TaskRun.APIVersion + } else { + childAPIVersion = taskRunVersion + } + if len(rprt.ResolvedConditionChecks) > 0 { + for _, c := range rprt.ResolvedConditionChecks { + condCheck := &v1beta1.PipelineRunChildConditionCheckStatus{ + PipelineRunConditionCheckStatus: v1beta1.PipelineRunConditionCheckStatus{ + ConditionName: c.ConditionRegisterName, + }, + ConditionCheckName: c.ConditionCheckName, + } + if c.ConditionCheck != nil { + condCheck.Status = c.NewConditionCheckStatus() + } + + childConditions = append(childConditions, condCheck) + } + } + } + + childRefs = append(childRefs, v1beta1.ChildStatusReference{ + TypeMeta: runtime.TypeMeta{ + APIVersion: childAPIVersion, + Kind: childTaskKind, + }, + Name: childName, + PipelineTaskName: rprt.PipelineTask.Name, + WhenExpressions: rprt.PipelineTask.WhenExpressions, + ConditionChecks: childConditions, + }) + + } + return childRefs +} + // getNextTasks returns a list of tasks which should be executed next i.e. // a list of tasks from candidateTasks which aren't yet indicated in state to be running and // a list of cancelled/failed tasks from candidateTasks which haven't exhausted their retries diff --git a/pkg/reconciler/pipelinerun/resources/pipelinerunstate_test.go b/pkg/reconciler/pipelinerun/resources/pipelinerunstate_test.go index ed1eec7c6ad..6510fe86a07 100644 --- a/pkg/reconciler/pipelinerun/resources/pipelinerunstate_test.go +++ b/pkg/reconciler/pipelinerun/resources/pipelinerunstate_test.go @@ -22,8 +22,12 @@ import ( "testing" "time" - "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "k8s.io/apimachinery/pkg/runtime" + + "k8s.io/apimachinery/pkg/selection" + + "github.com/google/go-cmp/cmp" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" resourcev1alpha1 "github.com/tektoncd/pipeline/pkg/apis/resource/v1alpha1" @@ -34,7 +38,6 @@ import ( "go.uber.org/zap" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/util/clock" "k8s.io/apimachinery/pkg/util/sets" "knative.dev/pkg/apis" @@ -2300,3 +2303,309 @@ func conditionCheckFromTaskRun(tr *v1beta1.TaskRun) *v1beta1.ConditionCheck { cc := v1beta1.ConditionCheck(*tr) return &cc } + +func TestPipelineRunState_GetChildReferences(t *testing.T) { + successConditionCheckName := "success-condition" + failingConditionCheckName := "fail-condition" + + successCondition := &v1alpha1.Condition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cond-1", + Namespace: "foo", + }, + } + failingCondition := &v1alpha1.Condition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cond-2", + Namespace: "foo", + }, + } + + successConditionCheck := v1beta1.ConditionCheck(v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{Name: successConditionCheckName}, + Status: v1beta1.TaskRunStatus{ + Status: duckv1beta1.Status{ + Conditions: duckv1beta1.Conditions{ + apis.Condition{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionTrue, + }, + }, + }, + TaskRunStatusFields: v1beta1.TaskRunStatusFields{ + Steps: []v1beta1.StepState{{ + ContainerState: corev1.ContainerState{ + Terminated: &corev1.ContainerStateTerminated{ExitCode: int32(0)}, + }, + }}, + }, + }, + }) + failingConditionCheck := v1beta1.ConditionCheck(v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{Name: failingConditionCheckName}, + Status: v1beta1.TaskRunStatus{ + Status: duckv1beta1.Status{ + Conditions: duckv1beta1.Conditions{ + apis.Condition{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionFalse, + }, + }, + }, + TaskRunStatusFields: v1beta1.TaskRunStatusFields{ + Steps: []v1beta1.StepState{{ + ContainerState: corev1.ContainerState{ + Terminated: &corev1.ContainerStateTerminated{ExitCode: int32(127)}, + }, + }}, + }, + }, + }) + + successrcc := ResolvedConditionCheck{ + ConditionRegisterName: successCondition.Name + "-0", + ConditionCheckName: successConditionCheckName, + Condition: successCondition, + ConditionCheck: &successConditionCheck, + } + failingrcc := ResolvedConditionCheck{ + ConditionRegisterName: failingCondition.Name + "-0", + ConditionCheckName: failingConditionCheckName, + Condition: failingCondition, + ConditionCheck: &failingConditionCheck, + } + + successConditionCheckStatus := &v1beta1.PipelineRunChildConditionCheckStatus{ + PipelineRunConditionCheckStatus: v1beta1.PipelineRunConditionCheckStatus{ + ConditionName: successrcc.ConditionRegisterName, + 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}}, + }, + }, + }, + ConditionCheckName: successConditionCheckName, + } + failingConditionCheckStatus := &v1beta1.PipelineRunChildConditionCheckStatus{ + PipelineRunConditionCheckStatus: v1beta1.PipelineRunConditionCheckStatus{ + ConditionName: failingrcc.ConditionRegisterName, + 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}}, + }, + }, + }, + ConditionCheckName: failingConditionCheckName, + } + + testCases := []struct { + name string + state PipelineRunState + childRefs []v1beta1.ChildStatusReference + }{ + { + name: "no-tasks", + state: PipelineRunState{}, + childRefs: nil, + }, + { + name: "unresolved-task", + state: PipelineRunState{{ + TaskRunName: "unresolved-task-run", + PipelineTask: &v1beta1.PipelineTask{ + Name: "unresolved-task-1", + TaskRef: &v1beta1.TaskRef{ + Name: "unresolved-task", + Kind: "Task", + APIVersion: "v1beta1", + }, + }, + }}, + childRefs: nil, + }, + { + name: "unresolved-custom-task", + state: PipelineRunState{{ + RunName: "unresolved-custom-task-run", + CustomTask: true, + PipelineTask: &v1beta1.PipelineTask{ + Name: "unresolved-custom-task-1", + TaskRef: &v1beta1.TaskRef{ + APIVersion: "example.dev/v0", + Kind: "Example", + Name: "unresolved-custom-task", + }, + }, + }}, + childRefs: nil, + }, + { + name: "single-task", + state: PipelineRunState{{ + TaskRunName: "single-task-run", + PipelineTask: &v1beta1.PipelineTask{ + Name: "single-task-1", + TaskRef: &v1beta1.TaskRef{ + Name: "single-task", + Kind: "Task", + APIVersion: "v1beta1", + }, + WhenExpressions: []v1beta1.WhenExpression{{ + Input: "foo", + Operator: selection.In, + Values: []string{"foo", "bar"}, + }}, + }, + TaskRun: &v1beta1.TaskRun{ + TypeMeta: metav1.TypeMeta{APIVersion: "tekton.dev/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{Name: "single-task-run"}, + }, + }}, + childRefs: []v1beta1.ChildStatusReference{{ + TypeMeta: runtime.TypeMeta{ + APIVersion: "tekton.dev/v1beta1", + Kind: "TaskRun", + }, + Name: "single-task-run", + PipelineTaskName: "single-task-1", + WhenExpressions: []v1beta1.WhenExpression{{ + Input: "foo", + Operator: selection.In, + Values: []string{"foo", "bar"}, + }}, + }}, + }, + { + name: "task-with-condition-check", + state: PipelineRunState{{ + TaskRunName: "task-with-condition-check-run", + PipelineTask: &v1beta1.PipelineTask{ + Name: "task-with-condition-check-1", + TaskRef: &v1beta1.TaskRef{ + Name: "task-with-condition-check", + Kind: "Task", + APIVersion: "v1beta1", + }, + }, + ResolvedConditionChecks: TaskConditionCheckState{&successrcc, &failingrcc}, + }}, + childRefs: []v1beta1.ChildStatusReference{{ + TypeMeta: runtime.TypeMeta{ + APIVersion: "tekton.dev/v1beta1", + Kind: "TaskRun", + }, + Name: "task-with-condition-check-run", + PipelineTaskName: "task-with-condition-check-1", + ConditionChecks: []*v1beta1.PipelineRunChildConditionCheckStatus{ + successConditionCheckStatus, + failingConditionCheckStatus, + }, + }}, + }, + { + name: "single-custom-task", + state: PipelineRunState{{ + RunName: "single-custom-task-run", + CustomTask: true, + PipelineTask: &v1beta1.PipelineTask{ + Name: "single-custom-task-1", + TaskRef: &v1beta1.TaskRef{ + APIVersion: "example.dev/v0", + Kind: "Example", + Name: "single-custom-task", + }, + WhenExpressions: []v1beta1.WhenExpression{{ + Input: "foo", + Operator: selection.In, + Values: []string{"foo", "bar"}, + }}, + }, + Run: &v1alpha1.Run{ + TypeMeta: metav1.TypeMeta{APIVersion: "tekton.dev/v1alpha1"}, + ObjectMeta: metav1.ObjectMeta{Name: "single-custom-task-run"}, + }, + }}, + childRefs: []v1beta1.ChildStatusReference{{ + TypeMeta: runtime.TypeMeta{ + APIVersion: "tekton.dev/v1alpha1", + Kind: "Run", + }, + Name: "single-custom-task-run", + PipelineTaskName: "single-custom-task-1", + WhenExpressions: []v1beta1.WhenExpression{{ + Input: "foo", + Operator: selection.In, + Values: []string{"foo", "bar"}, + }}, + }}, + }, + { + name: "task-and-custom-task", + state: PipelineRunState{{ + TaskRunName: "single-task-run", + PipelineTask: &v1beta1.PipelineTask{ + Name: "single-task-1", + TaskRef: &v1beta1.TaskRef{ + Name: "single-task", + Kind: "Task", + APIVersion: "v1beta1", + }, + }, + TaskRun: &v1beta1.TaskRun{ + TypeMeta: metav1.TypeMeta{APIVersion: "tekton.dev/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{Name: "single-task-run"}, + }, + }, { + RunName: "single-custom-task-run", + CustomTask: true, + PipelineTask: &v1beta1.PipelineTask{ + Name: "single-custom-task-1", + TaskRef: &v1beta1.TaskRef{ + APIVersion: "example.dev/v0", + Kind: "Example", + Name: "single-custom-task", + }, + }, + Run: &v1alpha1.Run{ + TypeMeta: metav1.TypeMeta{APIVersion: "tekton.dev/v1alpha1"}, + ObjectMeta: metav1.ObjectMeta{Name: "single-custom-task-run"}, + }, + }}, + childRefs: []v1beta1.ChildStatusReference{{ + TypeMeta: runtime.TypeMeta{ + APIVersion: "tekton.dev/v1beta1", + Kind: "TaskRun", + }, + Name: "single-task-run", + PipelineTaskName: "single-task-1", + }, { + TypeMeta: runtime.TypeMeta{ + APIVersion: "tekton.dev/v1alpha1", + Kind: "Run", + }, + Name: "single-custom-task-run", + PipelineTaskName: "single-custom-task-1", + }}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + childRefs := tc.state.GetChildReferences(v1beta1.SchemeGroupVersion.String(), v1alpha1.SchemeGroupVersion.String()) + if d := cmp.Diff(tc.childRefs, childRefs); d != "" { + t.Errorf("Didn't get expected child references for %s: %s", tc.name, diff.PrintWantGot(d)) + } + + }) + } +} diff --git a/test/custom_task_test.go b/test/custom_task_test.go index 1230d4226e6..a3906b96280 100644 --- a/test/custom_task_test.go +++ b/test/custom_task_test.go @@ -26,6 +26,8 @@ import ( "testing" "time" + "github.com/tektoncd/pipeline/pkg/apis/config" + "github.com/tektoncd/pipeline/test/parse" "github.com/google/go-cmp/cmp" @@ -49,7 +51,6 @@ const ( var supportedFeatureGates = map[string]string{ "enable-custom-tasks": "true", "enable-api-fields": "alpha", - "embedded-status": "full", } func TestCustomTask(t *testing.T) { @@ -59,6 +60,9 @@ func TestCustomTask(t *testing.T) { c, namespace := setup(ctx, t, requireAnyGate(supportedFeatureGates)) knativetest.CleanupOnInterrupt(func() { tearDown(ctx, t, c, namespace) }, t.Logf) defer tearDown(ctx, t, c, namespace) + + embeddedStatusValue := GetEmbeddedStatusValue(ctx, t, c.KubeClient) + customTaskRawSpec := []byte(`{"field1":123,"field2":"value"}`) metadataLabel := map[string]string{"test-label": "test"} // Create a PipelineRun that runs a Custom Task. @@ -121,11 +125,25 @@ spec: } // Get the Run name. - if len(pr.Status.Runs) != 2 { - t.Fatalf("PipelineRun had unexpected .status.runs; got %d, want 2", len(pr.Status.Runs)) + var runNames []string + if embeddedStatusValue != config.MinimalEmbeddedStatus { + if len(pr.Status.Runs) != 2 { + t.Fatalf("PipelineRun had unexpected .status.runs; got %d, want 2", len(pr.Status.Runs)) + } + for rn := range pr.Status.Runs { + runNames = append(runNames, rn) + } + } else { + for _, cr := range pr.Status.ChildReferences { + if cr.Kind == "Run" { + runNames = append(runNames, cr.Name) + } + } + if len(runNames) != 2 { + t.Fatalf("PipelineRun had unexpected number of Runs in .status.childReferences; got %d, want 2", len(runNames)) + } } - - for runName := range pr.Status.Runs { + for _, runName := range runNames { // Get the Run. r, err := c.RunClient.Get(ctx, runName, metav1.GetOptions{}) if err != nil { @@ -185,13 +203,25 @@ spec: } // Get the TaskRun name. - if len(pr.Status.TaskRuns) != 1 { - t.Fatalf("PipelineRun had unexpected .status.taskRuns; got %d, want 1", len(pr.Status.TaskRuns)) - } var taskRunName string - for k := range pr.Status.TaskRuns { - taskRunName = k - break + + if embeddedStatusValue != config.MinimalEmbeddedStatus { + if len(pr.Status.TaskRuns) != 1 { + t.Fatalf("PipelineRun had unexpected .status.taskRuns; got %d, want 1", len(pr.Status.TaskRuns)) + } + for k := range pr.Status.TaskRuns { + taskRunName = k + break + } + } else { + for _, cr := range pr.Status.ChildReferences { + if cr.Kind == "TaskRun" { + taskRunName = cr.Name + } + } + if taskRunName == "" { + t.Fatal("PipelineRun does not have expected TaskRun in .status.childReferences") + } } // Get the TaskRun. @@ -256,6 +286,9 @@ func TestPipelineRunCustomTaskTimeout(t *testing.T) { knativetest.CleanupOnInterrupt(func() { tearDown(context.Background(), t, c, namespace) }, t.Logf) defer tearDown(context.Background(), t, c, namespace) + + embeddedStatusValue := GetEmbeddedStatusValue(ctx, t, c.KubeClient) + pipeline := parse.MustParsePipeline(t, fmt.Sprintf(` metadata: name: %s @@ -294,36 +327,46 @@ spec: } // Get the Run name. - if len(pr.Status.Runs) != 1 { - t.Fatalf("PipelineRun had unexpected .status.runs; got %d, want 1", len(pr.Status.Runs)) - } + runName := "" - for runName := range pr.Status.Runs { - // Get the Run. - r, err := c.RunClient.Get(ctx, runName, metav1.GetOptions{}) - if err != nil { - t.Fatalf("Failed to get Run %q: %v", runName, err) + if embeddedStatusValue != config.MinimalEmbeddedStatus { + if len(pr.Status.Runs) != 1 { + t.Fatalf("PipelineRun had unexpected .status.runs; got %d, want 1", len(pr.Status.Runs)) } - if r.IsDone() { - t.Fatalf("Run unexpectedly done: %v", r.Status.GetCondition(apis.ConditionSucceeded)) + for rn := range pr.Status.Runs { + runName = rn } - - // Simulate a Custom Task controller updating the Run to be started/running, - // because, a run that has not started cannot timeout. - r.Status = v1alpha1.RunStatus{ - RunStatusFields: v1alpha1.RunStatusFields{ - StartTime: &metav1.Time{Time: time.Now()}, - }, - Status: duckv1.Status{ - Conditions: []apis.Condition{{ - Type: apis.ConditionSucceeded, - Status: corev1.ConditionUnknown, - }}, - }, - } - if _, err := c.RunClient.UpdateStatus(ctx, r, metav1.UpdateOptions{}); err != nil { - t.Fatalf("Failed to update Run to successful: %v", err) + } else { + if len(pr.Status.ChildReferences) != 1 { + t.Fatalf("PipelineRun had unexpected .status.childReferences; got %d, want 1", len(pr.Status.ChildReferences)) } + runName = pr.Status.ChildReferences[0].Name + } + + // Get the Run. + r, err := c.RunClient.Get(ctx, runName, metav1.GetOptions{}) + if err != nil { + t.Fatalf("Failed to get Run %q: %v", runName, err) + } + if r.IsDone() { + t.Fatalf("Run unexpectedly done: %v", r.Status.GetCondition(apis.ConditionSucceeded)) + } + + // Simulate a Custom Task controller updating the Run to be started/running, + // because, a run that has not started cannot timeout. + r.Status = v1alpha1.RunStatus{ + RunStatusFields: v1alpha1.RunStatusFields{ + StartTime: &metav1.Time{Time: time.Now()}, + }, + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionUnknown, + }}, + }, + } + if _, err := c.RunClient.UpdateStatus(ctx, r, metav1.UpdateOptions{}); err != nil { + t.Fatalf("Failed to update Run to successful: %v", err) } t.Logf("Waiting for PipelineRun %s in namespace %s to be timed out", pipelineRun.Name, namespace) diff --git a/test/gate.go b/test/featureflags.go similarity index 63% rename from test/gate.go rename to test/featureflags.go index 13292e1c411..6e510339449 100644 --- a/test/gate.go +++ b/test/featureflags.go @@ -6,6 +6,8 @@ import ( "strings" "testing" + "k8s.io/client-go/kubernetes" + "github.com/tektoncd/pipeline/pkg/apis/config" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "knative.dev/pkg/system" @@ -32,3 +34,17 @@ func requireAnyGate(gates map[string]string) func(context.Context, *testing.T, * t.Skipf("No feature flag matching %s", strings.Join(pairs, " or ")) } } + +// GetEmbeddedStatusValue gets the current value for the "embedded-status" feature flag. +// If the flag is not set, it returns the default value. +func GetEmbeddedStatusValue(ctx context.Context, t *testing.T, kubeClient kubernetes.Interface) string { + featureFlagsCM, err := kubeClient.CoreV1().ConfigMaps(system.Namespace()).Get(ctx, config.GetFeatureFlagsConfigName(), metav1.GetOptions{}) + if err != nil { + t.Fatalf("Failed to get ConfigMap `%s`: %s", config.GetFeatureFlagsConfigName(), err) + } + val := featureFlagsCM.Data["embedded-status"] + if val == "" { + return config.DefaultEmbeddedStatus + } + return val +} diff --git a/test/retry_test.go b/test/retry_test.go index 7bba265ea51..087c38d9790 100644 --- a/test/retry_test.go +++ b/test/retry_test.go @@ -24,6 +24,8 @@ import ( "testing" "time" + "github.com/tektoncd/pipeline/pkg/apis/config" + "github.com/tektoncd/pipeline/test/parse" corev1 "k8s.io/api/core/v1" @@ -43,6 +45,8 @@ func TestTaskRunRetry(t *testing.T) { knativetest.CleanupOnInterrupt(func() { tearDown(ctx, t, c, namespace) }, t.Logf) defer tearDown(ctx, t, c, namespace) + embeddedStatusValue := GetEmbeddedStatusValue(ctx, t, c.KubeClient) + // Create a PipelineRun with a single TaskRun that can only fail, // configured to retry 5 times. pipelineRunName := "retry-pipeline" @@ -74,12 +78,31 @@ spec: t.Fatalf("Failed to get PipelineRun %q: %v", pipelineRunName, err) } - // PipelineRunStatus should have 1 TaskRun status, and it should be failed. - if len(pr.Status.TaskRuns) != 1 { - t.Errorf("Got %d TaskRun statuses, wanted %d", len(pr.Status.TaskRuns), numRetries) - } - for taskRunName, trs := range pr.Status.TaskRuns { - if !isFailed(t, taskRunName, trs.Status.Conditions) { + if embeddedStatusValue != config.MinimalEmbeddedStatus { + // PipelineRunStatus should have 1 TaskRun status, and it should be failed. + if len(pr.Status.TaskRuns) != 1 { + t.Errorf("Got %d TaskRun statuses, wanted %d", len(pr.Status.TaskRuns), numRetries) + } + for taskRunName, trs := range pr.Status.TaskRuns { + if !isFailed(t, taskRunName, trs.Status.Conditions) { + t.Errorf("TaskRun status %q is not failed", taskRunName) + } + } + } else { + // PipelineRunStatus should have 1 child reference, and the TaskRun it refers to should be failed. + if len(pr.Status.ChildReferences) != 1 { + t.Fatalf("Got %d child references, wanted %d", len(pr.Status.ChildReferences), numRetries) + } + if pr.Status.ChildReferences[0].Kind != "TaskRun" { + t.Errorf("Got a child reference of kind %s, but expected TaskRun", pr.Status.ChildReferences[0].Kind) + } + taskRunName := pr.Status.ChildReferences[0].Name + + tr, err := c.TaskRunClient.Get(ctx, taskRunName, metav1.GetOptions{}) + if err != nil { + t.Fatalf("Failed to get TaskRun %q: %v", taskRunName, err) + } + if !isFailed(t, taskRunName, tr.Status.Conditions) { t.Errorf("TaskRun status %q is not failed", taskRunName) } } diff --git a/test/v1alpha1/retry_test.go b/test/v1alpha1/retry_test.go index aeb08ea853f..2f750b5b54a 100644 --- a/test/v1alpha1/retry_test.go +++ b/test/v1alpha1/retry_test.go @@ -24,6 +24,10 @@ import ( "testing" "time" + "github.com/tektoncd/pipeline/test" + + "github.com/tektoncd/pipeline/pkg/apis/config" + "github.com/tektoncd/pipeline/test/parse" corev1 "k8s.io/api/core/v1" @@ -43,6 +47,12 @@ func TestTaskRunRetry(t *testing.T) { knativetest.CleanupOnInterrupt(func() { tearDown(ctx, t, c, namespace) }, t.Logf) defer tearDown(ctx, t, c, namespace) + // Skip if the "embedded-status" feature flag has any value but "full". + embeddedStatusValue := test.GetEmbeddedStatusValue(ctx, t, c.KubeClient) + if embeddedStatusValue != config.FullEmbeddedStatus { + t.Skipf("Skipping because 'embedded-status' feature flag value is %s, not %s", embeddedStatusValue, config.FullEmbeddedStatus) + } + // Create a PipelineRun with a single TaskRun that can only fail, // configured to retry 5 times. pipelineRunName := "retry-pipeline"