Skip to content

Commit

Permalink
Add flag to list PipelineRuns from all namespaces
Browse files Browse the repository at this point in the history
Passing the --all-namespaces flag to the pipelinerun list
subcommands enables users to view pipelineruns in all namespaces
grouped by namespace and sorted by startime
  • Loading branch information
waveywaves authored and tekton-robot committed Mar 20, 2020
1 parent 23c0c06 commit fad74a6
Show file tree
Hide file tree
Showing 14 changed files with 334 additions and 49 deletions.
1 change: 1 addition & 0 deletions docs/cmd/tkn_pipelinerun_list.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 '!='
Expand Down
4 changes: 4 additions & 0 deletions docs/man/man1/tkn-pipelinerun-list.1
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
77 changes: 56 additions & 21 deletions pkg/cmd/pipelinerun/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"fmt"
"os"
"text/tabwriter"
"text/template"

"github.com/jonboulle/clockwork"
"github.com/spf13/cobra"
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand Down
19 changes: 18 additions & 1 deletion pkg/cmd/pipelinerun/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
NAME STARTED DURATION STATUS
pr3-1 --- --- ---
NAME STARTED DURATION STATUS
pr3-1 --- --- ---
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
NAME STARTED DURATION STATUS
pr0-1 --- --- ---
NAME STARTED DURATION STATUS
pr0-1 --- --- ---
Original file line number Diff line number Diff line change
@@ -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 --- --- ---
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
NAMESPACE NAME STARTED DURATION STATUS
namespace-lacher pr4-2 --- --- ---
namespace-tout pr4-1 --- --- ---
48 changes: 48 additions & 0 deletions pkg/pipelinerun/sort/by_namespace.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit fad74a6

Please sign in to comment.