From 2926cce3625ea3bf0aff20d26aaa4ed3953f8389 Mon Sep 17 00:00:00 2001 From: Vibhav Bobade Date: Sat, 11 Apr 2020 10:47:15 +0530 Subject: [PATCH 1/3] Add --no-headers and --all-namespaces flag for Task `list` subcommand --no-headers flag helps omit the headers or the index row of the output while printing. This allows the user to parse the output without having to deal with the headers in the output. --all-namespaces flag helps to print Tasks from all namespaces. These tasks are grouped by namespace and then sorted based on StartTime --- pkg/cmd/task/list.go | 85 +++++++--- pkg/cmd/task/list_test.go | 155 +++++++++++++++++- ...s_all_namespaces_no_headers_v1beta1.golden | 12 ++ ...t_Only_Tasks_all_namespaces_v1beta1.golden | 13 ++ ...kList_Only_Tasks_no_headers_v1beta1.golden | 6 + .../TestTaskList_Only_Tasks_v1alpha1.golden | 2 +- 6 files changed, 246 insertions(+), 27 deletions(-) create mode 100644 pkg/cmd/task/testdata/TestTaskList_Only_Tasks_all_namespaces_no_headers_v1beta1.golden create mode 100644 pkg/cmd/task/testdata/TestTaskList_Only_Tasks_all_namespaces_v1beta1.golden create mode 100644 pkg/cmd/task/testdata/TestTaskList_Only_Tasks_no_headers_v1beta1.golden diff --git a/pkg/cmd/task/list.go b/pkg/cmd/task/list.go index cb10d5689e..e422647247 100644 --- a/pkg/cmd/task/list.go +++ b/pkg/cmd/task/list.go @@ -18,25 +18,48 @@ import ( "fmt" "os" "text/tabwriter" + "text/template" + + "github.com/jonboulle/clockwork" + "github.com/tektoncd/cli/pkg/task" "github.com/spf13/cobra" "github.com/tektoncd/cli/pkg/actions" "github.com/tektoncd/cli/pkg/cli" "github.com/tektoncd/cli/pkg/formatted" - "github.com/tektoncd/cli/pkg/task" "github.com/tektoncd/cli/pkg/validate" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" cliopts "k8s.io/cli-runtime/pkg/genericclioptions" ) -const ( - emptyMsg = "No tasks found" - header = "NAME\tDESCRIPTION\tAGE" - body = "%s\t%s\t%s\n" -) +const listTemplate = `{{- $tl := len .Tasks.Items }}{{ if eq $tl 0 -}} +No Tasks found +{{ else -}} +{{- if not $.NoHeaders -}} +{{- if $.AllNamespaces -}} +NAMESPACE NAME DESCRIPTION AGE +{{ else -}} +NAME DESCRIPTION AGE +{{ end -}} +{{- end -}} +{{- range $_, $t := .Tasks.Items }}{{- if $t }} +{{- if $.AllNamespaces -}} +{{ $t.Namespace }} {{ $t.Name }} {{ formatDesc $t.Spec.Description }} {{ formatAge $t.CreationTimestamp $.Time }} +{{ else -}} +{{ $t.Name }} {{ formatDesc $t.Spec.Description }} {{ formatAge $t.CreationTimestamp $.Time }} +{{ end }}{{- end }}{{- end }} +{{- end -}} +` + +type ListOptions struct { + AllNamespaces bool + NoHeaders bool +} func listCommand(p cli.Params) *cobra.Command { + opts := &ListOptions{} f := cliopts.NewPrintFlags("list") c := &cobra.Command{ @@ -48,13 +71,13 @@ func listCommand(p cli.Params) *cobra.Command { }, RunE: func(cmd *cobra.Command, args []string) error { - if err := validate.NamespaceExists(p); err != nil { + if err := validate.NamespaceExists(p); err != nil && !opts.AllNamespaces { return err } output, err := cmd.LocalFlags().GetString("output") if err != nil { - fmt.Fprint(os.Stderr, "Error: output option not set properly \n") + fmt.Fprint(os.Stderr, "error: output option not set properly \n") return err } @@ -66,40 +89,56 @@ func listCommand(p cli.Params) *cobra.Command { Out: cmd.OutOrStdout(), Err: cmd.OutOrStderr(), } - return printTaskDetails(stream, p) + return printTaskDetails(stream, p, opts.AllNamespaces, opts.NoHeaders) }, } f.AddFlags(c) + c.Flags().BoolVarP(&opts.AllNamespaces, "all-namespaces", "A", opts.AllNamespaces, "list tasks from all namespaces") + c.Flags().BoolVarP(&opts.NoHeaders, "no-headers", "", opts.NoHeaders, "do not print column headers with output (default print column headers with output)") return c } -func printTaskDetails(s *cli.Stream, p cli.Params) error { +func printTaskDetails(s *cli.Stream, p cli.Params, allnamespaces bool, noheaders bool) error { cs, err := p.Clients() if err != nil { return err } - tasks, err := task.List(cs, metav1.ListOptions{}, p.Namespace()) + ns := p.Namespace() + if allnamespaces { + ns = "" + } + tasks, err := task.List(cs, metav1.ListOptions{}, ns) if err != nil { - fmt.Fprintf(os.Stderr, "Failed to list tasks from %s namespace \n", p.Namespace()) + fmt.Fprintf(os.Stderr, "failed to list tasks from %s namespace \n", ns) return err } - if len(tasks.Items) == 0 { - fmt.Fprintln(s.Err, emptyMsg) - return nil + var data = struct { + Tasks *v1beta1.TaskList + Time clockwork.Clock + AllNamespaces bool + NoHeaders bool + }{ + Tasks: tasks, + Time: p.Time(), + AllNamespaces: allnamespaces, + NoHeaders: noheaders, + } + + funcMap := template.FuncMap{ + "formatAge": formatted.Age, + "formatDesc": formatted.FormatDesc, } w := tabwriter.NewWriter(s.Out, 0, 5, 3, ' ', tabwriter.TabIndent) - fmt.Fprintln(w, header) - - for _, task := range tasks.Items { - fmt.Fprintf(w, body, - task.Name, - formatted.FormatDesc(task.Spec.Description), - formatted.Age(&task.CreationTimestamp, p.Time()), - ) + t := template.Must(template.New("List Tasks").Funcs(funcMap).Parse(listTemplate)) + + err = t.Execute(w, data) + if err != nil { + return err } + return w.Flush() } diff --git a/pkg/cmd/task/list_test.go b/pkg/cmd/task/list_test.go index d9757308ae..371a710912 100644 --- a/pkg/cmd/task/list_test.go +++ b/pkg/cmd/task/list_test.go @@ -68,7 +68,7 @@ func TestTaskList_Empty(t *testing.T) { if err != nil { t.Errorf("Unexpected error: %v", err) } - test.AssertOutput(t, emptyMsg+"\n", output) + test.AssertOutput(t, "No Tasks found\n", output) } func TestTaskList_Only_Tasks_v1alpha1(t *testing.T) { @@ -80,7 +80,7 @@ func TestTaskList_Only_Tasks_v1alpha1(t *testing.T) { tb.Task("bananas", "namespace", cb.TaskCreationTime(clock.Now().Add(-512*time.Hour))), tb.Task("apples", "namespace", tb.TaskSpec(tb.TaskDescription("")), cb.TaskCreationTime(clock.Now().Add(-513*time.Hour))), tb.Task("potatoes", "namespace", tb.TaskSpec(tb.TaskDescription("a test task")), cb.TaskCreationTime(clock.Now().Add(-514*time.Hour))), - tb.Task("onionss", "namespace", tb.TaskSpec(tb.TaskDescription("a test task to test description of task")), cb.TaskCreationTime(clock.Now().Add(-515*time.Hour))), + tb.Task("onions", "namespace", tb.TaskSpec(tb.TaskDescription("a test task to test description of task")), cb.TaskCreationTime(clock.Now().Add(-515*time.Hour))), } ns := []*corev1.Namespace{ @@ -154,7 +154,7 @@ func TestTaskList_Only_Tasks_v1beta1(t *testing.T) { cs, _ := test.SeedTestData(t, pipelinetest.Data{Tasks: tasks, Namespaces: ns}) p := &test.Params{Tekton: cs.Pipeline, Clock: clock, Kube: cs.Kube, Dynamic: dynamic} - cs.Pipeline.Resources = cb.APIResourceList("v1beta1", []string{"task"}) + cs.Pipeline.Resources = cb.APIResourceList(version, []string{"task"}) task := Command(p) output, err := test.ExecuteCommand(task, "list", "-n", "namespace") @@ -164,3 +164,152 @@ func TestTaskList_Only_Tasks_v1beta1(t *testing.T) { golden.Assert(t, output, fmt.Sprintf("%s.golden", t.Name())) } + +func TestTaskList_Only_Tasks_no_headers_v1beta1(t *testing.T) { + clock := clockwork.NewFakeClock() + + tasks := []*v1alpha1.Task{ + tb.Task("tomatoes", "namespace", cb.TaskCreationTime(clock.Now().Add(-1*time.Minute))), + tb.Task("mangoes", "namespace", cb.TaskCreationTime(clock.Now().Add(-20*time.Second))), + tb.Task("bananas", "namespace", cb.TaskCreationTime(clock.Now().Add(-512*time.Hour))), + tb.Task("apples", "namespace", tb.TaskSpec(tb.TaskDescription("")), cb.TaskCreationTime(clock.Now().Add(-513*time.Hour))), + tb.Task("potatoes", "namespace", tb.TaskSpec(tb.TaskDescription("a test task")), cb.TaskCreationTime(clock.Now().Add(-514*time.Hour))), + tb.Task("onions", "namespace", tb.TaskSpec(tb.TaskDescription("a test task to test description of task")), cb.TaskCreationTime(clock.Now().Add(-515*time.Hour))), + } + + ns := []*corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "namespace", + }, + }, + } + + version := "v1beta1" + tdc := testDynamic.Options{} + dynamic, err := tdc.Client( + cb.UnstructuredT(tasks[0], version), + cb.UnstructuredT(tasks[1], version), + cb.UnstructuredT(tasks[2], version), + cb.UnstructuredT(tasks[3], version), + cb.UnstructuredT(tasks[4], version), + cb.UnstructuredT(tasks[5], version), + ) + if err != nil { + t.Errorf("unable to create dynamic client: %v", err) + } + + cs, _ := test.SeedTestData(t, pipelinetest.Data{Tasks: tasks, Namespaces: ns}) + p := &test.Params{Tekton: cs.Pipeline, Clock: clock, Kube: cs.Kube, Dynamic: dynamic} + cs.Pipeline.Resources = cb.APIResourceList(version, []string{"task"}) + task := Command(p) + + output, err := test.ExecuteCommand(task, "list", "-n", "namespace", "--no-headers") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + golden.Assert(t, output, fmt.Sprintf("%s.golden", t.Name())) +} + +func TestTaskList_Only_Tasks_all_namespaces_v1beta1(t *testing.T) { + clock := clockwork.NewFakeClock() + + tasks := []*v1alpha1.Task{ + tb.Task("tomatoes", "namespace", cb.TaskCreationTime(clock.Now().Add(-1*time.Minute))), + tb.Task("mangoes", "namespace", cb.TaskCreationTime(clock.Now().Add(-20*time.Second))), + tb.Task("bananas", "namespace", cb.TaskCreationTime(clock.Now().Add(-512*time.Hour))), + tb.Task("apples", "namespace", tb.TaskSpec(tb.TaskDescription("")), cb.TaskCreationTime(clock.Now().Add(-513*time.Hour))), + tb.Task("potatoes", "namespace", tb.TaskSpec(tb.TaskDescription("a test task")), cb.TaskCreationTime(clock.Now().Add(-514*time.Hour))), + tb.Task("onions", "namespace", tb.TaskSpec(tb.TaskDescription("a test task to test description of task")), cb.TaskCreationTime(clock.Now().Add(-515*time.Hour))), + tb.Task("tomates", "espace-de-nom", cb.TaskCreationTime(clock.Now().Add(-1*time.Minute))), + tb.Task("mangues", "espace-de-nom", cb.TaskCreationTime(clock.Now().Add(-20*time.Second))), + tb.Task("bananes", "espace-de-nom", cb.TaskCreationTime(clock.Now().Add(-512*time.Hour))), + tb.Task("pommes", "espace-de-nom", tb.TaskSpec(tb.TaskDescription("")), cb.TaskCreationTime(clock.Now().Add(-513*time.Hour))), + tb.Task("patates", "espace-de-nom", tb.TaskSpec(tb.TaskDescription("a test task")), cb.TaskCreationTime(clock.Now().Add(-514*time.Hour))), + tb.Task("oignons", "espace-de-nom", tb.TaskSpec(tb.TaskDescription("a test task to test description of task")), cb.TaskCreationTime(clock.Now().Add(-515*time.Hour))), + } + + version := "v1beta1" + tdc := testDynamic.Options{} + dynamic, err := tdc.Client( + cb.UnstructuredT(tasks[0], version), + cb.UnstructuredT(tasks[1], version), + cb.UnstructuredT(tasks[2], version), + cb.UnstructuredT(tasks[3], version), + cb.UnstructuredT(tasks[4], version), + cb.UnstructuredT(tasks[5], version), + cb.UnstructuredT(tasks[6], version), + cb.UnstructuredT(tasks[7], version), + cb.UnstructuredT(tasks[8], version), + cb.UnstructuredT(tasks[9], version), + cb.UnstructuredT(tasks[10], version), + cb.UnstructuredT(tasks[11], version), + ) + if err != nil { + t.Errorf("unable to create dynamic client: %v", err) + } + + cs, _ := test.SeedTestData(t, pipelinetest.Data{Tasks: tasks}) + p := &test.Params{Tekton: cs.Pipeline, Clock: clock, Kube: cs.Kube, Dynamic: dynamic} + cs.Pipeline.Resources = cb.APIResourceList(version, []string{"task"}) + task := Command(p) + + output, err := test.ExecuteCommand(task, "list", "--all-namespaces") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + golden.Assert(t, output, fmt.Sprintf("%s.golden", t.Name())) +} + +func TestTaskList_Only_Tasks_all_namespaces_no_headers_v1beta1(t *testing.T) { + clock := clockwork.NewFakeClock() + + tasks := []*v1alpha1.Task{ + tb.Task("tomatoes", "namespace", cb.TaskCreationTime(clock.Now().Add(-1*time.Minute))), + tb.Task("mangoes", "namespace", cb.TaskCreationTime(clock.Now().Add(-20*time.Second))), + tb.Task("bananas", "namespace", cb.TaskCreationTime(clock.Now().Add(-512*time.Hour))), + tb.Task("apples", "namespace", tb.TaskSpec(tb.TaskDescription("")), cb.TaskCreationTime(clock.Now().Add(-513*time.Hour))), + tb.Task("potatoes", "namespace", tb.TaskSpec(tb.TaskDescription("a test task")), cb.TaskCreationTime(clock.Now().Add(-514*time.Hour))), + tb.Task("onions", "namespace", tb.TaskSpec(tb.TaskDescription("a test task to test description of task")), cb.TaskCreationTime(clock.Now().Add(-515*time.Hour))), + tb.Task("tomates", "espace-de-nom", cb.TaskCreationTime(clock.Now().Add(-1*time.Minute))), + tb.Task("mangues", "espace-de-nom", cb.TaskCreationTime(clock.Now().Add(-20*time.Second))), + tb.Task("bananes", "espace-de-nom", cb.TaskCreationTime(clock.Now().Add(-512*time.Hour))), + tb.Task("pommes", "espace-de-nom", tb.TaskSpec(tb.TaskDescription("")), cb.TaskCreationTime(clock.Now().Add(-513*time.Hour))), + tb.Task("patates", "espace-de-nom", tb.TaskSpec(tb.TaskDescription("a test task")), cb.TaskCreationTime(clock.Now().Add(-514*time.Hour))), + tb.Task("oignons", "espace-de-nom", tb.TaskSpec(tb.TaskDescription("a test task to test description of task")), cb.TaskCreationTime(clock.Now().Add(-515*time.Hour))), + } + + version := "v1beta1" + tdc := testDynamic.Options{} + dynamic, err := tdc.Client( + cb.UnstructuredT(tasks[0], version), + cb.UnstructuredT(tasks[1], version), + cb.UnstructuredT(tasks[2], version), + cb.UnstructuredT(tasks[3], version), + cb.UnstructuredT(tasks[4], version), + cb.UnstructuredT(tasks[5], version), + cb.UnstructuredT(tasks[6], version), + cb.UnstructuredT(tasks[7], version), + cb.UnstructuredT(tasks[8], version), + cb.UnstructuredT(tasks[9], version), + cb.UnstructuredT(tasks[10], version), + cb.UnstructuredT(tasks[11], version), + ) + if err != nil { + t.Errorf("unable to create dynamic client: %v", err) + } + + cs, _ := test.SeedTestData(t, pipelinetest.Data{Tasks: tasks}) + p := &test.Params{Tekton: cs.Pipeline, Clock: clock, Kube: cs.Kube, Dynamic: dynamic} + cs.Pipeline.Resources = cb.APIResourceList(version, []string{"task"}) + task := Command(p) + + output, err := test.ExecuteCommand(task, "list", "--all-namespaces", "--no-headers") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + golden.Assert(t, output, fmt.Sprintf("%s.golden", t.Name())) +} diff --git a/pkg/cmd/task/testdata/TestTaskList_Only_Tasks_all_namespaces_no_headers_v1beta1.golden b/pkg/cmd/task/testdata/TestTaskList_Only_Tasks_all_namespaces_no_headers_v1beta1.golden new file mode 100644 index 0000000000..c416dafe34 --- /dev/null +++ b/pkg/cmd/task/testdata/TestTaskList_Only_Tasks_all_namespaces_no_headers_v1beta1.golden @@ -0,0 +1,12 @@ +namespace tomatoes 1 minute ago +namespace mangoes 20 seconds ago +namespace bananas 3 weeks ago +namespace apples 3 weeks ago +namespace potatoes a test task 3 weeks ago +namespace onions a test task to test... 3 weeks ago +espace-de-nom tomates 1 minute ago +espace-de-nom mangues 20 seconds ago +espace-de-nom bananes 3 weeks ago +espace-de-nom pommes 3 weeks ago +espace-de-nom patates a test task 3 weeks ago +espace-de-nom oignons a test task to test... 3 weeks ago diff --git a/pkg/cmd/task/testdata/TestTaskList_Only_Tasks_all_namespaces_v1beta1.golden b/pkg/cmd/task/testdata/TestTaskList_Only_Tasks_all_namespaces_v1beta1.golden new file mode 100644 index 0000000000..a612853671 --- /dev/null +++ b/pkg/cmd/task/testdata/TestTaskList_Only_Tasks_all_namespaces_v1beta1.golden @@ -0,0 +1,13 @@ +NAMESPACE NAME DESCRIPTION AGE +namespace tomatoes 1 minute ago +namespace mangoes 20 seconds ago +namespace bananas 3 weeks ago +namespace apples 3 weeks ago +namespace potatoes a test task 3 weeks ago +namespace onions a test task to test... 3 weeks ago +espace-de-nom tomates 1 minute ago +espace-de-nom mangues 20 seconds ago +espace-de-nom bananes 3 weeks ago +espace-de-nom pommes 3 weeks ago +espace-de-nom patates a test task 3 weeks ago +espace-de-nom oignons a test task to test... 3 weeks ago diff --git a/pkg/cmd/task/testdata/TestTaskList_Only_Tasks_no_headers_v1beta1.golden b/pkg/cmd/task/testdata/TestTaskList_Only_Tasks_no_headers_v1beta1.golden new file mode 100644 index 0000000000..143ee82486 --- /dev/null +++ b/pkg/cmd/task/testdata/TestTaskList_Only_Tasks_no_headers_v1beta1.golden @@ -0,0 +1,6 @@ +tomatoes 1 minute ago +mangoes 20 seconds ago +bananas 3 weeks ago +apples 3 weeks ago +potatoes a test task 3 weeks ago +onions a test task to test... 3 weeks ago diff --git a/pkg/cmd/task/testdata/TestTaskList_Only_Tasks_v1alpha1.golden b/pkg/cmd/task/testdata/TestTaskList_Only_Tasks_v1alpha1.golden index ff901f0af3..ae6771fbd1 100644 --- a/pkg/cmd/task/testdata/TestTaskList_Only_Tasks_v1alpha1.golden +++ b/pkg/cmd/task/testdata/TestTaskList_Only_Tasks_v1alpha1.golden @@ -4,4 +4,4 @@ mangoes 20 seconds ago bananas 3 weeks ago apples 3 weeks ago potatoes a test task 3 weeks ago -onionss a test task to test... 3 weeks ago +onions a test task to test... 3 weeks ago From 2649b31e64a38764862fc96bc3f2969e3b56e191 Mon Sep 17 00:00:00 2001 From: Vibhav Bobade Date: Sat, 11 Apr 2020 10:53:12 +0530 Subject: [PATCH 2/3] Add --no-headers and --all-namespaces flag for Pipeline `list` --no-headers flag helps omit the headers or the index row of the output while printing. This allows the user to parse the output without having to deal with the headers in the output. --all-namespaces flag helps to print Pipelines from all namespaces. These tasks are grouped by namespace and then sorted based on StartTime. --- pkg/cmd/pipeline/list.go | 59 ++++++-- pkg/cmd/pipeline/list_test.go | 141 +++++++++++++++--- ...s_all_namespaces_no_headers_v1beta1.golden | 6 + ...ly_pipelines_all_namespaces_v1beta1.golden | 7 + ...t_only_pipelines_no_headers_v1beta1.golden | 3 + test/e2e/pipeline_e2e_test.go | 6 +- 6 files changed, 187 insertions(+), 35 deletions(-) create mode 100644 pkg/cmd/pipeline/testdata/TestPipelineList_only_pipelines_all_namespaces_no_headers_v1beta1.golden create mode 100644 pkg/cmd/pipeline/testdata/TestPipelineList_only_pipelines_all_namespaces_v1beta1.golden create mode 100644 pkg/cmd/pipeline/testdata/TestPipelineList_only_pipelines_no_headers_v1beta1.golden diff --git a/pkg/cmd/pipeline/list.go b/pkg/cmd/pipeline/list.go index 0bad8822bc..04b683337a 100644 --- a/pkg/cmd/pipeline/list.go +++ b/pkg/cmd/pipeline/list.go @@ -34,21 +34,41 @@ import ( ) const listTemplate = `{{- $pl := len .Pipelines.Items }}{{ if eq $pl 0 -}} -No pipelines -{{- else -}} +No Pipelines found +{{ else -}} +{{- if not $.NoHeaders -}} +{{- if $.AllNamespaces -}} +NAMESPACE NAME AGE LAST RUN STARTED DURATION STATUS +{{ else -}} NAME AGE LAST RUN STARTED DURATION STATUS +{{ end }} +{{- end -}} {{- range $_, $p := .Pipelines.Items }} {{- $pr := accessMap $.PipelineRuns $p.Name }} {{- if $pr }} +{{- if $.AllNamespaces -}} +{{ $p.Namespace }} {{ $p.Name }} {{ formatAge $p.CreationTimestamp $.Params.Time }} {{ $pr.Name }} {{ formatAge $pr.Status.StartTime $.Params.Time }} {{ formatDuration $pr.Status.StartTime $pr.Status.CompletionTime }} {{ formatCondition $pr.Status.Conditions }} +{{ else -}} {{ $p.Name }} {{ formatAge $p.CreationTimestamp $.Params.Time }} {{ $pr.Name }} {{ formatAge $pr.Status.StartTime $.Params.Time }} {{ formatDuration $pr.Status.StartTime $pr.Status.CompletionTime }} {{ formatCondition $pr.Status.Conditions }} +{{ end }} {{- else }} +{{- if $.AllNamespaces -}} +{{ $p.Namespace }} {{ $p.Name }} {{ formatAge $p.CreationTimestamp $.Params.Time }} --- --- --- --- +{{ else -}} {{ $p.Name }} {{ formatAge $p.CreationTimestamp $.Params.Time }} --- --- --- --- +{{ end }} {{- end }} {{- end }} -{{- end }} +{{- end -}} ` +type ListOptions struct { + AllNamespaces bool + NoHeaders bool +} + func listCommand(p cli.Params) *cobra.Command { + opts := &ListOptions{} f := cliopts.NewPrintFlags("list") c := &cobra.Command{ @@ -61,7 +81,7 @@ func listCommand(p cli.Params) *cobra.Command { SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { - if err := validate.NamespaceExists(p); err != nil { + if err := validate.NamespaceExists(p); err != nil && !opts.AllNamespaces { return err } @@ -79,36 +99,46 @@ func listCommand(p cli.Params) *cobra.Command { Out: cmd.OutOrStdout(), Err: cmd.OutOrStderr(), } - return printPipelineDetails(stream, p) + return printPipelineDetails(stream, p, opts.AllNamespaces, opts.NoHeaders) }, } f.AddFlags(c) + c.Flags().BoolVarP(&opts.AllNamespaces, "all-namespaces", "A", opts.AllNamespaces, "list pipelines from all namespaces") + c.Flags().BoolVarP(&opts.NoHeaders, "no-headers", "", opts.NoHeaders, "do not print column headers with output (default print column headers with output)") return c } -func printPipelineDetails(s *cli.Stream, p cli.Params) error { +func printPipelineDetails(s *cli.Stream, p cli.Params, allnamespaces bool, noheaders bool) error { cs, err := p.Clients() if err != nil { return err } - ps, prs, err := listPipelineDetails(cs, p.Namespace()) + ns := p.Namespace() + if allnamespaces { + ns = "" + } + ps, prs, err := listPipelineDetails(cs, ns) if err != nil { - fmt.Fprintf(s.Err, "Failed to list pipelines from %s namespace\n", p.Namespace()) + fmt.Fprintf(s.Err, "Failed to list pipelines from %s namespace\n", ns) return err } var data = struct { - Pipelines *v1beta1.PipelineList - PipelineRuns pipelineruns - Params cli.Params + Pipelines *v1beta1.PipelineList + PipelineRuns pipelineruns + Params cli.Params + AllNamespaces bool + NoHeaders bool }{ - Pipelines: ps, - PipelineRuns: prs, - Params: p, + Pipelines: ps, + PipelineRuns: prs, + Params: p, + AllNamespaces: allnamespaces, + NoHeaders: noheaders, } funcMap := template.FuncMap{ @@ -116,7 +146,6 @@ func printPipelineDetails(s *cli.Stream, p cli.Params) error { if pr, ok := prs[name]; ok { return &pr } - return nil }, "formatAge": formatted.Age, diff --git a/pkg/cmd/pipeline/list_test.go b/pkg/cmd/pipeline/list_test.go index 9be5bd2da8..dbfa04cfdc 100644 --- a/pkg/cmd/pipeline/list_test.go +++ b/pkg/cmd/pipeline/list_test.go @@ -82,14 +82,14 @@ func TestPipelinesList_empty(t *testing.T) { t.Errorf("Unexpected error: %v", err) } - test.AssertOutput(t, "No pipelines\n", output) + test.AssertOutput(t, "No Pipelines found\n", output) } func TestPipelineList_only_pipelines(t *testing.T) { pipelines := []pipelineDetails{ - {"tomatoes", 1 * time.Minute}, - {"mangoes", 20 * time.Second}, - {"bananas", 512 * time.Hour}, // 3 weeks + {"tomatoes", 1 * time.Minute, "namespace"}, + {"mangoes", 20 * time.Second, "namespace"}, + {"bananas", 512 * time.Hour, "namespace"}, // 3 weeks } nsList := []*corev1.Namespace{ @@ -101,7 +101,7 @@ func TestPipelineList_only_pipelines(t *testing.T) { } version := "v1alpha1" clock := clockwork.NewFakeClock() - cs, pdata := seedPipelines(t, clock, pipelines, "namespace", nsList) + cs, pdata := seedPipelines(t, clock, pipelines, nsList) cs.Pipeline.Resources = cb.APIResourceList(version, []string{"pipeline"}) tdc := testDynamic.Options{} dc, err := tdc.Client( @@ -124,9 +124,9 @@ func TestPipelineList_only_pipelines(t *testing.T) { func TestPipelineList_only_pipelines_v1beta1(t *testing.T) { pipelines := []pipelineDetails{ - {"tomatoes", 1 * time.Minute}, - {"mangoes", 20 * time.Second}, - {"bananas", 512 * time.Hour}, // 3 weeks + {"tomatoes", 1 * time.Minute, "namespace"}, + {"mangoes", 20 * time.Second, "namespace"}, + {"bananas", 512 * time.Hour, "namespace"}, // 3 weeks } nsList := []*corev1.Namespace{ @@ -138,7 +138,7 @@ func TestPipelineList_only_pipelines_v1beta1(t *testing.T) { } version := "v1beta1" clock := clockwork.NewFakeClock() - cs, pdata := seedPipelines(t, clock, pipelines, "namespace", nsList) + cs, pdata := seedPipelines(t, clock, pipelines, nsList) cs.Pipeline.Resources = cb.APIResourceList(version, []string{"pipeline"}) tdc := testDynamic.Options{} dc, err := tdc.Client( @@ -159,6 +159,115 @@ func TestPipelineList_only_pipelines_v1beta1(t *testing.T) { golden.Assert(t, output, fmt.Sprintf("%s.golden", t.Name())) } +func TestPipelineList_only_pipelines_no_headers_v1beta1(t *testing.T) { + pipelines := []pipelineDetails{ + {"tomatoes", 1 * time.Minute, "namespace"}, + {"mangoes", 20 * time.Second, "namespace"}, + {"bananas", 512 * time.Hour, "namespace"}, // 3 weeks + } + + nsList := []*corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "namespace", + }, + }, + } + version := "v1beta1" + clock := clockwork.NewFakeClock() + cs, pdata := seedPipelines(t, clock, pipelines, nsList) + cs.Pipeline.Resources = cb.APIResourceList(version, []string{"pipeline"}) + tdc := testDynamic.Options{} + dc, err := tdc.Client( + cb.UnstructuredP(pdata[0], version), + cb.UnstructuredP(pdata[1], version), + cb.UnstructuredP(pdata[2], version), + ) + if err != nil { + t.Errorf("unable to create dynamic clinet: %v", err) + } + p := &test.Params{Tekton: cs.Pipeline, Clock: clock, Kube: cs.Kube, Dynamic: dc} + + pipeline := Command(p) + output, err := test.ExecuteCommand(pipeline, "list", "-n", "namespace", "--no-headers") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + golden.Assert(t, output, fmt.Sprintf("%s.golden", t.Name())) +} + +func TestPipelineList_only_pipelines_all_namespaces_v1beta1(t *testing.T) { + pipelines := []pipelineDetails{ + {"tomatoes", 1 * time.Minute, "namespace"}, + {"mangoes", 20 * time.Second, "namespace"}, + {"bananas", 512 * time.Hour, "namespace"}, // 3 weeks + {"tomates", 1 * time.Minute, "espace-de-nom"}, + {"mangues", 20 * time.Second, "espace-de-nom"}, + {"bananes", 512 * time.Hour, "espace-de-nom"}, // 3 weeks + } + + version := "v1beta1" + clock := clockwork.NewFakeClock() + cs, pdata := seedPipelines(t, clock, pipelines, nil) + cs.Pipeline.Resources = cb.APIResourceList(version, []string{"pipeline"}) + tdc := testDynamic.Options{} + dc, err := tdc.Client( + cb.UnstructuredP(pdata[0], version), + cb.UnstructuredP(pdata[1], version), + cb.UnstructuredP(pdata[2], version), + cb.UnstructuredP(pdata[3], version), + cb.UnstructuredP(pdata[4], version), + cb.UnstructuredP(pdata[5], version), + ) + if err != nil { + t.Errorf("unable to create dynamic clinet: %v", err) + } + p := &test.Params{Tekton: cs.Pipeline, Clock: clock, Kube: cs.Kube, Dynamic: dc} + + pipeline := Command(p) + output, err := test.ExecuteCommand(pipeline, "list", "--all-namespaces") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + golden.Assert(t, output, fmt.Sprintf("%s.golden", t.Name())) +} + +func TestPipelineList_only_pipelines_all_namespaces_no_headers_v1beta1(t *testing.T) { + pipelines := []pipelineDetails{ + {"tomatoes", 1 * time.Minute, "namespace"}, + {"mangoes", 20 * time.Second, "namespace"}, + {"bananas", 512 * time.Hour, "namespace"}, // 3 weeks + {"tomates", 1 * time.Minute, "espace-de-nom"}, + {"mangues", 20 * time.Second, "espace-de-nom"}, + {"bananes", 512 * time.Hour, "espace-de-nom"}, // 3 weeks + } + + version := "v1beta1" + clock := clockwork.NewFakeClock() + cs, pdata := seedPipelines(t, clock, pipelines, nil) + cs.Pipeline.Resources = cb.APIResourceList(version, []string{"pipeline"}) + tdc := testDynamic.Options{} + dc, err := tdc.Client( + cb.UnstructuredP(pdata[0], version), + cb.UnstructuredP(pdata[1], version), + cb.UnstructuredP(pdata[2], version), + cb.UnstructuredP(pdata[3], version), + cb.UnstructuredP(pdata[4], version), + cb.UnstructuredP(pdata[5], version), + ) + if err != nil { + t.Errorf("unable to create dynamic clinet: %v", err) + } + p := &test.Params{Tekton: cs.Pipeline, Clock: clock, Kube: cs.Kube, Dynamic: dc} + + pipeline := Command(p) + output, err := test.ExecuteCommand(pipeline, "list", "--all-namespaces", "--no-headers") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + golden.Assert(t, output, fmt.Sprintf("%s.golden", t.Name())) +} + func TestPipelinesList_with_single_run(t *testing.T) { clock := clockwork.NewFakeClock() version := "v1alpha1" @@ -311,18 +420,16 @@ func TestPipelinesList_latest_run(t *testing.T) { } type pipelineDetails struct { - name string - age time.Duration + name string + age time.Duration + namespace string } -func seedPipelines(t *testing.T, clock clockwork.Clock, ps []pipelineDetails, ns string, nsList []*corev1.Namespace) (pipelinetest.Clients, []*v1alpha1.Pipeline) { +func seedPipelines(t *testing.T, clock clockwork.Clock, ps []pipelineDetails, nsList []*corev1.Namespace) (pipelinetest.Clients, []*v1alpha1.Pipeline) { pipelines := []*v1alpha1.Pipeline{} for _, p := range ps { - pipelines = append(pipelines, - tb.Pipeline(p.name, ns, - cb.PipelineCreationTimestamp(clock.Now().Add(p.age*-1)), - ), - ) + pipelines = append(pipelines, tb.Pipeline(p.name, p.namespace, + cb.PipelineCreationTimestamp(clock.Now().Add(p.age*-1)))) } cs, _ := test.SeedTestData(t, pipelinetest.Data{Pipelines: pipelines, Namespaces: nsList}) diff --git a/pkg/cmd/pipeline/testdata/TestPipelineList_only_pipelines_all_namespaces_no_headers_v1beta1.golden b/pkg/cmd/pipeline/testdata/TestPipelineList_only_pipelines_all_namespaces_no_headers_v1beta1.golden new file mode 100644 index 0000000000..8c1a98646b --- /dev/null +++ b/pkg/cmd/pipeline/testdata/TestPipelineList_only_pipelines_all_namespaces_no_headers_v1beta1.golden @@ -0,0 +1,6 @@ +namespace tomatoes 1 minute ago --- --- --- --- +namespace mangoes 20 seconds ago --- --- --- --- +namespace bananas 3 weeks ago --- --- --- --- +espace-de-nom tomates 1 minute ago --- --- --- --- +espace-de-nom mangues 20 seconds ago --- --- --- --- +espace-de-nom bananes 3 weeks ago --- --- --- --- diff --git a/pkg/cmd/pipeline/testdata/TestPipelineList_only_pipelines_all_namespaces_v1beta1.golden b/pkg/cmd/pipeline/testdata/TestPipelineList_only_pipelines_all_namespaces_v1beta1.golden new file mode 100644 index 0000000000..195d489668 --- /dev/null +++ b/pkg/cmd/pipeline/testdata/TestPipelineList_only_pipelines_all_namespaces_v1beta1.golden @@ -0,0 +1,7 @@ +NAMESPACE NAME AGE LAST RUN STARTED DURATION STATUS +namespace tomatoes 1 minute ago --- --- --- --- +namespace mangoes 20 seconds ago --- --- --- --- +namespace bananas 3 weeks ago --- --- --- --- +espace-de-nom tomates 1 minute ago --- --- --- --- +espace-de-nom mangues 20 seconds ago --- --- --- --- +espace-de-nom bananes 3 weeks ago --- --- --- --- diff --git a/pkg/cmd/pipeline/testdata/TestPipelineList_only_pipelines_no_headers_v1beta1.golden b/pkg/cmd/pipeline/testdata/TestPipelineList_only_pipelines_no_headers_v1beta1.golden new file mode 100644 index 0000000000..f0669afdd0 --- /dev/null +++ b/pkg/cmd/pipeline/testdata/TestPipelineList_only_pipelines_no_headers_v1beta1.golden @@ -0,0 +1,3 @@ +tomatoes 1 minute ago --- --- --- --- +mangoes 20 seconds ago --- --- --- --- +bananas 3 weeks ago --- --- --- --- diff --git a/test/e2e/pipeline_e2e_test.go b/test/e2e/pipeline_e2e_test.go index f37dc09764..f8e3f844d8 100644 --- a/test/e2e/pipeline_e2e_test.go +++ b/test/e2e/pipeline_e2e_test.go @@ -114,7 +114,7 @@ func TestPipelinesE2E(t *testing.T) { res.Assert(t, icmd.Expected{ ExitCode: 0, - Out: "No pipelines\n", + Out: "No Pipelines found\n", Err: icmd.None, }) }) @@ -359,7 +359,7 @@ func TestPipelinesNegativeE2E(t *testing.T) { res.Assert(t, icmd.Expected{ ExitCode: 0, - Out: "No pipelines\n", + Out: "No Pipelines found\n", Err: icmd.None, }) }) @@ -575,7 +575,7 @@ func TestDeletePipelinesE2E(t *testing.T) { res.Assert(t, icmd.Expected{ ExitCode: 0, - Out: "No pipelines\n", + Out: "No Pipelines found\n", Err: icmd.None, }) }) From aecdac349f692d432976ec716a0942d7644325e1 Mon Sep 17 00:00:00 2001 From: Vibhav Bobade Date: Sat, 11 Apr 2020 10:54:06 +0530 Subject: [PATCH 3/3] Docs --no-headers --all-namespaces flags for task/pipeline list Add man page and docs for the two flags --- docs/cmd/tkn_pipeline_list.md | 2 ++ docs/cmd/tkn_task_list.md | 2 ++ docs/man/man1/tkn-pipeline-list.1 | 8 ++++++++ docs/man/man1/tkn-task-list.1 | 8 ++++++++ 4 files changed, 20 insertions(+) diff --git a/docs/cmd/tkn_pipeline_list.md b/docs/cmd/tkn_pipeline_list.md index 098951220d..72c5d7e300 100644 --- a/docs/cmd/tkn_pipeline_list.md +++ b/docs/cmd/tkn_pipeline_list.md @@ -17,8 +17,10 @@ Lists pipelines in a namespace ### Options ``` + -A, --all-namespaces list pipelines 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 + --no-headers do not print column headers with output (default print column headers with output) -o, --output string Output format. One of: json|yaml|name|go-template|go-template-file|template|templatefile|jsonpath|jsonpath-file. --template string Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview]. ``` diff --git a/docs/cmd/tkn_task_list.md b/docs/cmd/tkn_task_list.md index f82ccdbb90..b71db050ff 100644 --- a/docs/cmd/tkn_task_list.md +++ b/docs/cmd/tkn_task_list.md @@ -17,8 +17,10 @@ Lists tasks in a namespace ### Options ``` + -A, --all-namespaces list tasks 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 + --no-headers do not print column headers with output (default print column headers with output) -o, --output string Output format. One of: json|yaml|name|go-template|go-template-file|template|templatefile|jsonpath|jsonpath-file. --template string Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview]. ``` diff --git a/docs/man/man1/tkn-pipeline-list.1 b/docs/man/man1/tkn-pipeline-list.1 index 68a658ce9c..2802accc1a 100644 --- a/docs/man/man1/tkn-pipeline-list.1 +++ b/docs/man/man1/tkn-pipeline-list.1 @@ -19,6 +19,10 @@ Lists pipelines in a namespace .SH OPTIONS +.PP +\fB\-A\fP, \fB\-\-all\-namespaces\fP[=false] + list pipelines 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. @@ -27,6 +31,10 @@ Lists pipelines in a namespace \fB\-h\fP, \fB\-\-help\fP[=false] help for list +.PP +\fB\-\-no\-headers\fP[=false] + do not print column headers with output (default print column headers with output) + .PP \fB\-o\fP, \fB\-\-output\fP="" Output format. One of: json|yaml|name|go\-template|go\-template\-file|template|templatefile|jsonpath|jsonpath\-file. diff --git a/docs/man/man1/tkn-task-list.1 b/docs/man/man1/tkn-task-list.1 index df26aee438..cb12b76d98 100644 --- a/docs/man/man1/tkn-task-list.1 +++ b/docs/man/man1/tkn-task-list.1 @@ -19,6 +19,10 @@ Lists tasks in a namespace .SH OPTIONS +.PP +\fB\-A\fP, \fB\-\-all\-namespaces\fP[=false] + list tasks 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. @@ -27,6 +31,10 @@ Lists tasks in a namespace \fB\-h\fP, \fB\-\-help\fP[=false] help for list +.PP +\fB\-\-no\-headers\fP[=false] + do not print column headers with output (default print column headers with output) + .PP \fB\-o\fP, \fB\-\-output\fP="" Output format. One of: json|yaml|name|go\-template|go\-template\-file|template|templatefile|jsonpath|jsonpath\-file.