pull/32691/merge
Abdullah Al Mahmud 3 days ago committed by GitHub
commit a867188925
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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)

@ -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")
}

@ -42,13 +42,13 @@ func addDryRunFlag(cmd *cobra.Command) {
// Determine the `action.DryRunStrategy` given -dry-run=<value>` 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
}

@ -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 {

@ -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 {

@ -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)
}
}

@ -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),
)

@ -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] <path|url>",
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)

@ -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,

@ -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))

@ -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 <plugin>...",
@ -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)
}
}
}

@ -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 <plugin>...",
@ -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

@ -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"
}

@ -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

@ -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)

@ -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")

@ -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)

@ -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")
}

@ -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
}

@ -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)
}

@ -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
}

@ -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"
}

@ -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")

@ -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
}

@ -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

Loading…
Cancel
Save