From 29ca07768ca455debb7992ebbf09b2db2058f56d Mon Sep 17 00:00:00 2001 From: Zbynek Roubalik <726523+zroubalik@users.noreply.github.com> Date: Wed, 4 Nov 2020 13:15:06 +0100 Subject: [PATCH] feat: list command - improved output (#205) Signed-off-by: Zbynek Roubalik --- client.go | 14 +++++++++++--- cmd/completion_util.go | 7 +++++-- cmd/list.go | 39 ++++++++++++++++++++++----------------- go.mod | 1 + knative/lister.go | 30 ++++++++++++++++++++++++++---- mock/lister.go | 8 +++++--- 6 files changed, 70 insertions(+), 29 deletions(-) diff --git a/client.go b/client.go index 40f3228c0a..b5a2f34abd 100644 --- a/client.go +++ b/client.go @@ -67,7 +67,15 @@ type Remover interface { // Lister of deployed services. type Lister interface { // List the Functions currently deployed. - List() ([]string, error) + List() ([]ListItem, error) +} + +type ListItem struct { + Name string `json:"name" yaml:"name"` + Runtime string `json:"runtime" yaml:"runtime"` + URL string `json:"url" yaml:"url"` + KService string `json:"kservice" yaml:"kservice"` + Ready string `json:"ready" yaml:"ready"` } // ProgressListener is notified of task progress. @@ -476,7 +484,7 @@ func (c *Client) Run(root string) error { } // List currently deployed Functions. -func (c *Client) List() ([]string, error) { +func (c *Client) List() ([]ListItem, error) { // delegate to concrete implementation of lister entirely. return c.lister.List() } @@ -549,7 +557,7 @@ func (n *noopRemover) Remove(string) error { return nil } type noopLister struct{ output io.Writer } -func (n *noopLister) List() ([]string, error) { return []string{}, nil } +func (n *noopLister) List() ([]ListItem, error) { return []ListItem{}, nil } type noopDNSProvider struct{ output io.Writer } diff --git a/cmd/completion_util.go b/cmd/completion_util.go index c55bdd6975..5ae88a5033 100644 --- a/cmd/completion_util.go +++ b/cmd/completion_util.go @@ -18,12 +18,15 @@ func CompleteFunctionList(cmd *cobra.Command, args []string, toComplete string) directive = cobra.ShellCompDirectiveError return } - s, err := lister.List() + list, err := lister.List() if err != nil { directive = cobra.ShellCompDirectiveError return } - strings = s + + for _, item := range list{ + strings = append(strings, item.Name) + } directive = cobra.ShellCompDirectiveDefault return } diff --git a/cmd/list.go b/cmd/list.go index 995e3cbe95..79fa0d7ca4 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "os" + "text/tabwriter" "github.com/ory/viper" "github.com/spf13/cobra" @@ -54,12 +55,13 @@ func runList(cmd *cobra.Command, args []string) (err error) { faas.WithVerbose(config.Verbose), faas.WithLister(lister)) - nn, err := client.List() + items, err := client.List() if err != nil { return } - write(os.Stdout, names(nn), config.Format) + write(os.Stdout, listItems(items), config.Format) + return } @@ -83,30 +85,33 @@ func newListConfig() listConfig { // Output Formatting (serializers) // ------------------------------- -type names []string +type listItems []faas.ListItem -func (nn names) Human(w io.Writer) error { - return nn.Plain(w) +func (items listItems) Human(w io.Writer) error { + return items.Plain(w) } -func (nn names) Plain(w io.Writer) error { - for _, name := range nn { - fmt.Fprintln(w, name) +func (items listItems) Plain(w io.Writer) error { + + // minwidth, tabwidth, padding, padchar, flags + tabWriter := tabwriter.NewWriter(w, 0, 8, 2, ' ', 0) + defer tabWriter.Flush() + + fmt.Fprintf(tabWriter, "%s\t%s\t%s\t%s\t%s\n", "NAME", "RUNTIME", "URL", "KSERVICE", "READY") + for _, item := range items { + fmt.Fprintf(tabWriter, "%s\t%s\t%s\t%s\t%s\n", item.Name, item.Runtime, item.URL, item.KService, item.Ready) } return nil } -func (nn names) JSON(w io.Writer) error { - return json.NewEncoder(w).Encode(nn) +func (items listItems) JSON(w io.Writer) error { + return json.NewEncoder(w).Encode(items) } -func (nn names) XML(w io.Writer) error { - return xml.NewEncoder(w).Encode(nn) +func (items listItems) XML(w io.Writer) error { + return xml.NewEncoder(w).Encode(items) } -func (nn names) YAML(w io.Writer) error { - // the yaml.v2 package refuses to directly serialize a []string unless - // exposed as a public struct member; so an inline anonymous is used. - ff := struct{ Names []string }{nn} - return yaml.NewEncoder(w).Encode(ff.Names) +func (items listItems) YAML(w io.Writer) error { + return yaml.NewEncoder(w).Encode(items) } diff --git a/go.mod b/go.mod index 20651b03a7..5b13290585 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( k8s.io/client-go v11.0.1-0.20190805182717-6502b5e7b1b5+incompatible knative.dev/client v0.17.2 knative.dev/eventing v0.17.5 + knative.dev/pkg v0.0.0-20200831162708-14fb2347fb77 knative.dev/serving v0.17.3 ) diff --git a/knative/lister.go b/knative/lister.go index 64da1271d5..ac29a76ed6 100644 --- a/knative/lister.go +++ b/knative/lister.go @@ -1,8 +1,11 @@ package knative import ( + corev1 "k8s.io/api/core/v1" clientservingv1 "knative.dev/client/pkg/serving/v1" + "knative.dev/pkg/apis" + "github.com/boson-project/faas" "github.com/boson-project/faas/k8s" ) @@ -28,7 +31,7 @@ func NewLister(namespaceOverride string) (l *Lister, err error) { return } -func (l *Lister) List() (names []string, err error) { +func (l *Lister) List() (items []faas.ListItem, err error) { client, err := NewServingClient(l.namespace) if err != nil { @@ -41,13 +44,32 @@ func (l *Lister) List() (names []string, err error) { } for _, service := range lst.Items { + // Convert the "subdomain-encoded" (i.e. kube-service-friendly) name // back out to a fully qualified service name. - n, err := k8s.FromK8sAllowedName(service.Name) + name, err := k8s.FromK8sAllowedName(service.Name) if err != nil { - return names, err + return items, err + } + + // get status + ready := corev1.ConditionUnknown + for _, con := range service.Status.Conditions { + if con.Type == apis.ConditionReady { + ready = con.Status + break + } } - names = append(names, n) + + listItem := faas.ListItem{ + Name: name, + Runtime: service.Labels["boson.dev/runtime"], + KService: service.Name, + URL: service.Status.URL.String(), + Ready: string(ready), + } + + items = append(items, listItem) } return } diff --git a/mock/lister.go b/mock/lister.go index 929346995c..053d482d0f 100644 --- a/mock/lister.go +++ b/mock/lister.go @@ -1,17 +1,19 @@ package mock +import "github.com/boson-project/faas" + type Lister struct { ListInvoked bool - ListFn func() ([]string, error) + ListFn func() ([]faas.ListItem, error) } func NewLister() *Lister { return &Lister{ - ListFn: func() ([]string, error) { return []string{}, nil }, + ListFn: func() ([]faas.ListItem, error) { return []faas.ListItem{}, nil }, } } -func (l *Lister) List() ([]string, error) { +func (l *Lister) List() ([]faas.ListItem, error) { l.ListInvoked = true return l.ListFn() }