Add helmfile test sub-command (#150)
**Feature** An additional sub-command to the helmfile binary; helmfile test **Why** Helm provides helm test (https://github.com/kubernetes/helm/blob/master/docs/chart_tests.md) as a method to run automated tests post chart install to ensure things are working as they should be. It would be nice to be able to run something like helmfile test against a particular helmfile in order to run helm tests against all charts/releases defined in the file. Either as part of the sync (i.e. helmfile sync --test) to be ran immediately after the corresponding chart is installed, or as a separate command ran after a sync (i.e. helmfile test). A chart without tests will exit with a 0 status, so it can be safely ran against any charts. **Notes** `--cleanup` (bool) & `--timeout` (default: 300) are available as first class arguments. Additional arguments can be passed to the helm binary as with other sub commands using `--args=` Resolves #144
This commit is contained in:
parent
5735efa8c0
commit
37f6ae8557
|
|
@ -156,6 +156,7 @@ COMMANDS:
|
|||
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)
|
||||
test tets releases from state file (helm test)
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
--file FILE, -f FILE load config from FILE (default: "helmfile.yaml")
|
||||
|
|
@ -200,6 +201,12 @@ To supply the secret functionality Helmfile needs the `helm secrets` plugin inst
|
|||
you should be able to simply execute `helm plugin install https://github.com/futuresimple/helm-secrets
|
||||
`.
|
||||
|
||||
### test
|
||||
|
||||
The `helmfile test` sub-command runs a `helm test` against specified releases in the manifest, default to all
|
||||
|
||||
Use `--cleanup` to delete pods upon completion.
|
||||
|
||||
## Paths Overview
|
||||
Using manifest files in conjunction with command line argument can be a bit confusing.
|
||||
|
||||
|
|
|
|||
|
|
@ -85,6 +85,12 @@ func (helm *execer) DeleteRelease(name string, flags ...string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) TestRelease(name string, flags ...string) error {
|
||||
out, err := helm.exec(append([]string{"test", name}, flags...)...)
|
||||
helm.write(out)
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) exec(args ...string) ([]byte, error) {
|
||||
cmdargs := args
|
||||
if len(helm.extra) > 0 {
|
||||
|
|
|
|||
|
|
@ -164,6 +164,25 @@ func Test_DeleteRelease_Flags(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_TestRelease(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
helm := MockExecer(&buffer, "dev")
|
||||
helm.TestRelease("release")
|
||||
expected := "exec: helm test release --kube-context dev\n"
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.TestRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
}
|
||||
}
|
||||
func Test_TestRelease_Flags(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
helm := MockExecer(&buffer, "dev")
|
||||
helm.TestRelease("release", "--cleanup", "--timeout", "60")
|
||||
expected := "exec: helm test release --cleanup --timeout 60 --kube-context dev\n"
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.TestRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ReleaseStatus(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
helm := MockExecer(&buffer, "dev")
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ type Interface interface {
|
|||
DiffRelease(name, chart string, flags ...string) error
|
||||
ReleaseStatus(name string) error
|
||||
DeleteRelease(name string, flags ...string) error
|
||||
TestRelease(name string, flags ...string) error
|
||||
|
||||
DecryptSecret(name string) (string, error)
|
||||
}
|
||||
|
|
|
|||
37
main.go
37
main.go
|
|
@ -271,6 +271,43 @@ func main() {
|
|||
return clean(state, errs)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "test",
|
||||
Usage: "test releases from state file (helm test)",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "cleanup",
|
||||
Usage: "delete test pods upon completion",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
Value: "",
|
||||
Usage: "pass additional args to helm exec",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "timeout",
|
||||
Value: 300,
|
||||
Usage: "maximum time for tests to run before being considered failed",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
state, helm, err := before(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cleanup := c.Bool("cleanup")
|
||||
timeout := c.Int("timeout")
|
||||
|
||||
args := c.String("args")
|
||||
if len(args) > 0 {
|
||||
helm.SetExtraArgs(strings.Split(args, " ")...)
|
||||
}
|
||||
|
||||
errs := state.TestReleases(helm, cleanup, timeout)
|
||||
return clean(state, errs)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := app.Run(os.Args)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
|
|
@ -382,6 +383,34 @@ func (state *HelmState) DeleteReleases(helm helmexec.Interface, purge bool) []er
|
|||
return nil
|
||||
}
|
||||
|
||||
// TestReleases wrapper for executing helm test on the releases
|
||||
func (state *HelmState) TestReleases(helm helmexec.Interface, cleanup bool, timeout int) []error {
|
||||
var wg sync.WaitGroup
|
||||
errs := []error{}
|
||||
|
||||
for _, release := range state.Releases {
|
||||
wg.Add(1)
|
||||
go func(wg *sync.WaitGroup, release ReleaseSpec) {
|
||||
flags := []string{}
|
||||
if cleanup {
|
||||
flags = append(flags, "--cleanup")
|
||||
}
|
||||
flags = append(flags, "--timeout", strconv.Itoa(timeout))
|
||||
if err := helm.TestRelease(release.Name, flags...); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
wg.Done()
|
||||
}(&wg, release)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if len(errs) != 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clean will remove any generated secrets
|
||||
func (state *HelmState) Clean() []error {
|
||||
errs := []error{}
|
||||
|
|
|
|||
|
|
@ -552,6 +552,9 @@ func (helm *mockHelmExec) DeleteRelease(name string, flags ...string) error {
|
|||
func (helm *mockHelmExec) DecryptSecret(name string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
func (helm *mockHelmExec) TestRelease(name string, flags ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestHelmState_SyncRepos(t *testing.T) {
|
||||
tests := []struct {
|
||||
|
|
|
|||
Loading…
Reference in New Issue