diff --git a/main.go b/main.go index 78460253..8fa37a55 100644 --- a/main.go +++ b/main.go @@ -471,7 +471,13 @@ func main() { { Name: "list", Usage: "list releases defined in state file", - Flags: []cli.Flag{}, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "output", + Value: "", + Usage: "output releases list as a json string", + }, + }, Action: action(func(run *app.App, c configImpl) error { return run.ListReleases(c) }), @@ -597,6 +603,12 @@ func (c configImpl) Timeout() int { return c.c.Int("timeout") } +// ListConfig + +func (c configImpl) Output() string { + return c.c.String("output") +} + // GlobalConfig func (c configImpl) HelmBinary() string { diff --git a/pkg/app/app.go b/pkg/app/app.go index bc1162ec..211797fd 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -14,7 +14,6 @@ import ( "syscall" "text/tabwriter" - "github.com/gosuri/uitable" "github.com/roboll/helmfile/pkg/argparser" "github.com/roboll/helmfile/pkg/helmexec" "github.com/roboll/helmfile/pkg/remote" @@ -60,6 +59,13 @@ type App struct { helmsMutex sync.Mutex } +type HelmRelease struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + Enabled bool `json:"enabled"` + Labels string `json:"labels"` +} + func New(conf ConfigProvider) *App { return Init(&App{ OverrideKubeContext: conf.KubeContext(), @@ -259,9 +265,8 @@ func (a *App) PrintState(c StateConfigProvider) error { }) } -func (a *App) ListReleases(c StateConfigProvider) error { - table := uitable.New() - table.AddRow("NAME", "NAMESPACE", "ENABLED", "LABELS") +func (a *App) ListReleases(c ListConfigProvider) error { + var releases []*HelmRelease err := a.VisitDesiredStatesWithReleasesFiltered(a.FileOrDir, func(st *state.HelmState) []error { //var releases m @@ -270,12 +275,28 @@ func (a *App) ListReleases(c StateConfigProvider) error { for k, v := range r.Labels { labels = fmt.Sprintf("%s,%s:%s", labels, k, v) } + labels = strings.Trim(labels, ",") installed := r.Installed == nil || *r.Installed - table.AddRow(r.Name, r.Namespace, fmt.Sprintf("%t", installed), strings.Trim(labels, ",")) + releases = append(releases, &HelmRelease{ + Name: r.Name, + Namespace: r.Namespace, + Enabled: installed, + Labels: labels, + }) } return []error{} }) - fmt.Println(table.String()) + + if err != nil { + return err + } + + if c.Output() == "json" { + err = FormatAsJson(releases) + } else { + err = FormatAsTable(releases) + } + return err } diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index ddcbcce7..0623b575 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -1962,7 +1962,8 @@ services: } type configImpl struct { - set []string + set []string + output string } func (c configImpl) Set() []string { @@ -1993,6 +1994,10 @@ func (c configImpl) Concurrency() int { return 1 } +func (c configImpl) Output() string { + return c.output +} + type applyConfig struct { args string values []string @@ -3849,6 +3854,63 @@ myrelease4 true id:myrelease1 assert.Equal(t, expected, out) } +func TestListWithJsonOutput(t *testing.T) { + files := map[string]string{ + "/path/to/helmfile.d/first.yaml": ` +releases: +- name: myrelease1 + chart: mychart1 + installed: no + labels: + id: myrelease1 +- name: myrelease2 + chart: mychart1 +`, + "/path/to/helmfile.d/second.yaml": ` +releases: +- name: myrelease3 + chart: mychart1 + installed: yes +- name: myrelease4 + chart: mychart1 + labels: + id: myrelease1 +`, + } + stdout := os.Stdout + defer func() { os.Stdout = stdout }() + + var buffer bytes.Buffer + logger := helmexec.NewLogger(&buffer, "debug") + + app := appWithFs(&App{ + OverrideHelmBinary: DefaultHelmBinary, + glob: filepath.Glob, + abs: filepath.Abs, + OverrideKubeContext: "default", + Env: "default", + Logger: logger, + Namespace: "testNamespace", + }, files) + + expectNoCallsToHelm(app) + + out := captureStdout(func() { + err := app.ListReleases(configImpl{ + output: "json", + }) + assert.NilError(t, err) + }) + + expected := "[" + + "{\"name\":\"myrelease1\",\"namespace\":\"\",\"enabled\":false,\"labels\":\"id:myrelease1\"}," + + "{\"name\":\"myrelease2\",\"namespace\":\"\",\"enabled\":true,\"labels\":\"\"}," + + "{\"name\":\"myrelease3\",\"namespace\":\"\",\"enabled\":true,\"labels\":\"\"}," + + "{\"name\":\"myrelease4\",\"namespace\":\"\",\"enabled\":true,\"labels\":\"id:myrelease1\"}" + + "]\n" + assert.Equal(t, expected, out) +} + func TestSetValuesTemplate(t *testing.T) { files := map[string]string{ "/path/to/helmfile.yaml": ` diff --git a/pkg/app/config.go b/pkg/app/config.go index 18d39dba..d64a8091 100644 --- a/pkg/app/config.go +++ b/pkg/app/config.go @@ -156,3 +156,7 @@ type loggingConfig interface { type interactive interface { Interactive() bool } + +type ListConfigProvider interface { + Output() string +} diff --git a/pkg/app/formatters.go b/pkg/app/formatters.go new file mode 100644 index 00000000..c60c6c6b --- /dev/null +++ b/pkg/app/formatters.go @@ -0,0 +1,33 @@ +package app + +import ( + "encoding/json" + "fmt" + + "github.com/gosuri/uitable" +) + +func FormatAsTable(releases []*HelmRelease) error { + table := uitable.New() + table.AddRow("NAME", "NAMESPACE", "ENABLED", "LABELS") + + for _, r := range releases { + table.AddRow(r.Name, r.Namespace, fmt.Sprintf("%t", r.Enabled), r.Labels) + } + + fmt.Println(table.String()) + + return nil +} + +func FormatAsJson(releases []*HelmRelease) error { + output, err := json.Marshal(releases) + + if err != nil { + return fmt.Errorf("error generating json: %v", err) + } + + fmt.Println(string(output)) + + return nil +}