Merge pull request #31411 from banjoh/em/reinstate-logger-param

feat: reinstate logger parameter to actions package
pull/31440/head
Scott Rigby 11 months ago committed by GitHub
commit f4c5220d99
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -20,6 +20,7 @@ import (
"context" "context"
"log/slog" "log/slog"
"os" "os"
"sync/atomic"
) )
// DebugEnabledFunc is a function type that determines if debug logging is enabled // DebugEnabledFunc is a function type that determines if debug logging is enabled
@ -85,3 +86,37 @@ func NewLogger(debugEnabled DebugEnabledFunc) *slog.Logger {
return slog.New(dynamicHandler) return slog.New(dynamicHandler)
} }
// LoggerSetterGetter is an interface that can set and get a logger
type LoggerSetterGetter interface {
// SetLogger sets a new slog.Handler
SetLogger(newHandler slog.Handler)
// Logger returns the slog.Logger created from the slog.Handler
Logger() *slog.Logger
}
type LogHolder struct {
// logger is an atomic.Pointer[slog.Logger] to store the slog.Logger
// We use atomic.Pointer for thread safety
logger atomic.Pointer[slog.Logger]
}
// Logger returns the logger for the LogHolder. If nil, returns slog.Default().
func (l *LogHolder) Logger() *slog.Logger {
if lg := l.logger.Load(); lg != nil {
return lg
}
return slog.New(slog.DiscardHandler) // Should never be reached
}
// SetLogger sets the logger for the LogHolder. If nil, sets the default logger.
func (l *LogHolder) SetLogger(newHandler slog.Handler) {
if newHandler == nil {
l.logger.Store(slog.New(slog.DiscardHandler)) // Assume nil as discarding logs
return
}
l.logger.Store(slog.New(newHandler))
}
// Ensure LogHolder implements LoggerSetterGetter
var _ LoggerSetterGetter = &LogHolder{}

@ -0,0 +1,115 @@
/*
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 logging
import (
"bytes"
"log/slog"
"testing"
"github.com/stretchr/testify/assert"
)
func TestLogHolder_Logger(t *testing.T) {
t.Run("should return new logger with a then set handler", func(t *testing.T) {
holder := &LogHolder{}
buf := &bytes.Buffer{}
handler := slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug})
holder.SetLogger(handler)
logger := holder.Logger()
assert.NotNil(t, logger)
// Test that the logger works
logger.Info("test message")
assert.Contains(t, buf.String(), "test message")
})
t.Run("should return discard - defaultlogger when no handler is set", func(t *testing.T) {
holder := &LogHolder{}
logger := holder.Logger()
assert.Equal(t, slog.Handler(slog.DiscardHandler), logger.Handler())
})
}
func TestLogHolder_SetLogger(t *testing.T) {
t.Run("sets logger with valid handler", func(t *testing.T) {
holder := &LogHolder{}
buf := &bytes.Buffer{}
handler := slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug})
holder.SetLogger(handler)
logger := holder.Logger()
assert.NotNil(t, logger)
// Compare the handler directly
assert.Equal(t, handler, logger.Handler())
})
t.Run("sets discard logger with nil handler", func(t *testing.T) {
holder := &LogHolder{}
holder.SetLogger(nil)
logger := holder.Logger()
assert.NotNil(t, logger)
assert.Equal(t, slog.Handler(slog.DiscardHandler), logger.Handler())
})
t.Run("can replace existing logger", func(t *testing.T) {
holder := &LogHolder{}
// Set first logger
buf1 := &bytes.Buffer{}
handler1 := slog.NewTextHandler(buf1, &slog.HandlerOptions{Level: slog.LevelDebug})
holder.SetLogger(handler1)
logger1 := holder.Logger()
assert.Equal(t, handler1, logger1.Handler())
// Replace with second logger
buf2 := &bytes.Buffer{}
handler2 := slog.NewTextHandler(buf2, &slog.HandlerOptions{Level: slog.LevelDebug})
holder.SetLogger(handler2)
logger2 := holder.Logger()
assert.Equal(t, handler2, logger2.Handler())
})
}
func TestLogHolder_InterfaceCompliance(t *testing.T) {
t.Run("implements LoggerSetterGetter interface", func(_ *testing.T) {
var _ LoggerSetterGetter = &LogHolder{}
})
t.Run("interface methods work correctly", func(t *testing.T) {
var holder LoggerSetterGetter = &LogHolder{}
buf := &bytes.Buffer{}
handler := slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug})
holder.SetLogger(handler)
logger := holder.Logger()
assert.NotNil(t, logger)
assert.Equal(t, handler, logger.Handler())
})
}

@ -40,6 +40,7 @@ import (
"sigs.k8s.io/kustomize/kyaml/kio" "sigs.k8s.io/kustomize/kyaml/kio"
kyaml "sigs.k8s.io/kustomize/kyaml/yaml" kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
"helm.sh/helm/v4/internal/logging"
"helm.sh/helm/v4/pkg/chart/common" "helm.sh/helm/v4/pkg/chart/common"
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"
@ -109,7 +110,32 @@ type Configuration struct {
// 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
// Mutex is an exclusive lock for concurrent access to the action
mutex sync.Mutex mutex sync.Mutex
// Embed a LogHolder to provide logger functionality
logging.LogHolder
}
type ConfigurationOption func(c *Configuration)
// Override the default logging handler
// If unspecified, the default logger will be used
func ConfigurationSetLogger(h slog.Handler) ConfigurationOption {
return func(c *Configuration) {
c.SetLogger(h)
}
}
func NewConfiguration(options ...ConfigurationOption) *Configuration {
c := &Configuration{}
c.SetLogger(slog.Default().Handler())
for _, o := range options {
o(c)
}
return c
} }
const ( const (
@ -376,8 +402,8 @@ func (cfg *Configuration) getCapabilities() (*common.Capabilities, error) {
apiVersions, err := GetVersionSet(dc) apiVersions, err := GetVersionSet(dc)
if err != nil { if err != nil {
if discovery.IsGroupDiscoveryFailedError(err) { if discovery.IsGroupDiscoveryFailedError(err) {
slog.Warn("the kubernetes server has an orphaned API service", slog.Any("error", err)) cfg.Logger().Warn("the kubernetes server has an orphaned API service", slog.Any("error", err))
slog.Warn("to fix this, kubectl delete apiservice <service-name>") cfg.Logger().Warn("to fix this, kubectl delete apiservice <service-name>")
} else { } else {
return nil, fmt.Errorf("could not get apiVersions from Kubernetes: %w", err) return nil, fmt.Errorf("could not get apiVersions from Kubernetes: %w", err)
} }
@ -476,13 +502,14 @@ func GetVersionSet(client discovery.ServerResourcesInterface) (common.VersionSet
// recordRelease with an update operation in case reuse has been set. // recordRelease with an update operation in case reuse has been set.
func (cfg *Configuration) recordRelease(r *release.Release) { func (cfg *Configuration) recordRelease(r *release.Release) {
if err := cfg.Releases.Update(r); err != nil { if err := cfg.Releases.Update(r); err != nil {
slog.Warn("failed to update release", "name", r.Name, "revision", r.Version, slog.Any("error", err)) cfg.Logger().Warn("failed to update release", "name", r.Name, "revision", r.Version, slog.Any("error", err))
} }
} }
// Init initializes the action configuration // Init initializes the action configuration
func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string) error { func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string) error {
kc := kube.New(getter) kc := kube.New(getter)
kc.SetLogger(cfg.Logger().Handler())
lazyClient := &lazyClient{ lazyClient := &lazyClient{
namespace: namespace, namespace: namespace,
@ -493,9 +520,11 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp
switch helmDriver { switch helmDriver {
case "secret", "secrets", "": case "secret", "secrets", "":
d := driver.NewSecrets(newSecretClient(lazyClient)) d := driver.NewSecrets(newSecretClient(lazyClient))
d.SetLogger(cfg.Logger().Handler())
store = storage.Init(d) store = storage.Init(d)
case "configmap", "configmaps": case "configmap", "configmaps":
d := driver.NewConfigMaps(newConfigMapClient(lazyClient)) d := driver.NewConfigMaps(newConfigMapClient(lazyClient))
d.SetLogger(cfg.Logger().Handler())
store = storage.Init(d) store = storage.Init(d)
case "memory": case "memory":
var d *driver.Memory var d *driver.Memory
@ -510,6 +539,7 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp
if d == nil { if d == nil {
d = driver.NewMemory() d = driver.NewMemory()
} }
d.SetLogger(cfg.Logger().Handler())
d.SetNamespace(namespace) d.SetNamespace(namespace)
store = storage.Init(d) store = storage.Init(d)
case "sql": case "sql":
@ -520,6 +550,7 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp
if err != nil { if err != nil {
return fmt.Errorf("unable to instantiate SQL driver: %w", err) return fmt.Errorf("unable to instantiate SQL driver: %w", err)
} }
d.SetLogger(cfg.Logger().Handler())
store = storage.Init(d) store = storage.Init(d)
default: default:
return fmt.Errorf("unknown driver %q", helmDriver) return fmt.Errorf("unknown driver %q", helmDriver)

@ -344,7 +344,7 @@ func TestConfiguration_Init(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
cfg := &Configuration{} cfg := NewConfiguration()
actualErr := cfg.Init(nil, "default", tt.helmDriver) actualErr := cfg.Init(nil, "default", tt.helmDriver)
if tt.expectErr { if tt.expectErr {

@ -17,8 +17,6 @@ limitations under the License.
package action package action
import ( import (
"log/slog"
"fmt" "fmt"
chartutil "helm.sh/helm/v4/pkg/chart/v2/util" chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
@ -55,6 +53,6 @@ func (h *History) Run(name string) ([]release.Releaser, error) {
return nil, fmt.Errorf("release name is invalid: %s", name) return nil, fmt.Errorf("release name is invalid: %s", name)
} }
slog.Debug("getting history for release", "release", name) h.cfg.Logger().Debug("getting history for release", "release", name)
return h.cfg.Releases.History(name) return h.cfg.Releases.History(name)
} }

@ -193,7 +193,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error {
// If the error is CRD already exists, continue. // If the error is CRD already exists, continue.
if apierrors.IsAlreadyExists(err) { if apierrors.IsAlreadyExists(err) {
crdName := res[0].Name crdName := res[0].Name
slog.Debug("CRD is already present. Skipping", "crd", crdName) i.cfg.Logger().Debug("CRD is already present. Skipping", "crd", crdName)
continue continue
} }
return fmt.Errorf("failed to install CRD %s: %w", obj.Name, err) return fmt.Errorf("failed to install CRD %s: %w", obj.Name, err)
@ -221,7 +221,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error {
return err return err
} }
slog.Debug("clearing discovery cache") i.cfg.Logger().Debug("clearing discovery cache")
discoveryClient.Invalidate() discoveryClient.Invalidate()
_, _ = discoveryClient.ServerGroups() _, _ = discoveryClient.ServerGroups()
@ -234,7 +234,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error {
return err return err
} }
if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok { if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok {
slog.Debug("clearing REST mapper cache") i.cfg.Logger().Debug("clearing REST mapper cache")
resettable.Reset() resettable.Reset()
} }
} }
@ -267,24 +267,24 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
if interactWithServer(i.DryRunStrategy) { if interactWithServer(i.DryRunStrategy) {
if err := i.cfg.KubeClient.IsReachable(); err != nil { if err := i.cfg.KubeClient.IsReachable(); err != nil {
slog.Error(fmt.Sprintf("cluster reachability check failed: %v", err)) i.cfg.Logger().Error(fmt.Sprintf("cluster reachability check failed: %v", err))
return nil, fmt.Errorf("cluster reachability check failed: %w", err) return nil, fmt.Errorf("cluster reachability check failed: %w", err)
} }
} }
// HideSecret must be used with dry run. Otherwise, return an error. // HideSecret must be used with dry run. Otherwise, return an error.
if !isDryRun(i.DryRunStrategy) && i.HideSecret { if !isDryRun(i.DryRunStrategy) && i.HideSecret {
slog.Error("hiding Kubernetes secrets requires a dry-run mode") i.cfg.Logger().Error("hiding Kubernetes secrets requires a dry-run mode")
return nil, errors.New("hiding Kubernetes secrets requires a dry-run mode") return nil, errors.New("hiding Kubernetes secrets requires a dry-run mode")
} }
if err := i.availableName(); err != nil { if err := i.availableName(); err != nil {
slog.Error("release name check failed", slog.Any("error", err)) i.cfg.Logger().Error("release name check failed", slog.Any("error", err))
return nil, fmt.Errorf("release name check failed: %w", err) return nil, fmt.Errorf("release name check failed: %w", err)
} }
if err := chartutil.ProcessDependencies(chrt, vals); err != nil { if err := chartutil.ProcessDependencies(chrt, vals); err != nil {
slog.Error("chart dependencies processing failed", slog.Any("error", err)) i.cfg.Logger().Error("chart dependencies processing failed", slog.Any("error", err))
return nil, fmt.Errorf("chart dependencies processing failed: %w", err) return nil, fmt.Errorf("chart dependencies processing failed: %w", err)
} }
@ -293,7 +293,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
if crds := chrt.CRDObjects(); interactWithServer(i.DryRunStrategy) && !i.SkipCRDs && len(crds) > 0 { if crds := chrt.CRDObjects(); interactWithServer(i.DryRunStrategy) && !i.SkipCRDs && len(crds) > 0 {
// On dry run, bail here // On dry run, bail here
if isDryRun(i.DryRunStrategy) { if isDryRun(i.DryRunStrategy) {
slog.Warn("This chart or one of its subcharts contains CRDs. Rendering may fail or contain inaccuracies.") i.cfg.Logger().Warn("This chart or one of its subcharts contains CRDs. Rendering may fail or contain inaccuracies.")
} else if err := i.installCRDs(crds); err != nil { } else if err := i.installCRDs(crds); err != nil {
return nil, err return nil, err
} }
@ -313,7 +313,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
mem.SetNamespace(i.Namespace) mem.SetNamespace(i.Namespace)
i.cfg.Releases = storage.Init(mem) i.cfg.Releases = storage.Init(mem)
} else if interactWithServer(i.DryRunStrategy) && len(i.APIVersions) > 0 { } else if interactWithServer(i.DryRunStrategy) && len(i.APIVersions) > 0 {
slog.Debug("API Version list given outside of client only mode, this list will be ignored") i.cfg.Logger().Debug("API Version list given outside of client only mode, this list will be ignored")
} }
// Make sure if RollbackOnFailure is set, that wait is set as well. This makes it so // Make sure if RollbackOnFailure is set, that wait is set as well. This makes it so
@ -539,7 +539,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource
// One possible strategy would be to do a timed retry to see if we can get // One possible strategy would be to do a timed retry to see if we can get
// this stored in the future. // this stored in the future.
if err := i.recordRelease(rel); err != nil { if err := i.recordRelease(rel); err != nil {
slog.Error("failed to record the release", slog.Any("error", err)) i.cfg.Logger().Error("failed to record the release", slog.Any("error", err))
} }
return rel, nil return rel, nil
@ -548,7 +548,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource
func (i *Install) failRelease(rel *release.Release, err error) (*release.Release, error) { func (i *Install) failRelease(rel *release.Release, err error) (*release.Release, error) {
rel.SetStatus(rcommon.StatusFailed, fmt.Sprintf("Release %q failed: %s", i.ReleaseName, err.Error())) rel.SetStatus(rcommon.StatusFailed, fmt.Sprintf("Release %q failed: %s", i.ReleaseName, err.Error()))
if i.RollbackOnFailure { if i.RollbackOnFailure {
slog.Debug("install failed and rollback-on-failure is set, uninstalling release", "release", i.ReleaseName) i.cfg.Logger().Debug("install failed and rollback-on-failure is set, uninstalling release", "release", i.ReleaseName)
uninstall := NewUninstall(i.cfg) uninstall := NewUninstall(i.cfg)
uninstall.DisableHooks = i.DisableHooks uninstall.DisableHooks = i.DisableHooks
uninstall.KeepHistory = false uninstall.KeepHistory = false

@ -19,7 +19,6 @@ package action
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"log/slog"
"strings" "strings"
"time" "time"
@ -76,26 +75,26 @@ func (r *Rollback) Run(name string) error {
r.cfg.Releases.MaxHistory = r.MaxHistory r.cfg.Releases.MaxHistory = r.MaxHistory
slog.Debug("preparing rollback", "name", name) r.cfg.Logger().Debug("preparing rollback", "name", name)
currentRelease, targetRelease, serverSideApply, err := r.prepareRollback(name) currentRelease, targetRelease, serverSideApply, err := r.prepareRollback(name)
if err != nil { if err != nil {
return err return err
} }
if !isDryRun(r.DryRunStrategy) { if !isDryRun(r.DryRunStrategy) {
slog.Debug("creating rolled back release", "name", name) r.cfg.Logger().Debug("creating rolled back release", "name", name)
if err := r.cfg.Releases.Create(targetRelease); err != nil { if err := r.cfg.Releases.Create(targetRelease); err != nil {
return err return err
} }
} }
slog.Debug("performing rollback", "name", name) r.cfg.Logger().Debug("performing rollback", "name", name)
if _, err := r.performRollback(currentRelease, targetRelease, serverSideApply); err != nil { if _, err := r.performRollback(currentRelease, targetRelease, serverSideApply); err != nil {
return err return err
} }
if !isDryRun(r.DryRunStrategy) { if !isDryRun(r.DryRunStrategy) {
slog.Debug("updating status for rolled back release", "name", name) r.cfg.Logger().Debug("updating status for rolled back release", "name", name)
if err := r.cfg.Releases.Update(targetRelease); err != nil { if err := r.cfg.Releases.Update(targetRelease); err != nil {
return err return err
} }
@ -151,7 +150,7 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele
return nil, nil, false, fmt.Errorf("release has no %d version", previousVersion) return nil, nil, false, fmt.Errorf("release has no %d version", previousVersion)
} }
slog.Debug("rolling back", "name", name, "currentVersion", currentRelease.Version, "targetVersion", previousVersion) r.cfg.Logger().Debug("rolling back", "name", name, "currentVersion", currentRelease.Version, "targetVersion", previousVersion)
previousReleasei, err := r.cfg.Releases.Get(name, previousVersion) previousReleasei, err := r.cfg.Releases.Get(name, previousVersion)
if err != nil { if err != nil {
@ -194,7 +193,7 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele
func (r *Rollback) performRollback(currentRelease, targetRelease *release.Release, serverSideApply bool) (*release.Release, error) { func (r *Rollback) performRollback(currentRelease, targetRelease *release.Release, serverSideApply bool) (*release.Release, error) {
if isDryRun(r.DryRunStrategy) { if isDryRun(r.DryRunStrategy) {
slog.Debug("dry run", "name", targetRelease.Name) r.cfg.Logger().Debug("dry run", "name", targetRelease.Name)
return targetRelease, nil return targetRelease, nil
} }
@ -214,7 +213,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
return targetRelease, err return targetRelease, err
} }
} else { } else {
slog.Debug("rollback hooks disabled", "name", targetRelease.Name) r.cfg.Logger().Debug("rollback hooks disabled", "name", targetRelease.Name)
} }
// It is safe to use "forceOwnership" here because these are resources currently rendered by the chart. // It is safe to use "forceOwnership" here because these are resources currently rendered by the chart.
@ -232,21 +231,21 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
if err != nil { if err != nil {
msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err)
slog.Warn(msg) r.cfg.Logger().Warn(msg)
currentRelease.Info.Status = common.StatusSuperseded currentRelease.Info.Status = common.StatusSuperseded
targetRelease.Info.Status = common.StatusFailed targetRelease.Info.Status = common.StatusFailed
targetRelease.Info.Description = msg targetRelease.Info.Description = msg
r.cfg.recordRelease(currentRelease) r.cfg.recordRelease(currentRelease)
r.cfg.recordRelease(targetRelease) r.cfg.recordRelease(targetRelease)
if r.CleanupOnFail { if r.CleanupOnFail {
slog.Debug("cleanup on fail set, cleaning up resources", "count", len(results.Created)) r.cfg.Logger().Debug("cleanup on fail set, cleaning up resources", "count", len(results.Created))
_, errs := r.cfg.KubeClient.Delete(results.Created, metav1.DeletePropagationBackground) _, errs := r.cfg.KubeClient.Delete(results.Created, metav1.DeletePropagationBackground)
if errs != nil { if errs != nil {
return targetRelease, fmt.Errorf( return targetRelease, fmt.Errorf(
"an error occurred while cleaning up resources. original rollback error: %w", "an error occurred while cleaning up resources. original rollback error: %w",
fmt.Errorf("unable to cleanup resources: %w", joinErrors(errs, ", "))) fmt.Errorf("unable to cleanup resources: %w", joinErrors(errs, ", ")))
} }
slog.Debug("resource cleanup complete") r.cfg.Logger().Debug("resource cleanup complete")
} }
return targetRelease, err return targetRelease, err
} }
@ -288,7 +287,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
if err != nil { if err != nil {
return nil, err return nil, err
} }
slog.Debug("superseding previous deployment", "version", rel.Version) r.cfg.Logger().Debug("superseding previous deployment", "version", rel.Version)
rel.Info.Status = common.StatusSuperseded rel.Info.Status = common.StatusSuperseded
r.cfg.recordRelease(rel) r.cfg.recordRelease(rel)
} }

@ -119,7 +119,7 @@ func (u *Uninstall) Run(name string) (*releasei.UninstallReleaseResponse, error)
return nil, fmt.Errorf("the release named %q is already deleted", name) return nil, fmt.Errorf("the release named %q is already deleted", name)
} }
slog.Debug("uninstall: deleting release", "name", name) u.cfg.Logger().Debug("uninstall: deleting release", "name", name)
rel.Info.Status = common.StatusUninstalling rel.Info.Status = common.StatusUninstalling
rel.Info.Deleted = time.Now() rel.Info.Deleted = time.Now()
rel.Info.Description = "Deletion in progress (or silently failed)" rel.Info.Description = "Deletion in progress (or silently failed)"
@ -131,18 +131,18 @@ func (u *Uninstall) Run(name string) (*releasei.UninstallReleaseResponse, error)
return res, err return res, err
} }
} else { } else {
slog.Debug("delete hooks disabled", "release", name) u.cfg.Logger().Debug("delete hooks disabled", "release", name)
} }
// From here on out, the release is currently considered to be in StatusUninstalling // From here on out, the release is currently considered to be in StatusUninstalling
// state. // state.
if err := u.cfg.Releases.Update(rel); err != nil { if err := u.cfg.Releases.Update(rel); err != nil {
slog.Debug("uninstall: Failed to store updated release", slog.Any("error", err)) u.cfg.Logger().Debug("uninstall: Failed to store updated release", slog.Any("error", err))
} }
deletedResources, kept, errs := u.deleteRelease(rel) deletedResources, kept, errs := u.deleteRelease(rel)
if errs != nil { if errs != nil {
slog.Debug("uninstall: Failed to delete release", slog.Any("error", errs)) u.cfg.Logger().Debug("uninstall: Failed to delete release", slog.Any("error", errs))
return nil, fmt.Errorf("failed to delete release: %s", name) return nil, fmt.Errorf("failed to delete release: %s", name)
} }
@ -170,7 +170,7 @@ func (u *Uninstall) Run(name string) (*releasei.UninstallReleaseResponse, error)
} }
if !u.KeepHistory { if !u.KeepHistory {
slog.Debug("purge requested", "release", name) u.cfg.Logger().Debug("purge requested", "release", name)
err := u.purgeReleases(rels...) err := u.purgeReleases(rels...)
if err != nil { if err != nil {
errs = append(errs, fmt.Errorf("uninstall: Failed to purge the release: %w", err)) errs = append(errs, fmt.Errorf("uninstall: Failed to purge the release: %w", err))
@ -185,7 +185,7 @@ func (u *Uninstall) Run(name string) (*releasei.UninstallReleaseResponse, error)
} }
if err := u.cfg.Releases.Update(rel); err != nil { if err := u.cfg.Releases.Update(rel); err != nil {
slog.Debug("uninstall: Failed to store updated release", slog.Any("error", err)) u.cfg.Logger().Debug("uninstall: Failed to store updated release", slog.Any("error", err))
} }
if len(errs) > 0 { if len(errs) > 0 {

@ -185,7 +185,7 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, ch chart.Char
return nil, fmt.Errorf("release name is invalid: %s", name) return nil, fmt.Errorf("release name is invalid: %s", name)
} }
slog.Debug("preparing upgrade", "name", name) u.cfg.Logger().Debug("preparing upgrade", "name", name)
currentRelease, upgradedRelease, serverSideApply, err := u.prepareUpgrade(name, chrt, vals) currentRelease, upgradedRelease, serverSideApply, err := u.prepareUpgrade(name, chrt, vals)
if err != nil { if err != nil {
return nil, err return nil, err
@ -193,7 +193,7 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, ch chart.Char
u.cfg.Releases.MaxHistory = u.MaxHistory u.cfg.Releases.MaxHistory = u.MaxHistory
slog.Debug("performing update", "name", name) u.cfg.Logger().Debug("performing update", "name", name)
res, err := u.performUpgrade(ctx, currentRelease, upgradedRelease, serverSideApply) res, err := u.performUpgrade(ctx, currentRelease, upgradedRelease, serverSideApply)
if err != nil { if err != nil {
return res, err return res, err
@ -201,7 +201,7 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, ch chart.Char
// Do not update for dry runs // Do not update for dry runs
if !isDryRun(u.DryRunStrategy) { if !isDryRun(u.DryRunStrategy) {
slog.Debug("updating status for upgraded release", "name", name) u.cfg.Logger().Debug("updating status for upgraded release", "name", name)
if err := u.cfg.Releases.Update(upgradedRelease); err != nil { if err := u.cfg.Releases.Update(upgradedRelease); err != nil {
return res, err return res, err
} }
@ -308,7 +308,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[str
return nil, nil, false, err return nil, nil, false, err
} }
slog.Debug("determined release apply method", slog.Bool("server_side_apply", serverSideApply), slog.String("previous_release_apply_method", lastRelease.ApplyMethod)) u.cfg.Logger().Debug("determined release apply method", slog.Bool("server_side_apply", serverSideApply), slog.String("previous_release_apply_method", lastRelease.ApplyMethod))
// Store an upgraded release. // Store an upgraded release.
upgradedRelease := &release.Release{ upgradedRelease := &release.Release{
@ -391,7 +391,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR
}) })
if isDryRun(u.DryRunStrategy) { if isDryRun(u.DryRunStrategy) {
slog.Debug("dry run for release", "name", upgradedRelease.Name) u.cfg.Logger().Debug("dry run for release", "name", upgradedRelease.Name)
if len(u.Description) > 0 { if len(u.Description) > 0 {
upgradedRelease.Info.Description = u.Description upgradedRelease.Info.Description = u.Description
} else { } else {
@ -400,7 +400,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR
return upgradedRelease, nil return upgradedRelease, nil
} }
slog.Debug("creating upgraded release", "name", upgradedRelease.Name) u.cfg.Logger().Debug("creating upgraded release", "name", upgradedRelease.Name)
if err := u.cfg.Releases.Create(upgradedRelease); err != nil { if err := u.cfg.Releases.Create(upgradedRelease); err != nil {
return nil, err return nil, err
} }
@ -457,7 +457,7 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele
return return
} }
} else { } else {
slog.Debug("upgrade hooks disabled", "name", upgradedRelease.Name) u.cfg.Logger().Debug("upgrade hooks disabled", "name", upgradedRelease.Name)
} }
upgradeClientSideFieldManager := isReleaseApplyMethodClientSideApply(originalRelease.ApplyMethod) && serverSideApply // Update client-side field manager if transitioning from client-side to server-side apply upgradeClientSideFieldManager := isReleaseApplyMethodClientSideApply(originalRelease.ApplyMethod) && serverSideApply // Update client-side field manager if transitioning from client-side to server-side apply
@ -515,13 +515,13 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele
func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, err error) (*release.Release, error) { func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, err error) (*release.Release, error) {
msg := fmt.Sprintf("Upgrade %q failed: %s", rel.Name, err) msg := fmt.Sprintf("Upgrade %q failed: %s", rel.Name, err)
slog.Warn("upgrade failed", "name", rel.Name, slog.Any("error", err)) u.cfg.Logger().Warn("upgrade failed", "name", rel.Name, slog.Any("error", err))
rel.Info.Status = rcommon.StatusFailed rel.Info.Status = rcommon.StatusFailed
rel.Info.Description = msg rel.Info.Description = msg
u.cfg.recordRelease(rel) u.cfg.recordRelease(rel)
if u.CleanupOnFail && len(created) > 0 { if u.CleanupOnFail && len(created) > 0 {
slog.Debug("cleanup on fail set", "cleaning_resources", len(created)) u.cfg.Logger().Debug("cleanup on fail set", "cleaning_resources", len(created))
_, errs := u.cfg.KubeClient.Delete(created, metav1.DeletePropagationBackground) _, errs := u.cfg.KubeClient.Delete(created, metav1.DeletePropagationBackground)
if errs != nil { if errs != nil {
return rel, fmt.Errorf( return rel, fmt.Errorf(
@ -533,11 +533,11 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e
), ),
) )
} }
slog.Debug("resource cleanup complete") u.cfg.Logger().Debug("resource cleanup complete")
} }
if u.RollbackOnFailure { if u.RollbackOnFailure {
slog.Debug("Upgrade failed and rollback-on-failure is set, rolling back to previous successful release") u.cfg.Logger().Debug("Upgrade failed and rollback-on-failure is set, rolling back to previous successful release")
// As a protection, get the last successful release before rollback. // As a protection, get the last successful release before rollback.
// If there are no successful releases, bail out // If there are no successful releases, bail out
@ -592,13 +592,13 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e
func (u *Upgrade) reuseValues(chart *chartv2.Chart, current *release.Release, newVals map[string]interface{}) (map[string]interface{}, error) { func (u *Upgrade) reuseValues(chart *chartv2.Chart, current *release.Release, newVals map[string]interface{}) (map[string]interface{}, error) {
if u.ResetValues { if u.ResetValues {
// If ResetValues is set, we completely ignore current.Config. // If ResetValues is set, we completely ignore current.Config.
slog.Debug("resetting values to the chart's original version") u.cfg.Logger().Debug("resetting values to the chart's original version")
return newVals, nil return newVals, nil
} }
// If the ReuseValues flag is set, we always copy the old values over the new config's values. // If the ReuseValues flag is set, we always copy the old values over the new config's values.
if u.ReuseValues { if u.ReuseValues {
slog.Debug("reusing the old release's values") u.cfg.Logger().Debug("reusing the old release's values")
// We have to regenerate the old coalesced values: // We have to regenerate the old coalesced values:
oldVals, err := util.CoalesceValues(current.Chart, current.Config) oldVals, err := util.CoalesceValues(current.Chart, current.Config)
@ -615,7 +615,7 @@ func (u *Upgrade) reuseValues(chart *chartv2.Chart, current *release.Release, ne
// If the ResetThenReuseValues flag is set, we use the new chart's values, but we copy the old config's values over the new config's values. // If the ResetThenReuseValues flag is set, we use the new chart's values, but we copy the old config's values over the new config's values.
if u.ResetThenReuseValues { if u.ResetThenReuseValues {
slog.Debug("merging values from old release to new values") u.cfg.Logger().Debug("merging values from old release to new values")
newVals = util.CoalesceTables(newVals, current.Config) newVals = util.CoalesceTables(newVals, current.Config)
@ -623,7 +623,7 @@ func (u *Upgrade) reuseValues(chart *chartv2.Chart, current *release.Release, ne
} }
if len(newVals) == 0 && len(current.Config) > 0 { if len(newVals) == 0 && len(current.Config) > 0 {
slog.Debug("copying values from old release", "name", current.Name, "version", current.Version) u.cfg.Logger().Debug("copying values from old release", "name", current.Name, "version", current.Version)
newVals = current.Config newVals = current.Config
} }
return newVals, nil return newVals, nil

@ -103,7 +103,7 @@ By default, the default directories depend on the Operating System. The defaults
var settings = cli.New() var settings = cli.New()
func NewRootCmd(out io.Writer, args []string, logSetup func(bool)) (*cobra.Command, error) { func NewRootCmd(out io.Writer, args []string, logSetup func(bool)) (*cobra.Command, error) {
actionConfig := new(action.Configuration) actionConfig := action.NewConfiguration()
cmd, err := newRootCmdWithConfig(actionConfig, out, args, logSetup) cmd, err := newRootCmdWithConfig(actionConfig, out, args, logSetup)
if err != nil { if err != nil {
return nil, err return nil, err

@ -39,6 +39,8 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil" "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"helm.sh/helm/v4/internal/logging"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -84,6 +86,9 @@ type Client struct {
Waiter Waiter
kubeClient kubernetes.Interface kubeClient kubernetes.Interface
// Embed a LogHolder to provide logger functionality
logging.LogHolder
} }
var _ Interface = (*Client)(nil) var _ Interface = (*Client)(nil)
@ -177,6 +182,7 @@ func New(getter genericclioptions.RESTClientGetter) *Client {
c := &Client{ c := &Client{
Factory: factory, Factory: factory,
} }
c.SetLogger(slog.Default().Handler())
return c return c
} }
@ -258,7 +264,7 @@ func ClientCreateOptionFieldValidationDirective(fieldValidationDirective FieldVa
// Create creates Kubernetes resources specified in the resource list. // Create creates Kubernetes resources specified in the resource list.
func (c *Client) Create(resources ResourceList, options ...ClientCreateOption) (*Result, error) { func (c *Client) Create(resources ResourceList, options ...ClientCreateOption) (*Result, error) {
slog.Debug("creating resource(s)", "resources", len(resources)) c.Logger().Debug("creating resource(s)", "resources", len(resources))
createOptions := clientCreateOptions{ createOptions := clientCreateOptions{
serverSideApply: true, // Default to server-side apply serverSideApply: true, // Default to server-side apply
@ -275,11 +281,11 @@ func (c *Client) Create(resources ResourceList, options ...ClientCreateOption) (
makeCreateApplyFunc := func() func(target *resource.Info) error { makeCreateApplyFunc := func() func(target *resource.Info) error {
if createOptions.serverSideApply { if createOptions.serverSideApply {
slog.Debug("using server-side apply for resource creation", slog.Bool("forceConflicts", createOptions.forceConflicts), slog.Bool("dryRun", createOptions.dryRun), slog.String("fieldValidationDirective", string(createOptions.fieldValidationDirective))) c.Logger().Debug("using server-side apply for resource creation", slog.Bool("forceConflicts", createOptions.forceConflicts), slog.Bool("dryRun", createOptions.dryRun), slog.String("fieldValidationDirective", string(createOptions.fieldValidationDirective)))
return func(target *resource.Info) error { return func(target *resource.Info) error {
err := patchResourceServerSide(target, createOptions.dryRun, createOptions.forceConflicts, createOptions.fieldValidationDirective) err := patchResourceServerSide(target, createOptions.dryRun, createOptions.forceConflicts, createOptions.fieldValidationDirective)
logger := slog.With( logger := c.Logger().With(
slog.String("namespace", target.Namespace), slog.String("namespace", target.Namespace),
slog.String("name", target.Name), slog.String("name", target.Name),
slog.String("gvk", target.Mapping.GroupVersionKind.String())) slog.String("gvk", target.Mapping.GroupVersionKind.String()))
@ -294,7 +300,7 @@ func (c *Client) Create(resources ResourceList, options ...ClientCreateOption) (
} }
} }
slog.Debug("using client-side apply for resource creation") c.Logger().Debug("using client-side apply for resource creation")
return createResource return createResource
} }
@ -349,7 +355,7 @@ func (c *Client) Get(resources ResourceList, related bool) (map[string][]runtime
objs, err = c.getSelectRelationPod(info, objs, isTable, &podSelectors) objs, err = c.getSelectRelationPod(info, objs, isTable, &podSelectors)
if err != nil { if err != nil {
slog.Warn("get the relation pod is failed", slog.Any("error", err)) c.Logger().Warn("get the relation pod is failed", slog.Any("error", err))
} }
} }
} }
@ -367,7 +373,7 @@ func (c *Client) getSelectRelationPod(info *resource.Info, objs map[string][]run
if info == nil { if info == nil {
return objs, nil return objs, nil
} }
slog.Debug("get relation pod of object", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind) c.Logger().Debug("get relation pod of object", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind)
selector, ok, _ := getSelectorFromObject(info.Object) selector, ok, _ := getSelectorFromObject(info.Object)
if !ok { if !ok {
return objs, nil return objs, nil
@ -504,7 +510,7 @@ func (c *Client) update(originals, targets ResourceList, updateApplyFunc UpdateA
updateErrors := []error{} updateErrors := []error{}
res := &Result{} res := &Result{}
slog.Debug("checking resources for changes", "resources", len(targets)) c.Logger().Debug("checking resources for changes", "resources", len(targets))
err := targets.Visit(func(target *resource.Info, err error) error { err := targets.Visit(func(target *resource.Info, err error) error {
if err != nil { if err != nil {
return err return err
@ -525,7 +531,7 @@ func (c *Client) update(originals, targets ResourceList, updateApplyFunc UpdateA
} }
kind := target.Mapping.GroupVersionKind.Kind kind := target.Mapping.GroupVersionKind.Kind
slog.Debug("created a new resource", "namespace", target.Namespace, "name", target.Name, "kind", kind) c.Logger().Debug("created a new resource", "namespace", target.Namespace, "name", target.Name, "kind", kind)
return nil return nil
} }
@ -553,22 +559,22 @@ func (c *Client) update(originals, targets ResourceList, updateApplyFunc UpdateA
} }
for _, info := range originals.Difference(targets) { for _, info := range originals.Difference(targets) {
slog.Debug("deleting resource", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind) c.Logger().Debug("deleting resource", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind)
if err := info.Get(); err != nil { if err := info.Get(); err != nil {
slog.Debug("unable to get object", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, slog.Any("error", err)) c.Logger().Debug("unable to get object", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, slog.Any("error", err))
continue continue
} }
annotations, err := metadataAccessor.Annotations(info.Object) annotations, err := metadataAccessor.Annotations(info.Object)
if err != nil { if err != nil {
slog.Debug("unable to get annotations", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, slog.Any("error", err)) c.Logger().Debug("unable to get annotations", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, slog.Any("error", err))
} }
if annotations != nil && annotations[ResourcePolicyAnno] == KeepPolicy { if annotations != nil && annotations[ResourcePolicyAnno] == KeepPolicy {
slog.Debug("skipping delete due to annotation", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, "annotation", ResourcePolicyAnno, "value", KeepPolicy) c.Logger().Debug("skipping delete due to annotation", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, "annotation", ResourcePolicyAnno, "value", KeepPolicy)
continue continue
} }
if err := deleteResource(info, metav1.DeletePropagationBackground); err != nil { if err := deleteResource(info, metav1.DeletePropagationBackground); err != nil {
slog.Debug("failed to delete resource", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, slog.Any("error", err)) c.Logger().Debug("failed to delete resource", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, slog.Any("error", err))
if !apierrors.IsNotFound(err) { if !apierrors.IsNotFound(err) {
updateErrors = append(updateErrors, fmt.Errorf("failed to delete resource %s: %w", info.Name, err)) updateErrors = append(updateErrors, fmt.Errorf("failed to delete resource %s: %w", info.Name, err))
} }
@ -713,23 +719,23 @@ func (c *Client) Update(originals, targets ResourceList, options ...ClientUpdate
makeUpdateApplyFunc := func() UpdateApplyFunc { makeUpdateApplyFunc := func() UpdateApplyFunc {
if updateOptions.forceReplace { if updateOptions.forceReplace {
slog.Debug( c.Logger().Debug(
"using resource replace update strategy", "using resource replace update strategy",
slog.String("fieldValidationDirective", string(updateOptions.fieldValidationDirective))) slog.String("fieldValidationDirective", string(updateOptions.fieldValidationDirective)))
return func(original, target *resource.Info) error { return func(original, target *resource.Info) error {
if err := replaceResource(target, updateOptions.fieldValidationDirective); err != nil { if err := replaceResource(target, updateOptions.fieldValidationDirective); err != nil {
slog.Debug("error replacing the resource", "namespace", target.Namespace, "name", target.Name, "kind", target.Mapping.GroupVersionKind.Kind, slog.Any("error", err)) c.Logger().Debug("error replacing the resource", "namespace", target.Namespace, "name", target.Name, "kind", target.Mapping.GroupVersionKind.Kind, slog.Any("error", err))
return err return err
} }
originalObject := original.Object originalObject := original.Object
kind := target.Mapping.GroupVersionKind.Kind kind := target.Mapping.GroupVersionKind.Kind
slog.Debug("replace succeeded", "name", original.Name, "initialKind", originalObject.GetObjectKind().GroupVersionKind().Kind, "kind", kind) c.Logger().Debug("replace succeeded", "name", original.Name, "initialKind", originalObject.GetObjectKind().GroupVersionKind().Kind, "kind", kind)
return nil return nil
} }
} else if updateOptions.serverSideApply { } else if updateOptions.serverSideApply {
slog.Debug( c.Logger().Debug(
"using server-side apply for resource update", "using server-side apply for resource update",
slog.Bool("forceConflicts", updateOptions.forceConflicts), slog.Bool("forceConflicts", updateOptions.forceConflicts),
slog.Bool("dryRun", updateOptions.dryRun), slog.Bool("dryRun", updateOptions.dryRun),
@ -737,7 +743,7 @@ func (c *Client) Update(originals, targets ResourceList, options ...ClientUpdate
slog.Bool("upgradeClientSideFieldManager", updateOptions.upgradeClientSideFieldManager)) slog.Bool("upgradeClientSideFieldManager", updateOptions.upgradeClientSideFieldManager))
return func(original, target *resource.Info) error { return func(original, target *resource.Info) error {
logger := slog.With( logger := c.Logger().With(
slog.String("namespace", target.Namespace), slog.String("namespace", target.Namespace),
slog.String("name", target.Name), slog.String("name", target.Name),
slog.String("gvk", target.Mapping.GroupVersionKind.String())) slog.String("gvk", target.Mapping.GroupVersionKind.String()))
@ -745,7 +751,7 @@ func (c *Client) Update(originals, targets ResourceList, options ...ClientUpdate
if updateOptions.upgradeClientSideFieldManager { if updateOptions.upgradeClientSideFieldManager {
patched, err := upgradeClientSideFieldManager(original, updateOptions.dryRun, updateOptions.fieldValidationDirective) patched, err := upgradeClientSideFieldManager(original, updateOptions.dryRun, updateOptions.fieldValidationDirective)
if err != nil { if err != nil {
slog.Debug("Error patching resource to replace CSA field management", slog.Any("error", err)) c.Logger().Debug("Error patching resource to replace CSA field management", slog.Any("error", err))
return err return err
} }
@ -765,7 +771,7 @@ func (c *Client) Update(originals, targets ResourceList, options ...ClientUpdate
} }
} }
slog.Debug("using client-side apply for resource update", slog.Bool("threeWayMergeForUnstructured", updateOptions.threeWayMergeForUnstructured)) c.Logger().Debug("using client-side apply for resource update", slog.Bool("threeWayMergeForUnstructured", updateOptions.threeWayMergeForUnstructured))
return func(original, target *resource.Info) error { return func(original, target *resource.Info) error {
return patchResourceClientSide(original.Object, target, updateOptions.threeWayMergeForUnstructured) return patchResourceClientSide(original.Object, target, updateOptions.threeWayMergeForUnstructured)
} }
@ -783,11 +789,11 @@ func (c *Client) Delete(resources ResourceList, policy metav1.DeletionPropagatio
res := &Result{} res := &Result{}
mtx := sync.Mutex{} mtx := sync.Mutex{}
err := perform(resources, func(target *resource.Info) error { err := perform(resources, func(target *resource.Info) error {
slog.Debug("starting delete resource", "namespace", target.Namespace, "name", target.Name, "kind", target.Mapping.GroupVersionKind.Kind) c.Logger().Debug("starting delete resource", "namespace", target.Namespace, "name", target.Name, "kind", target.Mapping.GroupVersionKind.Kind)
err := deleteResource(target, policy) err := deleteResource(target, policy)
if err == nil || apierrors.IsNotFound(err) { if err == nil || apierrors.IsNotFound(err) {
if err != nil { if err != nil {
slog.Debug("ignoring delete failure", "namespace", target.Namespace, "name", target.Name, "kind", target.Mapping.GroupVersionKind.Kind, slog.Any("error", err)) c.Logger().Debug("ignoring delete failure", "namespace", target.Namespace, "name", target.Name, "kind", target.Mapping.GroupVersionKind.Kind, slog.Any("error", err))
} }
mtx.Lock() mtx.Lock()
defer mtx.Unlock() defer mtx.Unlock()

@ -31,6 +31,7 @@ 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"
"helm.sh/helm/v4/internal/logging"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"
rspb "helm.sh/helm/v4/pkg/release/v1" rspb "helm.sh/helm/v4/pkg/release/v1"
) )
@ -44,14 +45,19 @@ const ConfigMapsDriverName = "ConfigMap"
// ConfigMapsInterface. // ConfigMapsInterface.
type ConfigMaps struct { type ConfigMaps struct {
impl corev1.ConfigMapInterface impl corev1.ConfigMapInterface
// Embed a LogHolder to provide logger functionality
logging.LogHolder
} }
// NewConfigMaps initializes a new ConfigMaps wrapping an implementation of // NewConfigMaps initializes a new ConfigMaps wrapping an implementation of
// the kubernetes ConfigMapsInterface. // the kubernetes ConfigMapsInterface.
func NewConfigMaps(impl corev1.ConfigMapInterface) *ConfigMaps { func NewConfigMaps(impl corev1.ConfigMapInterface) *ConfigMaps {
return &ConfigMaps{ c := &ConfigMaps{
impl: impl, impl: impl,
} }
c.SetLogger(slog.Default().Handler())
return c
} }
// Name returns the name of the driver. // Name returns the name of the driver.
@ -69,13 +75,13 @@ func (cfgmaps *ConfigMaps) Get(key string) (release.Releaser, error) {
return nil, ErrReleaseNotFound return nil, ErrReleaseNotFound
} }
slog.Debug("failed to get release", "key", key, slog.Any("error", err)) cfgmaps.Logger().Debug("failed to get release", "key", key, slog.Any("error", err))
return nil, err return nil, err
} }
// found the configmap, decode the base64 data string // found the configmap, decode the base64 data string
r, err := decodeRelease(obj.Data["release"]) r, err := decodeRelease(obj.Data["release"])
if err != nil { if err != nil {
slog.Debug("failed to decode data", "key", key, slog.Any("error", err)) cfgmaps.Logger().Debug("failed to decode data", "key", key, slog.Any("error", err))
return nil, err return nil, err
} }
r.Labels = filterSystemLabels(obj.Labels) r.Labels = filterSystemLabels(obj.Labels)
@ -92,7 +98,7 @@ func (cfgmaps *ConfigMaps) List(filter func(release.Releaser) bool) ([]release.R
list, err := cfgmaps.impl.List(context.Background(), opts) list, err := cfgmaps.impl.List(context.Background(), opts)
if err != nil { if err != nil {
slog.Debug("failed to list releases", slog.Any("error", err)) cfgmaps.Logger().Debug("failed to list releases", slog.Any("error", err))
return nil, err return nil, err
} }
@ -103,7 +109,7 @@ func (cfgmaps *ConfigMaps) List(filter func(release.Releaser) bool) ([]release.R
for _, item := range list.Items { for _, item := range list.Items {
rls, err := decodeRelease(item.Data["release"]) rls, err := decodeRelease(item.Data["release"])
if err != nil { if err != nil {
slog.Debug("failed to decode release", "item", item, slog.Any("error", err)) cfgmaps.Logger().Debug("failed to decode release", "item", item, slog.Any("error", err))
continue continue
} }
@ -131,7 +137,7 @@ func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]release.Releaser,
list, err := cfgmaps.impl.List(context.Background(), opts) list, err := cfgmaps.impl.List(context.Background(), opts)
if err != nil { if err != nil {
slog.Debug("failed to query with labels", slog.Any("error", err)) cfgmaps.Logger().Debug("failed to query with labels", slog.Any("error", err))
return nil, err return nil, err
} }
@ -143,7 +149,7 @@ func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]release.Releaser,
for _, item := range list.Items { for _, item := range list.Items {
rls, err := decodeRelease(item.Data["release"]) rls, err := decodeRelease(item.Data["release"])
if err != nil { if err != nil {
slog.Debug("failed to decode release", slog.Any("error", err)) cfgmaps.Logger().Debug("failed to decode release", slog.Any("error", err))
continue continue
} }
rls.Labels = item.Labels rls.Labels = item.Labels
@ -175,7 +181,7 @@ func (cfgmaps *ConfigMaps) Create(key string, rls release.Releaser) error {
// create a new configmap to hold the release // create a new configmap to hold the release
obj, err := newConfigMapsObject(key, rel, lbs) obj, err := newConfigMapsObject(key, rel, lbs)
if err != nil { if err != nil {
slog.Debug("failed to encode release", "name", rac.Name(), slog.Any("error", err)) cfgmaps.Logger().Debug("failed to encode release", "name", rac.Name(), slog.Any("error", err))
return err return err
} }
// push the configmap object out into the kubiverse // push the configmap object out into the kubiverse
@ -184,7 +190,7 @@ func (cfgmaps *ConfigMaps) Create(key string, rls release.Releaser) error {
return ErrReleaseExists return ErrReleaseExists
} }
slog.Debug("failed to create release", slog.Any("error", err)) cfgmaps.Logger().Debug("failed to create release", slog.Any("error", err))
return err return err
} }
return nil return nil
@ -208,13 +214,13 @@ func (cfgmaps *ConfigMaps) Update(key string, rel release.Releaser) error {
// create a new configmap object to hold the release // create a new configmap object to hold the release
obj, err := newConfigMapsObject(key, rls, lbs) obj, err := newConfigMapsObject(key, rls, lbs)
if err != nil { if err != nil {
slog.Debug("failed to encode release", "name", rls.Name, slog.Any("error", err)) cfgmaps.Logger().Debug("failed to encode release", "name", rls.Name, slog.Any("error", err))
return err return err
} }
// push the configmap object out into the kubiverse // push the configmap object out into the kubiverse
_, err = cfgmaps.impl.Update(context.Background(), obj, metav1.UpdateOptions{}) _, err = cfgmaps.impl.Update(context.Background(), obj, metav1.UpdateOptions{})
if err != nil { if err != nil {
slog.Debug("failed to update release", slog.Any("error", err)) cfgmaps.Logger().Debug("failed to update release", slog.Any("error", err))
return err return err
} }
return nil return nil

@ -17,10 +17,12 @@ limitations under the License.
package driver package driver
import ( import (
"log/slog"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"helm.sh/helm/v4/internal/logging"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"
) )
@ -42,11 +44,15 @@ type Memory struct {
namespace string namespace string
// A map of namespaces to releases // A map of namespaces to releases
cache map[string]memReleases cache map[string]memReleases
// Embed a LogHolder to provide logger functionality
logging.LogHolder
} }
// NewMemory initializes a new memory driver. // NewMemory initializes a new memory driver.
func NewMemory() *Memory { func NewMemory() *Memory {
return &Memory{cache: map[string]memReleases{}, namespace: "default"} m := &Memory{cache: map[string]memReleases{}, namespace: "default"}
m.SetLogger(slog.Default().Handler())
return m
} }
// SetNamespace sets a specific namespace in which releases will be accessed. // SetNamespace sets a specific namespace in which releases will be accessed.

@ -31,6 +31,7 @@ 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"
"helm.sh/helm/v4/internal/logging"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"
rspb "helm.sh/helm/v4/pkg/release/v1" rspb "helm.sh/helm/v4/pkg/release/v1"
) )
@ -44,14 +45,18 @@ const SecretsDriverName = "Secret"
// SecretsInterface. // SecretsInterface.
type Secrets struct { type Secrets struct {
impl corev1.SecretInterface impl corev1.SecretInterface
// Embed a LogHolder to provide logger functionality
logging.LogHolder
} }
// NewSecrets initializes a new Secrets wrapping an implementation of // NewSecrets initializes a new Secrets wrapping an implementation of
// the kubernetes SecretsInterface. // the kubernetes SecretsInterface.
func NewSecrets(impl corev1.SecretInterface) *Secrets { func NewSecrets(impl corev1.SecretInterface) *Secrets {
return &Secrets{ s := &Secrets{
impl: impl, impl: impl,
} }
s.SetLogger(slog.Default().Handler())
return s
} }
// Name returns the name of the driver. // Name returns the name of the driver.
@ -98,7 +103,7 @@ func (secrets *Secrets) List(filter func(release.Releaser) bool) ([]release.Rele
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 {
slog.Debug("list failed to decode release", "key", item.Name, slog.Any("error", err)) secrets.Logger().Debug("list failed to decode release", "key", item.Name, slog.Any("error", err))
continue continue
} }
@ -137,7 +142,7 @@ func (secrets *Secrets) Query(labels map[string]string) ([]release.Releaser, err
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 {
slog.Debug("failed to decode release", "key", item.Name, slog.Any("error", err)) secrets.Logger().Debug("failed to decode release", "key", item.Name, slog.Any("error", err))
continue continue
} }
rls.Labels = item.Labels rls.Labels = item.Labels

@ -32,6 +32,7 @@ import (
// Import pq for postgres dialect // Import pq for postgres dialect
_ "github.com/lib/pq" _ "github.com/lib/pq"
"helm.sh/helm/v4/internal/logging"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"
rspb "helm.sh/helm/v4/pkg/release/v1" rspb "helm.sh/helm/v4/pkg/release/v1"
) )
@ -89,6 +90,8 @@ type SQL struct {
db *sqlx.DB db *sqlx.DB
namespace string namespace string
statementBuilder sq.StatementBuilderType statementBuilder sq.StatementBuilderType
// Embed a LogHolder to provide logger functionality
logging.LogHolder
} }
// Name returns the name of the driver. // Name returns the name of the driver.
@ -109,13 +112,13 @@ func (s *SQL) checkAlreadyApplied(migrations []*migrate.Migration) bool {
records, err := migrate.GetMigrationRecords(s.db.DB, postgreSQLDialect) records, err := migrate.GetMigrationRecords(s.db.DB, postgreSQLDialect)
migrate.SetDisableCreateTable(false) migrate.SetDisableCreateTable(false)
if err != nil { if err != nil {
slog.Debug("failed to get migration records", slog.Any("error", err)) s.Logger().Debug("failed to get migration records", slog.Any("error", err))
return false return false
} }
for _, record := range records { for _, record := range records {
if _, ok := migrationsIDs[record.Id]; ok { if _, ok := migrationsIDs[record.Id]; ok {
slog.Debug("found previous migration", "id", record.Id, "appliedAt", record.AppliedAt) s.Logger().Debug("found previous migration", "id", record.Id, "appliedAt", record.AppliedAt)
delete(migrationsIDs, record.Id) delete(migrationsIDs, record.Id)
} }
} }
@ -123,7 +126,7 @@ func (s *SQL) checkAlreadyApplied(migrations []*migrate.Migration) bool {
// check if all migrations applied // check if all migrations applied
if len(migrationsIDs) != 0 { if len(migrationsIDs) != 0 {
for id := range migrationsIDs { for id := range migrationsIDs {
slog.Debug("find unapplied migration", "id", id) s.Logger().Debug("find unapplied migration", "id", id)
} }
return false return false
} }
@ -157,9 +160,9 @@ func (s *SQL) ensureDBSetup() error {
CREATE INDEX ON %s (%s); CREATE INDEX ON %s (%s);
CREATE INDEX ON %s (%s); CREATE INDEX ON %s (%s);
CREATE INDEX ON %s (%s); CREATE INDEX ON %s (%s);
GRANT ALL ON %s TO PUBLIC; GRANT ALL ON %s TO PUBLIC;
ALTER TABLE %s ENABLE ROW LEVEL SECURITY; ALTER TABLE %s ENABLE ROW LEVEL SECURITY;
`, `,
sqlReleaseTableName, sqlReleaseTableName,
@ -209,7 +212,7 @@ func (s *SQL) ensureDBSetup() error {
%s VARCHAR(%d) %s VARCHAR(%d)
); );
CREATE INDEX ON %s (%s, %s); CREATE INDEX ON %s (%s, %s);
GRANT ALL ON %s TO PUBLIC; GRANT ALL ON %s TO PUBLIC;
ALTER TABLE %s ENABLE ROW LEVEL SECURITY; ALTER TABLE %s ENABLE ROW LEVEL SECURITY;
`, `,
@ -293,6 +296,7 @@ func NewSQL(connectionString string, namespace string) (*SQL, error) {
} }
driver.namespace = namespace driver.namespace = namespace
driver.SetLogger(slog.Default().Handler())
return driver, nil return driver, nil
} }
@ -309,24 +313,24 @@ func (s *SQL) Get(key string) (release.Releaser, error) {
query, args, err := qb.ToSql() query, args, err := qb.ToSql()
if err != nil { if err != nil {
slog.Debug("failed to build query", slog.Any("error", err)) s.Logger().Debug("failed to build query", slog.Any("error", err))
return nil, err return nil, err
} }
// Get will return an error if the result is empty // Get will return an error if the result is empty
if err := s.db.Get(&record, query, args...); err != nil { if err := s.db.Get(&record, query, args...); err != nil {
slog.Debug("got SQL error when getting release", "key", key, slog.Any("error", err)) s.Logger().Debug("got SQL error when getting release", "key", key, slog.Any("error", err))
return nil, ErrReleaseNotFound return nil, ErrReleaseNotFound
} }
release, err := decodeRelease(record.Body) release, err := decodeRelease(record.Body)
if err != nil { if err != nil {
slog.Debug("failed to decode data", "key", key, slog.Any("error", err)) s.Logger().Debug("failed to decode data", "key", key, slog.Any("error", err))
return nil, err return nil, err
} }
if release.Labels, err = s.getReleaseCustomLabels(key, s.namespace); err != nil { if release.Labels, err = s.getReleaseCustomLabels(key, s.namespace); err != nil {
slog.Debug("failed to get release custom labels", "namespace", s.namespace, "key", key, slog.Any("error", err)) s.Logger().Debug("failed to get release custom labels", "namespace", s.namespace, "key", key, slog.Any("error", err))
return nil, err return nil, err
} }
@ -347,13 +351,13 @@ func (s *SQL) List(filter func(release.Releaser) bool) ([]release.Releaser, erro
query, args, err := sb.ToSql() query, args, err := sb.ToSql()
if err != nil { if err != nil {
slog.Debug("failed to build query", slog.Any("error", err)) s.Logger().Debug("failed to build query", slog.Any("error", err))
return nil, err return nil, err
} }
var records = []SQLReleaseWrapper{} var records = []SQLReleaseWrapper{}
if err := s.db.Select(&records, query, args...); err != nil { if err := s.db.Select(&records, query, args...); err != nil {
slog.Debug("failed to list", slog.Any("error", err)) s.Logger().Debug("failed to list", slog.Any("error", err))
return nil, err return nil, err
} }
@ -361,12 +365,12 @@ func (s *SQL) List(filter func(release.Releaser) bool) ([]release.Releaser, erro
for _, record := range records { for _, record := range records {
release, err := decodeRelease(record.Body) release, err := decodeRelease(record.Body)
if err != nil { if err != nil {
slog.Debug("failed to decode release", "record", record, slog.Any("error", err)) s.Logger().Debug("failed to decode release", "record", record, slog.Any("error", err))
continue continue
} }
if release.Labels, err = s.getReleaseCustomLabels(record.Key, record.Namespace); err != nil { if release.Labels, err = s.getReleaseCustomLabels(record.Key, record.Namespace); err != nil {
slog.Debug("failed to get release custom labels", "namespace", record.Namespace, "key", record.Key, slog.Any("error", err)) s.Logger().Debug("failed to get release custom labels", "namespace", record.Namespace, "key", record.Key, slog.Any("error", err))
return nil, err return nil, err
} }
maps.Copy(release.Labels, getReleaseSystemLabels(release)) maps.Copy(release.Labels, getReleaseSystemLabels(release))
@ -394,7 +398,7 @@ func (s *SQL) Query(labels map[string]string) ([]release.Releaser, error) {
if _, ok := labelMap[key]; ok { if _, ok := labelMap[key]; ok {
sb = sb.Where(sq.Eq{key: labels[key]}) sb = sb.Where(sq.Eq{key: labels[key]})
} else { } else {
slog.Debug("unknown label", "key", key) s.Logger().Debug("unknown label", "key", key)
return nil, fmt.Errorf("unknown label %s", key) return nil, fmt.Errorf("unknown label %s", key)
} }
} }
@ -407,13 +411,13 @@ func (s *SQL) Query(labels map[string]string) ([]release.Releaser, error) {
// Build our query // Build our query
query, args, err := sb.ToSql() query, args, err := sb.ToSql()
if err != nil { if err != nil {
slog.Debug("failed to build query", slog.Any("error", err)) s.Logger().Debug("failed to build query", slog.Any("error", err))
return nil, err return nil, err
} }
var records = []SQLReleaseWrapper{} var records = []SQLReleaseWrapper{}
if err := s.db.Select(&records, query, args...); err != nil { if err := s.db.Select(&records, query, args...); err != nil {
slog.Debug("failed to query with labels", slog.Any("error", err)) s.Logger().Debug("failed to query with labels", slog.Any("error", err))
return nil, err return nil, err
} }
@ -425,12 +429,12 @@ func (s *SQL) Query(labels map[string]string) ([]release.Releaser, error) {
for _, record := range records { for _, record := range records {
release, err := decodeRelease(record.Body) release, err := decodeRelease(record.Body)
if err != nil { if err != nil {
slog.Debug("failed to decode release", "record", record, slog.Any("error", err)) s.Logger().Debug("failed to decode release", "record", record, slog.Any("error", err))
continue continue
} }
if release.Labels, err = s.getReleaseCustomLabels(record.Key, record.Namespace); err != nil { if release.Labels, err = s.getReleaseCustomLabels(record.Key, record.Namespace); err != nil {
slog.Debug("failed to get release custom labels", "namespace", record.Namespace, "key", record.Key, slog.Any("error", err)) s.Logger().Debug("failed to get release custom labels", "namespace", record.Namespace, "key", record.Key, slog.Any("error", err))
return nil, err return nil, err
} }
@ -459,13 +463,13 @@ func (s *SQL) Create(key string, rel release.Releaser) error {
body, err := encodeRelease(rls) body, err := encodeRelease(rls)
if err != nil { if err != nil {
slog.Debug("failed to encode release", slog.Any("error", err)) s.Logger().Debug("failed to encode release", slog.Any("error", err))
return err return err
} }
transaction, err := s.db.Beginx() transaction, err := s.db.Beginx()
if err != nil { if err != nil {
slog.Debug("failed to start SQL transaction", slog.Any("error", err)) s.Logger().Debug("failed to start SQL transaction", slog.Any("error", err))
return fmt.Errorf("error beginning transaction: %v", err) return fmt.Errorf("error beginning transaction: %v", err)
} }
@ -494,7 +498,7 @@ func (s *SQL) Create(key string, rel release.Releaser) error {
int(time.Now().Unix()), int(time.Now().Unix()),
).ToSql() ).ToSql()
if err != nil { if err != nil {
slog.Debug("failed to build insert query", slog.Any("error", err)) s.Logger().Debug("failed to build insert query", slog.Any("error", err))
return err return err
} }
@ -508,17 +512,17 @@ func (s *SQL) Create(key string, rel release.Releaser) error {
Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}).
ToSql() ToSql()
if buildErr != nil { if buildErr != nil {
slog.Debug("failed to build select query", "error", buildErr) s.Logger().Debug("failed to build select query", "error", buildErr)
return err return err
} }
var record SQLReleaseWrapper var record SQLReleaseWrapper
if err := transaction.Get(&record, selectQuery, args...); err == nil { if err := transaction.Get(&record, selectQuery, args...); err == nil {
slog.Debug("release already exists", "key", key) s.Logger().Debug("release already exists", "key", key)
return ErrReleaseExists return ErrReleaseExists
} }
slog.Debug("failed to store release in SQL database", "key", key, slog.Any("error", err)) s.Logger().Debug("failed to store release in SQL database", "key", key, slog.Any("error", err))
return err return err
} }
@ -541,13 +545,13 @@ func (s *SQL) Create(key string, rel release.Releaser) error {
if err != nil { if err != nil {
defer transaction.Rollback() defer transaction.Rollback()
slog.Debug("failed to build insert query", slog.Any("error", err)) s.Logger().Debug("failed to build insert query", slog.Any("error", err))
return err return err
} }
if _, err := transaction.Exec(insertLabelsQuery, args...); err != nil { if _, err := transaction.Exec(insertLabelsQuery, args...); err != nil {
defer transaction.Rollback() defer transaction.Rollback()
slog.Debug("failed to write Labels", slog.Any("error", err)) s.Logger().Debug("failed to write Labels", slog.Any("error", err))
return err return err
} }
} }
@ -570,7 +574,7 @@ func (s *SQL) Update(key string, rel release.Releaser) error {
body, err := encodeRelease(rls) body, err := encodeRelease(rls)
if err != nil { if err != nil {
slog.Debug("failed to encode release", slog.Any("error", err)) s.Logger().Debug("failed to encode release", slog.Any("error", err))
return err return err
} }
@ -587,12 +591,12 @@ func (s *SQL) Update(key string, rel release.Releaser) error {
ToSql() ToSql()
if err != nil { if err != nil {
slog.Debug("failed to build update query", slog.Any("error", err)) s.Logger().Debug("failed to build update query", slog.Any("error", err))
return err return err
} }
if _, err := s.db.Exec(query, args...); err != nil { if _, err := s.db.Exec(query, args...); err != nil {
slog.Debug("failed to update release in SQL database", "key", key, slog.Any("error", err)) s.Logger().Debug("failed to update release in SQL database", "key", key, slog.Any("error", err))
return err return err
} }
@ -603,7 +607,7 @@ func (s *SQL) Update(key string, rel release.Releaser) error {
func (s *SQL) Delete(key string) (release.Releaser, error) { func (s *SQL) Delete(key string) (release.Releaser, error) {
transaction, err := s.db.Beginx() transaction, err := s.db.Beginx()
if err != nil { if err != nil {
slog.Debug("failed to start SQL transaction", slog.Any("error", err)) s.Logger().Debug("failed to start SQL transaction", slog.Any("error", err))
return nil, fmt.Errorf("error beginning transaction: %v", err) return nil, fmt.Errorf("error beginning transaction: %v", err)
} }
@ -614,20 +618,20 @@ func (s *SQL) Delete(key string) (release.Releaser, error) {
Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}).
ToSql() ToSql()
if err != nil { if err != nil {
slog.Debug("failed to build select query", slog.Any("error", err)) s.Logger().Debug("failed to build select query", slog.Any("error", err))
return nil, err return nil, err
} }
var record SQLReleaseWrapper var record SQLReleaseWrapper
err = transaction.Get(&record, selectQuery, args...) err = transaction.Get(&record, selectQuery, args...)
if err != nil { if err != nil {
slog.Debug("release not found", "key", key, slog.Any("error", err)) s.Logger().Debug("release not found", "key", key, slog.Any("error", err))
return nil, ErrReleaseNotFound return nil, ErrReleaseNotFound
} }
release, err := decodeRelease(record.Body) release, err := decodeRelease(record.Body)
if err != nil { if err != nil {
slog.Debug("failed to decode release", "key", key, slog.Any("error", err)) s.Logger().Debug("failed to decode release", "key", key, slog.Any("error", err))
transaction.Rollback() transaction.Rollback()
return nil, err return nil, err
} }
@ -639,18 +643,18 @@ func (s *SQL) Delete(key string) (release.Releaser, error) {
Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}).
ToSql() ToSql()
if err != nil { if err != nil {
slog.Debug("failed to build delete query", slog.Any("error", err)) s.Logger().Debug("failed to build delete query", slog.Any("error", err))
return nil, err return nil, err
} }
_, err = transaction.Exec(deleteQuery, args...) _, err = transaction.Exec(deleteQuery, args...)
if err != nil { if err != nil {
slog.Debug("failed perform delete query", slog.Any("error", err)) s.Logger().Debug("failed perform delete query", slog.Any("error", err))
return release, err return release, err
} }
if release.Labels, err = s.getReleaseCustomLabels(key, s.namespace); err != nil { if release.Labels, err = s.getReleaseCustomLabels(key, s.namespace); err != nil {
slog.Debug("failed to get release custom labels", "namespace", s.namespace, "key", key, slog.Any("error", err)) s.Logger().Debug("failed to get release custom labels", "namespace", s.namespace, "key", key, slog.Any("error", err))
return nil, err return nil, err
} }
@ -661,7 +665,7 @@ func (s *SQL) Delete(key string) (release.Releaser, error) {
ToSql() ToSql()
if err != nil { if err != nil {
slog.Debug("failed to build delete Labels query", slog.Any("error", err)) s.Logger().Debug("failed to build delete Labels query", slog.Any("error", err))
return nil, err return nil, err
} }
_, err = transaction.Exec(deleteCustomLabelsQuery, args...) _, err = transaction.Exec(deleteCustomLabelsQuery, args...)

@ -22,6 +22,7 @@ import (
"log/slog" "log/slog"
"strings" "strings"
"helm.sh/helm/v4/internal/logging"
"helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v4/pkg/release/common" "helm.sh/helm/v4/pkg/release/common"
rspb "helm.sh/helm/v4/pkg/release/v1" rspb "helm.sh/helm/v4/pkg/release/v1"
@ -44,13 +45,16 @@ type Storage struct {
// be retained, including the most recent release. Values of 0 or less are // be retained, including the most recent release. Values of 0 or less are
// ignored (meaning no limits are imposed). // ignored (meaning no limits are imposed).
MaxHistory int MaxHistory int
// Embed a LogHolder to provide logger functionality
logging.LogHolder
} }
// Get retrieves the release from storage. An error is returned // Get retrieves the release from storage. An error is returned
// if the storage driver failed to fetch the release, or the // if the storage driver failed to fetch the release, or the
// release identified by the key, version pair does not exist. // release identified by the key, version pair does not exist.
func (s *Storage) Get(name string, version int) (release.Releaser, error) { func (s *Storage) Get(name string, version int) (release.Releaser, error) {
slog.Debug("getting release", "key", makeKey(name, version)) s.Logger().Debug("getting release", "key", makeKey(name, version))
return s.Driver.Get(makeKey(name, version)) return s.Driver.Get(makeKey(name, version))
} }
@ -62,7 +66,7 @@ func (s *Storage) Create(rls release.Releaser) error {
if err != nil { if err != nil {
return err return err
} }
slog.Debug("creating release", "key", makeKey(rac.Name(), rac.Version())) s.Logger().Debug("creating release", "key", makeKey(rac.Name(), rac.Version()))
if s.MaxHistory > 0 { if s.MaxHistory > 0 {
// Want to make space for one more release. // Want to make space for one more release.
if err := s.removeLeastRecent(rac.Name(), s.MaxHistory-1); err != nil && if err := s.removeLeastRecent(rac.Name(), s.MaxHistory-1); err != nil &&
@ -81,7 +85,7 @@ func (s *Storage) Update(rls release.Releaser) error {
if err != nil { if err != nil {
return err return err
} }
slog.Debug("updating release", "key", makeKey(rac.Name(), rac.Version())) s.Logger().Debug("updating release", "key", makeKey(rac.Name(), rac.Version()))
return s.Driver.Update(makeKey(rac.Name(), rac.Version()), rls) return s.Driver.Update(makeKey(rac.Name(), rac.Version()), rls)
} }
@ -89,14 +93,14 @@ func (s *Storage) Update(rls release.Releaser) error {
// the storage backend fails to delete the release or if the release // the storage backend fails to delete the release or if the release
// does not exist. // does not exist.
func (s *Storage) Delete(name string, version int) (release.Releaser, error) { func (s *Storage) Delete(name string, version int) (release.Releaser, error) {
slog.Debug("deleting release", "key", makeKey(name, version)) s.Logger().Debug("deleting release", "key", makeKey(name, version))
return s.Driver.Delete(makeKey(name, version)) return s.Driver.Delete(makeKey(name, version))
} }
// ListReleases returns all releases from storage. An error is returned if the // ListReleases returns all releases from storage. An error is returned if the
// storage backend fails to retrieve the releases. // storage backend fails to retrieve the releases.
func (s *Storage) ListReleases() ([]release.Releaser, error) { func (s *Storage) ListReleases() ([]release.Releaser, error) {
slog.Debug("listing all releases in storage") s.Logger().Debug("listing all releases in storage")
return s.List(func(_ release.Releaser) bool { return true }) return s.List(func(_ release.Releaser) bool { return true })
} }
@ -118,13 +122,13 @@ func releaserToV1Release(rel release.Releaser) (*rspb.Release, error) {
// ListUninstalled returns all releases with Status == UNINSTALLED. An error is returned // ListUninstalled returns all releases with Status == UNINSTALLED. An error is returned
// if the storage backend fails to retrieve the releases. // if the storage backend fails to retrieve the releases.
func (s *Storage) ListUninstalled() ([]release.Releaser, error) { func (s *Storage) ListUninstalled() ([]release.Releaser, error) {
slog.Debug("listing uninstalled releases in storage") s.Logger().Debug("listing uninstalled releases in storage")
return s.List(func(rls release.Releaser) bool { return s.List(func(rls release.Releaser) bool {
rel, err := releaserToV1Release(rls) rel, err := releaserToV1Release(rls)
if err != nil { if err != nil {
// This will only happen if calling code does not pass the proper types. This is // This will only happen if calling code does not pass the proper types. This is
// a problem with the application and not user data. // a problem with the application and not user data.
slog.Error("unable to convert release to typed release", slog.Any("error", err)) s.Logger().Error("unable to convert release to typed release", slog.Any("error", err))
panic(fmt.Sprintf("unable to convert release to typed release: %s", err)) panic(fmt.Sprintf("unable to convert release to typed release: %s", err))
} }
return relutil.StatusFilter(common.StatusUninstalled).Check(rel) return relutil.StatusFilter(common.StatusUninstalled).Check(rel)
@ -134,13 +138,13 @@ func (s *Storage) ListUninstalled() ([]release.Releaser, error) {
// ListDeployed returns all releases with Status == DEPLOYED. An error is returned // ListDeployed returns all releases with Status == DEPLOYED. An error is returned
// if the storage backend fails to retrieve the releases. // if the storage backend fails to retrieve the releases.
func (s *Storage) ListDeployed() ([]release.Releaser, error) { func (s *Storage) ListDeployed() ([]release.Releaser, error) {
slog.Debug("listing all deployed releases in storage") s.Logger().Debug("listing all deployed releases in storage")
return s.List(func(rls release.Releaser) bool { return s.List(func(rls release.Releaser) bool {
rel, err := releaserToV1Release(rls) rel, err := releaserToV1Release(rls)
if err != nil { if err != nil {
// This will only happen if calling code does not pass the proper types. This is // This will only happen if calling code does not pass the proper types. This is
// a problem with the application and not user data. // a problem with the application and not user data.
slog.Error("unable to convert release to typed release", slog.Any("error", err)) s.Logger().Error("unable to convert release to typed release", slog.Any("error", err))
panic(fmt.Sprintf("unable to convert release to typed release: %s", err)) panic(fmt.Sprintf("unable to convert release to typed release: %s", err))
} }
return relutil.StatusFilter(common.StatusDeployed).Check(rel) return relutil.StatusFilter(common.StatusDeployed).Check(rel)
@ -187,7 +191,7 @@ func releaseListToV1List(ls []release.Releaser) ([]*rspb.Release, error) {
// DeployedAll returns all deployed releases with the provided name, or // DeployedAll returns all deployed releases with the provided name, or
// returns driver.NewErrNoDeployedReleases if not found. // returns driver.NewErrNoDeployedReleases if not found.
func (s *Storage) DeployedAll(name string) ([]release.Releaser, error) { func (s *Storage) DeployedAll(name string) ([]release.Releaser, error) {
slog.Debug("getting deployed releases", "name", name) s.Logger().Debug("getting deployed releases", "name", name)
ls, err := s.Query(map[string]string{ ls, err := s.Query(map[string]string{
"name": name, "name": name,
@ -206,7 +210,7 @@ func (s *Storage) DeployedAll(name string) ([]release.Releaser, error) {
// History returns the revision history for the release with the provided name, or // History returns the revision history for the release with the provided name, or
// returns driver.ErrReleaseNotFound if no such release name exists. // returns driver.ErrReleaseNotFound if no such release name exists.
func (s *Storage) History(name string) ([]release.Releaser, error) { func (s *Storage) History(name string) ([]release.Releaser, error) {
slog.Debug("getting release history", "name", name) s.Logger().Debug("getting release history", "name", name)
return s.Query(map[string]string{"name": name, "owner": "helm"}) return s.Query(map[string]string{"name": name, "owner": "helm"})
} }
@ -274,7 +278,7 @@ func (s *Storage) removeLeastRecent(name string, maximum int) error {
} }
} }
slog.Debug("pruned records", "count", len(toDelete), "release", name, "errors", len(errs)) s.Logger().Debug("pruned records", "count", len(toDelete), "release", name, "errors", len(errs))
switch c := len(errs); c { switch c := len(errs); c {
case 0: case 0:
return nil return nil
@ -289,7 +293,7 @@ func (s *Storage) deleteReleaseVersion(name string, version int) error {
key := makeKey(name, version) key := makeKey(name, version)
_, err := s.Delete(name, version) _, err := s.Delete(name, version)
if err != nil { if err != nil {
slog.Debug("error pruning release", "key", key, slog.Any("error", err)) s.Logger().Debug("error pruning release", "key", key, slog.Any("error", err))
return err return err
} }
return nil return nil
@ -297,7 +301,7 @@ func (s *Storage) deleteReleaseVersion(name string, version int) error {
// Last fetches the last revision of the named release. // Last fetches the last revision of the named release.
func (s *Storage) Last(name string) (release.Releaser, error) { func (s *Storage) Last(name string) (release.Releaser, error) {
slog.Debug("getting last revision", "name", name) s.Logger().Debug("getting last revision", "name", name)
h, err := s.History(name) h, err := s.History(name)
if err != nil { if err != nil {
return nil, err return nil, err
@ -331,7 +335,16 @@ func Init(d driver.Driver) *Storage {
if d == nil { if d == nil {
d = driver.NewMemory() d = driver.NewMemory()
} }
return &Storage{ s := &Storage{
Driver: d, Driver: d,
} }
// Get logger from driver if it implements the LoggerSetterGetter interface
if ls, ok := d.(logging.LoggerSetterGetter); ok {
ls.SetLogger(s.Logger().Handler())
} else {
// If the driver does not implement the LoggerSetterGetter interface, set the default logger
s.SetLogger(slog.Default().Handler())
}
return s
} }

Loading…
Cancel
Save