mirror of
https://github.com/helmfile/helmfile.git
synced 2026-10-07 17:00:17 +02:00
feat: show live output from the Helm binary (#286)
* feat: show live output from the Helm binary Signed-off-by: Rodrigo Fior Kuntzer <rodrigo@miro.com> * fixup! Merge branch 'main' into enable-live-output Signed-off-by: Yusuke Kuoka <ykuoka@gmail.com>
This commit is contained in:
+38
-21
@@ -28,6 +28,7 @@ type decryptedSecret struct {
|
||||
|
||||
type execer struct {
|
||||
helmBinary string
|
||||
enableLiveOutput bool
|
||||
version semver.Version
|
||||
runner Runner
|
||||
logger *zap.SugaredLogger
|
||||
@@ -78,7 +79,7 @@ func parseHelmVersion(versionStr string) (semver.Version, error) {
|
||||
|
||||
func getHelmVersion(helmBinary string, runner Runner) (semver.Version, error) {
|
||||
// Autodetect from `helm version`
|
||||
outBytes, err := runner.Execute(helmBinary, []string{"version", "--client", "--short"}, nil)
|
||||
outBytes, err := runner.Execute(helmBinary, []string{"version", "--client", "--short"}, nil, false)
|
||||
if err != nil {
|
||||
return semver.Version{}, fmt.Errorf("error determining helm version: %w", err)
|
||||
}
|
||||
@@ -110,7 +111,7 @@ func redactedURL(chart string) string {
|
||||
|
||||
// New for running helm commands
|
||||
// nolint: golint
|
||||
func New(helmBinary string, logger *zap.SugaredLogger, kubeContext string, runner Runner) *execer {
|
||||
func New(helmBinary string, enableLiveOutput bool, logger *zap.SugaredLogger, kubeContext string, runner Runner) *execer {
|
||||
// TODO: proper error handling
|
||||
version, err := getHelmVersion(helmBinary, runner)
|
||||
if err != nil {
|
||||
@@ -118,6 +119,7 @@ func New(helmBinary string, logger *zap.SugaredLogger, kubeContext string, runne
|
||||
}
|
||||
return &execer{
|
||||
helmBinary: helmBinary,
|
||||
enableLiveOutput: enableLiveOutput,
|
||||
version: version,
|
||||
logger: logger,
|
||||
kubeContext: kubeContext,
|
||||
@@ -134,6 +136,10 @@ func (helm *execer) SetHelmBinary(bin string) {
|
||||
helm.helmBinary = bin
|
||||
}
|
||||
|
||||
func (helm *execer) SetEnableLiveOutput(enableLiveOutput bool) {
|
||||
helm.enableLiveOutput = enableLiveOutput
|
||||
}
|
||||
|
||||
func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error {
|
||||
var args []string
|
||||
var out []byte
|
||||
@@ -174,7 +180,7 @@ func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, usernam
|
||||
args = append(args, "--insecure-skip-tls-verify")
|
||||
}
|
||||
helm.logger.Infof("Adding repo %v %v", name, repository)
|
||||
out, err = helm.exec(args, map[string]string{})
|
||||
out, err = helm.exec(args, map[string]string{}, nil)
|
||||
default:
|
||||
helm.logger.Errorf("ERROR: unknown type '%v' for repository %v", managed, name)
|
||||
out = nil
|
||||
@@ -186,7 +192,7 @@ func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, usernam
|
||||
|
||||
func (helm *execer) UpdateRepo() error {
|
||||
helm.logger.Info("Updating repo")
|
||||
out, err := helm.exec([]string{"repo", "update"}, map[string]string{})
|
||||
out, err := helm.exec([]string{"repo", "update"}, map[string]string{}, nil)
|
||||
helm.info(out)
|
||||
return err
|
||||
}
|
||||
@@ -210,14 +216,14 @@ func (helm *execer) RegistryLogin(repository string, username string, password s
|
||||
|
||||
func (helm *execer) BuildDeps(name, chart string) error {
|
||||
helm.logger.Infof("Building dependency release=%v, chart=%v", name, chart)
|
||||
out, err := helm.exec([]string{"dependency", "build", chart}, map[string]string{})
|
||||
out, err := helm.exec([]string{"dependency", "build", chart}, map[string]string{}, nil)
|
||||
helm.info(out)
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) UpdateDeps(chart string) error {
|
||||
helm.logger.Infof("Updating dependency %v", chart)
|
||||
out, err := helm.exec([]string{"dependency", "update", chart}, map[string]string{})
|
||||
out, err := helm.exec([]string{"dependency", "update", chart}, map[string]string{}, nil)
|
||||
helm.info(out)
|
||||
return err
|
||||
}
|
||||
@@ -233,7 +239,7 @@ func (helm *execer) SyncRelease(context HelmContext, name, chart string, flags .
|
||||
env["HELM_TILLER_HISTORY_MAX"] = strconv.Itoa(context.HistoryMax)
|
||||
}
|
||||
|
||||
out, err := helm.exec(append(append(preArgs, "upgrade", "--install", "--reset-values", name, chart), flags...), env)
|
||||
out, err := helm.exec(append(append(preArgs, "upgrade", "--install", "--reset-values", name, chart), flags...), env, nil)
|
||||
helm.write(nil, out)
|
||||
return err
|
||||
}
|
||||
@@ -242,7 +248,7 @@ func (helm *execer) ReleaseStatus(context HelmContext, name string, flags ...str
|
||||
helm.logger.Infof("Getting status %v", name)
|
||||
preArgs := context.GetTillerlessArgs(helm)
|
||||
env := context.getTillerlessEnv()
|
||||
out, err := helm.exec(append(append(preArgs, "status", name), flags...), env)
|
||||
out, err := helm.exec(append(append(preArgs, "status", name), flags...), env, nil)
|
||||
helm.write(nil, out)
|
||||
return err
|
||||
}
|
||||
@@ -258,7 +264,8 @@ func (helm *execer) List(context HelmContext, filter string, flags ...string) (s
|
||||
args = []string{"list", filter}
|
||||
}
|
||||
|
||||
out, err := helm.exec(append(append(preArgs, args...), flags...), env)
|
||||
enableLiveOutput := false
|
||||
out, err := helm.exec(append(append(preArgs, args...), flags...), env, &enableLiveOutput)
|
||||
// In v2 we have been expecting `helm list FILTER` prints nothing.
|
||||
// In v3 helm still prints the header like `NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION`,
|
||||
// which confuses helmfile's existing logic that treats any non-empty output from `helm list` is considered as the indication
|
||||
@@ -308,7 +315,8 @@ func (helm *execer) DecryptSecret(context HelmContext, name string, flags ...str
|
||||
if pluginVersion.Major() > 3 {
|
||||
secretArg = "decrypt"
|
||||
}
|
||||
secretBytes, err := helm.exec(append(append(preArgs, "secrets", secretArg, absPath), flags...), env)
|
||||
enableLiveOutput := false
|
||||
secretBytes, err := helm.exec(append(append(preArgs, "secrets", secretArg, absPath), flags...), env, &enableLiveOutput)
|
||||
if err != nil {
|
||||
secret.err = err
|
||||
return "", err
|
||||
@@ -370,7 +378,7 @@ func (helm *execer) TemplateRelease(name string, chart string, flags ...string)
|
||||
args = []string{"template", chart, "--name", name}
|
||||
}
|
||||
|
||||
out, err := helm.exec(append(args, flags...), map[string]string{})
|
||||
out, err := helm.exec(append(args, flags...), map[string]string{}, nil)
|
||||
|
||||
var outputToFile bool
|
||||
|
||||
@@ -407,7 +415,12 @@ func (helm *execer) DiffRelease(context HelmContext, name, chart string, suppres
|
||||
}
|
||||
preArgs := context.GetTillerlessArgs(helm)
|
||||
env := context.getTillerlessEnv()
|
||||
out, err := helm.exec(append(append(preArgs, "diff", "upgrade", "--reset-values", "--allow-unreleased", name, chart), flags...), env)
|
||||
var overrideEnableLiveOutput *bool = nil
|
||||
if suppressDiff {
|
||||
enableLiveOutput := false
|
||||
overrideEnableLiveOutput = &enableLiveOutput
|
||||
}
|
||||
out, err := helm.exec(append(append(preArgs, "diff", "upgrade", "--reset-values", "--allow-unreleased", name, chart), flags...), env, overrideEnableLiveOutput)
|
||||
// Do our best to write STDOUT only when diff existed
|
||||
// Unfortunately, this works only when you run helmfile with `--detailed-exitcode`
|
||||
detailedExitcodeEnabled := false
|
||||
@@ -433,14 +446,14 @@ func (helm *execer) DiffRelease(context HelmContext, name, chart string, suppres
|
||||
|
||||
func (helm *execer) Lint(name, chart string, flags ...string) error {
|
||||
helm.logger.Infof("Linting release=%v, chart=%v", name, chart)
|
||||
out, err := helm.exec(append([]string{"lint", chart}, flags...), map[string]string{})
|
||||
out, err := helm.exec(append([]string{"lint", chart}, flags...), map[string]string{}, nil)
|
||||
helm.write(nil, out)
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) Fetch(chart string, flags ...string) error {
|
||||
helm.logger.Infof("Fetching %v", redactedURL(chart))
|
||||
out, err := helm.exec(append([]string{"fetch", chart}, flags...), map[string]string{})
|
||||
out, err := helm.exec(append([]string{"fetch", chart}, flags...), map[string]string{}, nil)
|
||||
helm.info(out)
|
||||
return err
|
||||
}
|
||||
@@ -463,7 +476,7 @@ func (helm *execer) ChartPull(chart string, flags ...string) error {
|
||||
} else {
|
||||
helmArgs = []string{"chart", "pull", chart}
|
||||
}
|
||||
out, err := helm.exec(append(helmArgs, flags...), map[string]string{"HELM_EXPERIMENTAL_OCI": "1"})
|
||||
out, err := helm.exec(append(helmArgs, flags...), map[string]string{"HELM_EXPERIMENTAL_OCI": "1"}, nil)
|
||||
helm.info(out)
|
||||
return err
|
||||
}
|
||||
@@ -478,7 +491,7 @@ func (helm *execer) ChartExport(chart string, path string, flags ...string) erro
|
||||
} else {
|
||||
helmArgs = []string{"chart", "export", chart}
|
||||
}
|
||||
out, err := helm.exec(append(append(helmArgs, "--destination", path), flags...), map[string]string{"HELM_EXPERIMENTAL_OCI": "1"})
|
||||
out, err := helm.exec(append(append(helmArgs, "--destination", path), flags...), map[string]string{"HELM_EXPERIMENTAL_OCI": "1"}, nil)
|
||||
helm.info(out)
|
||||
return err
|
||||
}
|
||||
@@ -487,7 +500,7 @@ func (helm *execer) DeleteRelease(context HelmContext, name string, flags ...str
|
||||
helm.logger.Infof("Deleting %v", name)
|
||||
preArgs := context.GetTillerlessArgs(helm)
|
||||
env := context.getTillerlessEnv()
|
||||
out, err := helm.exec(append(append(preArgs, "delete", name), flags...), env)
|
||||
out, err := helm.exec(append(append(preArgs, "delete", name), flags...), env, nil)
|
||||
helm.write(nil, out)
|
||||
return err
|
||||
}
|
||||
@@ -497,12 +510,12 @@ func (helm *execer) TestRelease(context HelmContext, name string, flags ...strin
|
||||
preArgs := context.GetTillerlessArgs(helm)
|
||||
env := context.getTillerlessEnv()
|
||||
args := []string{"test", name}
|
||||
out, err := helm.exec(append(append(preArgs, args...), flags...), env)
|
||||
out, err := helm.exec(append(append(preArgs, args...), flags...), env, nil)
|
||||
helm.write(nil, out)
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) exec(args []string, env map[string]string) ([]byte, error) {
|
||||
func (helm *execer) exec(args []string, env map[string]string, overrideEnableLiveOutput *bool) ([]byte, error) {
|
||||
cmdargs := args
|
||||
if len(helm.extra) > 0 {
|
||||
cmdargs = append(cmdargs, helm.extra...)
|
||||
@@ -512,7 +525,11 @@ func (helm *execer) exec(args []string, env map[string]string) ([]byte, error) {
|
||||
}
|
||||
cmd := fmt.Sprintf("exec: %s %s", helm.helmBinary, strings.Join(cmdargs, " "))
|
||||
helm.logger.Debug(cmd)
|
||||
outBytes, err := helm.runner.Execute(helm.helmBinary, cmdargs, env)
|
||||
enableLiveOutput := helm.enableLiveOutput
|
||||
if overrideEnableLiveOutput != nil {
|
||||
enableLiveOutput = *overrideEnableLiveOutput
|
||||
}
|
||||
outBytes, err := helm.runner.Execute(helm.helmBinary, cmdargs, env, enableLiveOutput)
|
||||
return outBytes, err
|
||||
}
|
||||
|
||||
@@ -534,7 +551,7 @@ func (helm *execer) azcli(name string) ([]byte, error) {
|
||||
cmdargs := append(strings.Split("acr helm repo add --name", " "), name)
|
||||
cmd := fmt.Sprintf("exec: az %s", strings.Join(cmdargs, " "))
|
||||
helm.logger.Debug(cmd)
|
||||
outBytes, err := helm.runner.Execute("az", cmdargs, map[string]string{})
|
||||
outBytes, err := helm.runner.Execute("az", cmdargs, map[string]string{}, false)
|
||||
helm.logger.Debugf("%s: %s", cmd, outBytes)
|
||||
return outBytes, err
|
||||
}
|
||||
|
||||
+25
-14
@@ -29,13 +29,13 @@ func (mock *mockRunner) ExecuteStdIn(cmd string, args []string, env map[string]s
|
||||
return mock.output, mock.err
|
||||
}
|
||||
|
||||
func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string) ([]byte, error) {
|
||||
func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string, enableLiveOutput bool) ([]byte, error) {
|
||||
return mock.output, mock.err
|
||||
}
|
||||
|
||||
// nolint: golint
|
||||
func MockExecer(logger *zap.SugaredLogger, kubeContext string) *execer {
|
||||
execer := New("helm", logger, kubeContext, &mockRunner{})
|
||||
execer := New("helm", false, logger, kubeContext, &mockRunner{})
|
||||
return execer
|
||||
}
|
||||
|
||||
@@ -82,6 +82,17 @@ func Test_SetHelmBinary(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_SetEnableLiveOutput(t *testing.T) {
|
||||
helm := MockExecer(NewLogger(os.Stdout, "info"), "dev")
|
||||
if helm.enableLiveOutput {
|
||||
t.Error("helmexec.enableLiveOutput should not be enabled by default")
|
||||
}
|
||||
helm.SetEnableLiveOutput(true)
|
||||
if !helm.enableLiveOutput {
|
||||
t.Errorf("helmexec.SetEnableLiveOutput() - actual = %t expect = true", helm.enableLiveOutput)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AddRepo_Helm_3_3_2(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
@@ -582,7 +593,7 @@ func Test_exec(t *testing.T) {
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "")
|
||||
env := map[string]string{}
|
||||
_, err := helm.exec([]string{"version"}, env)
|
||||
_, err := helm.exec([]string{"version"}, env, nil)
|
||||
expected := `exec: helm version
|
||||
`
|
||||
if err != nil {
|
||||
@@ -593,14 +604,14 @@ func Test_exec(t *testing.T) {
|
||||
}
|
||||
|
||||
helm = MockExecer(logger, "dev")
|
||||
ret, _ := helm.exec([]string{"diff"}, env)
|
||||
ret, _ := helm.exec([]string{"diff"}, env, nil)
|
||||
if len(ret) != 0 {
|
||||
t.Error("helmexec.exec() - expected empty return value")
|
||||
}
|
||||
|
||||
buffer.Reset()
|
||||
helm = MockExecer(logger, "dev")
|
||||
_, err = helm.exec([]string{"diff", "release", "chart", "--timeout 10", "--wait", "--wait-for-jobs"}, env)
|
||||
_, err = helm.exec([]string{"diff", "release", "chart", "--timeout 10", "--wait", "--wait-for-jobs"}, env, nil)
|
||||
expected = `exec: helm --kube-context dev diff release chart --timeout 10 --wait --wait-for-jobs
|
||||
`
|
||||
if err != nil {
|
||||
@@ -611,7 +622,7 @@ func Test_exec(t *testing.T) {
|
||||
}
|
||||
|
||||
buffer.Reset()
|
||||
_, err = helm.exec([]string{"version"}, env)
|
||||
_, err = helm.exec([]string{"version"}, env, nil)
|
||||
expected = `exec: helm --kube-context dev version
|
||||
`
|
||||
if err != nil {
|
||||
@@ -623,7 +634,7 @@ func Test_exec(t *testing.T) {
|
||||
|
||||
buffer.Reset()
|
||||
helm.SetExtraArgs("foo")
|
||||
_, err = helm.exec([]string{"version"}, env)
|
||||
_, err = helm.exec([]string{"version"}, env, nil)
|
||||
expected = `exec: helm --kube-context dev version foo
|
||||
`
|
||||
if err != nil {
|
||||
@@ -636,7 +647,7 @@ func Test_exec(t *testing.T) {
|
||||
buffer.Reset()
|
||||
helm = MockExecer(logger, "")
|
||||
helm.SetHelmBinary("overwritten")
|
||||
_, err = helm.exec([]string{"version"}, env)
|
||||
_, err = helm.exec([]string{"version"}, env, nil)
|
||||
expected = `exec: overwritten version
|
||||
`
|
||||
if err != nil {
|
||||
@@ -896,20 +907,20 @@ exec: helm --kube-context dev template https://example_user:example_password@rep
|
||||
|
||||
func Test_IsHelm3(t *testing.T) {
|
||||
helm2Runner := mockRunner{output: []byte("Client: v2.16.0+ge13bc94\n")}
|
||||
helm := New("helm", NewLogger(os.Stdout, "info"), "dev", &helm2Runner)
|
||||
helm := New("helm", false, NewLogger(os.Stdout, "info"), "dev", &helm2Runner)
|
||||
if helm.IsHelm3() {
|
||||
t.Error("helmexec.IsHelm3() - Detected Helm 3 with Helm 2 version")
|
||||
}
|
||||
|
||||
helm3Runner := mockRunner{output: []byte("v3.0.0+ge29ce2a\n")}
|
||||
helm = New("helm", NewLogger(os.Stdout, "info"), "dev", &helm3Runner)
|
||||
helm = New("helm", false, NewLogger(os.Stdout, "info"), "dev", &helm3Runner)
|
||||
if !helm.IsHelm3() {
|
||||
t.Error("helmexec.IsHelm3() - Failed to detect Helm 3")
|
||||
}
|
||||
|
||||
t.Setenv(envvar.Helm3, "1")
|
||||
helm2Runner = mockRunner{output: []byte("Client: v2.16.0+ge13bc94\n")}
|
||||
helm = New("helm", NewLogger(os.Stdout, "info"), "dev", &helm2Runner)
|
||||
helm = New("helm", false, NewLogger(os.Stdout, "info"), "dev", &helm2Runner)
|
||||
if !helm.IsHelm3() {
|
||||
t.Errorf("helmexec.IsHelm3() - Helm3 not detected when %s is set", envvar.Helm3)
|
||||
}
|
||||
@@ -940,14 +951,14 @@ func Test_GetPluginVersion(t *testing.T) {
|
||||
|
||||
func Test_GetVersion(t *testing.T) {
|
||||
helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")}
|
||||
helm := New("helm", NewLogger(os.Stdout, "info"), "dev", &helm2Runner)
|
||||
helm := New("helm", false, NewLogger(os.Stdout, "info"), "dev", &helm2Runner)
|
||||
ver := helm.GetVersion()
|
||||
if ver.Major != 2 || ver.Minor != 16 || ver.Patch != 1 {
|
||||
t.Errorf("helmexec.GetVersion - did not detect correct Helm2 version; it was: %+v", ver)
|
||||
}
|
||||
|
||||
helm3Runner := mockRunner{output: []byte("v3.2.4+ge29ce2a\n")}
|
||||
helm = New("helm", NewLogger(os.Stdout, "info"), "dev", &helm3Runner)
|
||||
helm = New("helm", false, NewLogger(os.Stdout, "info"), "dev", &helm3Runner)
|
||||
ver = helm.GetVersion()
|
||||
if ver.Major != 3 || ver.Minor != 2 || ver.Patch != 4 {
|
||||
t.Errorf("helmexec.GetVersion - did not detect correct Helm3 version; it was: %+v", ver)
|
||||
@@ -956,7 +967,7 @@ func Test_GetVersion(t *testing.T) {
|
||||
|
||||
func Test_IsVersionAtLeast(t *testing.T) {
|
||||
helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")}
|
||||
helm := New("helm", NewLogger(os.Stdout, "info"), "dev", &helm2Runner)
|
||||
helm := New("helm", false, NewLogger(os.Stdout, "info"), "dev", &helm2Runner)
|
||||
if !helm.IsVersionAtLeast("2.1.0") {
|
||||
t.Error("helmexec.IsVersionAtLeast - 2.16.1 not atleast 2.1")
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ type Version struct {
|
||||
type Interface interface {
|
||||
SetExtraArgs(args ...string)
|
||||
SetHelmBinary(bin string)
|
||||
SetEnableLiveOutput(enableLiveOutput bool)
|
||||
|
||||
AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error
|
||||
UpdateRepo() error
|
||||
|
||||
+48
-5
@@ -1,6 +1,7 @@
|
||||
package helmexec
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -16,7 +17,7 @@ import (
|
||||
|
||||
// Runner interface for shell commands
|
||||
type Runner interface {
|
||||
Execute(cmd string, args []string, env map[string]string) ([]byte, error)
|
||||
Execute(cmd string, args []string, env map[string]string, enableLiveOutput bool) ([]byte, error)
|
||||
ExecuteStdIn(cmd string, args []string, env map[string]string, stdin io.Reader) ([]byte, error)
|
||||
}
|
||||
|
||||
@@ -28,13 +29,18 @@ type ShellRunner struct {
|
||||
}
|
||||
|
||||
// Execute a shell command
|
||||
func (shell ShellRunner) Execute(cmd string, args []string, env map[string]string) ([]byte, error) {
|
||||
func (shell ShellRunner) Execute(cmd string, args []string, env map[string]string, enableLiveOutput bool) ([]byte, error) {
|
||||
preparedCmd := exec.Command(cmd, args...)
|
||||
preparedCmd.Dir = shell.Dir
|
||||
preparedCmd.Env = mergeEnv(os.Environ(), env)
|
||||
return Output(preparedCmd, &logWriterGenerator{
|
||||
log: shell.Logger,
|
||||
})
|
||||
|
||||
if !enableLiveOutput {
|
||||
return Output(preparedCmd, &logWriterGenerator{
|
||||
log: shell.Logger,
|
||||
})
|
||||
} else {
|
||||
return LiveOutput(preparedCmd, os.Stdout)
|
||||
}
|
||||
}
|
||||
|
||||
// Execute a shell command
|
||||
@@ -94,6 +100,43 @@ func Output(c *exec.Cmd, logWriterGenerators ...*logWriterGenerator) ([]byte, er
|
||||
return stdout.Bytes(), err
|
||||
}
|
||||
|
||||
func LiveOutput(c *exec.Cmd, stdout io.Writer) ([]byte, error) {
|
||||
reader, writer := io.Pipe()
|
||||
scannerStopped := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(scannerStopped)
|
||||
scanner := bufio.NewScanner(reader)
|
||||
for scanner.Scan() {
|
||||
fmt.Fprintln(stdout, scanner.Text())
|
||||
}
|
||||
}()
|
||||
|
||||
c.Stdout = writer
|
||||
c.Stderr = writer
|
||||
err := c.Start()
|
||||
if err == nil {
|
||||
err = c.Wait()
|
||||
_ = writer.Close()
|
||||
<-scannerStopped
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
switch ee := err.(type) {
|
||||
case *exec.ExitError:
|
||||
// Propagate any non-zero exit status from the external command, rather than throwing it away,
|
||||
// so that helmfile could return its own exit code accordingly
|
||||
waitStatus := ee.Sys().(syscall.WaitStatus)
|
||||
exitStatus := waitStatus.ExitStatus()
|
||||
err = newExitError(c.Path, c.Args, exitStatus, ee, "", "")
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected error: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func mergeEnv(orig []string, new map[string]string) []string {
|
||||
wanted := env2map(orig)
|
||||
for k, v := range new {
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package helmexec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestShellRunner_Execute(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
want []byte
|
||||
stdoutWant string
|
||||
enableLiveOutput bool
|
||||
}{
|
||||
{
|
||||
name: "echo_template_no_live_output",
|
||||
want: []byte("template\n"),
|
||||
enableLiveOutput: false,
|
||||
},
|
||||
{
|
||||
name: "echo_template_enable_live_output",
|
||||
want: nil,
|
||||
enableLiveOutput: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
shell := ShellRunner{
|
||||
Logger: NewLogger(&buffer, "debug"),
|
||||
}
|
||||
got, err := shell.Execute("echo", strings.Split("template", " "), map[string]string{}, tt.enableLiveOutput)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Execute() has produced an error = %v", err)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("ExecuteStdIn() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLiveOutput(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cmd *exec.Cmd
|
||||
wantW string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "echo_template",
|
||||
cmd: exec.Command("echo", "template"),
|
||||
wantW: "template\n",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "helm_template",
|
||||
cmd: exec.Command("helm", "template"),
|
||||
wantW: `Error: "helm template" requires at least 1 argument
|
||||
|
||||
Usage: helm template [NAME] [CHART] [flags]
|
||||
`,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
w := &bytes.Buffer{}
|
||||
got, err := LiveOutput(tt.cmd, w)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("LiveOutput() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if gotW := w.String(); gotW != tt.wantW {
|
||||
t.Errorf("LiveOutput() gotW = %v, want %v", gotW, tt.wantW)
|
||||
}
|
||||
if got != nil {
|
||||
t.Errorf("LiveOutput() got unespected %v", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user