diff --git a/helmexec/exec.go b/helmexec/exec.go index d43670c3..00aabb88 100644 --- a/helmexec/exec.go +++ b/helmexec/exec.go @@ -3,9 +3,6 @@ package helmexec import ( "fmt" "io" - "io/ioutil" - "os" - "os/exec" "strings" ) @@ -14,15 +11,17 @@ const ( ) type execer struct { + runner Runner writer io.Writer kubeContext string extra []string } -func NewHelmExec(writer io.Writer, kubeContext string) Interface { +func New(writer io.Writer, kubeContext string) *execer { return &execer{ writer: writer, kubeContext: kubeContext, + runner: &ShellRunner{}, } } @@ -84,12 +83,6 @@ func (helm *execer) DeleteRelease(name string) error { } func (helm *execer) exec(args ...string) ([]byte, error) { - dir, err := ioutil.TempDir("", "helmfile-exec") - if err != nil { - return nil, err - } - defer os.RemoveAll(dir) - cmdargs := args if len(helm.extra) > 0 { cmdargs = append(cmdargs, helm.extra...) @@ -100,8 +93,5 @@ func (helm *execer) exec(args ...string) ([]byte, error) { if helm.writer != nil { helm.writer.Write([]byte(fmt.Sprintf("exec: helm %s\n", strings.Join(cmdargs, " ")))) } - - cmd := exec.Command(command, cmdargs...) - cmd.Dir = dir - return cmd.CombinedOutput() + return helm.runner.Execute(command, cmdargs) } diff --git a/helmexec/exec_test.go b/helmexec/exec_test.go new file mode 100644 index 00000000..c7d3f13a --- /dev/null +++ b/helmexec/exec_test.go @@ -0,0 +1,177 @@ +package helmexec + +import ( + "bytes" + "io" + "reflect" + "testing" +) + +// Mocking the command-line runner + +type mockRunner struct { + output []byte + err error +} + +func (mock *mockRunner) Execute(cmd string, args []string) ([]byte, error) { + return []byte{}, nil +} + +func MockExecer(writer io.Writer, kubeContext string) *execer { + execer := New(writer, kubeContext) + execer.runner = &mockRunner{} + return execer +} + +// Test methods + +func TestNewHelmExec(t *testing.T) { + buffer := bytes.NewBufferString("something") + helm := New(buffer, "dev") + if helm.kubeContext != "dev" { + t.Error("helmexec.New() - kubeContext") + } + if buffer.String() != "something" { + t.Error("helmexec.New() - changed buffer") + } + if len(helm.extra) != 0 { + t.Error("helmexec.New() - extra args not empty") + } +} + +func Test_SetExtraArgs(t *testing.T) { + helm := New(new(bytes.Buffer), "dev") + helm.SetExtraArgs() + if len(helm.extra) != 0 { + t.Error("helmexec.SetExtraArgs() - passing no arguments should not change extra field") + } + helm.SetExtraArgs("foo") + if !reflect.DeepEqual(helm.extra, []string{"foo"}) { + t.Error("helmexec.SetExtraArgs() - one extra argument missing") + } + helm.SetExtraArgs("alpha", "beta") + if !reflect.DeepEqual(helm.extra, []string{"alpha", "beta"}) { + t.Error("helmexec.SetExtraArgs() - two extra arguments missing (overwriting the previous value)") + } +} + +func Test_AddRepo(t *testing.T) { + var buffer bytes.Buffer + helm := MockExecer(&buffer, "dev") + helm.AddRepo("myRepo", "https://repo.example.com/", "cert.pem", "key.pem") + expected := "exec: helm repo add myRepo https://repo.example.com/ --cert-file cert.pem --key-file key.pem --kube-context dev\n" + if buffer.String() != expected { + t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected) + } + + buffer.Reset() + helm.AddRepo("myRepo", "https://repo.example.com/", "", "") + expected = "exec: helm repo add myRepo https://repo.example.com/ --kube-context dev\n" + if buffer.String() != expected { + t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected) + } +} + +func Test_UpdateRepo(t *testing.T) { + var buffer bytes.Buffer + helm := MockExecer(&buffer, "dev") + helm.UpdateRepo() + expected := "exec: helm repo update --kube-context dev\n" + if buffer.String() != expected { + t.Errorf("helmexec.UpdateRepo()\nactual = %v\nexpect = %v", buffer.String(), expected) + } +} + +func Test_SyncRelease(t *testing.T) { + var buffer bytes.Buffer + helm := MockExecer(&buffer, "dev") + helm.SyncRelease("release", "chart", "--timeout 10", "--wait") + expected := "exec: helm upgrade --install release chart --timeout 10 --wait --kube-context dev\n" + if buffer.String() != expected { + t.Errorf("helmexec.SyncRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) + } + + buffer.Reset() + helm.SyncRelease("release", "chart") + expected = "exec: helm upgrade --install release chart --kube-context dev\n" + if buffer.String() != expected { + t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected) + } +} + +func Test_DecryptSecret(t *testing.T) { + var buffer bytes.Buffer + helm := MockExecer(&buffer, "dev") + helm.DecryptSecret("secretName") + expected := "exec: helm secrets dec secretName --kube-context dev\n" + if buffer.String() != expected { + t.Errorf("helmexec.DecryptSecret()\nactual = %v\nexpect = %v", buffer.String(), expected) + } +} + +func Test_DiffRelease(t *testing.T) { + var buffer bytes.Buffer + helm := MockExecer(&buffer, "dev") + helm.DiffRelease("release", "chart", "--timeout 10", "--wait") + expected := "exec: helm diff release chart --timeout 10 --wait --kube-context dev\n" + if buffer.String() != expected { + t.Errorf("helmexec.DiffRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) + } + + buffer.Reset() + helm.DiffRelease("release", "chart") + expected = "exec: helm diff release chart --kube-context dev\n" + if buffer.String() != expected { + t.Errorf("helmexec.DiffRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) + } +} + +func Test_DeleteRelease(t *testing.T) { + var buffer bytes.Buffer + helm := MockExecer(&buffer, "dev") + helm.DeleteRelease("release") + expected := "exec: helm delete --purge release --kube-context dev\n" + if buffer.String() != expected { + t.Errorf("helmexec.DeleteRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) + } +} + +func Test_exec(t *testing.T) { + var buffer bytes.Buffer + helm := MockExecer(&buffer, "") + helm.exec("version") + expected := "exec: helm version\n" + if buffer.String() != expected { + t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected) + } + + helm = MockExecer(nil, "dev") + ret, _ := helm.exec("diff") + if len(ret) != 0 { + t.Error("helmexec.exec() - expected empty return value") + } + + buffer.Reset() + helm = MockExecer(&buffer, "dev") + helm.exec("diff", "release", "chart", "--timeout 10", "--wait") + expected = "exec: helm diff release chart --timeout 10 --wait --kube-context dev\n" + if buffer.String() != expected { + t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected) + } + + buffer.Reset() + helm.exec("version") + expected = "exec: helm version --kube-context dev\n" + if buffer.String() != expected { + t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected) + } + + buffer.Reset() + helm.SetExtraArgs("foo") + helm.exec("version") + expected = "exec: helm version foo --kube-context dev\n" + if buffer.String() != expected { + t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected) + } +} diff --git a/helmexec/runner.go b/helmexec/runner.go new file mode 100644 index 00000000..b1f5a7fa --- /dev/null +++ b/helmexec/runner.go @@ -0,0 +1,30 @@ +package helmexec + +import ( + "io/ioutil" + "os" + "os/exec" +) + +const ( + tmpPrefix = "helmfile-" + tmpSuffix = "-exec" +) + +type Runner interface { + Execute(cmd string, args []string) ([]byte, error) +} + +type ShellRunner struct{} + +func (shell ShellRunner) Execute(cmd string, args []string) ([]byte, error) { + dir, err := ioutil.TempDir("", tmpPrefix+cmd+tmpSuffix) + if err != nil { + return nil, err + } + defer os.RemoveAll(dir) + + preparedCmd := exec.Command(cmd, args...) + preparedCmd.Dir = dir + return preparedCmd.CombinedOutput() +} diff --git a/main.go b/main.go index 2d7e69cc..5d036d0d 100644 --- a/main.go +++ b/main.go @@ -278,7 +278,7 @@ func before(c *cli.Context) (*state.HelmState, helmexec.Interface, error) { clean(st, errs) }() - return st, helmexec.NewHelmExec(writer, kubeContext), nil + return st, helmexec.New(writer, kubeContext), nil } func clean(state *state.HelmState, errs []error) error {