diff --git a/docs/cmd/tkn_pipelinerun_list.md b/docs/cmd/tkn_pipelinerun_list.md index 7f26e2f9c2..d8787c677e 100644 --- a/docs/cmd/tkn_pipelinerun_list.md +++ b/docs/cmd/tkn_pipelinerun_list.md @@ -28,6 +28,7 @@ List all PipelineRuns in a namespace 'foo': ### Options ``` + -A, --all-namespaces list pipelineruns from all namespaces --allow-missing-template-keys If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats. (default true) -h, --help help for list --label string A selector (label query) to filter on, supports '=', '==', and '!=' diff --git a/docs/cmd/tkn_taskrun_list.md b/docs/cmd/tkn_taskrun_list.md index 035da7d48b..2fee88e38a 100644 --- a/docs/cmd/tkn_taskrun_list.md +++ b/docs/cmd/tkn_taskrun_list.md @@ -28,6 +28,7 @@ List all TaskRuns of Task 'foo' in namespace 'bar': ### Options ``` + -A, --all-namespaces list taskruns from all namespaces --allow-missing-template-keys If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats. (default true) -h, --help help for list --label string A selector (label query) to filter on, supports '=', '==', and '!=' diff --git a/docs/man/man1/tkn-pipelinerun-list.1 b/docs/man/man1/tkn-pipelinerun-list.1 index a893a5f732..5def1488d8 100644 --- a/docs/man/man1/tkn-pipelinerun-list.1 +++ b/docs/man/man1/tkn-pipelinerun-list.1 @@ -19,6 +19,10 @@ Lists pipelineruns in a namespace .SH OPTIONS +.PP +\fB\-A\fP, \fB\-\-all\-namespaces\fP[=false] + list pipelineruns from all namespaces + .PP \fB\-\-allow\-missing\-template\-keys\fP[=true] If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats. diff --git a/docs/man/man1/tkn-taskrun-list.1 b/docs/man/man1/tkn-taskrun-list.1 index b2002a2d95..d6f5950893 100644 --- a/docs/man/man1/tkn-taskrun-list.1 +++ b/docs/man/man1/tkn-taskrun-list.1 @@ -19,6 +19,10 @@ Lists TaskRuns in a namespace .SH OPTIONS +.PP +\fB\-A\fP, \fB\-\-all\-namespaces\fP[=false] + list taskruns from all namespaces + .PP \fB\-\-allow\-missing\-template\-keys\fP[=true] If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats. diff --git a/pkg/cmd/pipelinerun/list.go b/pkg/cmd/pipelinerun/list.go index 33c398ba75..429eb21ddc 100644 --- a/pkg/cmd/pipelinerun/list.go +++ b/pkg/cmd/pipelinerun/list.go @@ -18,6 +18,7 @@ import ( "fmt" "os" "text/tabwriter" + "text/template" "github.com/jonboulle/clockwork" "github.com/spf13/cobra" @@ -32,14 +33,30 @@ import ( cliopts "k8s.io/cli-runtime/pkg/genericclioptions" ) -const ( - emptyMsg = "No pipelineruns found" -) +const listTemplate = `{{- $prl := len .PipelineRuns.Items }}{{ if eq $prl 0 -}} +No PipelineRuns found +{{- else -}} +{{- if $.AllNamespaces }}NAMESPACE NAME STARTED DURATION STATUS +{{- else -}} +NAME STARTED DURATION STATUS +{{- end }} +{{- range $_, $pr := .PipelineRuns.Items }} +{{- if $pr }} +{{- if $.AllNamespaces }} +{{ $pr.Namespace }} {{ $pr.Name }} {{ formatAge $pr.Status.StartTime $.Time }} {{ formatDuration $pr.Status.StartTime $pr.Status.CompletionTime }} {{ formatCondition $pr.Status.Conditions }} +{{- else -}} +{{ printf "\n" }}{{ $pr.Name }} {{ formatAge $pr.Status.StartTime $.Time }} {{ formatDuration $pr.Status.StartTime $pr.Status.CompletionTime }} {{ formatCondition $pr.Status.Conditions }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +` type ListOptions struct { Limit int LabelSelector string Reverse bool + AllNamespaces bool } func listCommand(p cli.Params) *cobra.Command { @@ -79,7 +96,7 @@ List all PipelineRuns in a namespace 'foo': return nil } - prs, err := list(p, pipeline, opts.Limit, opts.LabelSelector) + prs, err := list(p, pipeline, opts.Limit, opts.LabelSelector, opts.AllNamespaces) if err != nil { fmt.Fprintf(os.Stderr, "Failed to list pipelineruns from %s namespace \n", p.Namespace()) return err @@ -113,7 +130,7 @@ List all PipelineRuns in a namespace 'foo': } if prs != nil { - err = printFormatted(stream, prs, p.Time()) + err = printFormatted(stream, prs, p.Time(), opts.AllNamespaces) } if err != nil { @@ -128,10 +145,12 @@ List all PipelineRuns in a namespace 'foo': c.Flags().IntVarP(&opts.Limit, "limit", "", 0, "limit pipelineruns listed (default: return all pipelineruns)") c.Flags().StringVarP(&opts.LabelSelector, "label", "", opts.LabelSelector, "A selector (label query) to filter on, supports '=', '==', and '!='") c.Flags().BoolVarP(&opts.Reverse, "reverse", "", opts.Reverse, "list pipelineruns in reverse order") + c.Flags().BoolVarP(&opts.AllNamespaces, "all-namespaces", "A", opts.AllNamespaces, "list pipelineruns from all namespaces") + return c } -func list(p cli.Params, pipeline string, limit int, labelselector string) (*v1alpha1.PipelineRunList, error) { +func list(p cli.Params, pipeline string, limit int, labelselector string, allnamespaces bool) (*v1alpha1.PipelineRunList, error) { var selector string var options v1.ListOptions @@ -156,7 +175,11 @@ func list(p cli.Params, pipeline string, limit int, labelselector string) (*v1al } } - prc := cs.Tekton.TektonV1alpha1().PipelineRuns(p.Namespace()) + ns := p.Namespace() + if allnamespaces { + ns = "" + } + prc := cs.Tekton.TektonV1alpha1().PipelineRuns(ns) prs, err := prc.List(options) if err != nil { return nil, err @@ -165,7 +188,11 @@ func list(p cli.Params, pipeline string, limit int, labelselector string) (*v1al prslen := len(prs.Items) if prslen != 0 { - prsort.SortByStartTime(prs.Items) + if allnamespaces { + prsort.SortByNamespace(prs.Items) + } else { + prsort.SortByStartTime(prs.Items) + } } // If greater than maximum amount of pipelineruns, return all pipelineruns by setting limit to default @@ -201,22 +228,30 @@ func reverse(prs *v1alpha1.PipelineRunList) { prs.Items = prItems } -func printFormatted(s *cli.Stream, prs *v1alpha1.PipelineRunList, c clockwork.Clock) error { - if len(prs.Items) == 0 { - fmt.Fprintln(s.Err, emptyMsg) - return nil +func printFormatted(s *cli.Stream, prs *v1alpha1.PipelineRunList, c clockwork.Clock, allnamespaces bool) error { + + var data = struct { + PipelineRuns *v1alpha1.PipelineRunList + Time clockwork.Clock + AllNamespaces bool + }{ + PipelineRuns: prs, + Time: c, + AllNamespaces: allnamespaces, + } + + funcMap := template.FuncMap{ + "formatAge": formatted.Age, + "formatDuration": formatted.Duration, + "formatCondition": formatted.Condition, } w := tabwriter.NewWriter(s.Out, 0, 5, 3, ' ', tabwriter.TabIndent) - fmt.Fprintln(w, "NAME\tSTARTED\tDURATION\tSTATUS\t") - for _, pr := range prs.Items { - - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t\n", - pr.Name, - formatted.Age(pr.Status.StartTime, c), - formatted.Duration(pr.Status.StartTime, pr.Status.CompletionTime), - formatted.Condition(pr.Status.Conditions), - ) + t := template.Must(template.New("List PipelineRuns").Funcs(funcMap).Parse(listTemplate)) + + err := t.Execute(w, data) + if err != nil { + return err } return w.Flush() diff --git a/pkg/cmd/pipelinerun/list_test.go b/pkg/cmd/pipelinerun/list_test.go index e9227caf6a..6ef7def5dd 100644 --- a/pkg/cmd/pipelinerun/list_test.go +++ b/pkg/cmd/pipelinerun/list_test.go @@ -87,6 +87,17 @@ func TestListPipelineRuns(t *testing.T) { ), } + prsMultipleNs := []*v1alpha1.PipelineRun{ + tb.PipelineRun("pr4-1", "namespace-tout", + tb.PipelineRunLabel("tekton.dev/pipeline", "random"), + tb.PipelineRunStatus(), + ), + tb.PipelineRun("pr4-2", "namespace-lacher", + tb.PipelineRunLabel("tekton.dev/pipeline", "random"), + tb.PipelineRunStatus(), + ), + } + ns := []*corev1.Namespace{ { ObjectMeta: metav1.ObjectMeta{ @@ -186,6 +197,12 @@ func TestListPipelineRuns(t *testing.T) { args: []string{"list", "--reverse", "-n", "namespace", "-o", "jsonpath={range .items[*]}{.metadata.name}{\"\\n\"}{end}"}, wantError: false, }, + { + name: "print pipelineruns in all namespaces", + command: command(t, prsMultipleNs, clock.Now(), ns), + args: []string{"list", "--all-namespaces"}, + wantError: false, + }, } for _, td := range tests { @@ -218,7 +235,7 @@ func TestListPipeline_empty(t *testing.T) { t.Errorf("Unexpected error: %v", err) } - test.AssertOutput(t, emptyMsg+"\n", output) + test.AssertOutput(t, "No PipelineRuns found\n", output) } func command(t *testing.T, prs []*v1alpha1.PipelineRun, now time.Time, ns []*corev1.Namespace) *cobra.Command { diff --git a/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-all_in_namespace.golden b/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-all_in_namespace.golden index c37b7030d9..46ea87b841 100644 --- a/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-all_in_namespace.golden +++ b/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-all_in_namespace.golden @@ -1,6 +1,6 @@ -NAME STARTED DURATION STATUS -pr0-1 --- --- --- -pr3-1 --- --- --- -pr1-1 59 minutes ago 1 minute Succeeded -pr2-2 2 hours ago 1 minute Failed -pr2-1 3 hours ago --- Succeeded(Running) +NAME STARTED DURATION STATUS +pr0-1 --- --- --- +pr3-1 --- --- --- +pr1-1 59 minutes ago 1 minute Succeeded +pr2-2 2 hours ago 1 minute Failed +pr2-1 3 hours ago --- Succeeded(Running) diff --git a/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-by_pipeline_name.golden b/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-by_pipeline_name.golden index 76796290c5..640117ebe9 100644 --- a/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-by_pipeline_name.golden +++ b/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-by_pipeline_name.golden @@ -1,2 +1,2 @@ -NAME STARTED DURATION STATUS -pr1-1 59 minutes ago 1 minute Succeeded +NAME STARTED DURATION STATUS +pr1-1 59 minutes ago 1 minute Succeeded diff --git a/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-filter_pipelineruns_by_label.golden b/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-filter_pipelineruns_by_label.golden index 9af28b98c2..d6090e15b8 100644 --- a/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-filter_pipelineruns_by_label.golden +++ b/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-filter_pipelineruns_by_label.golden @@ -1,2 +1,2 @@ -NAME STARTED DURATION STATUS -pr3-1 --- --- --- +NAME STARTED DURATION STATUS +pr3-1 --- --- --- diff --git a/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-filter_pipelineruns_by_label_with_in_query.golden b/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-filter_pipelineruns_by_label_with_in_query.golden index b0a0c0c6e7..c326a0402d 100644 --- a/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-filter_pipelineruns_by_label_with_in_query.golden +++ b/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-filter_pipelineruns_by_label_with_in_query.golden @@ -1,3 +1,3 @@ -NAME STARTED DURATION STATUS -pr3-1 --- --- --- -pr2-2 2 hours ago 1 minute Failed +NAME STARTED DURATION STATUS +pr3-1 --- --- --- +pr2-2 2 hours ago 1 minute Failed diff --git a/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-limit_pipelineruns_greater_than_maximum_case.golden b/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-limit_pipelineruns_greater_than_maximum_case.golden index c37b7030d9..46ea87b841 100644 --- a/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-limit_pipelineruns_greater_than_maximum_case.golden +++ b/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-limit_pipelineruns_greater_than_maximum_case.golden @@ -1,6 +1,6 @@ -NAME STARTED DURATION STATUS -pr0-1 --- --- --- -pr3-1 --- --- --- -pr1-1 59 minutes ago 1 minute Succeeded -pr2-2 2 hours ago 1 minute Failed -pr2-1 3 hours ago --- Succeeded(Running) +NAME STARTED DURATION STATUS +pr0-1 --- --- --- +pr3-1 --- --- --- +pr1-1 59 minutes ago 1 minute Succeeded +pr2-2 2 hours ago 1 minute Failed +pr2-1 3 hours ago --- Succeeded(Running) diff --git a/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-limit_pipelineruns_returned_to_1.golden b/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-limit_pipelineruns_returned_to_1.golden index 53a5d11a01..6d24f19973 100644 --- a/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-limit_pipelineruns_returned_to_1.golden +++ b/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-limit_pipelineruns_returned_to_1.golden @@ -1,2 +1,2 @@ -NAME STARTED DURATION STATUS -pr0-1 --- --- --- +NAME STARTED DURATION STATUS +pr0-1 --- --- --- diff --git a/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-print_in_reverse.golden b/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-print_in_reverse.golden index c4ef571633..f4ccfbeccb 100644 --- a/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-print_in_reverse.golden +++ b/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-print_in_reverse.golden @@ -1,6 +1,6 @@ -NAME STARTED DURATION STATUS -pr2-1 3 hours ago --- Succeeded(Running) -pr2-2 2 hours ago 1 minute Failed -pr1-1 59 minutes ago 1 minute Succeeded -pr3-1 --- --- --- -pr0-1 --- --- --- +NAME STARTED DURATION STATUS +pr2-1 3 hours ago --- Succeeded(Running) +pr2-2 2 hours ago 1 minute Failed +pr1-1 59 minutes ago 1 minute Succeeded +pr3-1 --- --- --- +pr0-1 --- --- --- diff --git a/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-print_pipelineruns_in_all_namespaces.golden b/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-print_pipelineruns_in_all_namespaces.golden new file mode 100644 index 0000000000..841d2e2523 --- /dev/null +++ b/pkg/cmd/pipelinerun/testdata/TestListPipelineRuns-print_pipelineruns_in_all_namespaces.golden @@ -0,0 +1,3 @@ +NAMESPACE NAME STARTED DURATION STATUS +namespace-lacher pr4-2 --- --- --- +namespace-tout pr4-1 --- --- --- diff --git a/pkg/cmd/taskrun/list.go b/pkg/cmd/taskrun/list.go index 5d9368e48e..a134d2cf85 100644 --- a/pkg/cmd/taskrun/list.go +++ b/pkg/cmd/taskrun/list.go @@ -18,11 +18,13 @@ import ( "fmt" "os" "text/tabwriter" + "text/template" + + "github.com/tektoncd/cli/pkg/formatted" "github.com/jonboulle/clockwork" "github.com/spf13/cobra" "github.com/tektoncd/cli/pkg/cli" - "github.com/tektoncd/cli/pkg/formatted" "github.com/tektoncd/cli/pkg/printer" trsort "github.com/tektoncd/cli/pkg/taskrun/sort" "github.com/tektoncd/cli/pkg/validate" @@ -32,14 +34,28 @@ import ( cliopts "k8s.io/cli-runtime/pkg/genericclioptions" ) -const ( - emptyMsg = "No TaskRuns found" -) +const listTemplate = `{{- $trl := len .TaskRuns.Items }}{{ if eq $trl 0 -}} +No TaskRuns found +{{- else -}} +{{- if $.AllNamespaces }}NAMESPACE NAME STARTED DURATION STATUS +{{- else -}} +NAME STARTED DURATION STATUS +{{- end }} +{{- range $_, $tr := .TaskRuns.Items }}{{- if $tr }}{{- if $.AllNamespaces }} +{{ $tr.Namespace }} {{ $tr.Name }} {{ formatAge $tr.Status.StartTime $.Time }} {{ formatDuration $tr.Status.StartTime $tr.Status.CompletionTime }} {{ formatCondition $tr.Status.Conditions }} +{{- else -}} +{{ printf "\n" }}{{ $tr.Name }} {{ formatAge $tr.Status.StartTime $.Time }} {{ formatDuration $tr.Status.StartTime $tr.Status.CompletionTime }} {{ formatCondition $tr.Status.Conditions }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +` type ListOptions struct { Limit int LabelSelector string Reverse bool + AllNamespaces bool } func listCommand(p cli.Params) *cobra.Command { @@ -79,7 +95,7 @@ List all TaskRuns of Task 'foo' in namespace 'bar': return nil } - trs, err := list(p, task, opts.Limit, opts.LabelSelector) + trs, err := list(p, task, opts.Limit, opts.LabelSelector, opts.AllNamespaces) if err != nil { fmt.Fprintf(os.Stderr, "Failed to list taskruns from %s namespace \n", p.Namespace()) return err @@ -113,7 +129,7 @@ List all TaskRuns of Task 'foo' in namespace 'bar': } if trs != nil { - err = printFormatted(stream, trs, p.Time()) + err = printFormatted(stream, trs, p.Time(), opts.AllNamespaces) } if err != nil { @@ -129,6 +145,7 @@ List all TaskRuns of Task 'foo' in namespace 'bar': c.Flags().IntVarP(&opts.Limit, "limit", "", 0, "limit taskruns listed (default: return all taskruns)") c.Flags().StringVarP(&opts.LabelSelector, "label", "", opts.LabelSelector, "A selector (label query) to filter on, supports '=', '==', and '!='") c.Flags().BoolVarP(&opts.Reverse, "reverse", "", opts.Reverse, "list taskruns in reverse order") + c.Flags().BoolVarP(&opts.AllNamespaces, "all-namespaces", "A", opts.AllNamespaces, "list taskruns from all namespaces") return c } @@ -144,7 +161,7 @@ func reverse(trs *v1alpha1.TaskRunList) { trs.Items = trItems } -func list(p cli.Params, task string, limit int, labelselector string) (*v1alpha1.TaskRunList, error) { +func list(p cli.Params, task string, limit int, labelselector string, allnamespaces bool) (*v1alpha1.TaskRunList, error) { var selector string var options v1.ListOptions @@ -169,7 +186,11 @@ func list(p cli.Params, task string, limit int, labelselector string) (*v1alpha1 } } - trc := cs.Tekton.TektonV1alpha1().TaskRuns(p.Namespace()) + ns := p.Namespace() + if allnamespaces { + ns = "" + } + trc := cs.Tekton.TektonV1alpha1().TaskRuns(ns) trs, err := trc.List(options) if err != nil { return nil, err @@ -178,7 +199,11 @@ func list(p cli.Params, task string, limit int, labelselector string) (*v1alpha1 trslen := len(trs.Items) if trslen != 0 { - trsort.SortByStartTime(trs.Items) + if allnamespaces { + trsort.SortByNamespace(trs.Items) + } else { + trsort.SortByStartTime(trs.Items) + } } // If greater than maximum amount of taskruns, return all taskruns by setting limit to default @@ -202,22 +227,31 @@ func list(p cli.Params, task string, limit int, labelselector string) (*v1alpha1 return trs, nil } -func printFormatted(s *cli.Stream, trs *v1alpha1.TaskRunList, c clockwork.Clock) error { - if len(trs.Items) == 0 { - fmt.Fprintln(s.Err, emptyMsg) - return nil +func printFormatted(s *cli.Stream, trs *v1alpha1.TaskRunList, c clockwork.Clock, allnamespaces bool) error { + + var data = struct { + TaskRuns *v1alpha1.TaskRunList + Time clockwork.Clock + AllNamespaces bool + }{ + TaskRuns: trs, + Time: c, + AllNamespaces: allnamespaces, + } + + funcMap := template.FuncMap{ + "formatAge": formatted.Age, + "formatDuration": formatted.Duration, + "formatCondition": formatted.Condition, } w := tabwriter.NewWriter(s.Out, 0, 5, 3, ' ', tabwriter.TabIndent) - fmt.Fprintln(w, "NAME\tSTARTED\tDURATION\tSTATUS\t") - for _, tr := range trs.Items { - - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t\n", - tr.Name, - formatted.Age(tr.Status.StartTime, c), - formatted.Duration(tr.Status.StartTime, tr.Status.CompletionTime), - formatted.Condition(tr.Status.Conditions), - ) + t := template.Must(template.New("List TaskRuns").Funcs(funcMap).Parse(listTemplate)) + + err := t.Execute(w, data) + if err != nil { + return err } + return w.Flush() } diff --git a/pkg/cmd/taskrun/list_test.go b/pkg/cmd/taskrun/list_test.go index 4840666ecb..462431ddd3 100644 --- a/pkg/cmd/taskrun/list_test.go +++ b/pkg/cmd/taskrun/list_test.go @@ -98,6 +98,29 @@ func TestListTaskRuns(t *testing.T) { ), } + trsMultipleNs := []*v1alpha1.TaskRun{ + tb.TaskRun("tr4-1", "tout", + tb.TaskRunLabel("tekton.dev/task", "random"), + tb.TaskRunSpec(tb.TaskRunTaskRef("random")), + tb.TaskRunStatus( + tb.StatusCondition(apis.Condition{ + Status: corev1.ConditionTrue, + Reason: resources.ReasonSucceeded, + }), + ), + ), + tb.TaskRun("tr4-2", "lacher", + tb.TaskRunLabel("tekton.dev/task", "random"), + tb.TaskRunSpec(tb.TaskRunTaskRef("random")), + tb.TaskRunStatus( + tb.StatusCondition(apis.Condition{ + Status: corev1.ConditionTrue, + Reason: resources.ReasonSucceeded, + }), + ), + ), + } + ns := []*corev1.Namespace{ { ObjectMeta: metav1.ObjectMeta{ @@ -206,6 +229,12 @@ func TestListTaskRuns(t *testing.T) { args: []string{"list", "--reverse", "-n", "foo", "-o", "jsonpath={range .items[*]}{.metadata.name}{\"\\n\"}{end}"}, wantError: false, }, + { + name: "print taskrunsruns in all namespaces", + command: command(t, trsMultipleNs, now, ns), + args: []string{"list", "--all-namespaces", "-n", "foo"}, + wantError: false, + }, } for _, td := range tests { diff --git a/pkg/cmd/taskrun/testdata/TestListTaskRuns-all_in_namespace.golden b/pkg/cmd/taskrun/testdata/TestListTaskRuns-all_in_namespace.golden index ee226a3c0a..f28c236ac5 100644 --- a/pkg/cmd/taskrun/testdata/TestListTaskRuns-all_in_namespace.golden +++ b/pkg/cmd/taskrun/testdata/TestListTaskRuns-all_in_namespace.golden @@ -1,6 +1,6 @@ -NAME STARTED DURATION STATUS -tr0-1 --- --- Succeeded -tr3-1 --- --- Failed -tr2-2 59 minutes ago 1 minute Failed -tr1-1 1 hour ago 1 minute Succeeded -tr2-1 1 hour ago --- Running +NAME STARTED DURATION STATUS +tr0-1 --- --- Succeeded +tr3-1 --- --- Failed +tr2-2 59 minutes ago 1 minute Failed +tr1-1 1 hour ago 1 minute Succeeded +tr2-1 1 hour ago --- Running diff --git a/pkg/cmd/taskrun/testdata/TestListTaskRuns-by_Task_name.golden b/pkg/cmd/taskrun/testdata/TestListTaskRuns-by_Task_name.golden index 7c34e36202..925e532d96 100644 --- a/pkg/cmd/taskrun/testdata/TestListTaskRuns-by_Task_name.golden +++ b/pkg/cmd/taskrun/testdata/TestListTaskRuns-by_Task_name.golden @@ -1,2 +1,2 @@ -NAME STARTED DURATION STATUS -tr1-1 1 hour ago 1 minute Succeeded +NAME STARTED DURATION STATUS +tr1-1 1 hour ago 1 minute Succeeded diff --git a/pkg/cmd/taskrun/testdata/TestListTaskRuns-filter_taskruns_by_label.golden b/pkg/cmd/taskrun/testdata/TestListTaskRuns-filter_taskruns_by_label.golden index a158b665c8..0794a70777 100644 --- a/pkg/cmd/taskrun/testdata/TestListTaskRuns-filter_taskruns_by_label.golden +++ b/pkg/cmd/taskrun/testdata/TestListTaskRuns-filter_taskruns_by_label.golden @@ -1,2 +1,2 @@ -NAME STARTED DURATION STATUS -tr3-1 --- --- Failed +NAME STARTED DURATION STATUS +tr3-1 --- --- Failed diff --git a/pkg/cmd/taskrun/testdata/TestListTaskRuns-filter_taskruns_by_label_with_in_query.golden b/pkg/cmd/taskrun/testdata/TestListTaskRuns-filter_taskruns_by_label_with_in_query.golden index d0542a21e3..d1aa365518 100644 --- a/pkg/cmd/taskrun/testdata/TestListTaskRuns-filter_taskruns_by_label_with_in_query.golden +++ b/pkg/cmd/taskrun/testdata/TestListTaskRuns-filter_taskruns_by_label_with_in_query.golden @@ -1,3 +1,3 @@ -NAME STARTED DURATION STATUS -tr3-1 --- --- Failed -tr2-2 59 minutes ago 1 minute Failed +NAME STARTED DURATION STATUS +tr3-1 --- --- Failed +tr2-2 59 minutes ago 1 minute Failed diff --git a/pkg/cmd/taskrun/testdata/TestListTaskRuns-limit_taskruns_greater_than_maximum_case.golden b/pkg/cmd/taskrun/testdata/TestListTaskRuns-limit_taskruns_greater_than_maximum_case.golden index ee226a3c0a..f28c236ac5 100644 --- a/pkg/cmd/taskrun/testdata/TestListTaskRuns-limit_taskruns_greater_than_maximum_case.golden +++ b/pkg/cmd/taskrun/testdata/TestListTaskRuns-limit_taskruns_greater_than_maximum_case.golden @@ -1,6 +1,6 @@ -NAME STARTED DURATION STATUS -tr0-1 --- --- Succeeded -tr3-1 --- --- Failed -tr2-2 59 minutes ago 1 minute Failed -tr1-1 1 hour ago 1 minute Succeeded -tr2-1 1 hour ago --- Running +NAME STARTED DURATION STATUS +tr0-1 --- --- Succeeded +tr3-1 --- --- Failed +tr2-2 59 minutes ago 1 minute Failed +tr1-1 1 hour ago 1 minute Succeeded +tr2-1 1 hour ago --- Running diff --git a/pkg/cmd/taskrun/testdata/TestListTaskRuns-limit_taskruns_returned_to_1.golden b/pkg/cmd/taskrun/testdata/TestListTaskRuns-limit_taskruns_returned_to_1.golden index 81555e3e7f..a61982f97e 100644 --- a/pkg/cmd/taskrun/testdata/TestListTaskRuns-limit_taskruns_returned_to_1.golden +++ b/pkg/cmd/taskrun/testdata/TestListTaskRuns-limit_taskruns_returned_to_1.golden @@ -1,2 +1,2 @@ -NAME STARTED DURATION STATUS -tr0-1 --- --- Succeeded +NAME STARTED DURATION STATUS +tr0-1 --- --- Succeeded diff --git a/pkg/cmd/taskrun/testdata/TestListTaskRuns-print_in_reverse.golden b/pkg/cmd/taskrun/testdata/TestListTaskRuns-print_in_reverse.golden index 0fb52daed7..f324092ba0 100644 --- a/pkg/cmd/taskrun/testdata/TestListTaskRuns-print_in_reverse.golden +++ b/pkg/cmd/taskrun/testdata/TestListTaskRuns-print_in_reverse.golden @@ -1,6 +1,6 @@ -NAME STARTED DURATION STATUS -tr2-1 1 hour ago --- Running -tr1-1 1 hour ago 1 minute Succeeded -tr2-2 59 minutes ago 1 minute Failed -tr3-1 --- --- Failed -tr0-1 --- --- Succeeded +NAME STARTED DURATION STATUS +tr2-1 1 hour ago --- Running +tr1-1 1 hour ago 1 minute Succeeded +tr2-2 59 minutes ago 1 minute Failed +tr3-1 --- --- Failed +tr0-1 --- --- Succeeded diff --git a/pkg/cmd/taskrun/testdata/TestListTaskRuns-print_taskrunsruns_in_all_namespaces.golden b/pkg/cmd/taskrun/testdata/TestListTaskRuns-print_taskrunsruns_in_all_namespaces.golden new file mode 100644 index 0000000000..24ba423004 --- /dev/null +++ b/pkg/cmd/taskrun/testdata/TestListTaskRuns-print_taskrunsruns_in_all_namespaces.golden @@ -0,0 +1,3 @@ +NAMESPACE NAME STARTED DURATION STATUS +lacher tr4-2 --- --- Succeeded +tout tr4-1 --- --- Succeeded diff --git a/pkg/cmd/taskrun/testdata/TestListTaskRuns_no_condition.golden b/pkg/cmd/taskrun/testdata/TestListTaskRuns_no_condition.golden index 8d8c3cf78e..17ea11c70d 100644 --- a/pkg/cmd/taskrun/testdata/TestListTaskRuns_no_condition.golden +++ b/pkg/cmd/taskrun/testdata/TestListTaskRuns_no_condition.golden @@ -1,2 +1,2 @@ -NAME STARTED DURATION STATUS -tr1-1 1 hour ago 1 minute --- +NAME STARTED DURATION STATUS +tr1-1 1 hour ago 1 minute --- diff --git a/pkg/pipelinerun/sort/by_namespace.go b/pkg/pipelinerun/sort/by_namespace.go new file mode 100644 index 0000000000..38faf47cf6 --- /dev/null +++ b/pkg/pipelinerun/sort/by_namespace.go @@ -0,0 +1,48 @@ +// 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 pipelinerun + +import ( + "sort" + + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" +) + +func SortByNamespace(prs []v1alpha1.PipelineRun) { + sort.Sort(byNamespace(prs)) +} + +type byNamespace []v1alpha1.PipelineRun + +func (prs byNamespace) compareNamespace(ins, jns string) (lt, eq bool) { + lt, eq = ins < jns, ins == jns + return lt, eq +} + +func (prs byNamespace) Len() int { return len(prs) } +func (prs byNamespace) Swap(i, j int) { prs[i], prs[j] = prs[j], prs[i] } +func (prs byNamespace) Less(i, j int) bool { + var lt, eq bool + if lt, eq = prs.compareNamespace(prs[i].Namespace, prs[j].Namespace); eq { + if prs[j].Status.StartTime == nil { + return false + } + if prs[i].Status.StartTime == nil { + return true + } + return prs[j].Status.StartTime.Before(prs[i].Status.StartTime) + } + return lt +} diff --git a/pkg/pipelinerun/sort/by_namespace_test.go b/pkg/pipelinerun/sort/by_namespace_test.go new file mode 100644 index 0000000000..dc60cd8530 --- /dev/null +++ b/pkg/pipelinerun/sort/by_namespace_test.go @@ -0,0 +1,177 @@ +// 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 pipelinerun + +import ( + "testing" + "time" + + "github.com/jonboulle/clockwork" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func Test_PipelineRunsByNamespace(t *testing.T) { + pr1 := v1alpha1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "abc", + Name: "pr0-1", + }, + } + + pr2 := v1alpha1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "def", + Name: "pr1-1", + }, + } + + pr3 := v1alpha1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ghi", + Name: "pr2-1", + }, + } + + prs := []v1alpha1.PipelineRun{ + pr2, + pr3, + pr1, + } + + SortByNamespace(prs) + + element1 := prs[0].Name + if element1 != "pr0-1" { + t.Errorf("SortPipelineRunsByNamespace should be pr0-1 but returned: %s", element1) + } + + element2 := prs[1].Name + if element2 != "pr1-1" { + t.Errorf("SortPipelineRunsByNamespace should be pr1-1 but returned: %s", element2) + } + + element3 := prs[2].Name + if element3 != "pr2-1" { + t.Errorf("SortPipelineRunsByNamespace should be pr2-1 but returned: %s", element3) + } +} + +func Test_PipelineRunsByNamespaceWithStartTime(t *testing.T) { + + clock := clockwork.NewFakeClock() + + pr00Started := clock.Now().Add(10 * time.Second) + pr01Started := clock.Now().Add(-1 * time.Hour) + pr10Started := clock.Now().Add(10 * time.Second) + pr11Started := clock.Now().Add(-1 * time.Hour) + pr20Started := clock.Now().Add(10 * time.Second) + pr21Started := clock.Now().Add(-1 * time.Hour) + + pr00 := v1alpha1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "abc", + Name: "pr0-0", + }, + } + + pr00.Status.StartTime = &metav1.Time{Time: pr00Started} + + pr01 := v1alpha1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "abc", + Name: "pr0-1", + }, + } + + pr01.Status.StartTime = &metav1.Time{Time: pr01Started} + + pr10 := v1alpha1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "def", + Name: "pr1-0", + }, + } + + pr10.Status.StartTime = &metav1.Time{Time: pr10Started} + + pr11 := v1alpha1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "def", + Name: "pr1-1", + }, + } + + pr11.Status.StartTime = &metav1.Time{Time: pr11Started} + + pr20 := v1alpha1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ghi", + Name: "pr2-0", + }, + } + + pr20.Status.StartTime = &metav1.Time{Time: pr20Started} + + pr21 := v1alpha1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ghi", + Name: "pr2-1", + }, + } + + pr21.Status.StartTime = &metav1.Time{Time: pr21Started} + + prs := []v1alpha1.PipelineRun{ + pr11, + pr21, + pr01, + pr10, + pr20, + pr00, + } + + SortByNamespace(prs) + + element1 := prs[0].Name + if element1 != "pr0-0" { + t.Errorf("SortPipelineRunsByNamespaceWithStartTime should be pr0-0 but returned: %s", element1) + } + + element2 := prs[1].Name + if element2 != "pr0-1" { + t.Errorf("SortPipelineRunsByNamespaceWithStartTime should be pr0-1 but returned: %s", element2) + } + + element3 := prs[2].Name + if element3 != "pr1-0" { + t.Errorf("SortPipelineRunsByNamespaceWithStartTime should be pr1-0 but returned: %s", element3) + } + + element4 := prs[3].Name + if element4 != "pr1-1" { + t.Errorf("SortPipelineRunsByNamespaceWithStartTime should be pr1-1 but returned: %s", element4) + } + + element5 := prs[4].Name + if element5 != "pr2-0" { + t.Errorf("SortPipelineRunsByNamespaceWithStartTime should be pr2-0 but returned: %s", element5) + } + + element6 := prs[5].Name + if element6 != "pr2-1" { + t.Errorf("SortPipelineRunsByNamespaceWithStartTime should be pr2-1 but returned: %s", element6) + } +} diff --git a/pkg/taskrun/sort/by_namespace.go b/pkg/taskrun/sort/by_namespace.go new file mode 100644 index 0000000000..7af5d9e5a6 --- /dev/null +++ b/pkg/taskrun/sort/by_namespace.go @@ -0,0 +1,33 @@ +package taskrun + +import ( + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" + "sort" +) + +func SortByNamespace(trs []v1alpha1.TaskRun) { + sort.Sort(byNamespace(trs)) +} + +type byNamespace []v1alpha1.TaskRun + +func (prs byNamespace) compareNamespace(ins, jns string) (lt, eq bool) { + lt, eq = ins < jns, ins == jns + return lt, eq +} + +func (trs byNamespace) Len() int { return len(trs) } +func (trs byNamespace) Swap(i, j int) { trs[i], trs[j] = trs[j], trs[i] } +func (trs byNamespace) Less(i, j int) bool { + var lt, eq bool + if lt, eq = trs.compareNamespace(trs[i].Namespace, trs[j].Namespace); eq { + if trs[j].Status.StartTime == nil { + return false + } + if trs[i].Status.StartTime == nil { + return true + } + return trs[j].Status.StartTime.Before(trs[i].Status.StartTime) + } + return lt +} diff --git a/pkg/taskrun/sort/by_namespace_test.go b/pkg/taskrun/sort/by_namespace_test.go new file mode 100644 index 0000000000..be4214d178 --- /dev/null +++ b/pkg/taskrun/sort/by_namespace_test.go @@ -0,0 +1,162 @@ +package taskrun + +import ( + "github.com/jonboulle/clockwork" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "testing" + "time" +) + +func Test_TaskRunsByNamespace(t *testing.T) { + tr1 := v1alpha1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "abc", + Name: "tr0-1", + }, + } + + tr2 := v1alpha1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "def", + Name: "tr1-1", + }, + } + + tr3 := v1alpha1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ghi", + Name: "tr2-1", + }, + } + + trs := []v1alpha1.TaskRun{ + tr2, + tr3, + tr1, + } + + SortByNamespace(trs) + + element1 := trs[0].Name + if element1 != "tr0-1" { + t.Errorf("SortTaskRunsByNamespace should be tr0-1 but returned: %s", element1) + } + + element2 := trs[1].Name + if element2 != "tr1-1" { + t.Errorf("SortTaskRunsByNamespace should be tr1-1 but returned: %s", element2) + } + + element3 := trs[2].Name + if element3 != "tr2-1" { + t.Errorf("SortTaskRunsByNamespace should be tr2-1 but returned: %s", element3) + } +} + +func Test_PipelineRunsByNamespaceWithStartTime(t *testing.T) { + + clock := clockwork.NewFakeClock() + + tr00Started := clock.Now().Add(10 * time.Second) + tr01Started := clock.Now().Add(-1 * time.Hour) + tr10Started := clock.Now().Add(10 * time.Second) + tr11Started := clock.Now().Add(-1 * time.Hour) + tr20Started := clock.Now().Add(10 * time.Second) + tr21Started := clock.Now().Add(-1 * time.Hour) + + tr00 := v1alpha1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "abc", + Name: "tr0-0", + }, + } + + tr00.Status.StartTime = &metav1.Time{Time: tr00Started} + + tr01 := v1alpha1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "abc", + Name: "tr0-1", + }, + } + + tr01.Status.StartTime = &metav1.Time{Time: tr01Started} + + tr10 := v1alpha1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "def", + Name: "tr1-0", + }, + } + + tr10.Status.StartTime = &metav1.Time{Time: tr10Started} + + tr11 := v1alpha1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "def", + Name: "tr1-1", + }, + } + + tr11.Status.StartTime = &metav1.Time{Time: tr11Started} + + tr20 := v1alpha1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ghi", + Name: "tr2-0", + }, + } + + tr20.Status.StartTime = &metav1.Time{Time: tr20Started} + + tr21 := v1alpha1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ghi", + Name: "tr2-1", + }, + } + + tr21.Status.StartTime = &metav1.Time{Time: tr21Started} + + trs := []v1alpha1.TaskRun{ + tr11, + tr21, + tr01, + tr10, + tr20, + tr00, + } + + SortByNamespace(trs) + + element1 := trs[0].Name + if element1 != "tr0-0" { + t.Errorf("SortTaskRunsByNamespaceWithStartTime should be tr0-0 but returned: %s", element1) + } + + element2 := trs[1].Name + if element2 != "tr0-1" { + t.Errorf("SortTaskRunsByNamespaceWithStartTime should be tr0-1 but returned: %s", element2) + } + + element3 := trs[2].Name + if element3 != "tr1-0" { + t.Errorf("SortTaskRunsByNamespaceWithStartTime should be tr1-0 but returned: %s", element3) + } + + element4 := trs[3].Name + if element4 != "tr1-1" { + t.Errorf("SortTaskRunsByNamespaceWithStartTime should be tr1-1 but returned: %s", element4) + } + + element5 := trs[4].Name + if element5 != "tr2-0" { + t.Errorf("SortTaskRunsByNamespaceWithStartTime should be tr2-0 but returned: %s", element5) + } + + element6 := trs[5].Name + if element6 != "tr2-1" { + t.Errorf("SortTaskRunsByNamespaceWithStartTime should be tr2-1 but returned: %s", element6) + } +} diff --git a/test/e2e/build_templates.go b/test/e2e/build_templates.go index 97d6e68157..86d692e6e8 100644 --- a/test/e2e/build_templates.go +++ b/test/e2e/build_templates.go @@ -318,36 +318,51 @@ type TaskRunData struct { Status string } -func ListAllTaskRunsOutput(t *testing.T, cs *Clients, td map[int]interface{}) string { - - const ( - emptyMsg = "No taskruns found" - header = "NAME\tSTARTED\tDURATION\tSTATUS\t" - body = "%s\t%s\t%s\t%s\t\n" - ) +func ListAllTaskRunsOutput(t *testing.T, cs *Clients, allnamespaces bool, td map[int]interface{}) string { + const listTemplate = `{{- $trl := len .TaskRuns.Items }}{{ if eq $trl 0 -}} +No TaskRuns found +{{- else -}} +NAME STARTED DURATION STATUS{{- if $.AllNamespaces }} NAMESPACE{{- end }} +{{- range $_, $tr := .TaskRuns.Items }} +{{- if $tr }} +{{ $tr.Name }} {{ formatAge $tr.Status.StartTime $.Time }} {{ formatDuration $tr.Status.StartTime $tr.Status.CompletionTime }} {{ formatCondition $tr.Status.Conditions }}{{- if $.AllNamespaces }} {{ $tr.Namespace }}{{- end }} +{{- end }} +{{- end }} +{{- end }} +` clock := clockwork.NewFakeClockAt(time.Now()) taskrun := GetTaskRunListWithTestData(t, cs, td) - trsort.SortByStartTime(taskrun.Items) + trslen := len(taskrun.Items) + + if trslen != 0 { + trsort.SortByStartTime(taskrun.Items) + } + var data = struct { + TaskRuns *v1alpha1.TaskRunList + Time clockwork.Clock + AllNamespaces bool + }{ + TaskRuns: taskrun, + Time: clock, + AllNamespaces: allnamespaces, + } + + funcMap := template.FuncMap{ + "formatAge": formatted.Age, + "formatDuration": formatted.Duration, + "formatCondition": formatted.Condition, + } var tmplBytes bytes.Buffer w := tabwriter.NewWriter(&tmplBytes, 0, 5, 3, ' ', tabwriter.TabIndent) + tmp := template.Must(template.New("List TaskRuns").Funcs(funcMap).Parse(listTemplate)) - if len(taskrun.Items) == 0 { - fmt.Fprintln(w, emptyMsg) - w.Flush() - return tmplBytes.String() + err := tmp.Execute(w, data) + if err != nil { + t.Errorf("Error: while parsing template %+v", err) } - fmt.Fprintln(w, header) - for _, tr := range taskrun.Items { - fmt.Fprintf(w, body, - tr.Name, - formatted.Age(tr.Status.StartTime, clock), - formatted.Duration(tr.Status.StartTime, tr.Status.CompletionTime), - formatted.Condition(tr.Status.Conditions), - ) - } w.Flush() return tmplBytes.String() } diff --git a/test/e2e/pipeline_e2e_test.go b/test/e2e/pipeline_e2e_test.go index 3055ee39ad..f37dc09764 100644 --- a/test/e2e/pipeline_e2e_test.go +++ b/test/e2e/pipeline_e2e_test.go @@ -222,7 +222,7 @@ Waiting for logs to be available... res := icmd.RunCmd(run("taskrun", "list", "-n", namespace)) - expected := ListAllTaskRunsOutput(t, c, map[int]interface{}{ + expected := ListAllTaskRunsOutput(t, c, false, map[int]interface{}{ 0: &TaskRunData{ Name: "output-pipeline-run-", Status: "Succeeded",