From 579fa4c765cf25fc104ebb54e1e58ad36f5f9580 Mon Sep 17 00:00:00 2001 From: Cedric Meury Date: Sun, 25 Mar 2018 00:49:15 +0100 Subject: [PATCH] status command retrieves release status --- README.md | 1 + helmexec/exec.go | 8 ++++++ helmexec/exec_test.go | 10 ++++++++ helmexec/helmexec.go | 1 + main.go | 32 +++++++++++++++++++++++ state/state.go | 43 +++++++++++++++++++++++++++++++ state/state_test.go | 59 +++++++++++++++++++++++++++++++++++++++++-- 7 files changed, 152 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 95d58bb7..68d071ff 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,7 @@ COMMANDS: charts sync charts from state file (helm upgrade --install) diff diff charts from state file against env (helm diff) sync sync all resources from state file (repos, charts and local chart deps) + status retrieve status of releases in state file delete delete charts from state file (helm delete) GLOBAL OPTIONS: diff --git a/helmexec/exec.go b/helmexec/exec.go index b1d2d9bc..e66e3cd6 100644 --- a/helmexec/exec.go +++ b/helmexec/exec.go @@ -59,6 +59,14 @@ func (helm *execer) SyncRelease(name, chart string, flags ...string) error { return err } +func (helm *execer) ReleaseStatus(name string) error { + out, err := helm.exec(append([]string{"status", name})...) + if helm.writer != nil { + helm.writer.Write(out) + } + return err +} + func (helm *execer) DecryptSecret(name string) (string, error) { out, err := helm.exec(append([]string{"secrets", "dec", name})...) helm.write(out) diff --git a/helmexec/exec_test.go b/helmexec/exec_test.go index 45476834..68946562 100644 --- a/helmexec/exec_test.go +++ b/helmexec/exec_test.go @@ -155,6 +155,16 @@ func Test_DeleteRelease(t *testing.T) { } } +func Test_ReleaseStatus(t *testing.T) { + var buffer bytes.Buffer + helm := MockExecer(&buffer, "dev") + helm.ReleaseStatus("myRelease") + expected := "exec: helm status myRelease --kube-context dev\n" + if buffer.String() != expected { + t.Errorf("helmexec.ReleaseStatus()\nactual = %v\nexpect = %v", buffer.String(), expected) + } +} + func Test_exec(t *testing.T) { var buffer bytes.Buffer helm := MockExecer(&buffer, "") diff --git a/helmexec/helmexec.go b/helmexec/helmexec.go index 28ec5902..45a59154 100644 --- a/helmexec/helmexec.go +++ b/helmexec/helmexec.go @@ -9,6 +9,7 @@ type Interface interface { UpdateDeps(chart string) error SyncRelease(name, chart string, flags ...string) error DiffRelease(name, chart string, flags ...string) error + ReleaseStatus(name string) error DeleteRelease(name string) error DecryptSecret(name string) (string, error) diff --git a/main.go b/main.go index bf2e4bec..3743cdf2 100644 --- a/main.go +++ b/main.go @@ -218,6 +218,38 @@ func main() { return clean(state, errs) }, }, + { + Name: "status", + Usage: "retrieve status of releases in state file", + Flags: []cli.Flag{ + cli.IntFlag{ + Name: "concurrency", + Value: 0, + Usage: "maximum number of concurrent helm processes to run, 0 is unlimited", + }, + cli.StringFlag{ + Name: "args", + Value: "", + Usage: "pass args to helm exec", + }, + }, + Action: func(c *cli.Context) error { + state, helm, err := before(c) + if err != nil { + return err + } + + workers := c.Int("concurrency") + + args := c.String("args") + if len(args) > 0 { + helm.SetExtraArgs(strings.Split(args, " ")...) + } + + errs := state.ReleaseStatuses(helm, workers) + return clean(state, errs) + }, + }, { Name: "delete", Usage: "delete charts from state file (helm delete)", diff --git a/state/state.go b/state/state.go index 31b6216c..df3b964f 100644 --- a/state/state.go +++ b/state/state.go @@ -298,6 +298,49 @@ func (state *HelmState) DiffReleases(helm helmexec.Interface, additionalValues [ return nil } +func (state *HelmState) ReleaseStatuses(helm helmexec.Interface, workerLimit int) []error { + var errs []error + jobQueue := make(chan ReleaseSpec) + doneQueue := make(chan bool) + errQueue := make(chan error) + + if workerLimit < 1 { + workerLimit = len(state.Releases) + } + for w := 1; w <= workerLimit; w++ { + go func() { + for release := range jobQueue { + if err := helm.ReleaseStatus(release.Name); err != nil { + errQueue <- err + } + doneQueue <- true + } + }() + } + + go func() { + for _, release := range state.Releases { + jobQueue <- release + } + close(jobQueue) + }() + + for i := 0; i < len(state.Releases); { + select { + case err := <-errQueue: + errs = append(errs, err) + case <-doneQueue: + i++ + } + } + + if len(errs) != 0 { + return errs + } + + return nil +} + // DeleteReleases wrapper for executing helm delete on the releases func (state *HelmState) DeleteReleases(helm helmexec.Interface) []error { var wg sync.WaitGroup diff --git a/state/state_test.go b/state/state_test.go index ef293769..04170232 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -468,8 +468,9 @@ func Test_normalizeChart(t *testing.T) { // mocking helmexec.Interface type mockHelmExec struct { - charts []string - repo []string + charts []string + repo []string + releases []string } func (helm *mockHelmExec) UpdateDeps(chart string) error { @@ -496,6 +497,13 @@ func (helm *mockHelmExec) SyncRelease(name, chart string, flags ...string) error func (helm *mockHelmExec) DiffRelease(name, chart string, flags ...string) error { return nil } +func (helm *mockHelmExec) ReleaseStatus(release string) error { + if strings.Contains(release, "error") { + return errors.New("error") + } + helm.releases = append(helm.releases, release) + return nil +} func (helm *mockHelmExec) DeleteRelease(name string) error { return nil } @@ -591,3 +599,50 @@ func TestHelmState_UpdateDeps(t *testing.T) { t.Errorf("HelmState.UpdateDeps() - no errors, but got: %v", len(errs)) } } + +func TestHelmState_ReleaseStatuses(t *testing.T) { + tests := []struct { + name string + releases []ReleaseSpec + helm *mockHelmExec + want []string + wantErr bool + }{ + { + name: "happy path", + releases: []ReleaseSpec{ + { + Name: "releaseA", + }, + }, + helm: &mockHelmExec{}, + want: []string{"releaseA"}, + }, + { + name: "happy path", + releases: []ReleaseSpec{ + { + Name: "error", + }, + }, + helm: &mockHelmExec{}, + wantErr: true, + }, + } + for _, tt := range tests { + i := func(t *testing.T) { + state := &HelmState{ + Releases: tt.releases, + } + errs := state.ReleaseStatuses(tt.helm, 1) + if (errs != nil) != tt.wantErr { + t.Errorf("ReleaseStatuses() for %s error = %v, wantErr %v", tt.name, errs, tt.wantErr) + return + } + if !reflect.DeepEqual(tt.helm.releases, tt.want) { + t.Errorf("HelmState.ReleaseStatuses() for [%s] = %v, want %v", tt.name, tt.helm.releases, tt.want) + } + } + t.Run(tt.name, i) + } +}