diff --git a/pkg/cmd/flags.go b/pkg/cmd/flags.go index 61db07883..3a51a0c17 100644 --- a/pkg/cmd/flags.go +++ b/pkg/cmd/flags.go @@ -56,9 +56,17 @@ func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) { f.StringArrayVar(&v.LiteralValues, "set-literal", []string{}, "set a literal STRING value on the command line") } +// AddWaitFlag adds the --wait flag to cmd, storing the selected strategy in wait. +// Deprecation warnings for boolean values are logged to the slog default logger. func AddWaitFlag(cmd *cobra.Command, wait *kube.WaitStrategy) { + addWaitFlag(cmd, wait, nil) +} + +// addWaitFlag is AddWaitFlag with the logger that receives deprecation +// warnings. A nil logger means the slog default logger at the time of logging. +func addWaitFlag(cmd *cobra.Command, wait *kube.WaitStrategy, logger *slog.Logger) { cmd.Flags().Var( - newWaitValue(kube.HookOnlyStrategy, wait), + newWaitValue(kube.HookOnlyStrategy, wait, logger), "wait", "wait until resources are ready (up to --timeout). Use '--wait' alone for 'watcher' strategy, or specify one of: 'watcher', 'hookOnly', 'legacy'. Default when flag is omitted: 'hookOnly'.", ) @@ -85,32 +93,42 @@ func defaultCLIWaitOptions() []kube.WaitOption { } } -type waitValue kube.WaitStrategy +type waitValue struct { + strategy *kube.WaitStrategy + logger *slog.Logger +} -func newWaitValue(defaultValue kube.WaitStrategy, ws *kube.WaitStrategy) *waitValue { +func newWaitValue(defaultValue kube.WaitStrategy, ws *kube.WaitStrategy, logger *slog.Logger) *waitValue { *ws = defaultValue - return (*waitValue)(ws) + return &waitValue{strategy: ws, logger: logger} } func (ws *waitValue) String() string { - if ws == nil { + if ws == nil || ws.strategy == nil { return "" } - return string(*ws) + return string(*ws.strategy) +} + +func (ws *waitValue) log() *slog.Logger { + if ws.logger != nil { + return ws.logger + } + return slog.Default() } func (ws *waitValue) Set(s string) error { switch s { case string(kube.StatusWatcherStrategy), string(kube.LegacyStrategy), string(kube.HookOnlyStrategy): - *ws = waitValue(s) + *ws.strategy = kube.WaitStrategy(s) return nil case "true": - slog.Warn("--wait=true is deprecated (boolean value) and can be replaced with --wait=watcher") - *ws = waitValue(kube.StatusWatcherStrategy) + ws.log().Warn("--wait=true is deprecated (boolean value) and can be replaced with --wait=watcher") + *ws.strategy = kube.StatusWatcherStrategy return nil case "false": - slog.Warn("--wait=false is deprecated (boolean value) and can be replaced with --wait=hookOnly") - *ws = waitValue(kube.HookOnlyStrategy) + ws.log().Warn("--wait=false is deprecated (boolean value) and can be replaced with --wait=hookOnly") + *ws.strategy = kube.HookOnlyStrategy return nil default: return fmt.Errorf("invalid wait input %q. Valid inputs are %s, %s, and %s", s, kube.StatusWatcherStrategy, kube.HookOnlyStrategy, kube.LegacyStrategy) diff --git a/pkg/cmd/flags_test.go b/pkg/cmd/flags_test.go index 09dc61638..7e30216be 100644 --- a/pkg/cmd/flags_test.go +++ b/pkg/cmd/flags_test.go @@ -17,14 +17,19 @@ limitations under the License. package cmd import ( + "bytes" "fmt" + "log/slog" "testing" "time" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/action" chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/release/common" release "helm.sh/helm/v4/pkg/release/v1" ) @@ -118,3 +123,48 @@ func TestPostRendererFlagSetOnce(t *testing.T) { // Set the plugin name again to a different value is not ok require.Error(t, str.Set("cat")) } + +func TestWaitValueLogsToInjectedLogger(t *testing.T) { + tests := []struct { + value string + want kube.WaitStrategy + wantLog string + }{ + {value: "true", want: kube.StatusWatcherStrategy, wantLog: "--wait=true is deprecated"}, + {value: "false", want: kube.HookOnlyStrategy, wantLog: "--wait=false is deprecated"}, + {value: "legacy", want: kube.LegacyStrategy}, + } + + for _, tt := range tests { + t.Run(tt.value, func(t *testing.T) { + var logBuf bytes.Buffer + var ws kube.WaitStrategy + v := newWaitValue(kube.HookOnlyStrategy, &ws, slog.New(slog.NewTextHandler(&logBuf, nil))) + assert.Equal(t, kube.HookOnlyStrategy, ws) + + require.NoError(t, v.Set(tt.value)) + assert.Equal(t, tt.want, ws) + assert.Equal(t, string(tt.want), v.String()) + if tt.wantLog == "" { + assert.Empty(t, logBuf.String()) + } else { + assert.Contains(t, logBuf.String(), tt.wantLog) + } + }) + } +} + +func TestAddWaitFlagLogsToDefaultLogger(t *testing.T) { + origDefault := slog.Default() + t.Cleanup(func() { slog.SetDefault(origDefault) }) + var logBuf bytes.Buffer + slog.SetDefault(slog.New(slog.NewTextHandler(&logBuf, nil))) + + cmd := &cobra.Command{Use: "test"} + var ws kube.WaitStrategy + AddWaitFlag(cmd, &ws) + require.NoError(t, cmd.Flags().Set("wait", "true")) + + assert.Equal(t, kube.StatusWatcherStrategy, ws) + assert.Contains(t, logBuf.String(), "--wait=true is deprecated") +} diff --git a/pkg/cmd/helpers.go b/pkg/cmd/helpers.go index 8866b8650..218ac9876 100644 --- a/pkg/cmd/helpers.go +++ b/pkg/cmd/helpers.go @@ -42,13 +42,13 @@ func addDryRunFlag(cmd *cobra.Command) { // Determine the `action.DryRunStrategy` given -dry-run=` flag (or absence of) // Legacy usage of the flag: boolean values, and `--dry-run` (without value) are supported, and log warnings emitted -func cmdGetDryRunFlagStrategy(cmd *cobra.Command, isTemplate bool) (action.DryRunStrategy, error) { +func cmdGetDryRunFlagStrategy(logger *slog.Logger, cmd *cobra.Command, isTemplate bool) (action.DryRunStrategy, error) { f := cmd.Flag("dry-run") v := f.Value.String() switch v { case f.NoOptDefVal: - slog.Warn(`--dry-run is deprecated and should be replaced with '--dry-run=client'`) + logger.Warn(`--dry-run is deprecated and should be replaced with '--dry-run=client'`) return action.DryRunClient, nil case string(action.DryRunClient): return action.DryRunClient, nil @@ -76,7 +76,7 @@ func cmdGetDryRunFlagStrategy(cmd *cobra.Command, isTemplate bool) (action.DryRu if b { result = action.DryRunClient } - slog.Warn(fmt.Sprintf(`boolean '--dry-run=%v' flag is deprecated and must be replaced with '--dry-run=%s'`, v, result)) + logger.Warn(fmt.Sprintf(`boolean '--dry-run=%v' flag is deprecated and must be replaced with '--dry-run=%s'`, v, result)) return result, nil } diff --git a/pkg/cmd/helpers_test.go b/pkg/cmd/helpers_test.go index 611d1a54c..dfbfa1f0e 100644 --- a/pkg/cmd/helpers_test.go +++ b/pkg/cmd/helpers_test.go @@ -82,6 +82,12 @@ func executeActionCommandC(store *storage.Storage, cmd string) (*cobra.Command, } func executeActionCommandStdinC(store *storage.Storage, in *os.File, cmd string) (*cobra.Command, string, error) { + return executeActionCommandWithLoggerC(store, in, NewLogger, cmd) +} + +// executeActionCommandWithLoggerC runs cmd against store with the root command +// logging through the logger returned by newLogger. +func executeActionCommandWithLoggerC(store *storage.Storage, in *os.File, newLogger func(bool) *slog.Logger, cmd string) (*cobra.Command, string, error) { args, err := shellwords.Parse(cmd) if err != nil { return nil, "", err @@ -95,7 +101,7 @@ func executeActionCommandStdinC(store *storage.Storage, in *os.File, cmd string) Capabilities: common.DefaultCapabilities, } - root, err := newRootCmdWithConfig(actionConfig, buf, args, SetupLogging) + root, err := newRootCmdWithConfig(actionConfig, buf, args, newLogger) if err != nil { return nil, "", err } @@ -272,7 +278,6 @@ func TestCmdGetDryRunFlagStrategy(t *testing.T) { for name, tc := range testCases { logBuf := new(bytes.Buffer) logger := slog.New(slog.NewJSONHandler(logBuf, nil)) - slog.SetDefault(logger) cmd := &cobra.Command{ Use: "helm", @@ -281,7 +286,7 @@ func TestCmdGetDryRunFlagStrategy(t *testing.T) { cmd.Flags().Parse([]string{"helm", tc.DryRunFlagArg}) t.Run(name, func(t *testing.T) { - dryRunStrategy, err := cmdGetDryRunFlagStrategy(cmd, tc.IsTemplate) + dryRunStrategy, err := cmdGetDryRunFlagStrategy(logger, cmd, tc.IsTemplate) if tc.ExpectedError { require.Error(t, err) } else { diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index 182b4a7ff..5a73f8be3 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -151,13 +151,13 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } client.SetRegistryClient(registryClient) - dryRunStrategy, err := cmdGetDryRunFlagStrategy(cmd, false) + dryRunStrategy, err := cmdGetDryRunFlagStrategy(cfg.Logger(), cmd, false) if err != nil { return err } client.DryRunStrategy = dryRunStrategy - rel, err := runInstall(args, client, valueOpts, out) + rel, err := runInstall(cfg.Logger(), args, client, valueOpts, out) if err != nil { return fmt.Errorf("INSTALLATION FAILED: %w", err) } @@ -173,7 +173,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } f := cmd.Flags() - addInstallFlags(cmd, f, client, valueOpts) + addInstallFlags(cmd, f, client, valueOpts, cfg.Logger()) // hide-secret is not available in all places the install flags are used so // it is added separately f.BoolVar(&client.HideSecret, "hide-secret", false, "hide Kubernetes Secrets when also using the --dry-run flag") @@ -184,7 +184,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return cmd } -func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) { +func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Install, valueOpts *values.Options, logger *slog.Logger) { f.BoolVar(&client.CreateNamespace, "create-namespace", false, "create the release namespace if not present") f.BoolVar(&client.ForceReplace, "force-replace", false, "force resource updates by replacement") f.BoolVar(&client.ForceReplace, "force", false, "deprecated") @@ -233,7 +233,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal addValueOptionsFlags(f, valueOpts) addChartPathOptionsFlags(f, &client.ChartPathOptions) - AddWaitFlag(cmd, &client.WaitStrategy) + addWaitFlag(cmd, &client.WaitStrategy, logger) cmd.MarkFlagsMutuallyExclusive("force-replace", "force-conflicts") cmd.MarkFlagsMutuallyExclusive("force", "force-conflicts") @@ -252,10 +252,10 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal } } -func runInstall(args []string, client *action.Install, valueOpts *values.Options, out io.Writer) (*release.Release, error) { - slog.Debug("Original chart version", "version", client.Version) +func runInstall(logger *slog.Logger, args []string, client *action.Install, valueOpts *values.Options, out io.Writer) (*release.Release, error) { + logger.Debug("Original chart version", "version", client.Version) if client.Version == "" && client.Devel { - slog.Debug("setting version to >0.0.0-0") + logger.Debug("setting version to >0.0.0-0") client.Version = ">0.0.0-0" } @@ -270,7 +270,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options return nil, err } - slog.Debug("Chart path", "path", cp) + logger.Debug("Chart path", "path", cp) p := getter.All(settings) vals, err := valueOpts.MergeValues(p) @@ -294,7 +294,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options } if ac.Deprecated() { - slog.Warn("this chart is deprecated") + logger.Warn("this chart is deprecated") } if req := ac.MetaDependencies(); len(req) > 0 { diff --git a/pkg/cmd/load_plugins.go b/pkg/cmd/load_plugins.go index 0c74ba51d..ec8eaa909 100644 --- a/pkg/cmd/load_plugins.go +++ b/pkg/cmd/load_plugins.go @@ -52,7 +52,7 @@ const ( // This follows a different pattern than the other commands because it has // to inspect its environment and then add commands to the base command // as it finds them. -func loadCLIPlugins(baseCmd *cobra.Command, out io.Writer) { +func loadCLIPlugins(baseCmd *cobra.Command, logger *slog.Logger, out io.Writer) { // If HELM_NO_PLUGINS is set to 1, do not load plugins. if os.Getenv("HELM_NO_PLUGINS") == "1" { return @@ -64,7 +64,7 @@ func loadCLIPlugins(baseCmd *cobra.Command, out io.Writer) { } found, err := plugin.FindPlugins(dirs, descriptor) if err != nil { - slog.Error("failed to load plugins", slog.String("error", err.Error())) + logger.Error("failed to load plugins", slog.String("error", err.Error())) return } @@ -136,7 +136,7 @@ func loadCLIPlugins(baseCmd *cobra.Command, out io.Writer) { for _, cmd := range baseCmd.Commands() { if cmd.Name() == c.Name() { - slog.Error("failed to load plugins: name conflicts", slog.String("name", c.Name())) + logger.Error("failed to load plugins: name conflicts", slog.String("name", c.Name())) return } } @@ -151,7 +151,7 @@ func loadCLIPlugins(baseCmd *cobra.Command, out io.Writer) { if (err == nil && ((subCmd.HasParent() && subCmd.Parent().Name() == "completion") || subCmd.Name() == cobra.ShellCompRequestCmd)) || /* for the tests */ subCmd == baseCmd.Root() { - loadCompletionForPlugin(c, plug) + loadCompletionForPlugin(logger, c, plug) } } } @@ -218,12 +218,12 @@ type pluginCommand struct { // loadCompletionForPlugin will load and parse any completion.yaml provided by the plugin // and add the dynamic completion hook to call the optional plugin.complete -func loadCompletionForPlugin(pluginCmd *cobra.Command, plug plugin.Plugin) { +func loadCompletionForPlugin(logger *slog.Logger, pluginCmd *cobra.Command, plug plugin.Plugin) { // Parse the yaml file providing the plugin's sub-commands and flags cmds, err := loadFile(plug.Dir() + string(filepath.Separator) + pluginStaticCompletionFile) if err != nil { // The file could be missing or invalid. No static completion for this plugin. - slog.Debug("plugin completion file loading", slog.String("error", err.Error())) + logger.Debug("plugin completion file loading", slog.String("error", err.Error())) // Continue to setup dynamic completion. cmds = &pluginCommand{} } @@ -231,18 +231,18 @@ func loadCompletionForPlugin(pluginCmd *cobra.Command, plug plugin.Plugin) { // Preserve the Usage string specified for the plugin cmds.Name = pluginCmd.Use - addPluginCommands(plug, pluginCmd, cmds) + addPluginCommands(logger, plug, pluginCmd, cmds) } // addPluginCommands is a recursive method that adds each different level // of sub-commands and flags for the plugins that have provided such information -func addPluginCommands(plug plugin.Plugin, baseCmd *cobra.Command, cmds *pluginCommand) { +func addPluginCommands(logger *slog.Logger, plug plugin.Plugin, baseCmd *cobra.Command, cmds *pluginCommand) { if cmds == nil { return } if cmds.Name == "" { - slog.Debug("sub-command name field missing", slog.String("commandPath", baseCmd.CommandPath())) + logger.Debug("sub-command name field missing", slog.String("commandPath", baseCmd.CommandPath())) return } @@ -311,7 +311,7 @@ func addPluginCommands(plug plugin.Plugin, baseCmd *cobra.Command, cmds *pluginC Run: func(_ *cobra.Command, _ []string) {}, } baseCmd.AddCommand(subCmd) - addPluginCommands(plug, subCmd, &cmd) + addPluginCommands(logger, plug, subCmd, &cmd) } } diff --git a/pkg/cmd/plugin.go b/pkg/cmd/plugin.go index ba904ef5f..a07efacc4 100644 --- a/pkg/cmd/plugin.go +++ b/pkg/cmd/plugin.go @@ -17,6 +17,7 @@ package cmd import ( "io" + "log/slog" "github.com/spf13/cobra" @@ -27,17 +28,17 @@ const pluginHelp = ` Manage client-side Helm plugins. ` -func newPluginCmd(out io.Writer) *cobra.Command { +func newPluginCmd(logger *slog.Logger, out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "plugin", Short: "install, list, or uninstall Helm plugins", Long: pluginHelp, } cmd.AddCommand( - newPluginInstallCmd(out), - newPluginListCmd(out), - newPluginUninstallCmd(out), - newPluginUpdateCmd(out), + newPluginInstallCmd(logger, out), + newPluginListCmd(logger, out), + newPluginUninstallCmd(logger, out), + newPluginUpdateCmd(logger, out), newPluginPackageCmd(out), newPluginVerifyCmd(out), ) diff --git a/pkg/cmd/plugin_install.go b/pkg/cmd/plugin_install.go index c248ed818..dc91f288d 100644 --- a/pkg/cmd/plugin_install.go +++ b/pkg/cmd/plugin_install.go @@ -45,6 +45,8 @@ type pluginInstallOptions struct { plainHTTP bool password string username string + + logger *slog.Logger } const pluginInstallDesc = ` @@ -58,8 +60,8 @@ treated as "local dev" and do not require signatures. Use --verify=false to explicitly skip signature verification (NOT recommended). ` -func newPluginInstallCmd(out io.Writer) *cobra.Command { - o := &pluginInstallOptions{} +func newPluginInstallCmd(logger *slog.Logger, out io.Writer) *cobra.Command { + o := &pluginInstallOptions{logger: logger} cmd := &cobra.Command{ Use: "install [options] ", Short: "install a Helm plugin", @@ -169,7 +171,7 @@ func (o *pluginInstallOptions) run(out io.Writer) error { fmt.Fprintf(out, "Plugin Hash Verified: %s\n", verifyResult.FileHash) } - slog.Debug("loading plugin", "path", i.Path()) + o.logger.Debug("loading plugin", "path", i.Path()) p, err := plugin.LoadDir(i.Path()) if err != nil { return fmt.Errorf("plugin is installed but unusable: %w", err) diff --git a/pkg/cmd/plugin_list.go b/pkg/cmd/plugin_list.go index 74e969e04..3a480d8fc 100644 --- a/pkg/cmd/plugin_list.go +++ b/pkg/cmd/plugin_list.go @@ -29,7 +29,7 @@ import ( "helm.sh/helm/v4/internal/plugin/schema" ) -func newPluginListCmd(out io.Writer) *cobra.Command { +func newPluginListCmd(logger *slog.Logger, out io.Writer) *cobra.Command { var pluginType string cmd := &cobra.Command{ Use: "list", @@ -37,7 +37,7 @@ func newPluginListCmd(out io.Writer) *cobra.Command { Short: "list installed Helm plugins", ValidArgsFunction: noMoreArgsCompFunc, RunE: func(_ *cobra.Command, _ []string) error { - slog.Debug("pluginDirs", "directory", settings.PluginsDirectory) + logger.Debug("pluginDirs", "directory", settings.PluginsDirectory) dirs := filepath.SplitList(settings.PluginsDirectory) descriptor := plugin.Descriptor{ Type: pluginType, diff --git a/pkg/cmd/plugin_test.go b/pkg/cmd/plugin_test.go index 6a7980119..325ace67f 100644 --- a/pkg/cmd/plugin_test.go +++ b/pkg/cmd/plugin_test.go @@ -88,7 +88,7 @@ func TestLoadCLIPlugins(t *testing.T) { out bytes.Buffer cmd cobra.Command ) - loadCLIPlugins(&cmd, &out) + loadCLIPlugins(&cmd, NewLogger(false), &out) fullEnvOutput := strings.Join([]string{ "HELM_PLUGIN_NAME=fullenv", @@ -155,7 +155,7 @@ func TestLoadPluginsWithSpace(t *testing.T) { out bytes.Buffer cmd cobra.Command ) - loadCLIPlugins(&cmd, &out) + loadCLIPlugins(&cmd, NewLogger(false), &out) envs := strings.Join([]string{ "fullenv", @@ -220,7 +220,7 @@ func TestLoadCLIPluginsForCompletion(t *testing.T) { cmd := &cobra.Command{ Use: "completion", } - loadCLIPlugins(cmd, &out) + loadCLIPlugins(cmd, NewLogger(false), &out) tests := []staticCompletionDetails{ {"args", []string{}, []string{}, []staticCompletionDetails{}}, @@ -313,7 +313,7 @@ func TestLoadCLIPlugins_HelmNoPlugins(t *testing.T) { out := bytes.NewBuffer(nil) cmd := &cobra.Command{} - loadCLIPlugins(cmd, out) + loadCLIPlugins(cmd, NewLogger(false), out) plugins := cmd.Commands() require.Empty(t, plugins, "Expected 0 plugins, got %d", len(plugins)) diff --git a/pkg/cmd/plugin_uninstall.go b/pkg/cmd/plugin_uninstall.go index 81d9b31ee..018833899 100644 --- a/pkg/cmd/plugin_uninstall.go +++ b/pkg/cmd/plugin_uninstall.go @@ -29,11 +29,12 @@ import ( ) type pluginUninstallOptions struct { - names []string + names []string + logger *slog.Logger } -func newPluginUninstallCmd(out io.Writer) *cobra.Command { - o := &pluginUninstallOptions{} +func newPluginUninstallCmd(logger *slog.Logger, out io.Writer) *cobra.Command { + o := &pluginUninstallOptions{logger: logger} cmd := &cobra.Command{ Use: "uninstall ...", @@ -61,7 +62,7 @@ func (o *pluginUninstallOptions) complete(args []string) error { } func (o *pluginUninstallOptions) run(out io.Writer) error { - slog.Debug("loading installer plugins", "dir", settings.PluginsDirectory) + o.logger.Debug("loading installer plugins", "dir", settings.PluginsDirectory) plugins, err := plugin.LoadAllDir(settings.PluginsDirectory, plugin.LogIgnorePluginLoadErrorFilterFunc) if err != nil { return err @@ -69,7 +70,7 @@ func (o *pluginUninstallOptions) run(out io.Writer) error { var errorPlugins []error for _, name := range o.names { if found := findPlugin(plugins, name); found != nil { - if err := uninstallPlugin(found); err != nil { + if err := uninstallPlugin(o.logger, found); err != nil { errorPlugins = append(errorPlugins, fmt.Errorf("failed to uninstall plugin %s, got error (%w)", name, err)) } else { fmt.Fprintf(out, "Uninstalled plugin: %s\n", name) @@ -84,7 +85,7 @@ func (o *pluginUninstallOptions) run(out io.Writer) error { return nil } -func uninstallPlugin(p plugin.Plugin) error { +func uninstallPlugin(logger *slog.Logger, p plugin.Plugin) error { if err := os.RemoveAll(p.Dir()); err != nil { return err } @@ -102,18 +103,18 @@ func uninstallPlugin(p plugin.Plugin) error { // Remove tarball file tarballPath := filepath.Join(pluginsDir, versionedBasename) if _, err := os.Stat(tarballPath); err == nil { - slog.Debug("removing versioned tarball", "path", tarballPath) + logger.Debug("removing versioned tarball", "path", tarballPath) if err := os.Remove(tarballPath); err != nil { - slog.Debug("failed to remove tarball file", "path", tarballPath, "error", err) + logger.Debug("failed to remove tarball file", "path", tarballPath, "error", err) } } // Remove provenance file provPath := filepath.Join(pluginsDir, versionedBasename+".prov") if _, err := os.Stat(provPath); err == nil { - slog.Debug("removing versioned provenance", "path", provPath) + logger.Debug("removing versioned provenance", "path", provPath) if err := os.Remove(provPath); err != nil { - slog.Debug("failed to remove provenance file", "path", provPath, "error", err) + logger.Debug("failed to remove provenance file", "path", provPath, "error", err) } } } diff --git a/pkg/cmd/plugin_update.go b/pkg/cmd/plugin_update.go index 1cba7ffa9..6922e21e0 100644 --- a/pkg/cmd/plugin_update.go +++ b/pkg/cmd/plugin_update.go @@ -29,11 +29,12 @@ import ( ) type pluginUpdateOptions struct { - names []string + names []string + logger *slog.Logger } -func newPluginUpdateCmd(out io.Writer) *cobra.Command { - o := &pluginUpdateOptions{} +func newPluginUpdateCmd(logger *slog.Logger, out io.Writer) *cobra.Command { + o := &pluginUpdateOptions{logger: logger} cmd := &cobra.Command{ Use: "update ...", @@ -61,7 +62,7 @@ func (o *pluginUpdateOptions) complete(args []string) error { } func (o *pluginUpdateOptions) run(out io.Writer) error { - slog.Debug("loading installed plugins", "path", settings.PluginsDirectory) + o.logger.Debug("loading installed plugins", "path", settings.PluginsDirectory) plugins, err := plugin.LoadAllDir(settings.PluginsDirectory, plugin.LogIgnorePluginLoadErrorFilterFunc) if err != nil { return err @@ -70,7 +71,7 @@ func (o *pluginUpdateOptions) run(out io.Writer) error { for _, name := range o.names { if found := findPlugin(plugins, name); found != nil { - if err := updatePlugin(found); err != nil { + if err := updatePlugin(o.logger, found); err != nil { errorPlugins = append(errorPlugins, fmt.Errorf("failed to update plugin %s, got error (%w)", name, err)) } else { fmt.Fprintf(out, "Updated plugin: %s\n", name) @@ -85,7 +86,7 @@ func (o *pluginUpdateOptions) run(out io.Writer) error { return nil } -func updatePlugin(p plugin.Plugin) error { +func updatePlugin(logger *slog.Logger, p plugin.Plugin) error { exactLocation, err := filepath.EvalSymlinks(p.Dir()) if err != nil { return err @@ -103,7 +104,7 @@ func updatePlugin(p plugin.Plugin) error { return err } - slog.Debug("loading plugin", "path", i.Path()) + logger.Debug("loading plugin", "path", i.Path()) updatedPlugin, err := plugin.LoadDir(i.Path()) if err != nil { return err diff --git a/pkg/cmd/pull.go b/pkg/cmd/pull.go index 9f5af462b..b430614d6 100644 --- a/pkg/cmd/pull.go +++ b/pkg/cmd/pull.go @@ -20,7 +20,6 @@ import ( "fmt" "io" "log" - "log/slog" "github.com/spf13/cobra" @@ -61,7 +60,7 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { RunE: func(_ *cobra.Command, args []string) error { client.Settings = settings if client.Version == "" && client.Devel { - slog.Debug("setting version to >0.0.0-0") + cfg.Logger().Debug("setting version to >0.0.0-0") client.Version = ">0.0.0-0" } diff --git a/pkg/cmd/registry_login.go b/pkg/cmd/registry_login.go index 605717e22..51a001062 100644 --- a/pkg/cmd/registry_login.go +++ b/pkg/cmd/registry_login.go @@ -63,7 +63,7 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman RunE: func(_ *cobra.Command, args []string) error { hostname := args[0] - username, password, err := getUsernamePassword(o.username, o.password, o.passwordFromStdinOpt) + username, password, err := getUsernamePassword(cfg.Logger(), o.username, o.password, o.passwordFromStdinOpt) if err != nil { return err } @@ -91,7 +91,7 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman } // Adapted from https://github.com/oras-project/oras -func getUsernamePassword(usernameOpt, passwordOpt string, passwordFromStdinOpt bool) (string, string, error) { +func getUsernamePassword(logger *slog.Logger, usernameOpt, passwordOpt string, passwordFromStdinOpt bool) (string, string, error) { var err error username := usernameOpt password := passwordOpt @@ -128,7 +128,7 @@ func getUsernamePassword(usernameOpt, passwordOpt string, passwordFromStdinOpt b } } default: - slog.Warn("using --password via the CLI is insecure. Use --password-stdin") + logger.Warn("using --password via the CLI is insecure. Use --password-stdin") } return username, password, nil diff --git a/pkg/cmd/registry_output_test.go b/pkg/cmd/registry_output_test.go index 77682a4c4..49a8ea50d 100644 --- a/pkg/cmd/registry_output_test.go +++ b/pkg/cmd/registry_output_test.go @@ -98,7 +98,7 @@ func runOCIChartCommand(t *testing.T, argsFn func(ref, registryConfig, contentCa Capabilities: common.DefaultCapabilities, } - root, err := newRootCmdWithConfig(actionConfig, stdout, args, SetupLogging) + root, err := newRootCmdWithConfig(actionConfig, stdout, args, NewLogger) require.NoError(t, err) root.SetOut(stdout) root.SetErr(stderr) diff --git a/pkg/cmd/rollback.go b/pkg/cmd/rollback.go index 62d0be4af..3b7c79f89 100644 --- a/pkg/cmd/rollback.go +++ b/pkg/cmd/rollback.go @@ -73,7 +73,7 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return fmt.Errorf("description must be %d characters or less, got %d", action.MaxDescriptionLength, descLen) } - dryRunStrategy, err := cmdGetDryRunFlagStrategy(cmd, false) + dryRunStrategy, err := cmdGetDryRunFlagStrategy(cfg.Logger(), cmd, false) if err != nil { return err } @@ -101,7 +101,7 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this rollback when rollback fails") f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit") addDryRunFlag(cmd) - AddWaitFlag(cmd, &client.WaitStrategy) + addWaitFlag(cmd, &client.WaitStrategy, cfg.Logger()) cmd.MarkFlagsMutuallyExclusive("force-replace", "force-conflicts") cmd.MarkFlagsMutuallyExclusive("force", "force-conflicts") diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index dbedca026..60c5d6651 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -103,9 +103,44 @@ By default, the default directories depend on the Operating System. The defaults var settings = cli.New() +// NewRootCmd creates the root Helm command. +// +// logSetup is called with the value of the --debug flag before any command is +// created. It is expected to install the process-wide slog default logger (see +// SetupLogging); Helm then logs through slog.Default(). Applications embedding +// the Helm CLI that do not want Helm to depend on or change the global logger +// should use NewRootCmdWithLogger instead. func NewRootCmd(out io.Writer, args []string, logSetup func(bool)) (*cobra.Command, error) { + return newRootCmd(out, args, loggerFromSetup(logSetup)) +} + +// loggerFromSetup adapts a NewRootCmd logSetup function: it runs logSetup and +// then uses whatever slog default logger that left in place. +func loggerFromSetup(logSetup func(bool)) func(bool) *slog.Logger { + return func(debug bool) *slog.Logger { + logSetup(debug) + return slog.Default() + } +} + +// NewRootCmdWithLogger creates the root Helm command, like NewRootCmd, but +// routes Helm's log output to the logger returned by newLogger instead of the +// slog default logger. newLogger is called with the value of the --debug flag. +// The logger is set on the action configuration and handed to every Helm +// command, and slog.SetDefault is never called. +// +// Pass NewLogger to keep the Helm CLI's own log format. If newLogger is nil or +// returns nil, NewLogger is used. +// +// A few library packages that do not accept a logger yet still log through +// the slog default logger. +func NewRootCmdWithLogger(out io.Writer, args []string, newLogger func(debug bool) *slog.Logger) (*cobra.Command, error) { + return newRootCmd(out, args, newLogger) +} + +func newRootCmd(out io.Writer, args []string, newLogger func(debug bool) *slog.Logger) (*cobra.Command, error) { actionConfig := action.NewConfiguration() - cmd, err := newRootCmdWithConfig(actionConfig, out, args, logSetup) + cmd, err := newRootCmdWithConfig(actionConfig, out, args, newLogger) if err != nil { return nil, err } @@ -122,6 +157,13 @@ func NewRootCmd(out io.Writer, args []string, logSetup func(bool)) (*cobra.Comma return cmd, nil } +// NewLogger returns the logger used by the Helm client: a text handler that +// writes to stderr without timestamps and emits debug records only when debug +// is true. +func NewLogger(debug bool) *slog.Logger { + return logging.NewLogger(func() bool { return debug }) +} + // SetupLogging sets up Helm logging used by the Helm client. // This function is passed to the NewRootCmd function to enable logging. Any other // application that uses the NewRootCmd function to setup all the Helm commands may @@ -129,9 +171,11 @@ func NewRootCmd(out io.Writer, args []string, logSetup func(bool)) (*cobra.Comma // enables applications using Helm commands to integrate with their existing logging // system. // The debug argument is the value if Helm is set for debugging (i.e. --debug flag) +// +// SetupLogging replaces the process-wide slog default logger. To give Helm a +// logger without changing the global one, use NewRootCmdWithLogger. func SetupLogging(debug bool) { - logger := logging.NewLogger(func() bool { return debug }) - slog.SetDefault(logger) + slog.SetDefault(NewLogger(debug)) } // configureColorOutput configures the color output based on the ColorMode setting @@ -148,7 +192,7 @@ func configureColorOutput(settings *cli.EnvSettings) { } } -func newRootCmdWithConfig(actionConfig *action.Configuration, out io.Writer, args []string, logSetup func(bool)) (*cobra.Command, error) { +func newRootCmdWithConfig(actionConfig *action.Configuration, out io.Writer, args []string, newLogger func(debug bool) *slog.Logger) (*cobra.Command, error) { cmd := &cobra.Command{ Use: "helm", Short: "The Helm package manager for Kubernetes.", @@ -178,18 +222,18 @@ func newRootCmdWithConfig(actionConfig *action.Configuration, out io.Writer, arg flags.ParseErrorsAllowlist.UnknownFlags = true flags.Parse(args) - logSetup(settings.Debug) - - // newRootCmdWithConfig is only called from NewRootCmd. NewRootCmd sets up - // NewConfiguration without a custom logger. So, the slog default is used. logSetup - // can change the default logger to the one in the logger package. This happens for - // the Helm client. This means the actionConfig logger is different from the slog - // default logger. If they are different we sync the actionConfig logger to the slog - // current default one. - if actionConfig.Logger() != slog.Default() { - actionConfig.SetLogger(slog.Default().Handler()) + var logger *slog.Logger + if newLogger != nil { + logger = newLogger(settings.Debug) + } + if logger == nil { + logger = NewLogger(settings.Debug) } + // The action configuration, and through it the Kubernetes client and the + // storage drivers, log through the same logger as the commands below. + actionConfig.SetLogger(logger.Handler()) + // Validate color mode setting switch settings.ColorMode { case "never", "auto", "always": @@ -272,7 +316,7 @@ func newRootCmdWithConfig(actionConfig *action.Configuration, out io.Writer, arg newLintCmd(out), newPackageCmd(out), newRepoCmd(out), - newSearchCmd(out), + newSearchCmd(logger, out), newVerifyCmd(out), // release commands @@ -289,7 +333,7 @@ func newRootCmdWithConfig(actionConfig *action.Configuration, out io.Writer, arg newCompletionCmd(out), newEnvCmd(out), - newPluginCmd(out), + newPluginCmd(logger, out), newVersionCmd(out), // Hidden documentation generator command: 'helm docs' @@ -302,7 +346,7 @@ func newRootCmdWithConfig(actionConfig *action.Configuration, out io.Writer, arg ) // Find and add CLI plugins - loadCLIPlugins(cmd, out) + loadCLIPlugins(cmd, logger, out) // Check for expired repositories checkForExpiredRepos(settings.RepositoryConfig) diff --git a/pkg/cmd/root_test.go b/pkg/cmd/root_test.go index b07bdbbf6..98ae9e637 100644 --- a/pkg/cmd/root_test.go +++ b/pkg/cmd/root_test.go @@ -18,6 +18,7 @@ package cmd import ( "bytes" + "io" "log/slog" "os" "path/filepath" @@ -26,10 +27,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "helm.sh/helm/v4/internal/logging" "helm.sh/helm/v4/internal/test/ensure" "helm.sh/helm/v4/pkg/action" "helm.sh/helm/v4/pkg/helmpath" "helm.sh/helm/v4/pkg/helmpath/xdg" + "helm.sh/helm/v4/pkg/storage" + "helm.sh/helm/v4/pkg/storage/driver" ) func TestRootCmd(t *testing.T) { @@ -128,10 +132,13 @@ func TestUnknownSubCmd(t *testing.T) { // } func TestRootCmdLogger(t *testing.T) { + origDefault := slog.Default() + t.Cleanup(func() { slog.SetDefault(origDefault) }) + args := []string{} buf := new(bytes.Buffer) actionConfig := action.NewConfiguration() - _, err := newRootCmdWithConfig(actionConfig, buf, args, SetupLogging) + _, err := newRootCmdWithConfig(actionConfig, buf, args, loggerFromSetup(SetupLogging)) require.NoError(t, err) l1 := actionConfig.Logger() @@ -139,3 +146,116 @@ func TestRootCmdLogger(t *testing.T) { assert.Equal(t, l2.Handler(), l1.Handler(), "expected actionConfig logger to be the slog default logger") } + +func TestRootCmdWithLoggerLeavesDefaultUntouched(t *testing.T) { + origDefault := slog.Default() + t.Cleanup(func() { slog.SetDefault(origDefault) }) + sentinel := slog.New(slog.DiscardHandler) + slog.SetDefault(sentinel) + + var gotDebug bool + injected := slog.New(slog.NewTextHandler(new(bytes.Buffer), nil)) + actionConfig := action.NewConfiguration() + _, err := newRootCmdWithConfig(actionConfig, io.Discard, []string{"--debug"}, func(debug bool) *slog.Logger { + gotDebug = debug + return injected + }) + require.NoError(t, err) + + assert.True(t, gotDebug, "expected the --debug flag value to be passed to the logger constructor") + assert.Same(t, sentinel, slog.Default(), "expected the slog default logger to be left untouched") + assert.Equal(t, injected.Handler(), actionConfig.Logger().Handler(), "expected actionConfig to use the injected logger") +} + +func TestRootCmdWithNilLogger(t *testing.T) { + origDefault := slog.Default() + t.Cleanup(func() { slog.SetDefault(origDefault) }) + sentinel := slog.New(slog.DiscardHandler) + slog.SetDefault(sentinel) + + tests := map[string]func(bool) *slog.Logger{ + "nil constructor": nil, + "constructor return nil": func(bool) *slog.Logger { return nil }, + } + for name, newLogger := range tests { + t.Run(name, func(t *testing.T) { + actionConfig := action.NewConfiguration() + _, err := newRootCmdWithConfig(actionConfig, io.Discard, []string{}, newLogger) + require.NoError(t, err) + + assert.Same(t, sentinel, slog.Default(), "expected the slog default logger to be left untouched") + assert.IsType(t, &logging.DebugCheckHandler{}, actionConfig.Logger().Handler(), "expected the Helm CLI logger as the fallback") + }) + } +} + +func TestRootCmdWithLoggerRoutesCommandLogs(t *testing.T) { + defer resetEnv()() + + // A record logged through the slog default logger instead of the injected + // one ends up in globalBuf. + origDefault := slog.Default() + t.Cleanup(func() { slog.SetDefault(origDefault) }) + var globalBuf bytes.Buffer + sentinel := slog.New(slog.NewTextHandler(&globalBuf, &slog.HandlerOptions{Level: slog.LevelDebug})) + slog.SetDefault(sentinel) + + repoFile := "testdata/helmhome/helm/repositories.yaml" + repoCache := "testdata/helmhome/helm/repository" + + tests := []struct { + name string + cmd string + wantLog string + }{ + { + name: "install --wait=true deprecation warning", + cmd: "install aeneas testdata/testcharts/empty --wait=true", + wantLog: "--wait=true is deprecated", + }, + { + name: "template --dry-run deprecation warning", + cmd: "template testdata/testcharts/empty --dry-run", + wantLog: "--dry-run is deprecated", + }, + { + name: "upgrade --install debug output", + cmd: "upgrade --install funny-bunny testdata/testcharts/empty --dry-run=true", + wantLog: "Original chart version", + }, + { + name: "search repo debug output", + cmd: "search repo alpine --repository-config " + repoFile + " --repository-cache " + repoCache, + wantLog: "original chart version", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + globalBuf.Reset() + var injectedBuf bytes.Buffer + injected := slog.New(slog.NewTextHandler(&injectedBuf, &slog.HandlerOptions{Level: slog.LevelDebug})) + + // Mirror action.Configuration.Init, which hands the configuration's + // logger to the storage driver. + mem := driver.NewMemory() + mem.SetLogger(injected.Handler()) + store := storage.Init(mem) + + _, _, err := executeActionCommandWithLoggerC(store, nil, func(bool) *slog.Logger { return injected }, tt.cmd) + require.NoError(t, err) + + assert.Contains(t, injectedBuf.String(), tt.wantLog) + assert.NotContains(t, globalBuf.String(), tt.wantLog, "expected the record not to go through the slog default logger") + assert.Same(t, sentinel, slog.Default(), "expected the slog default logger to be left untouched") + }) + } +} + +func TestNewLogger(t *testing.T) { + ctx := t.Context() + + assert.False(t, NewLogger(false).Enabled(ctx, slog.LevelDebug), "expected debug records to be dropped without --debug") + assert.True(t, NewLogger(false).Enabled(ctx, slog.LevelWarn)) + assert.True(t, NewLogger(true).Enabled(ctx, slog.LevelDebug), "expected debug records with --debug") +} diff --git a/pkg/cmd/search.go b/pkg/cmd/search.go index eb6c6e0b5..a8c685497 100644 --- a/pkg/cmd/search.go +++ b/pkg/cmd/search.go @@ -18,6 +18,7 @@ package cmd import ( "io" + "log/slog" "github.com/spf13/cobra" ) @@ -28,15 +29,15 @@ they can be stored including the Artifact Hub and repositories you have added. Use search subcommands to search different locations for charts. ` -func newSearchCmd(out io.Writer) *cobra.Command { +func newSearchCmd(logger *slog.Logger, out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "search [keyword]", Short: "search for a keyword in charts", Long: searchDesc, } - cmd.AddCommand(newSearchHubCmd(out)) - cmd.AddCommand(newSearchRepoCmd(out)) + cmd.AddCommand(newSearchHubCmd(logger, out)) + cmd.AddCommand(newSearchRepoCmd(logger, out)) return cmd } diff --git a/pkg/cmd/search_hub.go b/pkg/cmd/search_hub.go index 3f7afa3f4..98145d617 100644 --- a/pkg/cmd/search_hub.go +++ b/pkg/cmd/search_hub.go @@ -57,10 +57,11 @@ type searchHubOptions struct { outputFormat output.Format listRepoURL bool failOnNoResult bool + logger *slog.Logger } -func newSearchHubCmd(out io.Writer) *cobra.Command { - o := &searchHubOptions{} +func newSearchHubCmd(logger *slog.Logger, out io.Writer) *cobra.Command { + o := &searchHubOptions{logger: logger} cmd := &cobra.Command{ Use: "hub [KEYWORD]", @@ -91,7 +92,7 @@ func (o *searchHubOptions) run(ctx context.Context, out io.Writer, args []string q := strings.Join(args, " ") results, err := c.SearchWithContext(ctx, q) if err != nil { - slog.Debug("search failed", slog.Any("error", err)) + o.logger.Debug("search failed", slog.Any("error", err)) return fmt.Errorf("unable to perform search against %q", o.searchEndpoint) } diff --git a/pkg/cmd/search_repo.go b/pkg/cmd/search_repo.go index 2d9d967f8..4268407ea 100644 --- a/pkg/cmd/search_repo.go +++ b/pkg/cmd/search_repo.go @@ -73,10 +73,11 @@ type searchRepoOptions struct { repoCacheDir string outputFormat output.Format failOnNoResult bool + logger *slog.Logger } -func newSearchRepoCmd(out io.Writer) *cobra.Command { - o := &searchRepoOptions{} +func newSearchRepoCmd(logger *slog.Logger, out io.Writer) *cobra.Command { + o := &searchRepoOptions{logger: logger} cmd := &cobra.Command{ Use: "repo [keyword]", @@ -131,17 +132,17 @@ func (o *searchRepoOptions) run(out io.Writer, args []string) error { } func (o *searchRepoOptions) setupSearchedVersion() { - slog.Debug("original chart version", "version", o.version) + o.logger.Debug("original chart version", "version", o.version) if o.version != "" { return } if o.devel { // search for releases and prereleases (alpha, beta, and release candidate releases). - slog.Debug("setting version to >0.0.0-0") + o.logger.Debug("setting version to >0.0.0-0") o.version = ">0.0.0-0" } else { // search only for stable releases, prerelease versions will be skipped - slog.Debug("setting version to >0.0.0") + o.logger.Debug("setting version to >0.0.0") o.version = ">0.0.0" } } @@ -190,7 +191,7 @@ func (o *searchRepoOptions) buildIndex() (*search.Index, error) { f := filepath.Join(o.repoCacheDir, helmpath.CacheIndexFile(n)) ind, err := repo.LoadIndexFile(f) if err != nil { - slog.Warn("repo is corrupt or missing", slog.String("repo", n), slog.Any("error", err)) + o.logger.Warn("repo is corrupt or missing", slog.String("repo", n), slog.Any("error", err)) continue } diff --git a/pkg/cmd/show.go b/pkg/cmd/show.go index 161a38723..2f15b84ee 100644 --- a/pkg/cmd/show.go +++ b/pkg/cmd/show.go @@ -88,7 +88,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if err != nil { return err } - output, err := runShow(args, client) + output, err := runShow(cfg.Logger(), args, client) if err != nil { return err } @@ -109,7 +109,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if err != nil { return err } - output, err := runShow(args, client) + output, err := runShow(cfg.Logger(), args, client) if err != nil { return err } @@ -130,7 +130,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if err != nil { return err } - output, err := runShow(args, client) + output, err := runShow(cfg.Logger(), args, client) if err != nil { return err } @@ -151,7 +151,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if err != nil { return err } - output, err := runShow(args, client) + output, err := runShow(cfg.Logger(), args, client) if err != nil { return err } @@ -172,7 +172,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if err != nil { return err } - output, err := runShow(args, client) + output, err := runShow(cfg.Logger(), args, client) if err != nil { return err } @@ -210,10 +210,10 @@ func addShowFlags(subCmd *cobra.Command, client *action.Show) { } } -func runShow(args []string, client *action.Show) (string, error) { - slog.Debug("original chart version", "version", client.Version) +func runShow(logger *slog.Logger, args []string, client *action.Show) (string, error) { + logger.Debug("original chart version", "version", client.Version) if client.Version == "" && client.Devel { - slog.Debug("setting version to >0.0.0-0") + logger.Debug("setting version to >0.0.0-0") client.Version = ">0.0.0-0" } diff --git a/pkg/cmd/template.go b/pkg/cmd/template.go index 5645ab33d..6e3e0ec74 100644 --- a/pkg/cmd/template.go +++ b/pkg/cmd/template.go @@ -92,7 +92,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } client.SetRegistryClient(registryClient) - dryRunStrategy, err := cmdGetDryRunFlagStrategy(cmd, true) + dryRunStrategy, err := cmdGetDryRunFlagStrategy(cfg.Logger(), cmd, true) if err != nil { return err } @@ -105,7 +105,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client.Replace = true // Skip the name check client.APIVersions = common.VersionSet(extraAPIs) client.IncludeCRDs = includeCrds - rel, err := runInstall(args, client, valueOpts, out) + rel, err := runInstall(cfg.Logger(), args, client, valueOpts, out) if err != nil && !settings.Debug { if rel != nil { @@ -207,7 +207,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } f := cmd.Flags() - addInstallFlags(cmd, f, client, valueOpts) + addInstallFlags(cmd, f, client, valueOpts, cfg.Logger()) f.StringArrayVarP(&showFiles, "show-only", "s", []string{}, "only show manifests rendered from the given templates") f.StringVar(&client.OutputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout") f.BoolVar(&validate, "validate", false, "deprecated") diff --git a/pkg/cmd/uninstall.go b/pkg/cmd/uninstall.go index 315c29a8d..7d4790dda 100644 --- a/pkg/cmd/uninstall.go +++ b/pkg/cmd/uninstall.go @@ -82,7 +82,7 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.StringVar(&client.DeletionPropagation, "cascade", "background", "Must be \"background\", \"orphan\", or \"foreground\". Selects the deletion cascading strategy for the dependents. Defaults to background. Use \"foreground\" with --wait to ensure resources with finalizers are fully deleted before returning.") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.StringVar(&client.Description, "description", "", "add a custom description") - AddWaitFlag(cmd, &client.WaitStrategy) + addWaitFlag(cmd, &client.WaitStrategy, cfg.Logger()) return cmd } diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index 688842141..47252bbb3 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -113,7 +113,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } client.SetRegistryClient(registryClient) - dryRunStrategy, err := cmdGetDryRunFlagStrategy(cmd, false) + dryRunStrategy, err := cmdGetDryRunFlagStrategy(cfg.Logger(), cmd, false) if err != nil { return err } @@ -126,7 +126,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { histClient := action.NewHistory(cfg) histClient.Max = 1 versions, err := histClient.Run(args[0]) - if errors.Is(err, driver.ErrReleaseNotFound) || isReleaseUninstalled(versions) { + if errors.Is(err, driver.ErrReleaseNotFound) || isReleaseUninstalled(cfg.Logger(), versions) { // Only print this to stdout for table output if outfmt == output.Table { fmt.Fprintf(out, "Release %q does not exist. Installing it now.\n", args[0]) @@ -159,11 +159,11 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { instClient.ForceConflicts = client.ForceConflicts instClient.ServerSideApply = client.ServerSideApply != "false" - if isReleaseUninstalled(versions) { + if isReleaseUninstalled(cfg.Logger(), versions) { instClient.Replace = true } - rel, err := runInstall(args, instClient, valueOpts, out) + rel, err := runInstall(cfg.Logger(), args, instClient, valueOpts, out) if err != nil { return err } @@ -180,7 +180,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } if client.Version == "" && client.Devel { - slog.Debug("setting version to >0.0.0-0") + cfg.Logger().Debug("setting version to >0.0.0-0") client.Version = ">0.0.0-0" } @@ -239,7 +239,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } if ac.Deprecated() { - slog.Warn("this chart is deprecated") + cfg.Logger().Warn("this chart is deprecated") } // Create context and prepare the handle of SIGTERM @@ -312,7 +312,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { addValueOptionsFlags(f, valueOpts) bindOutputFlag(cmd, &outfmt) bindPostRenderFlag(cmd, &client.PostRenderer, settings) - AddWaitFlag(cmd, &client.WaitStrategy) + addWaitFlag(cmd, &client.WaitStrategy, cfg.Logger()) cmd.MarkFlagsMutuallyExclusive("force-replace", "force-conflicts") cmd.MarkFlagsMutuallyExclusive("force", "force-conflicts") @@ -329,10 +329,10 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return cmd } -func isReleaseUninstalled(versionsi []ri.Releaser) bool { +func isReleaseUninstalled(logger *slog.Logger, versionsi []ri.Releaser) bool { versions, err := releaseListToV1List(versionsi) if err != nil { - slog.Error("cannot convert release list to v1 release list", "error", err) + logger.Error("cannot convert release list to v1 release list", "error", err) return false } return len(versions) > 0 && versions[len(versions)-1].Info.Status == common.StatusUninstalled