Skip to content

Commit

Permalink
feat: add/expose cli flags
Browse files Browse the repository at this point in the history
Every time I execute commands, I would like to opt out of force-updates

This adds additional flags to the cli:

- `--skip-deps` - hoisted up to the global level
- `--disable-force-update` - exposes the ability to disable the force-update flag

Resolves #roboll/helmfile/issues/795
Relates to roboll/helmfile/pull/1494#pullrequestreview-506233792

Still a rookie at go (especially of this size), any feedback is appreciated
  • Loading branch information
hans-m-song committed Mar 30, 2023
1 parent 5650661 commit e980174
Show file tree
Hide file tree
Showing 21 changed files with 98 additions and 31 deletions.
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ func setGlobalOptionsForRootCmd(fs *pflag.FlagSet, globalOptions *config.GlobalO
fs.StringVarP(&globalOptions.Environment, "environment", "e", "", `specify the environment name. defaults to "default"`)
fs.StringArrayVar(&globalOptions.StateValuesSet, "state-values-set", nil, "set state values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2). Used to override .Values within the helmfile template (not values template).")
fs.StringArrayVar(&globalOptions.StateValuesFile, "state-values-file", nil, "specify state values in a YAML file. Used to override .Values within the helmfile template (not values template).")
fs.BoolVar(&globalOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`)
fs.BoolVar(&globalOptions.DisableForceUpdate, "disable-force-update", false, `do not force helm repos to update when executing "helm repo add"`)
fs.BoolVarP(&globalOptions.Quiet, "quiet", "q", false, "Silence output. Equivalent to log-level warn")
fs.StringVar(&globalOptions.KubeContext, "kube-context", "", "Set kubectl context. Uses current context by default")
fs.BoolVar(&globalOptions.Debug, "debug", false, "Enable verbose output for Helm and set log-level to debug, this disables --quiet/-q effect")
Expand Down
4 changes: 3 additions & 1 deletion pkg/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type App struct {
OverrideKubeContext string
OverrideHelmBinary string
EnableLiveOutput bool
DisableForceUpdate bool

Logger *zap.SugaredLogger
Env string
Expand Down Expand Up @@ -66,6 +67,7 @@ func New(conf ConfigProvider) *App {
OverrideKubeContext: conf.KubeContext(),
OverrideHelmBinary: conf.HelmBinary(),
EnableLiveOutput: conf.EnableLiveOutput(),
DisableForceUpdate: conf.DisableForceUpdate(),
Logger: conf.Logger(),
Env: conf.Env(),
Namespace: conf.Namespace(),
Expand Down Expand Up @@ -784,7 +786,7 @@ func (a *App) getHelm(st *state.HelmState) helmexec.Interface {
key := createHelmKey(bin, kubectx)

if _, ok := a.helms[key]; !ok {
a.helms[key] = helmexec.New(bin, a.EnableLiveOutput, a.Logger, kubectx, &helmexec.ShellRunner{
a.helms[key] = helmexec.New(bin, helmexec.HelmExecOptions{EnableLiveOutput: a.EnableLiveOutput, DisableForceUpdate: a.DisableForceUpdate}, a.Logger, kubectx, &helmexec.ShellRunner{
Logger: a.Logger,
})
}
Expand Down
5 changes: 4 additions & 1 deletion pkg/app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2396,7 +2396,7 @@ func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string
}

func MockExecer(logger *zap.SugaredLogger, kubeContext string) helmexec.Interface {
execer := helmexec.New("helm", false, logger, kubeContext, &mockRunner{})
execer := helmexec.New("helm", helmexec.HelmExecOptions{}, logger, kubeContext, &mockRunner{})
return execer
}

Expand Down Expand Up @@ -2446,6 +2446,9 @@ func (helm *mockHelmExec) SetHelmBinary(bin string) {
func (helm *mockHelmExec) SetEnableLiveOutput(enableLiveOutput bool) {
}

func (helm *mockHelmExec) SetDisableForceUpdate(forceUpdate bool) {
}

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})
return nil
Expand Down
1 change: 1 addition & 0 deletions pkg/app/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type ConfigProvider interface {
Args() string
HelmBinary() string
EnableLiveOutput() bool
DisableForceUpdate() bool

FileOrDir() string
KubeContext() string
Expand Down
2 changes: 1 addition & 1 deletion pkg/app/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ func (h *HelmfileInit) WhetherContinue(ask string) error {

func (h *HelmfileInit) CheckHelmPlugins() error {
settings := cli.New()
helm := helmexec.New(h.helmBinary, false, h.logger, "", h.runner)
helm := helmexec.New(h.helmBinary, helmexec.HelmExecOptions{}, h.logger, "", h.runner)
for _, p := range helmPlugins {
pluginVersion, err := helmexec.GetPluginVersion(p.name, settings.PluginsDirectory)
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions pkg/app/mocks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ func (helm *noCallHelmExec) SetHelmBinary(bin string) {
func (helm *noCallHelmExec) SetEnableLiveOutput(enableLiveOutput bool) {
helm.doPanic()
}
func (helm *noCallHelmExec) SetDisableForceUpdate(forceUpdate bool) {
helm.doPanic()
}

func (helm *noCallHelmExec) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error {
helm.doPanic()
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func (a *ApplyImpl) SkipCleanup() bool {

// SkipDeps returns the skip deps.
func (a *ApplyImpl) SkipDeps() bool {
return a.ApplyOptions.SkipDeps
return a.GlobalOptions.SkipDeps || a.ApplyOptions.SkipDeps
}

// SkipDiffOnInstall returns the skip diff on install.
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (c *DeleteImpl) Purge() bool {

// SkipDeps returns the skip deps
func (c *DeleteImpl) SkipDeps() bool {
return c.DeleteOptions.SkipDeps
return c.GlobalOptions.SkipDeps || c.DeleteOptions.SkipDeps
}

// SkipCharts returns skipCharts flag
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func (c *DestroyImpl) Concurrency() int {

// SkipDeps returns the skip deps
func (c *DestroyImpl) SkipDeps() bool {
return c.DestroyOptions.SkipDeps
return c.GlobalOptions.SkipDeps || c.DestroyOptions.SkipDeps
}

// SkipCharts returns skipCharts flag
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func (t *DiffImpl) Set() []string {

// SkipDeps returns the skip deps
func (t *DiffImpl) SkipDeps() bool {
return t.DiffOptions.SkipDeps
return t.GlobalOptions.SkipDeps || t.DiffOptions.SkipDeps
}

// SkipNeeds returns the skip needs
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (c *FetchImpl) Concurrency() int {

// SkipDeps returns the skip deps
func (c *FetchImpl) SkipDeps() bool {
return c.FetchOptions.SkipDeps
return c.GlobalOptions.SkipDeps || c.FetchOptions.SkipDeps
}

// OutputDir returns the args
Expand Down
9 changes: 9 additions & 0 deletions pkg/config/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ type GlobalOptions struct {
StateValuesSet []string
// StateValuesFiles is a list of state values files to use.
StateValuesFile []string
// SkipDeps is true if the running "helm repo update" and "helm dependency build" should be skipped
SkipDeps bool
// DisableForceUpdate is true if force updating repos is not desirable when executing "helm repo add"
DisableForceUpdate bool
// Quiet is true if the output should be quiet.
Quiet bool
// KubeContext is the name of the kubectl context to use.
Expand Down Expand Up @@ -132,6 +136,11 @@ func (g *GlobalImpl) EnableLiveOutput() bool {
return g.GlobalOptions.EnableLiveOutput
}

// DisableForceUpdate return when to disable forcing updates to repos upon adding
func (g *GlobalImpl) DisableForceUpdate() bool {
return g.GlobalOptions.DisableForceUpdate
}

// Logger returns the logger
func (g *GlobalImpl) Logger() *zap.SugaredLogger {
return g.GlobalOptions.logger
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (l *LintImpl) Concurrency() int {

// SkipDeps returns the skip deps
func (l *LintImpl) SkipDeps() bool {
return l.LintOptions.SkipDeps
return l.GlobalOptions.SkipDeps || l.LintOptions.SkipDeps
}

// Set returns the Set
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func (t *SyncImpl) Set() []string {

// SkipDeps returns the skip deps
func (t *SyncImpl) SkipDeps() bool {
return t.SyncOptions.SkipDeps
return t.GlobalOptions.SkipDeps || t.SyncOptions.SkipDeps
}

// SkipNeeds returns the skip needs
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func (t *TemplateImpl) SkipCleanup() bool {

// SkipDeps returns the skip deps
func (t *TemplateImpl) SkipDeps() bool {
return t.TemplateOptions.SkipDeps
return t.GlobalOptions.SkipDeps || t.TemplateOptions.SkipDeps
}

// SkipNeeds returns the skip needs
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (t *TestImpl) Concurrency() int {

// SkipDeps returns the skip deps
func (t *TestImpl) SkipDeps() bool {
return t.TestOptions.SkipDeps
return t.GlobalOptions.SkipDeps || t.TestOptions.SkipDeps
}

// Cleanup returns the cleanup
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/write-values.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (c *WriteValuesImpl) Concurrency() int {

// SkipDeps returns the skip deps
func (c *WriteValuesImpl) SkipDeps() bool {
return c.WriteValuesOptions.SkipDeps
return c.GlobalOptions.SkipDeps || c.WriteValuesOptions.SkipDeps
}

// Set returns the Set
Expand Down
2 changes: 2 additions & 0 deletions pkg/exectest/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ func (helm *Helm) SetHelmBinary(bin string) {
}
func (helm *Helm) SetEnableLiveOutput(enableLiveOutput bool) {
}
func (helm *Helm) SetDisableForceUpdate(forceUpdate bool) {
}
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}
return nil
Expand Down
21 changes: 15 additions & 6 deletions pkg/helmexec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,14 @@ type decryptedSecret struct {
err error
}

type HelmExecOptions struct {
EnableLiveOutput bool
DisableForceUpdate bool
}

type execer struct {
helmBinary string
enableLiveOutput bool
options HelmExecOptions
version *semver.Version
runner Runner
logger *zap.SugaredLogger
Expand Down Expand Up @@ -110,15 +115,15 @@ func redactedURL(chart string) string {
}

// New for running helm commands
func New(helmBinary string, enableLiveOutput bool, logger *zap.SugaredLogger, kubeContext string, runner Runner) *execer {
func New(helmBinary string, options HelmExecOptions, logger *zap.SugaredLogger, kubeContext string, runner Runner) *execer {
// TODO: proper error handling
version, err := GetHelmVersion(helmBinary, runner)
if err != nil {
panic(err)
}
return &execer{
helmBinary: helmBinary,
enableLiveOutput: enableLiveOutput,
options: options,
version: version,
logger: logger,
kubeContext: kubeContext,
Expand All @@ -136,7 +141,11 @@ func (helm *execer) SetHelmBinary(bin string) {
}

func (helm *execer) SetEnableLiveOutput(enableLiveOutput bool) {
helm.enableLiveOutput = enableLiveOutput
helm.options.EnableLiveOutput = enableLiveOutput
}

func (helm *execer) SetDisableForceUpdate(forceUpdate bool) {
helm.options.DisableForceUpdate = forceUpdate
}

func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error {
Expand All @@ -156,7 +165,7 @@ func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, usernam

// See https://github.com/helm/helm/pull/8777
if cons, err := semver.NewConstraint(">= 3.3.2"); err == nil {
if cons.Check(helm.version) {
if !helm.options.DisableForceUpdate && cons.Check(helm.version) {
args = append(args, "--force-update")
}
} else {
Expand Down Expand Up @@ -525,7 +534,7 @@ func (helm *execer) exec(args []string, env map[string]string, overrideEnableLiv
}
cmd := fmt.Sprintf("exec: %s %s", helm.helmBinary, strings.Join(cmdargs, " "))
helm.logger.Debug(cmd)
enableLiveOutput := helm.enableLiveOutput
enableLiveOutput := helm.options.EnableLiveOutput
if overrideEnableLiveOutput != nil {
enableLiveOutput = *overrideEnableLiveOutput
}
Expand Down
59 changes: 47 additions & 12 deletions pkg/helmexec/exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string
}

func MockExecer(logger *zap.SugaredLogger, kubeContext string) *execer {
execer := New("helm", false, logger, kubeContext, &mockRunner{})
execer := New("helm", HelmExecOptions{}, logger, kubeContext, &mockRunner{})
return execer
}

Expand Down Expand Up @@ -85,12 +85,23 @@ 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")
if helm.options.EnableLiveOutput {
t.Error("helmexec.options.EnableLiveOutput should not be enabled by default")
}
helm.SetEnableLiveOutput(true)
if !helm.enableLiveOutput {
t.Errorf("helmexec.SetEnableLiveOutput() - actual = %t expect = true", helm.enableLiveOutput)
if !helm.options.EnableLiveOutput {
t.Errorf("helmexec.SetEnableLiveOutput() - actual = %t expect = true", helm.options.EnableLiveOutput)
}
}

func Test_SetDisableForceUpdate(t *testing.T) {
helm := MockExecer(NewLogger(os.Stdout, "info"), "dev")
if !helm.options.DisableForceUpdate {
t.Error("helmexec.options.ForceUpdate should be enabled by default")
}
helm.SetDisableForceUpdate(false)
if !helm.options.DisableForceUpdate {
t.Errorf("helmexec.SetDisableForceUpdate() - actual = %t expect = false", helm.options.DisableForceUpdate)
}
}

Expand All @@ -117,6 +128,30 @@ exec: helm --kube-context dev repo add myRepo https://repo.example.com/ --force-
}
}

func Test_AddRepo_Helm_3_3_2_NoForceUpdate(t *testing.T) {
var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug")
helm := &execer{
helmBinary: "helm",
options: HelmExecOptions{DisableForceUpdate: true},
version: semver.MustParse("3.3.2"),
logger: logger,
kubeContext: "dev",
runner: &mockRunner{},
}
err := helm.AddRepo("myRepo", "https://repo.example.com/", "", "cert.pem", "key.pem", "", "", "", "", "")
expected := `Adding repo myRepo https://repo.example.com/
exec: helm --kube-context dev repo add myRepo https://repo.example.com/ --force-update --cert-file cert.pem --key-file key.pem
`
if err != nil {
t.Errorf("unexpected error: %v", err)
}

if buffer.String() != expected {
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
}

func Test_AddRepo(t *testing.T) {
var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug")
Expand Down Expand Up @@ -322,7 +357,7 @@ func Test_BuildDeps(t *testing.T) {
var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug")
helm3Runner := mockRunner{output: []byte("v3.2.4+ge29ce2a")}
helm := New("helm", false, logger, "dev", &helm3Runner)
helm := New("helm", HelmExecOptions{}, logger, "dev", &helm3Runner)
err := helm.BuildDeps("foo", "./chart/foo", []string{"--skip-refresh"}...)
expected := `Building dependency release=foo, chart=./chart/foo
exec: helm --kube-context dev dependency build ./chart/foo --skip-refresh
Expand Down Expand Up @@ -364,7 +399,7 @@ v3.2.4+ge29ce2a

buffer.Reset()
helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94")}
helm = New("helm", false, logger, "dev", &helm2Runner)
helm = New("helm", HelmExecOptions{}, logger, "dev", &helm2Runner)
err = helm.BuildDeps("foo", "./chart/foo")
expected = `Building dependency release=foo, chart=./chart/foo
exec: helm --kube-context dev dependency build ./chart/foo
Expand Down Expand Up @@ -870,13 +905,13 @@ exec: helm --kube-context dev template release https://example_user:example_pass

func Test_IsHelm3(t *testing.T) {
helm2Runner := mockRunner{output: []byte("Client: v2.16.0+ge13bc94\n")}
helm := New("helm", false, NewLogger(os.Stdout, "info"), "dev", &helm2Runner)
helm := New("helm", HelmExecOptions{}, 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", false, NewLogger(os.Stdout, "info"), "dev", &helm3Runner)
helm = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "dev", &helm3Runner)
if !helm.IsHelm3() {
t.Error("helmexec.IsHelm3() - Failed to detect Helm 3")
}
Expand Down Expand Up @@ -907,14 +942,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", false, NewLogger(os.Stdout, "info"), "dev", &helm2Runner)
helm := New("helm", HelmExecOptions{}, 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", false, NewLogger(os.Stdout, "info"), "dev", &helm3Runner)
helm = New("helm", HelmExecOptions{}, 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)
Expand All @@ -923,7 +958,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", false, NewLogger(os.Stdout, "info"), "dev", &helm2Runner)
helm := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "dev", &helm2Runner)
if !helm.IsVersionAtLeast("2.1.0") {
t.Error("helmexec.IsVersionAtLeast - 2.16.1 not atleast 2.1")
}
Expand Down

0 comments on commit e980174

Please sign in to comment.