diff --git a/pkg/cmd/pipelinerun/logs_test.go b/pkg/cmd/pipelinerun/logs_test.go index 9c63ded9f1..3ca5f4b688 100644 --- a/pkg/cmd/pipelinerun/logs_test.go +++ b/pkg/cmd/pipelinerun/logs_test.go @@ -34,6 +34,7 @@ import ( pipelinetest "github.com/tektoncd/pipeline/test/v1alpha1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/dynamic" "knative.dev/pkg/apis" @@ -258,13 +259,13 @@ func TestPipelinerunLogs(t *testing.T) { }, } - trs := []*v1alpha1.TaskRun{ + trs := []*v1beta1.TaskRun{ { ObjectMeta: metav1.ObjectMeta{ Namespace: ns, Name: tr1Name, }, - Spec: v1alpha1.TaskRunSpec{ + Spec: v1beta1.TaskRunSpec{ TaskRef: &v1beta1.TaskRef{ Name: task1Name, }, @@ -307,7 +308,7 @@ func TestPipelinerunLogs(t *testing.T) { Namespace: ns, Name: tr2Name, }, - Spec: v1alpha1.TaskRunSpec{ + Spec: v1beta1.TaskRunSpec{ TaskRef: &v1beta1.TaskRef{ Name: task2Name, }, @@ -347,23 +348,36 @@ func TestPipelinerunLogs(t *testing.T) { }, } - prs := []*v1alpha1.PipelineRun{ + prs := []*v1beta1.PipelineRun{ { ObjectMeta: metav1.ObjectMeta{ Name: prName, Namespace: ns, Labels: map[string]string{"tekton.dev/pipeline": prName}, }, - Spec: v1alpha1.PipelineRunSpec{ - PipelineRef: &v1alpha1.PipelineRef{ + Spec: v1beta1.PipelineRunSpec{ + PipelineRef: &v1beta1.PipelineRef{ Name: pipelineName, }, }, - Status: v1alpha1.PipelineRunStatus{ - PipelineRunStatusFields: v1alpha1.PipelineRunStatusFields{ - TaskRuns: map[string]*v1alpha1.PipelineRunTaskRunStatus{ - tr1Name: {PipelineTaskName: task1Name, Status: &trs[0].Status}, - tr2Name: {PipelineTaskName: task2Name, Status: &trs[1].Status}, + Status: v1beta1.PipelineRunStatus{ + PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ + ChildReferences: []v1beta1.ChildStatusReference{ + { + Name: tr1Name, + PipelineTaskName: task1Name, + TypeMeta: runtime.TypeMeta{ + APIVersion: "tekton.dev/v1beta1", + Kind: "TaskRun", + }, + }, { + Name: tr2Name, + PipelineTaskName: task2Name, + TypeMeta: runtime.TypeMeta{ + APIVersion: "tekton.dev/v1beta1", + Kind: "TaskRun", + }, + }, }, }, Status: duckv1beta1.Status{ @@ -377,23 +391,23 @@ func TestPipelinerunLogs(t *testing.T) { }, }, } - pps := []*v1alpha1.Pipeline{ + pps := []*v1beta1.Pipeline{ { ObjectMeta: metav1.ObjectMeta{ Name: pipelineName, Namespace: ns, }, - Spec: v1alpha1.PipelineSpec{ - Tasks: []v1alpha1.PipelineTask{ + Spec: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{ { Name: task1Name, - TaskRef: &v1alpha1.TaskRef{ + TaskRef: &v1beta1.TaskRef{ Name: task1Name, }, }, { Name: task2Name, - TaskRef: &v1alpha1.TaskRef{ + TaskRef: &v1beta1.TaskRef{ Name: task2Name, }, }, @@ -532,19 +546,19 @@ func TestPipelinerunLogs(t *testing.T) { for _, s := range scenarios { t.Run(s.name, func(t *testing.T) { - cs, _ := test.SeedTestData(t, pipelinetest.Data{PipelineRuns: prs, Pipelines: pps, TaskRuns: trs, Pods: p, Namespaces: nsList}) + cs, _ := test.SeedV1beta1TestData(t, pipelinev1beta1test.Data{PipelineRuns: prs, Pipelines: pps, TaskRuns: trs, Pods: p, Namespaces: nsList}) cs.Pipeline.Resources = cb.APIResourceList(versionA1, []string{"task", "taskrun", "pipeline", "pipelinerun"}) tdc := testDynamic.Options{} dc, err := tdc.Client( - cb.UnstructuredP(pps[0], versionA1), - cb.UnstructuredPR(prs[0], versionA1), - cb.UnstructuredTR(trs[0], versionA1), - cb.UnstructuredTR(trs[1], versionA1), + cb.UnstructuredV1beta1P(pps[0], versionA1), + cb.UnstructuredV1beta1PR(prs[0], versionA1), + cb.UnstructuredV1beta1TR(trs[0], versionA1), + cb.UnstructuredV1beta1TR(trs[1], versionA1), ) if err != nil { t.Errorf("unable to create dynamic client: %v", err) } - prlo := logOptsv1aplha1(prName, ns, cs, dc, fake.Streamer(fakeLogs), s.allSteps, false, s.prefixing, s.tasks...) + prlo := logOptsv1beta1(prName, ns, cs, dc, fake.Streamer(fakeLogs), s.allSteps, false, s.prefixing, s.tasks...) output, _ := fetchLogs(prlo) expected := strings.Join(s.expectedLogs, "\n") + "\n" diff --git a/pkg/pipelinerun/pipelinerun.go b/pkg/pipelinerun/pipelinerun.go index bae2788e44..742fa31faa 100644 --- a/pkg/pipelinerun/pipelinerun.go +++ b/pkg/pipelinerun/pipelinerun.go @@ -26,6 +26,8 @@ import ( prsort "github.com/tektoncd/cli/pkg/pipelinerun/sort" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + "github.com/tektoncd/pipeline/pkg/client/clientset/versioned" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -71,8 +73,8 @@ func List(c *cli.Clients, opts metav1.ListOptions, ns string) (*v1beta1.Pipeline return nil, err } - var runs *v1beta1.PipelineRunList - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredPR.UnstructuredContent(), &runs); err != nil { + var prList *v1beta1.PipelineRunList + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredPR.UnstructuredContent(), &prList); err != nil { return nil, err } if err != nil { @@ -80,7 +82,19 @@ func List(c *cli.Clients, opts metav1.ListOptions, ns string) (*v1beta1.Pipeline return nil, err } - return runs, nil + var populatedPRs []v1beta1.PipelineRun + + for _, pr := range prList.Items { + updatedPR, err := populatePipelineRunTaskStatuses(c, ns, pr) + if err != nil { + return nil, err + } + populatedPRs = append(populatedPRs, *updatedPR) + } + + prList.Items = populatedPRs + + return prList, nil } // It will fetch the resource based on the api available and return v1beta1 form @@ -117,7 +131,13 @@ func GetV1beta1(c *cli.Clients, prname string, opts metav1.GetOptions, ns string fmt.Fprintf(os.Stderr, "Failed to get pipelinerun from %s namespace \n", ns) return nil, err } - return pipelinerun, nil + + populatedPR, err := populatePipelineRunTaskStatuses(c, ns, *pipelinerun) + if err != nil { + return nil, err + } + + return populatedPR, nil } // It will fetch the resource in v1alpha1 struct format @@ -209,3 +229,71 @@ func createUnstructured(obj runtime.Object, c *cli.Clients, opts metav1.CreateOp return pipelinerun, nil } + +func populatePipelineRunTaskStatuses(c *cli.Clients, ns string, pr v1beta1.PipelineRun) (*v1beta1.PipelineRun, error) { + taskRunMap, runMap, err := getFullPipelineTaskStatuses(context.Background(), c.Tekton, ns, &pr) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to get TaskRun and Run statuses for PipelineRun %s from namespace %s\n", pr.Name, ns) + return nil, err + } + pr.Status.TaskRuns = taskRunMap + pr.Status.Runs = runMap + + return &pr, nil +} + +// getFullPipelineTaskStatuses returns populated TaskRun and Run status maps for a PipelineRun from its ChildReferences. +// If the PipelineRun has no ChildReferences, its .Status.TaskRuns and .Status.Runs will be returned instead. +// TODO(abayer): Remove in favor of github.com/tektoncd/pipeline/pkg/status.GetFullPipelineTaskStatuses when CLI can move to Pipeline v0.36.0 or later. +func getFullPipelineTaskStatuses(ctx context.Context, client versioned.Interface, ns string, pr *v1beta1.PipelineRun) (map[string]*v1beta1.PipelineRunTaskRunStatus, + map[string]*v1beta1.PipelineRunRunStatus, error) { + // If the PipelineRun is nil, just return + if pr == nil { + return nil, nil, nil + } + + // If there are no child references or either TaskRuns or Runs is non-zero, return the existing TaskRuns and Runs maps + if len(pr.Status.ChildReferences) == 0 || len(pr.Status.TaskRuns) > 0 || len(pr.Status.Runs) > 0 { + return pr.Status.TaskRuns, pr.Status.Runs, nil + } + + trStatuses := make(map[string]*v1beta1.PipelineRunTaskRunStatus) + runStatuses := make(map[string]*v1beta1.PipelineRunRunStatus) + + for _, cr := range pr.Status.ChildReferences { + switch cr.Kind { + case "TaskRun": + tr, err := client.TektonV1beta1().TaskRuns(ns).Get(ctx, cr.Name, metav1.GetOptions{}) + if err != nil && !errors.IsNotFound(err) { + return nil, nil, err + } + + trStatuses[cr.Name] = &v1beta1.PipelineRunTaskRunStatus{ + PipelineTaskName: cr.PipelineTaskName, + WhenExpressions: cr.WhenExpressions, + } + + if tr != nil { + trStatuses[cr.Name].Status = &tr.Status + } + case "Run": + r, err := client.TektonV1alpha1().Runs(ns).Get(ctx, cr.Name, metav1.GetOptions{}) + if err != nil && !errors.IsNotFound(err) { + return nil, nil, err + } + + runStatuses[cr.Name] = &v1beta1.PipelineRunRunStatus{ + PipelineTaskName: cr.PipelineTaskName, + WhenExpressions: cr.WhenExpressions, + } + + if r != nil { + runStatuses[cr.Name].Status = &r.Status + } + default: + // Don't do anything for unknown types. + } + } + + return trStatuses, runStatuses, nil +} diff --git a/pkg/pipelinerun/pipelinerun_test.go b/pkg/pipelinerun/pipelinerun_test.go index 1e13057992..3a6a04751d 100644 --- a/pkg/pipelinerun/pipelinerun_test.go +++ b/pkg/pipelinerun/pipelinerun_test.go @@ -15,9 +15,11 @@ package pipelinerun import ( + "fmt" "testing" "time" + "github.com/google/go-cmp/cmp" "github.com/jonboulle/clockwork" "github.com/tektoncd/cli/pkg/test" cb "github.com/tektoncd/cli/pkg/test/builder" @@ -25,9 +27,12 @@ import ( "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" pipelinev1beta1test "github.com/tektoncd/pipeline/test" + "github.com/tektoncd/pipeline/test/diff" + "github.com/tektoncd/pipeline/test/parse" pipelinetest "github.com/tektoncd/pipeline/test/v1alpha1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/apis" duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1" ) @@ -38,6 +43,7 @@ func TestPipelineRunsList_with_single_run(t *testing.T) { runDuration := 1 * time.Minute prdata := []*v1alpha1.PipelineRun{ + { ObjectMeta: metav1.ObjectMeta{ Name: "pipelinerun", @@ -459,6 +465,376 @@ func TestPipelineRunGet_v1beta1(t *testing.T) { test.AssertOutput(t, "pipelinerun1", got.Name) } +func TestPipelineRunGet_MinimalEmbeddedStatus(t *testing.T) { + version := "v1beta1" + clock := clockwork.NewFakeClock() + pr1Started := clock.Now().Add(10 * time.Second) + runDuration := 1 * time.Minute + + prdata := []*v1beta1.PipelineRun{ + parse.MustParsePipelineRun(t, fmt.Sprintf(` +metadata: + name: pipelinerun1 + namespace: ns + labels: + tekton.dev/pipeline: pipeline +spec: + pipelineRef: + name: pipeline +status: + conditions: + - lastTransitionTime: null + message: All Tasks have completed executing + reason: Succeeded + status: "True" + type: Succeeded + startTime: %s + completionTime: %s + childReferences: + - apiVersion: tekton.dev/v1beta1 + kind: TaskRun + name: task-run-1 + pipelineTaskName: tr1 + - apiVersion: tekton.dev/v1beta1 + kind: TaskRun + name: task-run-2 + pipelineTaskName: tr2 + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: run-1 + pipelineTaskName: r1 + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: run-2 + pipelineTaskName: r2 +`, pr1Started.Format(time.RFC3339), pr1Started.Add(runDuration).Format(time.RFC3339))), + parse.MustParsePipelineRun(t, fmt.Sprintf(` +metadata: + name: pipelinerun2 + namespace: ns + labels: + tekton.dev/pipeline: pipeline +spec: + pipelineRef: + name: pipeline +status: + conditions: + - lastTransitionTime: null + message: All Tasks have completed executing + reason: Succeeded + status: "True" + type: Succeeded + startTime: %s + completionTime: %s + taskRuns: + task-run-1: + pipelineTaskName: tr1 + status: + conditions: + - reason: Succeeded + status: "True" + type: Succeeded + task-run-2: + pipelineTaskName: tr2 + status: + conditions: + - reason: Failed + status: "False" + type: Succeeded + runs: + run-1: + pipelineTaskName: r1 + status: + conditions: + - reason: Succeeded + status: "True" + type: Succeeded + run-2: + pipelineTaskName: r2 + status: + conditions: + - reason: Failed + status: "False" + type: Succeeded +`, pr1Started.Format(time.RFC3339), pr1Started.Add(runDuration).Format(time.RFC3339))), + } + + trData := []*v1beta1.TaskRun{ + parse.MustParseTaskRun(t, ` +metadata: + name: task-run-1 + namespace: ns +spec: + taskRef: + name: someTask +status: + conditions: + - reason: Succeeded + status: "True" + type: Succeeded +`), + parse.MustParseTaskRun(t, ` +metadata: + name: task-run-2 + namespace: ns +spec: + taskRef: + name: someTask +status: + conditions: + - reason: Failed + status: "False" + type: Succeeded +`), + } + + runsData := []*v1alpha1.Run{ + parse.MustParseRun(t, ` +metadata: + name: run-1 + namespace: ns +spec: + ref: + name: someCustomTask +status: + conditions: + - reason: Succeeded + status: "True" + type: Succeeded +`), + parse.MustParseRun(t, ` +metadata: + name: run-2 + namespace: ns +spec: + ref: + name: someCustomTask +status: + conditions: + - reason: Failed + status: "False" + type: Succeeded +`), + } + + cs, _ := test.SeedV1beta1TestData(t, pipelinev1beta1test.Data{ + PipelineRuns: prdata, + TaskRuns: trData, + Runs: runsData, + }) + + cs.Pipeline.Resources = cb.APIResourceList(version, []string{"pipelinerun"}) + tdc := testDynamic.Options{} + dc, err := tdc.Client( + cb.UnstructuredV1beta1PR(prdata[0], version), + cb.UnstructuredV1beta1PR(prdata[1], version), + ) + if err != nil { + t.Errorf("unable to create dynamic client: %v", err) + } + + p := &test.Params{Tekton: cs.Pipeline, Clock: clock, Kube: cs.Kube, Dynamic: dc} + c, err := p.Clients() + if err != nil { + t.Errorf("unable to create client: %v", err) + } + + got, err := Get(c, "pipelinerun1", metav1.GetOptions{}, "ns") + if err != nil { + t.Errorf("unexpected Error") + } + test.AssertOutput(t, "pipelinerun1", got.Name) + + tr1 := got.Status.TaskRuns[trData[0].Name] + if tr1 == nil { + t.Fatalf("TaskRun status map does not contain expected TaskRun %s", trData[0].Name) + } + test.AssertOutput(t, string(v1beta1.TaskRunReasonSuccessful), tr1.Status.GetCondition(apis.ConditionSucceeded).Reason) + tr2 := got.Status.TaskRuns[trData[1].Name] + if tr2 == nil { + t.Fatalf("TaskRun status map does not contain expected TaskRun %s", trData[1].Name) + } + test.AssertOutput(t, string(v1beta1.TaskRunReasonFailed), tr2.Status.GetCondition(apis.ConditionSucceeded).Reason) + + r1 := got.Status.Runs[runsData[0].Name] + if r1 == nil { + t.Fatalf("Run status map does not contain expected Run %s", runsData[0].Name) + } + test.AssertOutput(t, "Succeeded", r1.Status.GetCondition(apis.ConditionSucceeded).Reason) + r2 := got.Status.Runs[runsData[1].Name] + if r2 == nil { + t.Fatalf("Run status map does not contain expected Run %s", runsData[1].Name) + } + test.AssertOutput(t, "Failed", r2.Status.GetCondition(apis.ConditionSucceeded).Reason) + + gotFull, err := Get(c, "pipelinerun2", metav1.GetOptions{}, "ns") + if err != nil { + t.Errorf("unexpected Error") + } + + if d := cmp.Diff(got.Status.TaskRuns, gotFull.Status.TaskRuns); d != "" { + t.Errorf("mismatch between minimal and full TaskRun statuses: %s", diff.PrintWantGot(d)) + } + if d := cmp.Diff(got.Status.Runs, gotFull.Status.Runs); d != "" { + t.Errorf("mismatch between minimal and full Run statuses: %s", diff.PrintWantGot(d)) + } +} + +func TestPipelineRunList_MinimalEmbeddedStatus(t *testing.T) { + version := "v1beta1" + clock := clockwork.NewFakeClock() + pr1Started := clock.Now().Add(10 * time.Second) + runDuration := 1 * time.Minute + + prdata := []*v1beta1.PipelineRun{parse.MustParsePipelineRun(t, fmt.Sprintf(` +metadata: + name: pipelinerun1 + namespace: ns + labels: + tekton.dev/pipeline: pipeline +spec: + pipelineRef: + name: pipeline +status: + conditions: + - lastTransitionTime: null + message: All Tasks have completed executing + reason: Succeeded + status: "True" + type: Succeeded + startTime: %s + completionTime: %s + childReferences: + - apiVersion: tekton.dev/v1beta1 + kind: TaskRun + name: task-run-1 + pipelineTaskName: tr1 + - apiVersion: tekton.dev/v1beta1 + kind: TaskRun + name: task-run-2 + pipelineTaskName: tr2 + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: run-1 + pipelineTaskName: r1 + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: run-2 + pipelineTaskName: r2 +`, pr1Started.Format(time.RFC3339), pr1Started.Add(runDuration).Format(time.RFC3339))), + } + + trData := []*v1beta1.TaskRun{ + parse.MustParseTaskRun(t, ` +metadata: + name: task-run-1 + namespace: ns +spec: + taskRef: + name: someTask +status: + conditions: + - reason: Succeeded + status: "True" + type: Succeeded +`), + parse.MustParseTaskRun(t, ` +metadata: + name: task-run-2 + namespace: ns +spec: + taskRef: + name: someTask +status: + conditions: + - reason: Failed + status: "False" + type: Succeeded +`), + } + + runsData := []*v1alpha1.Run{ + parse.MustParseRun(t, ` +metadata: + name: run-1 + namespace: ns +spec: + ref: + name: someCustomTask +status: + conditions: + - reason: Succeeded + status: "True" + type: Succeeded +`), + parse.MustParseRun(t, ` +metadata: + name: run-2 + namespace: ns +spec: + ref: + name: someCustomTask +status: + conditions: + - reason: Failed + status: "False" + type: Succeeded +`), + } + + cs, _ := test.SeedV1beta1TestData(t, pipelinev1beta1test.Data{ + PipelineRuns: prdata, + TaskRuns: trData, + Runs: runsData, + }) + + cs.Pipeline.Resources = cb.APIResourceList(version, []string{"pipelinerun"}) + tdc := testDynamic.Options{} + dc, err := tdc.Client( + cb.UnstructuredV1beta1PR(prdata[0], version), + ) + if err != nil { + t.Errorf("unable to create dynamic client: %v", err) + } + + p := &test.Params{Tekton: cs.Pipeline, Clock: clock, Kube: cs.Kube, Dynamic: dc} + c, err := p.Clients() + if err != nil { + t.Errorf("unable to create client: %v", err) + } + + prList, err := List(c, metav1.ListOptions{}, "ns") + if err != nil { + t.Errorf("unexpected Error") + } + test.AssertOutput(t, 1, len(prList.Items)) + + got := prList.Items[0] + test.AssertOutput(t, "pipelinerun1", got.Name) + + tr1 := got.Status.TaskRuns[trData[0].Name] + if tr1 == nil { + t.Fatalf("TaskRun status map does not contain expected TaskRun %s", trData[0].Name) + } + test.AssertOutput(t, string(v1beta1.TaskRunReasonSuccessful), tr1.Status.GetCondition(apis.ConditionSucceeded).Reason) + tr2 := got.Status.TaskRuns[trData[1].Name] + if tr2 == nil { + t.Fatalf("TaskRun status map does not contain expected TaskRun %s", trData[1].Name) + } + test.AssertOutput(t, string(v1beta1.TaskRunReasonFailed), tr2.Status.GetCondition(apis.ConditionSucceeded).Reason) + + r1 := got.Status.Runs[runsData[0].Name] + if r1 == nil { + t.Fatalf("Run status map does not contain expected Run %s", runsData[0].Name) + } + test.AssertOutput(t, "Succeeded", r1.Status.GetCondition(apis.ConditionSucceeded).Reason) + r2 := got.Status.Runs[runsData[1].Name] + if r2 == nil { + t.Fatalf("Run status map does not contain expected Run %s", runsData[1].Name) + } + test.AssertOutput(t, "Failed", r2.Status.GetCondition(apis.ConditionSucceeded).Reason) +} + func TestPipelineRunCreate(t *testing.T) { version := "v1alpha1" prdata := v1beta1.PipelineRun{ diff --git a/pkg/pipelinerun/tracker.go b/pkg/pipelinerun/tracker.go index e04c57f4af..0157d928a1 100644 --- a/pkg/pipelinerun/tracker.go +++ b/pkg/pipelinerun/tracker.go @@ -93,7 +93,14 @@ func (t *Tracker) Monitor(allowed []string) <-chan []trh.Run { if !ok || pr == nil { return } + + trMap, runMap, err := getFullPipelineTaskStatuses(context.Background(), t.Tekton, t.Ns, pr) + if err != nil { + return + } pr.DeepCopyInto(&pipelinerunConverted) + pipelinerunConverted.Status.TaskRuns = trMap + pipelinerunConverted.Status.Runs = runMap } trC <- t.findNewTaskruns(&pipelinerunConverted, allowed) diff --git a/pkg/pipelinerun/tracker_test.go b/pkg/pipelinerun/tracker_test.go index 21eb66e52c..0319f43298 100644 --- a/pkg/pipelinerun/tracker_test.go +++ b/pkg/pipelinerun/tracker_test.go @@ -18,16 +18,18 @@ import ( "testing" "time" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/tektoncd/cli/pkg/actions" trh "github.com/tektoncd/cli/pkg/taskrun" "github.com/tektoncd/cli/pkg/test" - clitest "github.com/tektoncd/cli/pkg/test" cb "github.com/tektoncd/cli/pkg/test/builder" - "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" "github.com/tektoncd/pipeline/pkg/client/clientset/versioned" - pipelinetest "github.com/tektoncd/pipeline/test/v1alpha1" + pipelinev1beta1test "github.com/tektoncd/pipeline/test" + "github.com/tektoncd/pipeline/test/diff" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/watch" @@ -54,13 +56,15 @@ func TestTracker_pipelinerun_complete(t *testing.T) { ) scenarios := []struct { - name string - tasks []string - expected []trh.Run + name string + tasks []string + fullEmbeddedStatus bool + expected []trh.Run }{ { - name: "for all tasks", - tasks: allTasks, + name: "for all tasks, full status", + tasks: allTasks, + fullEmbeddedStatus: true, expected: []trh.Run{ { Name: tr1Name, @@ -70,10 +74,33 @@ func TestTracker_pipelinerun_complete(t *testing.T) { Task: task2Name, }, }, - }, - { - name: "for one task", - tasks: onlyTask1, + }, { + name: "for one task, full status", + tasks: onlyTask1, + fullEmbeddedStatus: true, + expected: []trh.Run{ + { + Name: tr1Name, + Task: task1Name, + }, + }, + }, { + name: "for all tasks, minimal status", + tasks: allTasks, + fullEmbeddedStatus: false, + expected: []trh.Run{ + { + Name: tr1Name, + Task: task1Name, + }, { + Name: tr2Name, + Task: task2Name, + }, + }, + }, { + name: "for one task, minimal status", + tasks: onlyTask1, + fullEmbeddedStatus: false, expected: []trh.Run{ { Name: tr1Name, @@ -84,19 +111,19 @@ func TestTracker_pipelinerun_complete(t *testing.T) { } for _, s := range scenarios { - taskruns := []*v1alpha1.TaskRun{ + taskruns := []*v1beta1.TaskRun{ { ObjectMeta: metav1.ObjectMeta{ Name: tr1Name, Namespace: ns, }, - Spec: v1alpha1.TaskRunSpec{ + Spec: v1beta1.TaskRunSpec{ TaskRef: &v1beta1.TaskRef{ Name: task1Name, }, }, - Status: v1alpha1.TaskRunStatus{ - TaskRunStatusFields: v1alpha1.TaskRunStatusFields{ + Status: v1beta1.TaskRunStatus{ + TaskRunStatusFields: v1beta1.TaskRunStatusFields{ PodName: tr1Pod, }, }, @@ -106,20 +133,20 @@ func TestTracker_pipelinerun_complete(t *testing.T) { Name: tr2Name, Namespace: ns, }, - Spec: v1alpha1.TaskRunSpec{ + Spec: v1beta1.TaskRunSpec{ TaskRef: &v1beta1.TaskRef{ Name: task2Name, }, }, - Status: v1alpha1.TaskRunStatus{ - TaskRunStatusFields: v1alpha1.TaskRunStatusFields{ + Status: v1beta1.TaskRunStatus{ + TaskRunStatusFields: v1beta1.TaskRunStatusFields{ PodName: tr2Pod, }, }, }, } - initialPR := []*v1alpha1.PipelineRun{ + initialPR := []*v1beta1.PipelineRun{ { ObjectMeta: metav1.ObjectMeta{ Name: prName, @@ -135,16 +162,27 @@ func TestTracker_pipelinerun_complete(t *testing.T) { }, }, }, - PipelineRunStatusFields: v1alpha1.PipelineRunStatusFields{ - TaskRuns: map[string]*v1alpha1.PipelineRunTaskRunStatus{ - tr1Name: {PipelineTaskName: task1Name, Status: &taskruns[0].Status}, - }, - }, + PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{}, }, }, } - pr := &v1alpha1.PipelineRun{ - Status: v1alpha1.PipelineRunStatus{ + if s.fullEmbeddedStatus { + initialPR[0].Status.TaskRuns = map[string]*v1beta1.PipelineRunTaskRunStatus{ + tr1Name: {PipelineTaskName: task1Name, Status: &taskruns[0].Status}, + } + } else { + initialPR[0].Status.ChildReferences = []v1beta1.ChildStatusReference{{ + Name: tr1Name, + PipelineTaskName: task1Name, + TypeMeta: runtime.TypeMeta{ + APIVersion: "tekton.dev/v1beta1", + Kind: "TaskRun", + }, + }} + } + + pr := &v1beta1.PipelineRun{ + Status: v1beta1.PipelineRunStatus{ Status: duckv1beta1.Status{ Conditions: duckv1beta1.Conditions{ { @@ -153,23 +191,48 @@ func TestTracker_pipelinerun_complete(t *testing.T) { }, }, }, - PipelineRunStatusFields: v1alpha1.PipelineRunStatusFields{ - TaskRuns: map[string]*v1alpha1.PipelineRunTaskRunStatus{ + PipelineRunStatusFields: v1beta1.PipelineRunStatusFields{ + TaskRuns: map[string]*v1beta1.PipelineRunTaskRunStatus{ tr1Name: {PipelineTaskName: task1Name, Status: &taskruns[0].Status}, tr2Name: {PipelineTaskName: task2Name, Status: &taskruns[1].Status}, }, }, }, } + if s.fullEmbeddedStatus { + initialPR[0].Status.TaskRuns = map[string]*v1beta1.PipelineRunTaskRunStatus{ + tr1Name: {PipelineTaskName: task1Name, Status: &taskruns[0].Status}, + tr2Name: {PipelineTaskName: task2Name, Status: &taskruns[1].Status}, + } + } else { + initialPR[0].Status.ChildReferences = []v1beta1.ChildStatusReference{{ + Name: tr1Name, + PipelineTaskName: task1Name, + TypeMeta: runtime.TypeMeta{ + APIVersion: "tekton.dev/v1beta1", + Kind: "TaskRun", + }, + }, { + Name: tr2Name, + PipelineTaskName: task2Name, + TypeMeta: runtime.TypeMeta{ + APIVersion: "tekton.dev/v1beta1", + Kind: "TaskRun", + }, + }} + } - tc := startPipelineRun(t, pipelinetest.Data{PipelineRuns: initialPR, TaskRuns: taskruns}, pr.Status) + tc := startPipelineRun(t, pipelinev1beta1test.Data{PipelineRuns: initialPR, TaskRuns: taskruns}, pr.Status) tracker := NewTracker(pipelineName, ns, tc) if err := actions.InitializeAPIGroupRes(tracker.Tekton.Discovery()); err != nil { t.Errorf("failed to initialize APIGroup Resource") } output := taskRunsFor(s.tasks, tracker) - clitest.AssertOutput(t, s.expected, output) + if d := cmp.Diff(s.expected, output, cmpopts.SortSlices(func(i, j trh.Run) bool { return i.Name < j.Name })); d != "" { + t.Errorf("Unexpected output: %s", diff.PrintWantGot(d)) + } + } } @@ -181,13 +244,13 @@ func taskRunsFor(onlyTasks []string, tracker *Tracker) []trh.Run { return output } -func startPipelineRun(t *testing.T, data pipelinetest.Data, prStatus ...v1alpha1.PipelineRunStatus) versioned.Interface { - cs, _ := test.SeedTestData(t, data) +func startPipelineRun(t *testing.T, data pipelinev1beta1test.Data, prStatus ...v1beta1.PipelineRunStatus) versioned.Interface { + cs, _ := test.SeedV1beta1TestData(t, data) // to keep pushing the taskrun over the period(simulate watch) watcher := watch.NewFake() cs.Pipeline.PrependWatchReactor("pipelineruns", k8stest.DefaultWatchReactor(watcher, nil)) - cs.Pipeline.Resources = cb.APIResourceList("v1alpha1", []string{"task", "taskrun", "pipeline", "pipelinerun"}) + cs.Pipeline.Resources = cb.APIResourceList("v1beta1", []string{"task", "taskrun", "pipeline", "pipelinerun"}) go func() { for _, status := range prStatus { time.Sleep(time.Second * 2) diff --git a/test/e2e-tests.sh b/test/e2e-tests.sh index b594b13796..937f6d3b75 100755 --- a/test/e2e-tests.sh +++ b/test/e2e-tests.sh @@ -38,6 +38,12 @@ tkn() { fi } +function set_minimal_embedded_status() { + jsonpatch=$(printf "{\"data\": {\"embedded-status\": \"%s\"}}" "minimal") + echo "feature-flags ConfigMap patch: ${jsonpatch}" + kubectl patch configmap feature-flags -n tekton-pipelines -p "$jsonpatch" +} + kubectl get crds|grep tekton\\.dev && fail_test "TektonCD CRDS should not be installed, you should reset them before each runs" # command before creation of resources @@ -172,4 +178,13 @@ fi go_test_e2e ./test/e2e/... || failed=1 (( failed )) && fail_test +# Re-run the PipelineRun tests with "embedded-status" set to "minimal" +header "Running Go e2e tests with Pipeline embedded-status set to minimal" + +set_minimal_embedded_status +failed=0 + +go_test_e2e ./test/e2e/pipelinerun/... || failed=1 +(( failed )) && fail_test + success diff --git a/tools/go.mod b/tools/go.mod index c6b05022f9..ffecaa0aa8 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -1,4 +1,4 @@ -module github.com/tektoncd/pipeline/tools +module github.com/tektoncd/cli/tools go 1.17 diff --git a/vendor/github.com/tektoncd/pipeline/test/diff/print.go b/vendor/github.com/tektoncd/pipeline/test/diff/print.go new file mode 100644 index 0000000000..f38803c348 --- /dev/null +++ b/vendor/github.com/tektoncd/pipeline/test/diff/print.go @@ -0,0 +1,27 @@ +/* +Copyright 2020 The Tekton Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package diff + +import "fmt" + +// PrintWantGot takes a diff string generated by cmp.Diff and returns it +// in a consistent format for reuse across all of our tests. This +// func assumes that the order of arguments passed to cmp.Diff was +// (want, got) or, in other words, the expectedResult then the actualResult. +func PrintWantGot(diff string) string { + return fmt.Sprintf("(-want, +got): %s", diff) +} diff --git a/vendor/github.com/tektoncd/pipeline/test/parse/yaml.go b/vendor/github.com/tektoncd/pipeline/test/parse/yaml.go new file mode 100644 index 0000000000..6693a6d124 --- /dev/null +++ b/vendor/github.com/tektoncd/pipeline/test/parse/yaml.go @@ -0,0 +1,151 @@ +/* + Copyright 2021 The Tekton Authors + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package parse + +import ( + "testing" + + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" + resourcev1alpha1 "github.com/tektoncd/pipeline/pkg/apis/resource/v1alpha1" + + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + "github.com/tektoncd/pipeline/pkg/client/clientset/versioned/scheme" + "k8s.io/apimachinery/pkg/runtime" +) + +// MustParseTaskRun takes YAML and parses it into a *v1beta1.TaskRun +func MustParseTaskRun(t *testing.T, yaml string) *v1beta1.TaskRun { + var tr v1beta1.TaskRun + yaml = `apiVersion: tekton.dev/v1beta1 +kind: TaskRun +` + yaml + mustParseYAML(t, yaml, &tr) + return &tr +} + +// MustParseAlphaTaskRun takes YAML and parses it into a *v1alpha1.TaskRun +func MustParseAlphaTaskRun(t *testing.T, yaml string) *v1alpha1.TaskRun { + var tr v1alpha1.TaskRun + yaml = `apiVersion: tekton.dev/v1alpha1 +kind: TaskRun +` + yaml + mustParseYAML(t, yaml, &tr) + return &tr +} + +// MustParseRun takes YAML and parses it into a *v1alpha1.Run +func MustParseRun(t *testing.T, yaml string) *v1alpha1.Run { + var r v1alpha1.Run + yaml = `apiVersion: tekton.dev/v1alpha1 +kind: Run +` + yaml + mustParseYAML(t, yaml, &r) + return &r +} + +// MustParseTask takes YAML and parses it into a *v1beta1.Task +func MustParseTask(t *testing.T, yaml string) *v1beta1.Task { + var task v1beta1.Task + yaml = `apiVersion: tekton.dev/v1beta1 +kind: Task +` + yaml + mustParseYAML(t, yaml, &task) + return &task +} + +// MustParseClusterTask takes YAML and parses it into a *v1beta1.ClusterTask +func MustParseClusterTask(t *testing.T, yaml string) *v1beta1.ClusterTask { + var clusterTask v1beta1.ClusterTask + yaml = `apiVersion: tekton.dev/v1beta1 +kind: ClusterTask +` + yaml + mustParseYAML(t, yaml, &clusterTask) + return &clusterTask +} + +// MustParseAlphaTask takes YAML and parses it into a *v1alpha1.Task +func MustParseAlphaTask(t *testing.T, yaml string) *v1alpha1.Task { + var task v1alpha1.Task + yaml = `apiVersion: tekton.dev/v1alpha1 +kind: Task +` + yaml + mustParseYAML(t, yaml, &task) + return &task +} + +// MustParsePipelineRun takes YAML and parses it into a *v1beta1.PipelineRun +func MustParsePipelineRun(t *testing.T, yaml string) *v1beta1.PipelineRun { + var pr v1beta1.PipelineRun + yaml = `apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +` + yaml + mustParseYAML(t, yaml, &pr) + return &pr +} + +// MustParseAlphaPipelineRun takes YAML and parses it into a *v1alpha1.PipelineRun +func MustParseAlphaPipelineRun(t *testing.T, yaml string) *v1alpha1.PipelineRun { + var pr v1alpha1.PipelineRun + yaml = `apiVersion: tekton.dev/v1alpha1 +kind: PipelineRun +` + yaml + mustParseYAML(t, yaml, &pr) + return &pr +} + +// MustParsePipeline takes YAML and parses it into a *v1beta1.Pipeline +func MustParsePipeline(t *testing.T, yaml string) *v1beta1.Pipeline { + var pipeline v1beta1.Pipeline + yaml = `apiVersion: tekton.dev/v1beta1 +kind: Pipeline +` + yaml + mustParseYAML(t, yaml, &pipeline) + return &pipeline +} + +// MustParseAlphaPipeline takes YAML and parses it into a *v1alpha1.Pipeline +func MustParseAlphaPipeline(t *testing.T, yaml string) *v1alpha1.Pipeline { + var pipeline v1alpha1.Pipeline + yaml = `apiVersion: tekton.dev/v1alpha1 +kind: Pipeline +` + yaml + mustParseYAML(t, yaml, &pipeline) + return &pipeline +} + +// MustParsePipelineResource takes YAML and parses it into a *resourcev1alpha1.PipelineResource +func MustParsePipelineResource(t *testing.T, yaml string) *resourcev1alpha1.PipelineResource { + var resource resourcev1alpha1.PipelineResource + yaml = `apiVersion: tekton.dev/v1alpha1 +kind: PipelineResource +` + yaml + mustParseYAML(t, yaml, &resource) + return &resource +} + +// MustParseCondition takes YAML and parses it into a *v1alpha1.Condition +func MustParseCondition(t *testing.T, yaml string) *v1alpha1.Condition { + var cond v1alpha1.Condition + yaml = `apiVersion: tekton.dev/v1alpha1 +kind: Condition +` + yaml + mustParseYAML(t, yaml, &cond) + return &cond +} + +func mustParseYAML(t *testing.T, yaml string, i runtime.Object) { + if _, _, err := scheme.Codecs.UniversalDeserializer().Decode([]byte(yaml), nil, i); err != nil { + t.Fatalf("mustParseYAML (%s): %v", yaml, err) + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 3df2cc1ab0..c939035a7b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1419,6 +1419,8 @@ github.com/tektoncd/pipeline/pkg/remote github.com/tektoncd/pipeline/pkg/remote/oci github.com/tektoncd/pipeline/pkg/substitution github.com/tektoncd/pipeline/test +github.com/tektoncd/pipeline/test/diff +github.com/tektoncd/pipeline/test/parse github.com/tektoncd/pipeline/test/v1alpha1 # github.com/tektoncd/plumbing v0.0.0-20220329085922-d765a5cba75f ## explicit; go 1.13