From a0db07ea235ec69676d942b0a1c447e1fd272134 Mon Sep 17 00:00:00 2001 From: Sam Leavens Date: Mon, 18 Sep 2017 11:32:09 -0700 Subject: [PATCH] feat(helm): add --format flag to 'helm list' When 'helm list --format (text|json|yaml) is run, this will print the list of releases in the specified format. WIP #1534 --- cmd/helm/list.go | 94 ++++++++++++++++++++++++++++++++++++++----- cmd/helm/list_test.go | 16 ++++++++ 2 files changed, 100 insertions(+), 10 deletions(-) diff --git a/cmd/helm/list.go b/cmd/helm/list.go index f6cdaacfe..a228126fa 100644 --- a/cmd/helm/list.go +++ b/cmd/helm/list.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "encoding/json" "fmt" "io" "strings" @@ -24,10 +25,13 @@ import ( "github.com/gosuri/uitable" "github.com/spf13/cobra" + "bytes" + "github.com/ghodss/yaml" "k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/timeconv" + "time" ) var listHelp = ` @@ -73,9 +77,19 @@ type listCmd struct { namespace string superseded bool pending bool + format string client helm.Interface } +type listRelease struct { + Name string + Revision int32 + Updated string + Status string + Namespace string + Chart string +} + func newListCmd(client helm.Interface, out io.Writer) *cobra.Command { list := &listCmd{ out: out, @@ -112,6 +126,7 @@ func newListCmd(client helm.Interface, out io.Writer) *cobra.Command { f.BoolVar(&list.failed, "failed", false, "show failed releases") f.BoolVar(&list.pending, "pending", false, "show pending releases") f.StringVar(&list.namespace, "namespace", "", "show releases within a specific namespace") + f.StringVar(&list.format, "format", "text", "format output as text, json, or yaml") // TODO: Do we want this as a feature of 'helm list'? //f.BoolVar(&list.superseded, "history", true, "show historical releases") @@ -150,19 +165,20 @@ func (l *listCmd) run() error { return nil } - if res.Next != "" && !l.short { + if res.Next != "" && !l.short && l.format == "text" { fmt.Fprintf(l.out, "\tnext: %s\n", res.Next) } - rels := res.Releases - + format := l.format if l.short { - for _, r := range rels { - fmt.Fprintln(l.out, r.Name) - } - return nil + format = "short" + } + + formatted, err := formatList(res.Releases, format) + if err != nil { + return err } - fmt.Fprintln(l.out, formatList(rels)) + fmt.Fprint(l.out, formatted) return nil } @@ -207,7 +223,26 @@ func (l *listCmd) statusCodes() []release.Status_Code { return status } -func formatList(rels []*release.Release) string { +func formatList(rels []*release.Release, format string) (string, error) { + var res string + var err error + + switch format { + case "text": + res = formatText(rels) + case "short": + res = formatShort(rels) + case "json": + res = fmt.Sprintf("%s\n", formatJSON(rels)) + case "yaml": + res = fmt.Sprintf("%s", formatYAML(rels)) + default: + err = fmt.Errorf("invalid format \"%s\"", format) + } + return res, err +} + +func formatText(rels []*release.Release) string { table := uitable.New() table.MaxColWidth = 60 table.AddRow("NAME", "REVISION", "UPDATED", "STATUS", "CHART", "NAMESPACE") @@ -219,5 +254,44 @@ func formatList(rels []*release.Release) string { n := r.Namespace table.AddRow(r.Name, v, t, s, c, n) } - return table.String() + return fmt.Sprintf("%s\n", table.String()) +} + +func formatShort(rels []*release.Release) string { + var buffer bytes.Buffer + for _, r := range rels { + _, err := buffer.WriteString(fmt.Sprintf("%s\n", r.Name)) + if err != nil { + panic(err) + } + } + return buffer.String() +} + +func formatJSON(rels []*release.Release) []byte { + listReleases := []listRelease{} + for _, r := range rels { + lr := listRelease{ + Name: r.Name, + Revision: r.Version, + Updated: timeconv.Format(r.Info.LastDeployed, time.RFC3339), + Status: r.Info.Status.Code.String(), + Chart: fmt.Sprintf("%s-%s", r.Chart.Metadata.Name, r.Chart.Metadata.Version), + Namespace: r.Namespace, + } + listReleases = append(listReleases, lr) + } + res, err := json.MarshalIndent(listReleases, "", " ") + if err != nil { + panic(err) + } + return res +} + +func formatYAML(rels []*release.Release) []byte { + res, err := yaml.JSONToYAML(formatJSON(rels)) + if err != nil { + panic(err) + } + return res } diff --git a/cmd/helm/list_test.go b/cmd/helm/list_test.go index 5660eb26a..5b5cb5671 100644 --- a/cmd/helm/list_test.go +++ b/cmd/helm/list_test.go @@ -48,6 +48,22 @@ func TestListCmd(t *testing.T) { }, expected: "NAME \tREVISION\tUPDATED \tSTATUS \tCHART \tNAMESPACE\natlas\t1 \t(.*)\tDEPLOYED\tfoo-0.1.0-beta.1\tdefault \n", }, + { + name: "list format JSON", + args: []string{"--format", "json"}, + resp: []*release.Release{ + releaseMock(&releaseOptions{name: "atlas"}), + }, + expected: "\"Name\": \"atlas\",\n", + }, + { + name: "list format YAML", + args: []string{"--format", "yaml"}, + resp: []*release.Release{ + releaseMock(&releaseOptions{name: "atlas"}), + }, + expected: "- Chart: foo-0.1.0-beta.1\n Name: atlas\n Namespace: default\n Revision: 1\n Status: DEPLOYED\n", + }, { name: "list, one deployed, one failed", args: []string{"-q"},