Migrate to pure slog without a custom wrapper

Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr>
pull/30708/head
Benoit Tigeot 5 months ago
parent 947658a96e
commit b2380720eb
No known key found for this signature in database
GPG Key ID: 8E6D4FC8AEBDA62C

@ -23,7 +23,6 @@ import (
// Import to initialize client auth plugins. // Import to initialize client auth plugins.
_ "k8s.io/client-go/plugin/pkg/client/auth" _ "k8s.io/client-go/plugin/pkg/client/auth"
logadapter "helm.sh/helm/v4/internal/log"
helmcmd "helm.sh/helm/v4/pkg/cmd" helmcmd "helm.sh/helm/v4/pkg/cmd"
"helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/kube"
) )
@ -38,16 +37,15 @@ func main() {
// another name (e.g., helm2 or helm3) does not change the name of the // another name (e.g., helm2 or helm3) does not change the name of the
// manager as picked up by the automated name detection. // manager as picked up by the automated name detection.
kube.ManagedFieldsManager = "helm" kube.ManagedFieldsManager = "helm"
logger := logadapter.NewReadableTextLogger(os.Stderr, false)
cmd, err := helmcmd.NewRootCmd(os.Stdout, os.Args[1:]) cmd, err := helmcmd.NewRootCmd(os.Stdout, os.Args[1:])
if err != nil { if err != nil {
logger.Warn("%+v", err) helmcmd.Logger.Warn("%+v", err)
os.Exit(1) os.Exit(1)
} }
if err := cmd.Execute(); err != nil { if err := cmd.Execute(); err != nil {
logger.Debug("error", err) helmcmd.Logger.Debug("error", err)
switch e := err.(type) { switch e := err.(type) {
case helmcmd.PluginError: case helmcmd.PluginError:
os.Exit(e.Code) os.Exit(e.Code)

@ -1,46 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package log
// Logger defines a minimal logging interface compatible with structured logging.
// It provides methods for different log levels with structured key-value pairs.
type Logger interface {
// Debug logs a message at debug level with structured key-value pairs.
Debug(msg string, args ...any)
// Warn logs a message at warning level with structured key-value pairs.
Warn(msg string, args ...any)
// Error logs a message at error level with structured key-value pairs.
Error(msg string, args ...any)
}
// NopLogger is a logger implementation that discards all log messages.
type NopLogger struct{}
// Debug implements Logger.Debug by doing nothing.
func (NopLogger) Debug(_ string, _ ...any) {}
// Warn implements Logger.Warn by doing nothing.
func (NopLogger) Warn(_ string, _ ...any) {}
// Error implements Logger.Error by doing nothing.
func (NopLogger) Error(_ string, _ ...any) {}
// DefaultLogger provides a no-op logger that discards all messages.
// It can be used as a default when no logger is provided.
var DefaultLogger Logger = NopLogger{}

@ -1,72 +0,0 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package log
import (
"io"
"log/slog"
)
// SlogAdapter adapts a standard library slog.Logger to the Logger interface.
type SlogAdapter struct {
logger *slog.Logger
}
// Debug implements Logger.Debug by forwarding to the underlying slog.Logger.
func (a SlogAdapter) Debug(msg string, args ...any) {
a.logger.Debug(msg, args...)
}
// Warn implements Logger.Warn by forwarding to the underlying slog.Logger.
func (a SlogAdapter) Warn(msg string, args ...any) {
a.logger.Warn(msg, args...)
}
// Error implements Logger.Error by forwarding to the underlying slog.Logger.
func (a SlogAdapter) Error(msg string, args ...any) {
// TODO: Handle error with `slog.Any`: slog.Info("something went wrong", slog.Any("err", err))
a.logger.Error(msg, args...)
}
// NewSlogAdapter creates a Logger that forwards log messages to a slog.Logger.
func NewSlogAdapter(logger *slog.Logger) Logger {
if logger == nil {
return DefaultLogger
}
return SlogAdapter{logger: logger}
}
// NewReadableTextLogger creates a Logger that outputs in a readable text format without timestamps
func NewReadableTextLogger(output io.Writer, debugEnabled bool) Logger {
level := slog.LevelInfo
if debugEnabled {
level = slog.LevelDebug
}
handler := slog.NewTextHandler(output, &slog.HandlerOptions{
Level: level,
// Ignore the time key to avoid cluttering the output with timestamps
ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey {
return slog.Attr{}
}
return a
},
})
return NewSlogAdapter(slog.New(handler))
}

@ -18,6 +18,7 @@ package monocular
import ( import (
"errors" "errors"
"log/slog"
"net/url" "net/url"
) )
@ -30,8 +31,7 @@ type Client struct {
// The base URL for requests // The base URL for requests
BaseURL string BaseURL string
// The internal logger to use Log *slog.Logger
Log func(string, ...interface{})
} }
// New creates a new client // New creates a new client
@ -44,12 +44,10 @@ func New(u string) (*Client, error) {
return &Client{ return &Client{
BaseURL: u, BaseURL: u,
Log: nopLogger, Log: slog.Default(),
}, nil }, nil
} }
var nopLogger = func(_ string, _ ...interface{}) {}
// Validate if the base URL for monocular is valid. // Validate if the base URL for monocular is valid.
func validate(u string) error { func validate(u string) error {

@ -20,6 +20,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"log/slog"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@ -33,7 +34,6 @@ import (
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
logadapter "helm.sh/helm/v4/internal/log"
chart "helm.sh/helm/v4/pkg/chart/v2" chart "helm.sh/helm/v4/pkg/chart/v2"
chartutil "helm.sh/helm/v4/pkg/chart/v2/util" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/engine" "helm.sh/helm/v4/pkg/engine"
@ -96,7 +96,7 @@ type Configuration struct {
// Capabilities describes the capabilities of the Kubernetes cluster. // Capabilities describes the capabilities of the Kubernetes cluster.
Capabilities *chartutil.Capabilities Capabilities *chartutil.Capabilities
Log logadapter.Logger Log *slog.Logger
// HookOutputFunc called with container name and returns and expects writer that will receive the log output. // HookOutputFunc called with container name and returns and expects writer that will receive the log output.
HookOutputFunc func(namespace, pod, container string) io.Writer HookOutputFunc func(namespace, pod, container string) io.Writer
@ -375,7 +375,7 @@ func (cfg *Configuration) recordRelease(r *release.Release) {
} }
// Init initializes the action configuration // Init initializes the action configuration
func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string, log logadapter.Logger) error { func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string, log *slog.Logger) error {
kc := kube.New(getter) kc := kube.New(getter)
kc.Log = log kc.Log = log

@ -18,12 +18,12 @@ package action
import ( import (
"fmt" "fmt"
"io" "io"
"log/slog"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
fakeclientset "k8s.io/client-go/kubernetes/fake" fakeclientset "k8s.io/client-go/kubernetes/fake"
logadapter "helm.sh/helm/v4/internal/log"
chart "helm.sh/helm/v4/pkg/chart/v2" chart "helm.sh/helm/v4/pkg/chart/v2"
chartutil "helm.sh/helm/v4/pkg/chart/v2/util" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
kubefake "helm.sh/helm/v4/pkg/kube/fake" kubefake "helm.sh/helm/v4/pkg/kube/fake"
@ -47,7 +47,7 @@ func actionConfigFixture(t *testing.T) *Configuration {
KubeClient: &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}}, KubeClient: &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}},
Capabilities: chartutil.DefaultCapabilities, Capabilities: chartutil.DefaultCapabilities,
RegistryClient: registryClient, RegistryClient: registryClient,
Log: logadapter.DefaultLogger, // TODO: permit to log in test as before with `var verbose = flag.Bool("test.log", false, "enable test logging")`` Log: slog.New(slog.NewTextHandler(io.Discard, nil)), // TODO: permit to log in test as before with `var verbose = flag.Bool("test.log", false, "enable test logging")``
} }
} }

@ -0,0 +1,43 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cli
import (
"log/slog"
"os"
)
func NewLogger(debug bool) *slog.Logger {
level := slog.LevelInfo
if debug {
level = slog.LevelDebug
}
// Create a handler that removes timestamps
handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: level,
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
// Remove the time attribute
if a.Key == slog.TimeKey {
return slog.Attr{}
}
return a
},
})
return slog.New(handler)
}

@ -82,11 +82,11 @@ func (ws *waitValue) Set(s string) error {
*ws = waitValue(s) *ws = waitValue(s)
return nil return nil
case "true": case "true":
Warning("--wait=true is deprecated (boolean value) and can be replaced with --wait=watcher") Logger.Warn("--wait=true is deprecated (boolean value) and can be replaced with --wait=watcher")
*ws = waitValue(kube.StatusWatcherStrategy) *ws = waitValue(kube.StatusWatcherStrategy)
return nil return nil
case "false": case "false":
Warning("--wait=false is deprecated (boolean value) and can be replaced by omitting the --wait flag") Logger.Warn("--wait=false is deprecated (boolean value) and can be replaced by omitting the --wait flag")
*ws = waitValue(kube.HookOnlyStrategy) *ws = waitValue(kube.HookOnlyStrategy)
return nil return nil
default: default:

@ -19,6 +19,7 @@ package cmd
import ( import (
"bytes" "bytes"
"io" "io"
"log/slog"
"os" "os"
"strings" "strings"
"testing" "testing"
@ -26,7 +27,6 @@ import (
shellwords "github.com/mattn/go-shellwords" shellwords "github.com/mattn/go-shellwords"
"github.com/spf13/cobra" "github.com/spf13/cobra"
logadapter "helm.sh/helm/v4/internal/log"
"helm.sh/helm/v4/internal/test" "helm.sh/helm/v4/internal/test"
"helm.sh/helm/v4/pkg/action" "helm.sh/helm/v4/pkg/action"
chartutil "helm.sh/helm/v4/pkg/chart/v2/util" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
@ -93,7 +93,7 @@ func executeActionCommandStdinC(store *storage.Storage, in *os.File, cmd string)
Releases: store, Releases: store,
KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard},
Capabilities: chartutil.DefaultCapabilities, Capabilities: chartutil.DefaultCapabilities,
Log: logadapter.DefaultLogger, Log: slog.New(slog.NewTextHandler(io.Discard, nil)),
} }
root, err := newRootCmdWithConfig(actionConfig, buf, args) root, err := newRootCmdWithConfig(actionConfig, buf, args)

@ -229,9 +229,9 @@ 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) { func runInstall(args []string, client *action.Install, valueOpts *values.Options, out io.Writer) (*release.Release, error) {
logger.Debug("Original chart version", "version", client.Version) Logger.Debug("Original chart version", "version", client.Version)
if client.Version == "" && client.Devel { if client.Version == "" && client.Devel {
logger.Debug("setting version to >0.0.0-0") Logger.Debug("setting version to >0.0.0-0")
client.Version = ">0.0.0-0" client.Version = ">0.0.0-0"
} }
@ -246,7 +246,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
return nil, err return nil, err
} }
logger.Debug("Chart path", "path", cp) Logger.Debug("Chart path", "path", cp)
p := getter.All(settings) p := getter.All(settings)
vals, err := valueOpts.MergeValues(p) vals, err := valueOpts.MergeValues(p)
@ -265,7 +265,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
} }
if chartRequested.Metadata.Deprecated { if chartRequested.Metadata.Deprecated {
logger.Warn("this chart is deprecated") Logger.Warn("this chart is deprecated")
} }
if req := chartRequested.Metadata.Dependencies; req != nil { if req := chartRequested.Metadata.Dependencies; req != nil {

@ -19,14 +19,12 @@ package cmd
import ( import (
"fmt" "fmt"
"io" "io"
"log/slog"
"os" "os"
"strconv" "strconv"
"github.com/gosuri/uitable" "github.com/gosuri/uitable"
"github.com/spf13/cobra" "github.com/spf13/cobra"
logadapter "helm.sh/helm/v4/internal/log"
"helm.sh/helm/v4/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v4/pkg/cli/output" "helm.sh/helm/v4/pkg/cli/output"
"helm.sh/helm/v4/pkg/cmd/require" "helm.sh/helm/v4/pkg/cmd/require"
@ -63,8 +61,6 @@ flag with the '--offset' flag allows you to page through results.
func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewList(cfg) client := action.NewList(cfg)
var outfmt output.Format var outfmt output.Format
slogger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
adapter := logadapter.NewSlogAdapter(slogger)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "list", Use: "list",
@ -75,7 +71,7 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
ValidArgsFunction: noMoreArgsCompFunc, ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(cmd *cobra.Command, _ []string) error { RunE: func(cmd *cobra.Command, _ []string) error {
if client.AllNamespaces { if client.AllNamespaces {
if err := cfg.Init(settings.RESTClientGetter(), "", os.Getenv("HELM_DRIVER"), adapter); err != nil { if err := cfg.Init(settings.RESTClientGetter(), "", os.Getenv("HELM_DRIVER"), Logger); err != nil {
return err return err
} }
} }

@ -66,7 +66,7 @@ func runHook(p *plugin.Plugin, event string) error {
prog := exec.Command(main, argv...) prog := exec.Command(main, argv...)
logger.Debug("running hook", "event", event, "program", prog) Logger.Debug("running hook", "event", event, "program", prog)
prog.Stdout, prog.Stderr = os.Stdout, os.Stderr prog.Stdout, prog.Stderr = os.Stdout, os.Stderr
if err := prog.Run(); err != nil { if err := prog.Run(); err != nil {

@ -79,7 +79,7 @@ func (o *pluginInstallOptions) run(out io.Writer) error {
return err return err
} }
logger.Debug("loading plugin", "path", i.Path()) Logger.Debug("loading plugin", "path", i.Path())
p, err := plugin.LoadDir(i.Path()) p, err := plugin.LoadDir(i.Path())
if err != nil { if err != nil {
return errors.Wrap(err, "plugin is installed but unusable") return errors.Wrap(err, "plugin is installed but unusable")

@ -32,7 +32,7 @@ func newPluginListCmd(out io.Writer) *cobra.Command {
Short: "list installed Helm plugins", Short: "list installed Helm plugins",
ValidArgsFunction: noMoreArgsCompFunc, ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(_ *cobra.Command, _ []string) error { RunE: func(_ *cobra.Command, _ []string) error {
logger.Debug("pluginDirs", settings.PluginsDirectory) Logger.Debug("pluginDirs", settings.PluginsDirectory)
plugins, err := plugin.FindPlugins(settings.PluginsDirectory) plugins, err := plugin.FindPlugins(settings.PluginsDirectory)
if err != nil { if err != nil {
return err return err

@ -60,7 +60,7 @@ func (o *pluginUninstallOptions) complete(args []string) error {
} }
func (o *pluginUninstallOptions) run(out io.Writer) error { func (o *pluginUninstallOptions) run(out io.Writer) error {
logger.Debug("loading installer plugins", "dir", settings.PluginsDirectory) Logger.Debug("loading installer plugins", "dir", settings.PluginsDirectory)
plugins, err := plugin.FindPlugins(settings.PluginsDirectory) plugins, err := plugin.FindPlugins(settings.PluginsDirectory)
if err != nil { if err != nil {
return err return err

@ -62,7 +62,7 @@ func (o *pluginUpdateOptions) complete(args []string) error {
func (o *pluginUpdateOptions) run(out io.Writer) error { func (o *pluginUpdateOptions) run(out io.Writer) error {
installer.Debug = settings.Debug installer.Debug = settings.Debug
logger.Debug("loading installed plugins", "path", settings.PluginsDirectory) Logger.Debug("loading installed plugins", "path", settings.PluginsDirectory)
plugins, err := plugin.FindPlugins(settings.PluginsDirectory) plugins, err := plugin.FindPlugins(settings.PluginsDirectory)
if err != nil { if err != nil {
return err return err
@ -104,7 +104,7 @@ func updatePlugin(p *plugin.Plugin) error {
return err return err
} }
logger.Debug("loading plugin", "path", i.Path()) Logger.Debug("loading plugin", "path", i.Path())
updatedPlugin, err := plugin.LoadDir(i.Path()) updatedPlugin, err := plugin.LoadDir(i.Path())
if err != nil { if err != nil {
return err return err

@ -60,7 +60,7 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
RunE: func(_ *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
client.Settings = settings client.Settings = settings
if client.Version == "" && client.Devel { if client.Version == "" && client.Devel {
logger.Debug("setting version to >0.0.0-0") Logger.Debug("setting version to >0.0.0-0")
client.Version = ">0.0.0-0" client.Version = ">0.0.0-0"
} }

@ -122,7 +122,7 @@ func getUsernamePassword(usernameOpt string, passwordOpt string, passwordFromStd
} }
} }
} else { } else {
logger.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 return username, password, nil

@ -31,7 +31,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
logadapter "helm.sh/helm/v4/internal/log"
"helm.sh/helm/v4/internal/tlsutil" "helm.sh/helm/v4/internal/tlsutil"
"helm.sh/helm/v4/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/pkg/cli"
@ -96,7 +95,7 @@ By default, the default directories depend on the Operating System. The defaults
` `
var settings = cli.New() var settings = cli.New()
var logger = logadapter.NewReadableTextLogger(os.Stderr, settings.Debug) var Logger = cli.NewLogger(settings.Debug)
func NewRootCmd(out io.Writer, args []string) (*cobra.Command, error) { func NewRootCmd(out io.Writer, args []string) (*cobra.Command, error) {
actionConfig := new(action.Configuration) actionConfig := new(action.Configuration)
@ -106,7 +105,7 @@ func NewRootCmd(out io.Writer, args []string) (*cobra.Command, error) {
} }
cobra.OnInitialize(func() { cobra.OnInitialize(func() {
helmDriver := os.Getenv("HELM_DRIVER") helmDriver := os.Getenv("HELM_DRIVER")
if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), helmDriver, logger); err != nil { if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), helmDriver, Logger); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if helmDriver == "memory" { if helmDriver == "memory" {

@ -89,7 +89,7 @@ func (o *searchHubOptions) run(out io.Writer, args []string) error {
q := strings.Join(args, " ") q := strings.Join(args, " ")
results, err := c.Search(q) results, err := c.Search(q)
if err != nil { if err != nil {
logger.Debug("search failed", "error", err) Logger.Debug("search failed", "error", err)
return fmt.Errorf("unable to perform search against %q", o.searchEndpoint) return fmt.Errorf("unable to perform search against %q", o.searchEndpoint)
} }

@ -130,17 +130,17 @@ func (o *searchRepoOptions) run(out io.Writer, args []string) error {
} }
func (o *searchRepoOptions) setupSearchedVersion() { func (o *searchRepoOptions) setupSearchedVersion() {
logger.Debug("original chart version", "version", o.version) Logger.Debug("original chart version", "version", o.version)
if o.version != "" { if o.version != "" {
return return
} }
if o.devel { // search for releases and prereleases (alpha, beta, and release candidate releases). if o.devel { // search for releases and prereleases (alpha, beta, and release candidate releases).
logger.Debug("setting version to >0.0.0-0") Logger.Debug("setting version to >0.0.0-0")
o.version = ">0.0.0-0" o.version = ">0.0.0-0"
} else { // search only for stable releases, prerelease versions will be skipped } else { // search only for stable releases, prerelease versions will be skipped
logger.Debug("setting version to >0.0.0") Logger.Debug("setting version to >0.0.0")
o.version = ">0.0.0" o.version = ">0.0.0"
} }
} }
@ -189,7 +189,7 @@ func (o *searchRepoOptions) buildIndex() (*search.Index, error) {
f := filepath.Join(o.repoCacheDir, helmpath.CacheIndexFile(n)) f := filepath.Join(o.repoCacheDir, helmpath.CacheIndexFile(n))
ind, err := repo.LoadIndexFile(f) ind, err := repo.LoadIndexFile(f)
if err != nil { if err != nil {
logger.Warn("repo is corrupt or missing", "repo", n, "error", err) Logger.Warn("repo is corrupt or missing", "repo", n, "error", err)
continue continue
} }

@ -211,9 +211,9 @@ func addShowFlags(subCmd *cobra.Command, client *action.Show) {
} }
func runShow(args []string, client *action.Show) (string, error) { func runShow(args []string, client *action.Show) (string, error) {
logger.Debug("original chart version", "version", client.Version) Logger.Debug("original chart version", "version", client.Version)
if client.Version == "" && client.Devel { if client.Version == "" && client.Devel {
logger.Debug("setting version to >0.0.0-0") Logger.Debug("setting version to >0.0.0-0")
client.Version = ">0.0.0-0" client.Version = ">0.0.0-0"
} }

@ -173,7 +173,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
if client.Version == "" && client.Devel { if client.Version == "" && client.Devel {
logger.Debug("setting version to >0.0.0-0") Logger.Debug("setting version to >0.0.0-0")
client.Version = ">0.0.0-0" client.Version = ">0.0.0-0"
} }
@ -225,7 +225,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
if ch.Metadata.Deprecated { if ch.Metadata.Deprecated {
logger.Warn("this chart is deprecated") Logger.Warn("this chart is deprecated")
} }
// Create context and prepare the handle of SIGTERM // Create context and prepare the handle of SIGTERM

@ -22,6 +22,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
@ -51,8 +52,6 @@ import (
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"k8s.io/client-go/util/retry" "k8s.io/client-go/util/retry"
cmdutil "k8s.io/kubectl/pkg/cmd/util" cmdutil "k8s.io/kubectl/pkg/cmd/util"
logadapter "helm.sh/helm/v4/internal/log"
) )
// ErrNoObjectsVisited indicates that during a visit operation, no matching objects were found. // ErrNoObjectsVisited indicates that during a visit operation, no matching objects were found.
@ -75,7 +74,7 @@ type Client struct {
// needs. The smaller surface area of the interface means there is a lower // needs. The smaller surface area of the interface means there is a lower
// chance of it changing. // chance of it changing.
Factory Factory Factory Factory
Log logadapter.Logger Log *slog.Logger
// Namespace allows to bypass the kubeconfig file for the choice of the namespace // Namespace allows to bypass the kubeconfig file for the choice of the namespace
Namespace string Namespace string
@ -164,7 +163,7 @@ func New(getter genericclioptions.RESTClientGetter) *Client {
factory := cmdutil.NewFactory(getter) factory := cmdutil.NewFactory(getter)
c := &Client{ c := &Client{
Factory: factory, Factory: factory,
Log: nopLogger, Log: slog.Default(),
} }
return c return c
} }

@ -19,6 +19,7 @@ package kube
import ( import (
"bytes" "bytes"
"io" "io"
"log/slog"
"net/http" "net/http"
"strings" "strings"
"testing" "testing"
@ -34,8 +35,6 @@ import (
"k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest/fake" "k8s.io/client-go/rest/fake"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing" cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
logadapter "helm.sh/helm/v4/internal/log"
) )
var unstructuredSerializer = resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer var unstructuredSerializer = resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer
@ -109,7 +108,7 @@ func newTestClient(t *testing.T) *Client {
return &Client{ return &Client{
Factory: testFactory.WithNamespace("default"), Factory: testFactory.WithNamespace("default"),
Log: logadapter.DefaultLogger, Log: slog.New(slog.NewTextHandler(io.Discard, nil)),
} }
} }

@ -19,6 +19,8 @@ package kube // import "helm.sh/helm/v4/pkg/kube"
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"log/slog"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1" batchv1 "k8s.io/api/batch/v1"
@ -32,7 +34,6 @@ import (
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/kubernetes/scheme"
logadapter "helm.sh/helm/v4/internal/log"
deploymentutil "helm.sh/helm/v4/internal/third_party/k8s.io/kubernetes/deployment/util" deploymentutil "helm.sh/helm/v4/internal/third_party/k8s.io/kubernetes/deployment/util"
) )
@ -58,13 +59,13 @@ func CheckJobs(checkJobs bool) ReadyCheckerOption {
// NewReadyChecker creates a new checker. Passed ReadyCheckerOptions can // NewReadyChecker creates a new checker. Passed ReadyCheckerOptions can
// be used to override defaults. // be used to override defaults.
func NewReadyChecker(cl kubernetes.Interface, logger logadapter.Logger, opts ...ReadyCheckerOption) ReadyChecker { func NewReadyChecker(cl kubernetes.Interface, logger *slog.Logger, opts ...ReadyCheckerOption) ReadyChecker {
c := ReadyChecker{ c := ReadyChecker{
client: cl, client: cl,
log: logger, log: logger,
} }
if c.log == nil { if c.log == nil {
c.log = logadapter.DefaultLogger c.log = slog.New(slog.NewTextHandler(io.Discard, nil))
} }
for _, opt := range opts { for _, opt := range opts {
opt(&c) opt(&c)
@ -75,7 +76,7 @@ func NewReadyChecker(cl kubernetes.Interface, logger logadapter.Logger, opts ...
// ReadyChecker is a type that can check core Kubernetes types for readiness. // ReadyChecker is a type that can check core Kubernetes types for readiness.
type ReadyChecker struct { type ReadyChecker struct {
client kubernetes.Interface client kubernetes.Interface
log logadapter.Logger log *slog.Logger
checkJobs bool checkJobs bool
pausedAsReady bool pausedAsReady bool
} }

@ -17,6 +17,8 @@ package kube // import "helm.sh/helm/v4/pkg/kube"
import ( import (
"context" "context"
"io"
"log/slog"
"testing" "testing"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
@ -30,8 +32,6 @@ import (
"k8s.io/cli-runtime/pkg/resource" "k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
logadapter "helm.sh/helm/v4/internal/log"
) )
const defaultNamespace = metav1.NamespaceDefault const defaultNamespace = metav1.NamespaceDefault
@ -39,7 +39,7 @@ const defaultNamespace = metav1.NamespaceDefault
func Test_ReadyChecker_IsReady_Pod(t *testing.T) { func Test_ReadyChecker_IsReady_Pod(t *testing.T) {
type fields struct { type fields struct {
client kubernetes.Interface client kubernetes.Interface
log logadapter.Logger log *slog.Logger
checkJobs bool checkJobs bool
pausedAsReady bool pausedAsReady bool
} }
@ -59,7 +59,7 @@ func Test_ReadyChecker_IsReady_Pod(t *testing.T) {
name: "IsReady Pod", name: "IsReady Pod",
fields: fields{ fields: fields{
client: fake.NewClientset(), client: fake.NewClientset(),
log: func(string, ...interface{}) {}, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
checkJobs: true, checkJobs: true,
pausedAsReady: false, pausedAsReady: false,
}, },
@ -75,7 +75,7 @@ func Test_ReadyChecker_IsReady_Pod(t *testing.T) {
name: "IsReady Pod returns error", name: "IsReady Pod returns error",
fields: fields{ fields: fields{
client: fake.NewClientset(), client: fake.NewClientset(),
log: func(string, ...interface{}) {}, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
checkJobs: true, checkJobs: true,
pausedAsReady: false, pausedAsReady: false,
}, },
@ -115,7 +115,7 @@ func Test_ReadyChecker_IsReady_Pod(t *testing.T) {
func Test_ReadyChecker_IsReady_Job(t *testing.T) { func Test_ReadyChecker_IsReady_Job(t *testing.T) {
type fields struct { type fields struct {
client kubernetes.Interface client kubernetes.Interface
log logadapter.Logger log *slog.Logger
checkJobs bool checkJobs bool
pausedAsReady bool pausedAsReady bool
} }
@ -135,7 +135,7 @@ func Test_ReadyChecker_IsReady_Job(t *testing.T) {
name: "IsReady Job error while getting job", name: "IsReady Job error while getting job",
fields: fields{ fields: fields{
client: fake.NewClientset(), client: fake.NewClientset(),
log: func(string, ...interface{}) {}, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
checkJobs: true, checkJobs: true,
pausedAsReady: false, pausedAsReady: false,
}, },
@ -151,7 +151,7 @@ func Test_ReadyChecker_IsReady_Job(t *testing.T) {
name: "IsReady Job", name: "IsReady Job",
fields: fields{ fields: fields{
client: fake.NewClientset(), client: fake.NewClientset(),
log: func(string, ...interface{}) {}, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
checkJobs: true, checkJobs: true,
pausedAsReady: false, pausedAsReady: false,
}, },
@ -190,7 +190,7 @@ func Test_ReadyChecker_IsReady_Job(t *testing.T) {
func Test_ReadyChecker_IsReady_Deployment(t *testing.T) { func Test_ReadyChecker_IsReady_Deployment(t *testing.T) {
type fields struct { type fields struct {
client kubernetes.Interface client kubernetes.Interface
log logadapter.Logger log *slog.Logger
checkJobs bool checkJobs bool
pausedAsReady bool pausedAsReady bool
} }
@ -211,7 +211,7 @@ func Test_ReadyChecker_IsReady_Deployment(t *testing.T) {
name: "IsReady Deployments error while getting current Deployment", name: "IsReady Deployments error while getting current Deployment",
fields: fields{ fields: fields{
client: fake.NewClientset(), client: fake.NewClientset(),
log: func(string, ...interface{}) {}, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
checkJobs: true, checkJobs: true,
pausedAsReady: false, pausedAsReady: false,
}, },
@ -228,7 +228,7 @@ func Test_ReadyChecker_IsReady_Deployment(t *testing.T) {
name: "IsReady Deployments", //TODO fix this one name: "IsReady Deployments", //TODO fix this one
fields: fields{ fields: fields{
client: fake.NewClientset(), client: fake.NewClientset(),
log: func(string, ...interface{}) {}, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
checkJobs: true, checkJobs: true,
pausedAsReady: false, pausedAsReady: false,
}, },
@ -272,7 +272,7 @@ func Test_ReadyChecker_IsReady_Deployment(t *testing.T) {
func Test_ReadyChecker_IsReady_PersistentVolumeClaim(t *testing.T) { func Test_ReadyChecker_IsReady_PersistentVolumeClaim(t *testing.T) {
type fields struct { type fields struct {
client kubernetes.Interface client kubernetes.Interface
log logadapter.Logger log *slog.Logger
checkJobs bool checkJobs bool
pausedAsReady bool pausedAsReady bool
} }
@ -292,7 +292,7 @@ func Test_ReadyChecker_IsReady_PersistentVolumeClaim(t *testing.T) {
name: "IsReady PersistentVolumeClaim", name: "IsReady PersistentVolumeClaim",
fields: fields{ fields: fields{
client: fake.NewClientset(), client: fake.NewClientset(),
log: func(string, ...interface{}) {}, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
checkJobs: true, checkJobs: true,
pausedAsReady: false, pausedAsReady: false,
}, },
@ -308,7 +308,7 @@ func Test_ReadyChecker_IsReady_PersistentVolumeClaim(t *testing.T) {
name: "IsReady PersistentVolumeClaim with error", name: "IsReady PersistentVolumeClaim with error",
fields: fields{ fields: fields{
client: fake.NewClientset(), client: fake.NewClientset(),
log: func(string, ...interface{}) {}, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
checkJobs: true, checkJobs: true,
pausedAsReady: false, pausedAsReady: false,
}, },
@ -347,7 +347,7 @@ func Test_ReadyChecker_IsReady_PersistentVolumeClaim(t *testing.T) {
func Test_ReadyChecker_IsReady_Service(t *testing.T) { func Test_ReadyChecker_IsReady_Service(t *testing.T) {
type fields struct { type fields struct {
client kubernetes.Interface client kubernetes.Interface
log logadapter.Logger log *slog.Logger
checkJobs bool checkJobs bool
pausedAsReady bool pausedAsReady bool
} }
@ -367,7 +367,7 @@ func Test_ReadyChecker_IsReady_Service(t *testing.T) {
name: "IsReady Service", name: "IsReady Service",
fields: fields{ fields: fields{
client: fake.NewClientset(), client: fake.NewClientset(),
log: func(string, ...interface{}) {}, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
checkJobs: true, checkJobs: true,
pausedAsReady: false, pausedAsReady: false,
}, },
@ -383,7 +383,7 @@ func Test_ReadyChecker_IsReady_Service(t *testing.T) {
name: "IsReady Service with error", name: "IsReady Service with error",
fields: fields{ fields: fields{
client: fake.NewClientset(), client: fake.NewClientset(),
log: func(string, ...interface{}) {}, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
checkJobs: true, checkJobs: true,
pausedAsReady: false, pausedAsReady: false,
}, },
@ -422,7 +422,7 @@ func Test_ReadyChecker_IsReady_Service(t *testing.T) {
func Test_ReadyChecker_IsReady_DaemonSet(t *testing.T) { func Test_ReadyChecker_IsReady_DaemonSet(t *testing.T) {
type fields struct { type fields struct {
client kubernetes.Interface client kubernetes.Interface
log logadapter.Logger log *slog.Logger
checkJobs bool checkJobs bool
pausedAsReady bool pausedAsReady bool
} }
@ -442,7 +442,7 @@ func Test_ReadyChecker_IsReady_DaemonSet(t *testing.T) {
name: "IsReady DaemonSet", name: "IsReady DaemonSet",
fields: fields{ fields: fields{
client: fake.NewClientset(), client: fake.NewClientset(),
log: func(string, ...interface{}) {}, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
checkJobs: true, checkJobs: true,
pausedAsReady: false, pausedAsReady: false,
}, },
@ -458,7 +458,7 @@ func Test_ReadyChecker_IsReady_DaemonSet(t *testing.T) {
name: "IsReady DaemonSet with error", name: "IsReady DaemonSet with error",
fields: fields{ fields: fields{
client: fake.NewClientset(), client: fake.NewClientset(),
log: func(string, ...interface{}) {}, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
checkJobs: true, checkJobs: true,
pausedAsReady: false, pausedAsReady: false,
}, },
@ -497,7 +497,7 @@ func Test_ReadyChecker_IsReady_DaemonSet(t *testing.T) {
func Test_ReadyChecker_IsReady_StatefulSet(t *testing.T) { func Test_ReadyChecker_IsReady_StatefulSet(t *testing.T) {
type fields struct { type fields struct {
client kubernetes.Interface client kubernetes.Interface
log logadapter.Logger log *slog.Logger
checkJobs bool checkJobs bool
pausedAsReady bool pausedAsReady bool
} }
@ -517,7 +517,7 @@ func Test_ReadyChecker_IsReady_StatefulSet(t *testing.T) {
name: "IsReady StatefulSet", name: "IsReady StatefulSet",
fields: fields{ fields: fields{
client: fake.NewClientset(), client: fake.NewClientset(),
log: func(string, ...interface{}) {}, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
checkJobs: true, checkJobs: true,
pausedAsReady: false, pausedAsReady: false,
}, },
@ -533,7 +533,7 @@ func Test_ReadyChecker_IsReady_StatefulSet(t *testing.T) {
name: "IsReady StatefulSet with error", name: "IsReady StatefulSet with error",
fields: fields{ fields: fields{
client: fake.NewClientset(), client: fake.NewClientset(),
log: func(string, ...interface{}) {}, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
checkJobs: true, checkJobs: true,
pausedAsReady: false, pausedAsReady: false,
}, },
@ -572,7 +572,7 @@ func Test_ReadyChecker_IsReady_StatefulSet(t *testing.T) {
func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) { func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) {
type fields struct { type fields struct {
client kubernetes.Interface client kubernetes.Interface
log logadapter.Logger log *slog.Logger
checkJobs bool checkJobs bool
pausedAsReady bool pausedAsReady bool
} }
@ -592,7 +592,7 @@ func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) {
name: "IsReady ReplicationController", name: "IsReady ReplicationController",
fields: fields{ fields: fields{
client: fake.NewClientset(), client: fake.NewClientset(),
log: func(string, ...interface{}) {}, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
checkJobs: true, checkJobs: true,
pausedAsReady: false, pausedAsReady: false,
}, },
@ -608,7 +608,7 @@ func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) {
name: "IsReady ReplicationController with error", name: "IsReady ReplicationController with error",
fields: fields{ fields: fields{
client: fake.NewClientset(), client: fake.NewClientset(),
log: func(string, ...interface{}) {}, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
checkJobs: true, checkJobs: true,
pausedAsReady: false, pausedAsReady: false,
}, },
@ -624,7 +624,7 @@ func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) {
name: "IsReady ReplicationController and pods not ready for object", name: "IsReady ReplicationController and pods not ready for object",
fields: fields{ fields: fields{
client: fake.NewClientset(), client: fake.NewClientset(),
log: func(string, ...interface{}) {}, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
checkJobs: true, checkJobs: true,
pausedAsReady: false, pausedAsReady: false,
}, },
@ -663,7 +663,7 @@ func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) {
func Test_ReadyChecker_IsReady_ReplicaSet(t *testing.T) { func Test_ReadyChecker_IsReady_ReplicaSet(t *testing.T) {
type fields struct { type fields struct {
client kubernetes.Interface client kubernetes.Interface
log logadapter.Logger log *slog.Logger
checkJobs bool checkJobs bool
pausedAsReady bool pausedAsReady bool
} }
@ -683,7 +683,7 @@ func Test_ReadyChecker_IsReady_ReplicaSet(t *testing.T) {
name: "IsReady ReplicaSet", name: "IsReady ReplicaSet",
fields: fields{ fields: fields{
client: fake.NewClientset(), client: fake.NewClientset(),
log: func(string, ...interface{}) {}, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
checkJobs: true, checkJobs: true,
pausedAsReady: false, pausedAsReady: false,
}, },
@ -699,7 +699,7 @@ func Test_ReadyChecker_IsReady_ReplicaSet(t *testing.T) {
name: "IsReady ReplicaSet not ready", name: "IsReady ReplicaSet not ready",
fields: fields{ fields: fields{
client: fake.NewClientset(), client: fake.NewClientset(),
log: func(string, ...interface{}) {}, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
checkJobs: true, checkJobs: true,
pausedAsReady: false, pausedAsReady: false,
}, },

@ -20,6 +20,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"log/slog"
"sort" "sort"
"time" "time"
@ -42,7 +43,7 @@ import (
type statusWaiter struct { type statusWaiter struct {
client dynamic.Interface client dynamic.Interface
restMapper meta.RESTMapper restMapper meta.RESTMapper
log func(string, ...interface{}) log *slog.Logger
} }
func alwaysReady(_ *unstructured.Unstructured) (*status.Result, error) { func alwaysReady(_ *unstructured.Unstructured) (*status.Result, error) {
@ -55,7 +56,7 @@ func alwaysReady(_ *unstructured.Unstructured) (*status.Result, error) {
func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.Duration) error { func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.Duration) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel() defer cancel()
w.log("waiting for %d pods and jobs to complete with a timeout of %s", len(resourceList), timeout) w.log.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout)
sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper)
jobSR := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) jobSR := helmStatusReaders.NewCustomJobStatusReader(w.restMapper)
podSR := helmStatusReaders.NewCustomPodStatusReader(w.restMapper) podSR := helmStatusReaders.NewCustomPodStatusReader(w.restMapper)
@ -76,7 +77,7 @@ func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.D
func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) error { func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) error {
ctx, cancel := context.WithTimeout(context.TODO(), timeout) ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel() defer cancel()
w.log("beginning wait for %d resources with timeout of %s", len(resourceList), timeout) w.log.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout)
sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper)
return w.wait(ctx, resourceList, sw) return w.wait(ctx, resourceList, sw)
} }
@ -84,7 +85,7 @@ func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) er
func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Duration) error { func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Duration) error {
ctx, cancel := context.WithTimeout(context.TODO(), timeout) ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel() defer cancel()
w.log("beginning wait for %d resources with timeout of %s", len(resourceList), timeout) w.log.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout)
sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper)
newCustomJobStatusReader := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) newCustomJobStatusReader := helmStatusReaders.NewCustomJobStatusReader(w.restMapper)
customSR := statusreaders.NewStatusReader(w.restMapper, newCustomJobStatusReader) customSR := statusreaders.NewStatusReader(w.restMapper, newCustomJobStatusReader)
@ -95,7 +96,7 @@ func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Dura
func (w *statusWaiter) WaitForDelete(resourceList ResourceList, timeout time.Duration) error { func (w *statusWaiter) WaitForDelete(resourceList ResourceList, timeout time.Duration) error {
ctx, cancel := context.WithTimeout(context.TODO(), timeout) ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel() defer cancel()
w.log("beginning wait for %d resources to be deleted with timeout of %s", len(resourceList), timeout) w.log.Debug("waiting for resources to be deleted", "count", len(resourceList), "timeout", timeout)
sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper)
return w.waitForDelete(ctx, resourceList, sw) return w.waitForDelete(ctx, resourceList, sw)
} }
@ -179,7 +180,7 @@ func (w *statusWaiter) wait(ctx context.Context, resourceList ResourceList, sw w
return nil return nil
} }
func statusObserver(cancel context.CancelFunc, desired status.Status, logFn func(string, ...interface{})) collector.ObserverFunc { func statusObserver(cancel context.CancelFunc, desired status.Status, logger *slog.Logger) collector.ObserverFunc {
return func(statusCollector *collector.ResourceStatusCollector, _ event.Event) { return func(statusCollector *collector.ResourceStatusCollector, _ event.Event) {
var rss []*event.ResourceStatus var rss []*event.ResourceStatus
var nonDesiredResources []*event.ResourceStatus var nonDesiredResources []*event.ResourceStatus
@ -209,8 +210,7 @@ func statusObserver(cancel context.CancelFunc, desired status.Status, logFn func
return nonDesiredResources[i].Identifier.Name < nonDesiredResources[j].Identifier.Name return nonDesiredResources[i].Identifier.Name < nonDesiredResources[j].Identifier.Name
}) })
first := nonDesiredResources[0] first := nonDesiredResources[0]
logFn("waiting for resource: name: %s, kind: %s, desired status: %s, actual status: %s \n", logger.Debug("waiting for resource", "name", first.Identifier.Name, "kind", first.Identifier.GroupKind.Kind, "expectedStatus", desired, "actualStatus", first.Status)
first.Identifier.Name, first.Identifier.GroupKind.Kind, desired, first.Status)
} }
} }
} }

@ -18,6 +18,8 @@ package kube // import "helm.sh/helm/v3/pkg/kube"
import ( import (
"errors" "errors"
"io"
"log/slog"
"testing" "testing"
"time" "time"
@ -217,7 +219,7 @@ func TestStatusWaitForDelete(t *testing.T) {
statusWaiter := statusWaiter{ statusWaiter := statusWaiter{
restMapper: fakeMapper, restMapper: fakeMapper,
client: fakeClient, client: fakeClient,
log: t.Logf, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
} }
objsToCreate := getRuntimeObjFromManifests(t, tt.manifestsToCreate) objsToCreate := getRuntimeObjFromManifests(t, tt.manifestsToCreate)
for _, objToCreate := range objsToCreate { for _, objToCreate := range objsToCreate {
@ -258,7 +260,7 @@ func TestStatusWaitForDeleteNonExistentObject(t *testing.T) {
statusWaiter := statusWaiter{ statusWaiter := statusWaiter{
restMapper: fakeMapper, restMapper: fakeMapper,
client: fakeClient, client: fakeClient,
log: t.Logf, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
} }
// Don't create the object to test that the wait for delete works when the object doesn't exist // Don't create the object to test that the wait for delete works when the object doesn't exist
objManifest := getRuntimeObjFromManifests(t, []string{podCurrentManifest}) objManifest := getRuntimeObjFromManifests(t, []string{podCurrentManifest})
@ -317,7 +319,7 @@ func TestStatusWait(t *testing.T) {
statusWaiter := statusWaiter{ statusWaiter := statusWaiter{
client: fakeClient, client: fakeClient,
restMapper: fakeMapper, restMapper: fakeMapper,
log: t.Logf, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
} }
objs := getRuntimeObjFromManifests(t, tt.objManifests) objs := getRuntimeObjFromManifests(t, tt.objManifests)
for _, obj := range objs { for _, obj := range objs {
@ -371,7 +373,7 @@ func TestWaitForJobComplete(t *testing.T) {
statusWaiter := statusWaiter{ statusWaiter := statusWaiter{
client: fakeClient, client: fakeClient,
restMapper: fakeMapper, restMapper: fakeMapper,
log: t.Logf, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
} }
objs := getRuntimeObjFromManifests(t, tt.objManifests) objs := getRuntimeObjFromManifests(t, tt.objManifests)
for _, obj := range objs { for _, obj := range objs {
@ -431,7 +433,7 @@ func TestWatchForReady(t *testing.T) {
statusWaiter := statusWaiter{ statusWaiter := statusWaiter{
client: fakeClient, client: fakeClient,
restMapper: fakeMapper, restMapper: fakeMapper,
log: t.Logf, log: slog.New(slog.NewTextHandler(io.Discard, nil)),
} }
objs := getRuntimeObjFromManifests(t, tt.objManifests) objs := getRuntimeObjFromManifests(t, tt.objManifests)
for _, obj := range objs { for _, obj := range objs {

@ -44,15 +44,13 @@ import (
watchtools "k8s.io/client-go/tools/watch" watchtools "k8s.io/client-go/tools/watch"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
logadapter "helm.sh/helm/v4/internal/log"
) )
// legacyWaiter is the legacy implementation of the Waiter interface. This logic was used by default in Helm 3 // legacyWaiter is the legacy implementation of the Waiter interface. This logic was used by default in Helm 3
// Helm 4 now uses the StatusWaiter implementation instead // Helm 4 now uses the StatusWaiter implementation instead
type legacyWaiter struct { type legacyWaiter struct {
c ReadyChecker c ReadyChecker
log func(string, ...interface{}) log *slog.Logger
kubeClient *kubernetes.Clientset kubeClient *kubernetes.Clientset
} }
@ -69,7 +67,7 @@ func (hw *legacyWaiter) WaitWithJobs(resources ResourceList, timeout time.Durati
// waitForResources polls to get the current status of all pods, PVCs, Services and // waitForResources polls to get the current status of all pods, PVCs, Services and
// Jobs(optional) until all are ready or a timeout is reached // Jobs(optional) until all are ready or a timeout is reached
func (hw *legacyWaiter) waitForResources(created ResourceList, timeout time.Duration) error { func (hw *legacyWaiter) waitForResources(created ResourceList, timeout time.Duration) error {
hw.log("beginning wait for %d resources with timeout of %v", len(created), timeout) hw.log.Debug("beginning wait for resources", "count", len(created), "timeout", timeout)
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel() defer cancel()
@ -87,10 +85,10 @@ func (hw *legacyWaiter) waitForResources(created ResourceList, timeout time.Dura
if waitRetries > 0 && hw.isRetryableError(err, v) { if waitRetries > 0 && hw.isRetryableError(err, v) {
numberOfErrors[i]++ numberOfErrors[i]++
if numberOfErrors[i] > waitRetries { if numberOfErrors[i] > waitRetries {
hw.log("Max number of retries reached") hw.log.Debug("max number of retries reached", "resource", v.Name, "retries", numberOfErrors[i])
return false, err return false, err
} }
hw.log("Retrying as current number of retries %d less than max number of retries %d", numberOfErrors[i]-1, waitRetries) hw.log.Debug("retrying resource readiness", "resource", v.Name, "currentRetries", numberOfErrors[i]-1, "maxRetries", waitRetries)
return false, nil return false, nil
} }
numberOfErrors[i] = 0 numberOfErrors[i] = 0
@ -106,14 +104,14 @@ func (hw *legacyWaiter) isRetryableError(err error, resource *resource.Info) boo
if err == nil { if err == nil {
return false return false
} }
hw.log("Error received when checking status of resource %s. Error: '%s', Resource details: '%s'", resource.Name, err, resource) hw.log.Debug("error received when checking resource status", "resource", resource.Name, "error", err)
if ev, ok := err.(*apierrors.StatusError); ok { if ev, ok := err.(*apierrors.StatusError); ok {
statusCode := ev.Status().Code statusCode := ev.Status().Code
retryable := hw.isRetryableHTTPStatusCode(statusCode) retryable := hw.isRetryableHTTPStatusCode(statusCode)
hw.log("Status code received: %d. Retryable error? %t", statusCode, retryable) hw.log.Debug("status code received", "resource", resource.Name, "statusCode", statusCode, "retryable", retryable)
return retryable return retryable
} }
hw.log("Retryable error? %t", true) hw.log.Debug("retryable error assumed", "resource", resource.Name)
return true return true
} }
@ -123,7 +121,7 @@ func (hw *legacyWaiter) isRetryableHTTPStatusCode(httpStatusCode int32) bool {
// waitForDeletedResources polls to check if all the resources are deleted or a timeout is reached // waitForDeletedResources polls to check if all the resources are deleted or a timeout is reached
func (hw *legacyWaiter) WaitForDelete(deleted ResourceList, timeout time.Duration) error { func (hw *legacyWaiter) WaitForDelete(deleted ResourceList, timeout time.Duration) error {
slog.Debug("beginning wait for resources to be deleted", "count", len(deleted), "timeout", timeout) hw.log.Debug("beginning wait for resources to be deleted", "count", len(deleted), "timeout", timeout)
startTime := time.Now() startTime := time.Now()
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), timeout)
@ -141,9 +139,9 @@ func (hw *legacyWaiter) WaitForDelete(deleted ResourceList, timeout time.Duratio
elapsed := time.Since(startTime).Round(time.Second) elapsed := time.Since(startTime).Round(time.Second)
if err != nil { if err != nil {
slog.Debug("wait for resources failed", "elapsed", elapsed, slog.Any("error", err)) hw.log.Debug("wait for resources failed", "elapsed", elapsed, "error", err)
} else { } else {
slog.Debug("wait for resources succeeded", "elapsed", elapsed) hw.log.Debug("wait for resources succeeded", "elapsed", elapsed)
} }
return err return err
@ -251,7 +249,7 @@ func (hw *legacyWaiter) watchUntilReady(timeout time.Duration, info *resource.In
return nil return nil
} }
hw.log("Watching for changes to %s %s with timeout of %v", kind, info.Name, timeout) hw.log.Debug("watching for resource changes", "kind", kind, "resource", info.Name, "timeout", timeout)
// Use a selector on the name of the resource. This should be unique for the // Use a selector on the name of the resource. This should be unique for the
// given version and kind // given version and kind
@ -279,7 +277,8 @@ func (hw *legacyWaiter) watchUntilReady(timeout time.Duration, info *resource.In
// we get. We care mostly about jobs, where what we want to see is // we get. We care mostly about jobs, where what we want to see is
// the status go into a good state. For other types, like ReplicaSet // the status go into a good state. For other types, like ReplicaSet
// we don't really do anything to support these as hooks. // we don't really do anything to support these as hooks.
hw.log("Add/Modify event for %s: %v", info.Name, e.Type) hw.log.Debug("add/modify event received", "resource", info.Name, "eventType", e.Type)
switch kind { switch kind {
case "Job": case "Job":
return hw.waitForJob(obj, info.Name) return hw.waitForJob(obj, info.Name)
@ -288,11 +287,11 @@ func (hw *legacyWaiter) watchUntilReady(timeout time.Duration, info *resource.In
} }
return true, nil return true, nil
case watch.Deleted: case watch.Deleted:
hw.log("Deleted event for %s", info.Name) hw.log.Debug("deleted event received", "resource", info.Name)
return true, nil return true, nil
case watch.Error: case watch.Error:
// Handle error and return with an error. // Handle error and return with an error.
hw.log("Error event for %s", info.Name) hw.log.Error("error event received", "resource", info.Name)
return true, errors.Errorf("failed to deploy %s", info.Name) return true, errors.Errorf("failed to deploy %s", info.Name)
default: default:
return false, nil return false, nil
@ -314,11 +313,12 @@ func (hw *legacyWaiter) waitForJob(obj runtime.Object, name string) (bool, error
if c.Type == batchv1.JobComplete && c.Status == "True" { if c.Type == batchv1.JobComplete && c.Status == "True" {
return true, nil return true, nil
} else if c.Type == batchv1.JobFailed && c.Status == "True" { } else if c.Type == batchv1.JobFailed && c.Status == "True" {
hw.log.Error("job failed", "job", name, "reason", c.Reason)
return true, errors.Errorf("job %s failed: %s", name, c.Reason) return true, errors.Errorf("job %s failed: %s", name, c.Reason)
} }
} }
hw.log("%s: Jobs active: %d, jobs failed: %d, jobs succeeded: %d", name, o.Status.Active, o.Status.Failed, o.Status.Succeeded) hw.log.Debug("job status update", "job", name, "active", o.Status.Active, "failed", o.Status.Failed, "succeeded", o.Status.Succeeded)
return false, nil return false, nil
} }
@ -333,14 +333,15 @@ func (hw *legacyWaiter) waitForPodSuccess(obj runtime.Object, name string) (bool
switch o.Status.Phase { switch o.Status.Phase {
case corev1.PodSucceeded: case corev1.PodSucceeded:
hw.log("Pod %s succeeded", o.Name) hw.log.Debug("pod succeeded", "pod", o.Name)
return true, nil return true, nil
case corev1.PodFailed: case corev1.PodFailed:
hw.log.Error("pod failed", "pod", o.Name)
return true, errors.Errorf("pod %s failed", o.Name) return true, errors.Errorf("pod %s failed", o.Name)
case corev1.PodPending: case corev1.PodPending:
hw.log("Pod %s pending", o.Name) hw.log.Debug("pod pending", "pod", o.Name)
case corev1.PodRunning: case corev1.PodRunning:
hw.log("Pod %s running", o.Name) hw.log.Debug("pod running", "pod", o.Name)
} }
return false, nil return false, nil

@ -19,6 +19,7 @@ package driver // import "helm.sh/helm/v4/pkg/storage/driver"
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -31,7 +32,6 @@ import (
"k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1" corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
logadapter "helm.sh/helm/v4/internal/log"
rspb "helm.sh/helm/v4/pkg/release/v1" rspb "helm.sh/helm/v4/pkg/release/v1"
) )
@ -44,7 +44,7 @@ const ConfigMapsDriverName = "ConfigMap"
// ConfigMapsInterface. // ConfigMapsInterface.
type ConfigMaps struct { type ConfigMaps struct {
impl corev1.ConfigMapInterface impl corev1.ConfigMapInterface
Log logadapter.Logger Log *slog.Logger
} }
// NewConfigMaps initializes a new ConfigMaps wrapping an implementation of // NewConfigMaps initializes a new ConfigMaps wrapping an implementation of

@ -19,6 +19,8 @@ package driver // import "helm.sh/helm/v4/pkg/storage/driver"
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"log/slog"
"testing" "testing"
sqlmock "github.com/DATA-DOG/go-sqlmock" sqlmock "github.com/DATA-DOG/go-sqlmock"
@ -31,7 +33,6 @@ import (
kblabels "k8s.io/apimachinery/pkg/labels" kblabels "k8s.io/apimachinery/pkg/labels"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1" corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
logadapter "helm.sh/helm/v4/internal/log"
rspb "helm.sh/helm/v4/pkg/release/v1" rspb "helm.sh/helm/v4/pkg/release/v1"
) )
@ -265,6 +266,6 @@ func newTestFixtureSQL(t *testing.T, _ ...*rspb.Release) (*SQL, sqlmock.Sqlmock)
db: sqlxDB, db: sqlxDB,
namespace: "default", namespace: "default",
statementBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar), statementBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar),
Log: logadapter.DefaultLogger, Log: slog.New(slog.NewTextHandler(io.Discard, nil)),
}, mock }, mock
} }

@ -19,6 +19,7 @@ package driver // import "helm.sh/helm/v4/pkg/storage/driver"
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -31,7 +32,6 @@ import (
"k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1" corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
logadapter "helm.sh/helm/v4/internal/log"
rspb "helm.sh/helm/v4/pkg/release/v1" rspb "helm.sh/helm/v4/pkg/release/v1"
) )
@ -44,7 +44,7 @@ const SecretsDriverName = "Secret"
// SecretsInterface. // SecretsInterface.
type Secrets struct { type Secrets struct {
impl corev1.SecretInterface impl corev1.SecretInterface
Log logadapter.Logger Log *slog.Logger
} }
// NewSecrets initializes a new Secrets wrapping an implementation of // NewSecrets initializes a new Secrets wrapping an implementation of
@ -96,7 +96,7 @@ func (secrets *Secrets) List(filter func(*rspb.Release) bool) ([]*rspb.Release,
for _, item := range list.Items { for _, item := range list.Items {
rls, err := decodeRelease(string(item.Data["release"])) rls, err := decodeRelease(string(item.Data["release"]))
if err != nil { if err != nil {
secrets.Log.Debug("list: failed to decode release: %v: %s", item, err) secrets.Log.Debug("list failed to decode release", "key", item.Name, "error", err)
continue continue
} }
@ -135,7 +135,7 @@ func (secrets *Secrets) Query(labels map[string]string) ([]*rspb.Release, error)
for _, item := range list.Items { for _, item := range list.Items {
rls, err := decodeRelease(string(item.Data["release"])) rls, err := decodeRelease(string(item.Data["release"]))
if err != nil { if err != nil {
secrets.Log.Debug("query: failed to decode release: %s", err) secrets.Log.Debug("failed to decode release", "key", item.Name, "error", err)
continue continue
} }
rls.Labels = item.ObjectMeta.Labels rls.Labels = item.ObjectMeta.Labels

@ -18,6 +18,7 @@ package driver // import "helm.sh/helm/v4/pkg/storage/driver"
import ( import (
"fmt" "fmt"
"log/slog"
"sort" "sort"
"strconv" "strconv"
"time" "time"
@ -30,7 +31,6 @@ import (
// Import pq for postgres dialect // Import pq for postgres dialect
_ "github.com/lib/pq" _ "github.com/lib/pq"
logadapter "helm.sh/helm/v4/internal/log"
rspb "helm.sh/helm/v4/pkg/release/v1" rspb "helm.sh/helm/v4/pkg/release/v1"
) )
@ -88,7 +88,7 @@ type SQL struct {
namespace string namespace string
statementBuilder sq.StatementBuilderType statementBuilder sq.StatementBuilderType
Log logadapter.Logger Log *slog.Logger
} }
// Name returns the name of the driver. // Name returns the name of the driver.
@ -277,7 +277,7 @@ type SQLReleaseCustomLabelWrapper struct {
} }
// NewSQL initializes a new sql driver. // NewSQL initializes a new sql driver.
func NewSQL(connectionString string, logger logadapter.Logger, namespace string) (*SQL, error) { func NewSQL(connectionString string, logger *slog.Logger, namespace string) (*SQL, error) {
db, err := sqlx.Connect(postgreSQLDialect, connectionString) db, err := sqlx.Connect(postgreSQLDialect, connectionString)
if err != nil { if err != nil {
return nil, err return nil, err

Loading…
Cancel
Save