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:
parent
c828d22a5c
commit
8408b021f0
|
|
@ -52,16 +52,32 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
helm-version:
|
include:
|
||||||
- v3.4.2
|
# We intend to support 2 helm minor version at a time.
|
||||||
- v3.5.4
|
# What's why we include only 2 helm minor versions in this matrix.
|
||||||
- v3.6.3
|
# See https://github.com/helmfile/helmfile/pull/286#issuecomment-1250161182 for more context.
|
||||||
- v3.7.2
|
- helm-version: v3.8.2
|
||||||
- v3.8.2
|
plugin-secrets-version: 3.15.0
|
||||||
- v3.9.4
|
extra-helmfile-flags:
|
||||||
plugin-secrets-version:
|
- helm-version: v3.8.2
|
||||||
- 3.15.0
|
# We assume that the helm-secrets plugin is supposed to
|
||||||
- 4.0.0
|
# work with the two most recent helm minor versions.
|
||||||
|
# Once it turned out to be not practically true,
|
||||||
|
# we will mark this combination as failable,
|
||||||
|
# and instruct users to upgrade helm and helm-secrets at once.
|
||||||
|
plugin-secrets-version: 4.0.0
|
||||||
|
extra-helmfile-flags:
|
||||||
|
- helm-version: v3.9.4
|
||||||
|
plugin-secrets-version: 3.15.0
|
||||||
|
extra-helmfile-flags:
|
||||||
|
- helm-version: v3.9.4
|
||||||
|
plugin-secrets-version: 4.0.0
|
||||||
|
extra-helmfile-flags:
|
||||||
|
# In case you need to test some optional helmfile features,
|
||||||
|
# enable it via extra-helmfile-flags below.
|
||||||
|
- helm-version: v3.9.4
|
||||||
|
plugin-secrets-version: 4.0.0
|
||||||
|
extra-helmfile-flags: "--enable-live-output"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Cache libraries
|
- name: Cache libraries
|
||||||
|
|
@ -102,4 +118,5 @@ jobs:
|
||||||
HELM_SECRETS_VERSION: ${{ matrix.plugin-secrets-version }}
|
HELM_SECRETS_VERSION: ${{ matrix.plugin-secrets-version }}
|
||||||
HELMFILE_HELM3: 1
|
HELMFILE_HELM3: 1
|
||||||
TERM: xterm
|
TERM: xterm
|
||||||
|
EXTRA_HELMFILE_FLAGS: ${{ matrix.extra-helmfile-flags }}
|
||||||
run: make integration
|
run: make integration
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,8 @@ A release must match all labels in a group in order to be used. Multiple groups
|
||||||
"--selector tier=frontend,tier!=proxy --selector tier=backend" will match all frontend, non-proxy releases AND all backend releases.
|
"--selector tier=frontend,tier!=proxy --selector tier=backend" will match all frontend, non-proxy releases AND all backend releases.
|
||||||
The name of a release can be used as a label: "--selector name=myrelease"`)
|
The name of a release can be used as a label: "--selector name=myrelease"`)
|
||||||
fs.BoolVar(&globalOptions.AllowNoMatchingRelease, "allow-no-matching-release", false, `Do not exit with an error code if the provided selector has no matching releases.`)
|
fs.BoolVar(&globalOptions.AllowNoMatchingRelease, "allow-no-matching-release", false, `Do not exit with an error code if the provided selector has no matching releases.`)
|
||||||
|
fs.BoolVar(&globalOptions.EnableLiveOutput, "enable-live-output", globalOptions.EnableLiveOutput, `Show live output from the Helm binary Stdout/Stderr into Helmfile own Stdout/Stderr.
|
||||||
|
It only applies for the Helm CLI commands, Stdout/Stderr for Hooks are still displayed only when it's execution finishes.`)
|
||||||
// avoid 'pflag: help requested' error (#251)
|
// avoid 'pflag: help requested' error (#251)
|
||||||
fs.BoolP("help", "h", false, "help for helmfile")
|
fs.BoolP("help", "h", false, "help for helmfile")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import (
|
||||||
type App struct {
|
type App struct {
|
||||||
OverrideKubeContext string
|
OverrideKubeContext string
|
||||||
OverrideHelmBinary string
|
OverrideHelmBinary string
|
||||||
|
EnableLiveOutput bool
|
||||||
|
|
||||||
Logger *zap.SugaredLogger
|
Logger *zap.SugaredLogger
|
||||||
Env string
|
Env string
|
||||||
|
|
@ -64,6 +65,7 @@ func New(conf ConfigProvider) *App {
|
||||||
return Init(&App{
|
return Init(&App{
|
||||||
OverrideKubeContext: conf.KubeContext(),
|
OverrideKubeContext: conf.KubeContext(),
|
||||||
OverrideHelmBinary: conf.HelmBinary(),
|
OverrideHelmBinary: conf.HelmBinary(),
|
||||||
|
EnableLiveOutput: conf.EnableLiveOutput(),
|
||||||
Logger: conf.Logger(),
|
Logger: conf.Logger(),
|
||||||
Env: conf.Env(),
|
Env: conf.Env(),
|
||||||
Namespace: conf.Namespace(),
|
Namespace: conf.Namespace(),
|
||||||
|
|
@ -84,6 +86,10 @@ func Init(app *App) *App {
|
||||||
panic(fmt.Sprintf("Failed to initialize vals runtime: %v", err))
|
panic(fmt.Sprintf("Failed to initialize vals runtime: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if app.EnableLiveOutput {
|
||||||
|
app.Logger.Info("Live output is enabled")
|
||||||
|
}
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,6 +219,9 @@ func (a *App) Template(c TemplateConfigProvider) error {
|
||||||
return a.ForEachState(func(run *Run) (ok bool, errs []error) {
|
return a.ForEachState(func(run *Run) (ok bool, errs []error) {
|
||||||
includeCRDs := c.IncludeCRDs()
|
includeCRDs := c.IncludeCRDs()
|
||||||
|
|
||||||
|
// Live output should never be enabled for the "template" subcommand to avoid breaking `helmfile template | kubectl apply -f -`
|
||||||
|
run.helm.SetEnableLiveOutput(false)
|
||||||
|
|
||||||
// `helm template` in helm v2 does not support local chart.
|
// `helm template` in helm v2 does not support local chart.
|
||||||
// So, we set forceDownload=true for helm v2 only
|
// So, we set forceDownload=true for helm v2 only
|
||||||
prepErr := run.withPreparedCharts("template", state.ChartPrepareOptions{
|
prepErr := run.withPreparedCharts("template", state.ChartPrepareOptions{
|
||||||
|
|
@ -717,6 +726,7 @@ func (a *App) loadDesiredStateFromYaml(file string, opts ...LoadOpts) (*state.He
|
||||||
|
|
||||||
overrideKubeContext: a.OverrideKubeContext,
|
overrideKubeContext: a.OverrideKubeContext,
|
||||||
overrideHelmBinary: a.OverrideHelmBinary,
|
overrideHelmBinary: a.OverrideHelmBinary,
|
||||||
|
enableLiveOutput: a.EnableLiveOutput,
|
||||||
getHelm: a.getHelm,
|
getHelm: a.getHelm,
|
||||||
valsRuntime: a.valsRuntime,
|
valsRuntime: a.valsRuntime,
|
||||||
}
|
}
|
||||||
|
|
@ -755,7 +765,7 @@ func (a *App) getHelm(st *state.HelmState) helmexec.Interface {
|
||||||
key := createHelmKey(bin, kubectx)
|
key := createHelmKey(bin, kubectx)
|
||||||
|
|
||||||
if _, ok := a.helms[key]; !ok {
|
if _, ok := a.helms[key]; !ok {
|
||||||
a.helms[key] = helmexec.New(bin, a.Logger, kubectx, &helmexec.ShellRunner{
|
a.helms[key] = helmexec.New(bin, a.EnableLiveOutput, a.Logger, kubectx, &helmexec.ShellRunner{
|
||||||
Logger: a.Logger,
|
Logger: a.Logger,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2488,12 +2488,12 @@ func (mock *mockRunner) ExecuteStdIn(cmd string, args []string, env map[string]s
|
||||||
return []byte{}, nil
|
return []byte{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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 []byte{}, nil
|
return []byte{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func MockExecer(logger *zap.SugaredLogger, kubeContext string) helmexec.Interface {
|
func MockExecer(logger *zap.SugaredLogger, kubeContext string) helmexec.Interface {
|
||||||
execer := helmexec.New("helm", logger, kubeContext, &mockRunner{})
|
execer := helmexec.New("helm", false, logger, kubeContext, &mockRunner{})
|
||||||
return execer
|
return execer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2538,6 +2538,8 @@ func (helm *mockHelmExec) SetExtraArgs(args ...string) {
|
||||||
}
|
}
|
||||||
func (helm *mockHelmExec) SetHelmBinary(bin string) {
|
func (helm *mockHelmExec) SetHelmBinary(bin string) {
|
||||||
}
|
}
|
||||||
|
func (helm *mockHelmExec) SetEnableLiveOutput(enableLiveOutput bool) {
|
||||||
|
}
|
||||||
func (helm *mockHelmExec) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error {
|
func (helm *mockHelmExec) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error {
|
||||||
helm.repos = append(helm.repos, mockRepo{Name: name})
|
helm.repos = append(helm.repos, mockRepo{Name: name})
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import "go.uber.org/zap"
|
||||||
type ConfigProvider interface {
|
type ConfigProvider interface {
|
||||||
Args() string
|
Args() string
|
||||||
HelmBinary() string
|
HelmBinary() string
|
||||||
|
EnableLiveOutput() bool
|
||||||
|
|
||||||
FileOrDir() string
|
FileOrDir() string
|
||||||
KubeContext() string
|
KubeContext() string
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ const (
|
||||||
type desiredStateLoader struct {
|
type desiredStateLoader struct {
|
||||||
overrideKubeContext string
|
overrideKubeContext string
|
||||||
overrideHelmBinary string
|
overrideHelmBinary string
|
||||||
|
enableLiveOutput bool
|
||||||
|
|
||||||
env string
|
env string
|
||||||
namespace string
|
namespace string
|
||||||
|
|
@ -162,7 +163,7 @@ func (ld *desiredStateLoader) loadFileWithOverrides(inheritedEnv, overrodeEnv *e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *desiredStateLoader) underlying() *state.StateCreator {
|
func (a *desiredStateLoader) underlying() *state.StateCreator {
|
||||||
c := state.NewCreator(a.logger, a.fs, a.valsRuntime, a.getHelm, a.overrideHelmBinary, a.remote)
|
c := state.NewCreator(a.logger, a.fs, a.valsRuntime, a.getHelm, a.overrideHelmBinary, a.remote, a.enableLiveOutput)
|
||||||
c.LoadFile = a.loadFile
|
c.LoadFile = a.loadFile
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,9 @@ func (helm *noCallHelmExec) SetExtraArgs(args ...string) {
|
||||||
func (helm *noCallHelmExec) SetHelmBinary(bin string) {
|
func (helm *noCallHelmExec) SetHelmBinary(bin string) {
|
||||||
helm.doPanic()
|
helm.doPanic()
|
||||||
}
|
}
|
||||||
|
func (helm *noCallHelmExec) SetEnableLiveOutput(enableLiveOutput bool) {
|
||||||
|
helm.doPanic()
|
||||||
|
}
|
||||||
func (helm *noCallHelmExec) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error {
|
func (helm *noCallHelmExec) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error {
|
||||||
helm.doPanic()
|
helm.doPanic()
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ type GlobalOptions struct {
|
||||||
AllowNoMatchingRelease bool
|
AllowNoMatchingRelease bool
|
||||||
// logger is the logger to use.
|
// logger is the logger to use.
|
||||||
logger *zap.SugaredLogger
|
logger *zap.SugaredLogger
|
||||||
|
// EnableLiveOutput enables live output from the Helm binary stdout/stderr into Helmfile own stdout/stderr
|
||||||
|
EnableLiveOutput bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logger returns the logger to use.
|
// Logger returns the logger to use.
|
||||||
|
|
@ -120,6 +122,11 @@ func (g *GlobalImpl) StateValuesFiles() []string {
|
||||||
return g.GlobalOptions.StateValuesFile
|
return g.GlobalOptions.StateValuesFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnableLiveOutput return when to pipe the stdout and stderr from Helm live to the helmfile stdout
|
||||||
|
func (g *GlobalImpl) EnableLiveOutput() bool {
|
||||||
|
return g.GlobalOptions.EnableLiveOutput
|
||||||
|
}
|
||||||
|
|
||||||
// Logger returns the logger
|
// Logger returns the logger
|
||||||
func (g *GlobalImpl) Logger() *zap.SugaredLogger {
|
func (g *GlobalImpl) Logger() *zap.SugaredLogger {
|
||||||
return g.GlobalOptions.logger
|
return g.GlobalOptions.logger
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ func (bus *Bus) Trigger(evt string, evtErr error, context map[string]interface{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes, err := bus.Runner.Execute(command, args, map[string]string{})
|
bytes, err := bus.Runner.Execute(command, args, map[string]string{}, false)
|
||||||
bus.Logger.Debugf("hook[%s]: %s\n", name, string(bytes))
|
bus.Logger.Debugf("hook[%s]: %s\n", name, string(bytes))
|
||||||
if hook.ShowLogs {
|
if hook.ShowLogs {
|
||||||
prefix := fmt.Sprintf("\nhook[%s] logs | ", evt)
|
prefix := fmt.Sprintf("\nhook[%s] logs | ", evt)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ func (r *runner) ExecuteStdIn(cmd string, args []string, env map[string]string,
|
||||||
return []byte(""), nil
|
return []byte(""), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) Execute(cmd string, args []string, env map[string]string) ([]byte, error) {
|
func (r *runner) Execute(cmd string, args []string, env map[string]string, enableLiveOutput bool) ([]byte, error) {
|
||||||
if cmd == "ng" {
|
if cmd == "ng" {
|
||||||
return nil, fmt.Errorf("cmd failed due to invalid cmd: %s", cmd)
|
return nil, fmt.Errorf("cmd failed due to invalid cmd: %s", cmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,8 @@ func (helm *Helm) SetExtraArgs(args ...string) {
|
||||||
}
|
}
|
||||||
func (helm *Helm) SetHelmBinary(bin string) {
|
func (helm *Helm) SetHelmBinary(bin string) {
|
||||||
}
|
}
|
||||||
|
func (helm *Helm) SetEnableLiveOutput(enableLiveOutput bool) {
|
||||||
|
}
|
||||||
func (helm *Helm) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error {
|
func (helm *Helm) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error {
|
||||||
helm.Repo = []string{name, repository, cafile, certfile, keyfile, username, password, managed, passCredentials, skipTLSVerify}
|
helm.Repo = []string{name, repository, cafile, certfile, keyfile, username, password, managed, passCredentials, skipTLSVerify}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ type decryptedSecret struct {
|
||||||
|
|
||||||
type execer struct {
|
type execer struct {
|
||||||
helmBinary string
|
helmBinary string
|
||||||
|
enableLiveOutput bool
|
||||||
version semver.Version
|
version semver.Version
|
||||||
runner Runner
|
runner Runner
|
||||||
logger *zap.SugaredLogger
|
logger *zap.SugaredLogger
|
||||||
|
|
@ -78,7 +79,7 @@ func parseHelmVersion(versionStr string) (semver.Version, error) {
|
||||||
|
|
||||||
func getHelmVersion(helmBinary string, runner Runner) (semver.Version, error) {
|
func getHelmVersion(helmBinary string, runner Runner) (semver.Version, error) {
|
||||||
// Autodetect from `helm version`
|
// 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 {
|
if err != nil {
|
||||||
return semver.Version{}, fmt.Errorf("error determining helm version: %w", err)
|
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
|
// New for running helm commands
|
||||||
// nolint: golint
|
// 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
|
// TODO: proper error handling
|
||||||
version, err := getHelmVersion(helmBinary, runner)
|
version, err := getHelmVersion(helmBinary, runner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -118,6 +119,7 @@ func New(helmBinary string, logger *zap.SugaredLogger, kubeContext string, runne
|
||||||
}
|
}
|
||||||
return &execer{
|
return &execer{
|
||||||
helmBinary: helmBinary,
|
helmBinary: helmBinary,
|
||||||
|
enableLiveOutput: enableLiveOutput,
|
||||||
version: version,
|
version: version,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
kubeContext: kubeContext,
|
kubeContext: kubeContext,
|
||||||
|
|
@ -134,6 +136,10 @@ func (helm *execer) SetHelmBinary(bin string) {
|
||||||
helm.helmBinary = bin
|
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 {
|
func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error {
|
||||||
var args []string
|
var args []string
|
||||||
var out []byte
|
var out []byte
|
||||||
|
|
@ -174,7 +180,7 @@ func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, usernam
|
||||||
args = append(args, "--insecure-skip-tls-verify")
|
args = append(args, "--insecure-skip-tls-verify")
|
||||||
}
|
}
|
||||||
helm.logger.Infof("Adding repo %v %v", name, repository)
|
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:
|
default:
|
||||||
helm.logger.Errorf("ERROR: unknown type '%v' for repository %v", managed, name)
|
helm.logger.Errorf("ERROR: unknown type '%v' for repository %v", managed, name)
|
||||||
out = nil
|
out = nil
|
||||||
|
|
@ -186,7 +192,7 @@ func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, usernam
|
||||||
|
|
||||||
func (helm *execer) UpdateRepo() error {
|
func (helm *execer) UpdateRepo() error {
|
||||||
helm.logger.Info("Updating repo")
|
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)
|
helm.info(out)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -210,14 +216,14 @@ func (helm *execer) RegistryLogin(repository string, username string, password s
|
||||||
|
|
||||||
func (helm *execer) BuildDeps(name, chart string) error {
|
func (helm *execer) BuildDeps(name, chart string) error {
|
||||||
helm.logger.Infof("Building dependency release=%v, chart=%v", name, chart)
|
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)
|
helm.info(out)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (helm *execer) UpdateDeps(chart string) error {
|
func (helm *execer) UpdateDeps(chart string) error {
|
||||||
helm.logger.Infof("Updating dependency %v", chart)
|
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)
|
helm.info(out)
|
||||||
return err
|
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)
|
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)
|
helm.write(nil, out)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -242,7 +248,7 @@ func (helm *execer) ReleaseStatus(context HelmContext, name string, flags ...str
|
||||||
helm.logger.Infof("Getting status %v", name)
|
helm.logger.Infof("Getting status %v", name)
|
||||||
preArgs := context.GetTillerlessArgs(helm)
|
preArgs := context.GetTillerlessArgs(helm)
|
||||||
env := context.getTillerlessEnv()
|
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)
|
helm.write(nil, out)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -258,7 +264,8 @@ func (helm *execer) List(context HelmContext, filter string, flags ...string) (s
|
||||||
args = []string{"list", filter}
|
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 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`,
|
// 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
|
// 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 {
|
if pluginVersion.Major() > 3 {
|
||||||
secretArg = "decrypt"
|
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 {
|
if err != nil {
|
||||||
secret.err = err
|
secret.err = err
|
||||||
return "", err
|
return "", err
|
||||||
|
|
@ -370,7 +378,7 @@ func (helm *execer) TemplateRelease(name string, chart string, flags ...string)
|
||||||
args = []string{"template", chart, "--name", name}
|
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
|
var outputToFile bool
|
||||||
|
|
||||||
|
|
@ -407,7 +415,12 @@ func (helm *execer) DiffRelease(context HelmContext, name, chart string, suppres
|
||||||
}
|
}
|
||||||
preArgs := context.GetTillerlessArgs(helm)
|
preArgs := context.GetTillerlessArgs(helm)
|
||||||
env := context.getTillerlessEnv()
|
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
|
// Do our best to write STDOUT only when diff existed
|
||||||
// Unfortunately, this works only when you run helmfile with `--detailed-exitcode`
|
// Unfortunately, this works only when you run helmfile with `--detailed-exitcode`
|
||||||
detailedExitcodeEnabled := false
|
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 {
|
func (helm *execer) Lint(name, chart string, flags ...string) error {
|
||||||
helm.logger.Infof("Linting release=%v, chart=%v", name, chart)
|
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)
|
helm.write(nil, out)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (helm *execer) Fetch(chart string, flags ...string) error {
|
func (helm *execer) Fetch(chart string, flags ...string) error {
|
||||||
helm.logger.Infof("Fetching %v", redactedURL(chart))
|
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)
|
helm.info(out)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -463,7 +476,7 @@ func (helm *execer) ChartPull(chart string, flags ...string) error {
|
||||||
} else {
|
} else {
|
||||||
helmArgs = []string{"chart", "pull", chart}
|
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)
|
helm.info(out)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -478,7 +491,7 @@ func (helm *execer) ChartExport(chart string, path string, flags ...string) erro
|
||||||
} else {
|
} else {
|
||||||
helmArgs = []string{"chart", "export", chart}
|
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)
|
helm.info(out)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -487,7 +500,7 @@ func (helm *execer) DeleteRelease(context HelmContext, name string, flags ...str
|
||||||
helm.logger.Infof("Deleting %v", name)
|
helm.logger.Infof("Deleting %v", name)
|
||||||
preArgs := context.GetTillerlessArgs(helm)
|
preArgs := context.GetTillerlessArgs(helm)
|
||||||
env := context.getTillerlessEnv()
|
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)
|
helm.write(nil, out)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -497,12 +510,12 @@ func (helm *execer) TestRelease(context HelmContext, name string, flags ...strin
|
||||||
preArgs := context.GetTillerlessArgs(helm)
|
preArgs := context.GetTillerlessArgs(helm)
|
||||||
env := context.getTillerlessEnv()
|
env := context.getTillerlessEnv()
|
||||||
args := []string{"test", name}
|
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)
|
helm.write(nil, out)
|
||||||
return err
|
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
|
cmdargs := args
|
||||||
if len(helm.extra) > 0 {
|
if len(helm.extra) > 0 {
|
||||||
cmdargs = append(cmdargs, helm.extra...)
|
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, " "))
|
cmd := fmt.Sprintf("exec: %s %s", helm.helmBinary, strings.Join(cmdargs, " "))
|
||||||
helm.logger.Debug(cmd)
|
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
|
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)
|
cmdargs := append(strings.Split("acr helm repo add --name", " "), name)
|
||||||
cmd := fmt.Sprintf("exec: az %s", strings.Join(cmdargs, " "))
|
cmd := fmt.Sprintf("exec: az %s", strings.Join(cmdargs, " "))
|
||||||
helm.logger.Debug(cmd)
|
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)
|
helm.logger.Debugf("%s: %s", cmd, outBytes)
|
||||||
return outBytes, err
|
return outBytes, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,13 @@ func (mock *mockRunner) ExecuteStdIn(cmd string, args []string, env map[string]s
|
||||||
return mock.output, mock.err
|
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
|
return mock.output, mock.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint: golint
|
// nolint: golint
|
||||||
func MockExecer(logger *zap.SugaredLogger, kubeContext string) *execer {
|
func MockExecer(logger *zap.SugaredLogger, kubeContext string) *execer {
|
||||||
execer := New("helm", logger, kubeContext, &mockRunner{})
|
execer := New("helm", false, logger, kubeContext, &mockRunner{})
|
||||||
return execer
|
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) {
|
func Test_AddRepo_Helm_3_3_2(t *testing.T) {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
logger := NewLogger(&buffer, "debug")
|
logger := NewLogger(&buffer, "debug")
|
||||||
|
|
@ -582,7 +593,7 @@ func Test_exec(t *testing.T) {
|
||||||
logger := NewLogger(&buffer, "debug")
|
logger := NewLogger(&buffer, "debug")
|
||||||
helm := MockExecer(logger, "")
|
helm := MockExecer(logger, "")
|
||||||
env := map[string]string{}
|
env := map[string]string{}
|
||||||
_, err := helm.exec([]string{"version"}, env)
|
_, err := helm.exec([]string{"version"}, env, nil)
|
||||||
expected := `exec: helm version
|
expected := `exec: helm version
|
||||||
`
|
`
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -593,14 +604,14 @@ func Test_exec(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
helm = MockExecer(logger, "dev")
|
helm = MockExecer(logger, "dev")
|
||||||
ret, _ := helm.exec([]string{"diff"}, env)
|
ret, _ := helm.exec([]string{"diff"}, env, nil)
|
||||||
if len(ret) != 0 {
|
if len(ret) != 0 {
|
||||||
t.Error("helmexec.exec() - expected empty return value")
|
t.Error("helmexec.exec() - expected empty return value")
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
helm = MockExecer(logger, "dev")
|
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
|
expected = `exec: helm --kube-context dev diff release chart --timeout 10 --wait --wait-for-jobs
|
||||||
`
|
`
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -611,7 +622,7 @@ func Test_exec(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
_, err = helm.exec([]string{"version"}, env)
|
_, err = helm.exec([]string{"version"}, env, nil)
|
||||||
expected = `exec: helm --kube-context dev version
|
expected = `exec: helm --kube-context dev version
|
||||||
`
|
`
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -623,7 +634,7 @@ func Test_exec(t *testing.T) {
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
helm.SetExtraArgs("foo")
|
helm.SetExtraArgs("foo")
|
||||||
_, err = helm.exec([]string{"version"}, env)
|
_, err = helm.exec([]string{"version"}, env, nil)
|
||||||
expected = `exec: helm --kube-context dev version foo
|
expected = `exec: helm --kube-context dev version foo
|
||||||
`
|
`
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -636,7 +647,7 @@ func Test_exec(t *testing.T) {
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
helm = MockExecer(logger, "")
|
helm = MockExecer(logger, "")
|
||||||
helm.SetHelmBinary("overwritten")
|
helm.SetHelmBinary("overwritten")
|
||||||
_, err = helm.exec([]string{"version"}, env)
|
_, err = helm.exec([]string{"version"}, env, nil)
|
||||||
expected = `exec: overwritten version
|
expected = `exec: overwritten version
|
||||||
`
|
`
|
||||||
if err != nil {
|
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) {
|
func Test_IsHelm3(t *testing.T) {
|
||||||
helm2Runner := mockRunner{output: []byte("Client: v2.16.0+ge13bc94\n")}
|
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() {
|
if helm.IsHelm3() {
|
||||||
t.Error("helmexec.IsHelm3() - Detected Helm 3 with Helm 2 version")
|
t.Error("helmexec.IsHelm3() - Detected Helm 3 with Helm 2 version")
|
||||||
}
|
}
|
||||||
|
|
||||||
helm3Runner := mockRunner{output: []byte("v3.0.0+ge29ce2a\n")}
|
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() {
|
if !helm.IsHelm3() {
|
||||||
t.Error("helmexec.IsHelm3() - Failed to detect Helm 3")
|
t.Error("helmexec.IsHelm3() - Failed to detect Helm 3")
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Setenv(envvar.Helm3, "1")
|
t.Setenv(envvar.Helm3, "1")
|
||||||
helm2Runner = mockRunner{output: []byte("Client: v2.16.0+ge13bc94\n")}
|
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() {
|
if !helm.IsHelm3() {
|
||||||
t.Errorf("helmexec.IsHelm3() - Helm3 not detected when %s is set", envvar.Helm3)
|
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) {
|
func Test_GetVersion(t *testing.T) {
|
||||||
helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")}
|
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()
|
ver := helm.GetVersion()
|
||||||
if ver.Major != 2 || ver.Minor != 16 || ver.Patch != 1 {
|
if ver.Major != 2 || ver.Minor != 16 || ver.Patch != 1 {
|
||||||
t.Errorf("helmexec.GetVersion - did not detect correct Helm2 version; it was: %+v", ver)
|
t.Errorf("helmexec.GetVersion - did not detect correct Helm2 version; it was: %+v", ver)
|
||||||
}
|
}
|
||||||
|
|
||||||
helm3Runner := mockRunner{output: []byte("v3.2.4+ge29ce2a\n")}
|
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()
|
ver = helm.GetVersion()
|
||||||
if ver.Major != 3 || ver.Minor != 2 || ver.Patch != 4 {
|
if ver.Major != 3 || ver.Minor != 2 || ver.Patch != 4 {
|
||||||
t.Errorf("helmexec.GetVersion - did not detect correct Helm3 version; it was: %+v", ver)
|
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) {
|
func Test_IsVersionAtLeast(t *testing.T) {
|
||||||
helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")}
|
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") {
|
if !helm.IsVersionAtLeast("2.1.0") {
|
||||||
t.Error("helmexec.IsVersionAtLeast - 2.16.1 not atleast 2.1")
|
t.Error("helmexec.IsVersionAtLeast - 2.16.1 not atleast 2.1")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ type Version struct {
|
||||||
type Interface interface {
|
type Interface interface {
|
||||||
SetExtraArgs(args ...string)
|
SetExtraArgs(args ...string)
|
||||||
SetHelmBinary(bin string)
|
SetHelmBinary(bin string)
|
||||||
|
SetEnableLiveOutput(enableLiveOutput bool)
|
||||||
|
|
||||||
AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error
|
AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error
|
||||||
UpdateRepo() error
|
UpdateRepo() error
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package helmexec
|
package helmexec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -16,7 +17,7 @@ import (
|
||||||
|
|
||||||
// Runner interface for shell commands
|
// Runner interface for shell commands
|
||||||
type Runner interface {
|
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)
|
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
|
// 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 := exec.Command(cmd, args...)
|
||||||
preparedCmd.Dir = shell.Dir
|
preparedCmd.Dir = shell.Dir
|
||||||
preparedCmd.Env = mergeEnv(os.Environ(), env)
|
preparedCmd.Env = mergeEnv(os.Environ(), env)
|
||||||
|
|
||||||
|
if !enableLiveOutput {
|
||||||
return Output(preparedCmd, &logWriterGenerator{
|
return Output(preparedCmd, &logWriterGenerator{
|
||||||
log: shell.Logger,
|
log: shell.Logger,
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
return LiveOutput(preparedCmd, os.Stdout)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute a shell command
|
// Execute a shell command
|
||||||
|
|
@ -94,6 +100,43 @@ func Output(c *exec.Cmd, logWriterGenerators ...*logWriterGenerator) ([]byte, er
|
||||||
return stdout.Bytes(), err
|
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 {
|
func mergeEnv(orig []string, new map[string]string) []string {
|
||||||
wanted := env2map(orig)
|
wanted := env2map(orig)
|
||||||
for k, v := range new {
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -55,10 +55,12 @@ type StateCreator struct {
|
||||||
|
|
||||||
overrideHelmBinary string
|
overrideHelmBinary string
|
||||||
|
|
||||||
|
enableLiveOutput bool
|
||||||
|
|
||||||
remote *remote.Remote
|
remote *remote.Remote
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCreator(logger *zap.SugaredLogger, fs *filesystem.FileSystem, valsRuntime vals.Evaluator, getHelm func(*HelmState) helmexec.Interface, overrideHelmBinary string, remote *remote.Remote) *StateCreator {
|
func NewCreator(logger *zap.SugaredLogger, fs *filesystem.FileSystem, valsRuntime vals.Evaluator, getHelm func(*HelmState) helmexec.Interface, overrideHelmBinary string, remote *remote.Remote, enableLiveOutput bool) *StateCreator {
|
||||||
return &StateCreator{
|
return &StateCreator{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
|
||||||
|
|
@ -68,6 +70,7 @@ func NewCreator(logger *zap.SugaredLogger, fs *filesystem.FileSystem, valsRuntim
|
||||||
getHelm: getHelm,
|
getHelm: getHelm,
|
||||||
|
|
||||||
overrideHelmBinary: overrideHelmBinary,
|
overrideHelmBinary: overrideHelmBinary,
|
||||||
|
enableLiveOutput: enableLiveOutput,
|
||||||
|
|
||||||
remote: remote,
|
remote: remote,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,10 @@ type stateTestEnv struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (testEnv stateTestEnv) MustLoadState(t *testing.T, file, envName string) *HelmState {
|
func (testEnv stateTestEnv) MustLoadState(t *testing.T, file, envName string) *HelmState {
|
||||||
|
return testEnv.MustLoadStateWithEnableLiveOutput(t, file, envName, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (testEnv stateTestEnv) MustLoadStateWithEnableLiveOutput(t *testing.T, file, envName string, enableLiveOutput bool) *HelmState {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
testFs := testhelper.NewTestFs(testEnv.Files)
|
testFs := testhelper.NewTestFs(testEnv.Files)
|
||||||
|
|
@ -79,7 +83,7 @@ func (testEnv stateTestEnv) MustLoadState(t *testing.T, file, envName string) *H
|
||||||
}
|
}
|
||||||
|
|
||||||
r := remote.NewRemote(logger, testFs.Cwd, testFs.ToFileSystem())
|
r := remote.NewRemote(logger, testFs.Cwd, testFs.ToFileSystem())
|
||||||
state, err := NewCreator(logger, testFs.ToFileSystem(), nil, nil, "", r).
|
state, err := NewCreator(logger, testFs.ToFileSystem(), nil, nil, "", r, enableLiveOutput).
|
||||||
ParseAndLoad([]byte(yamlContent), filepath.Dir(file), file, envName, true, nil)
|
ParseAndLoad([]byte(yamlContent), filepath.Dir(file), file, envName, true, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
|
@ -149,7 +153,7 @@ releaseNamespace: mynamespace
|
||||||
env := environment.Environment{
|
env := environment.Environment{
|
||||||
Name: "production",
|
Name: "production",
|
||||||
}
|
}
|
||||||
state, err := NewCreator(logger, testFs.ToFileSystem(), nil, nil, "", r).
|
state, err := NewCreator(logger, testFs.ToFileSystem(), nil, nil, "", r, false).
|
||||||
ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", true, &env)
|
ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", true, &env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
|
@ -236,7 +240,7 @@ overrideNamespace: myns
|
||||||
testFs.Cwd = "/example/path/to"
|
testFs.Cwd = "/example/path/to"
|
||||||
|
|
||||||
r := remote.NewRemote(logger, testFs.Cwd, testFs.ToFileSystem())
|
r := remote.NewRemote(logger, testFs.Cwd, testFs.ToFileSystem())
|
||||||
state, err := NewCreator(logger, testFs.ToFileSystem(), nil, nil, "", r).
|
state, err := NewCreator(logger, testFs.ToFileSystem(), nil, nil, "", r, false).
|
||||||
ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", true, nil)
|
ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", true, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
|
|
||||||
7
test/e2e/template/helmfile/testdata/snapshot/chart_need_enable_live_output/config.yaml
vendored
Normal file
7
test/e2e/template/helmfile/testdata/snapshot/chart_need_enable_live_output/config.yaml
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
localChartRepoServer:
|
||||||
|
enabled: true
|
||||||
|
port: 18080
|
||||||
|
chartifyTempDir: temp1
|
||||||
|
helmfileArgs:
|
||||||
|
- --enable-live-output
|
||||||
|
- template
|
||||||
31
test/e2e/template/helmfile/testdata/snapshot/chart_need_enable_live_output/input.yaml
vendored
Normal file
31
test/e2e/template/helmfile/testdata/snapshot/chart_need_enable_live_output/input.yaml
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
repositories:
|
||||||
|
- name: myrepo
|
||||||
|
url: http://localhost:18080/
|
||||||
|
|
||||||
|
releases:
|
||||||
|
- name: foo
|
||||||
|
chart: ../../charts/raw
|
||||||
|
values:
|
||||||
|
- templates:
|
||||||
|
- |
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: {{`{{ .Release.Name }}`}}-1
|
||||||
|
namespace: {{`{{ .Release.Namespace }}`}}
|
||||||
|
data:
|
||||||
|
foo: FOO
|
||||||
|
dep:
|
||||||
|
templates:
|
||||||
|
- |
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: {{`{{ .Release.Name }}`}}-2
|
||||||
|
namespace: {{`{{ .Release.Namespace }}`}}
|
||||||
|
data:
|
||||||
|
bar: BAR
|
||||||
|
dependencies:
|
||||||
|
- alias: dep
|
||||||
|
chart: myrepo/raw
|
||||||
|
version: 0.1.0
|
||||||
27
test/e2e/template/helmfile/testdata/snapshot/chart_need_enable_live_output/output.yaml
vendored
Normal file
27
test/e2e/template/helmfile/testdata/snapshot/chart_need_enable_live_output/output.yaml
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
Live output is enabled
|
||||||
|
Adding repo myrepo http://localhost:18080/
|
||||||
|
"myrepo" has been added to your repositories
|
||||||
|
|
||||||
|
Building dependency release=foo, chart=$WD/temp1/foo
|
||||||
|
Templating release=foo, chart=$WD/temp1/foo
|
||||||
|
---
|
||||||
|
# Source: raw/templates/charts/dep/templates/resources.yaml
|
||||||
|
# Source: raw/charts/dep/templates/resources.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: foo-2
|
||||||
|
namespace: default
|
||||||
|
data:
|
||||||
|
bar: BAR
|
||||||
|
---
|
||||||
|
# Source: raw/templates/resources.yaml
|
||||||
|
# Source: raw/templates/resources.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: foo-1
|
||||||
|
namespace: default
|
||||||
|
data:
|
||||||
|
foo: FOO
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ if [[ ! -d "${dir}" ]]; then dir="${PWD}"; fi
|
||||||
# GLOBALS -----------------------------------------------------------------------------------------------------------
|
# GLOBALS -----------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
test_ns="helmfile-tests-$(date +"%Y%m%d-%H%M%S")"
|
test_ns="helmfile-tests-$(date +"%Y%m%d-%H%M%S")"
|
||||||
helmfile="./helmfile --namespace=${test_ns}"
|
helmfile="./helmfile ${EXTRA_HELMFILE_FLAGS} --namespace=${test_ns}"
|
||||||
helm="helm --kube-context=minikube"
|
helm="helm --kube-context=minikube"
|
||||||
kubectl="kubectl --context=minikube --namespace=${test_ns}"
|
kubectl="kubectl --context=minikube --namespace=${test_ns}"
|
||||||
helm_dir="${PWD}/${dir}/.helm"
|
helm_dir="${PWD}/${dir}/.helm"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue