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

feat: reinstate logger parameter to actions package
pull/31440/head
Scott Rigby 2 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"
"log/slog"
"os"
"sync/atomic"
)
// 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)
}
// 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"
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
"helm.sh/helm/v4/internal/logging"
"helm.sh/helm/v4/pkg/chart/common"
chart "helm.sh/helm/v4/pkg/chart/v2"
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 func(namespace, pod, container string) io.Writer
// Mutex is an exclusive lock for concurrent access to the action
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 (
@ -376,8 +402,8 @@ func (cfg *Configuration) getCapabilities() (*common.Capabilities, error) {
apiVersions, err := GetVersionSet(dc)
if err != nil {
if discovery.IsGroupDiscoveryFailedError(err) {
slog.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("the kubernetes server has an orphaned API service", slog.Any("error", err))
cfg.Logger().Warn("to fix this, kubectl delete apiservice <service-name>")
} else {
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.
func (cfg *Configuration) recordRelease(r *release.Release) {
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
func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string) error {
kc := kube.New(getter)
kc.SetLogger(cfg.Logger().Handler())
lazyClient := &lazyClient{
namespace: namespace,
@ -493,9 +520,11 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp
switch helmDriver {
case "secret", "secrets", "":
d := driver.NewSecrets(newSecretClient(lazyClient))
d.SetLogger(cfg.Logger().Handler())
store = storage.Init(d)
case "configmap", "configmaps":
d := driver.NewConfigMaps(newConfigMapClient(lazyClient))
d.SetLogger(cfg.Logger().Handler())
store = storage.Init(d)
case "memory":
var d *driver.Memory
@ -510,6 +539,7 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp
if d == nil {
d = driver.NewMemory()
}
d.SetLogger(cfg.Logger().Handler())
d.SetNamespace(namespace)
store = storage.Init(d)
case "sql":
@ -520,6 +550,7 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp
if err != nil {
return fmt.Errorf("unable to instantiate SQL driver: %w", err)
}
d.SetLogger(cfg.Logger().Handler())
store = storage.Init(d)
default:
return fmt.Errorf("unknown driver %q", helmDriver)

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

@ -17,8 +17,6 @@ limitations under the License.
package action
import (
"log/slog"
"fmt"
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)
}
slog.Debug("getting history for release", "release", name)
h.cfg.Logger().Debug("getting history for release", "release", 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 apierrors.IsAlreadyExists(err) {
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
}
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
}
slog.Debug("clearing discovery cache")
i.cfg.Logger().Debug("clearing discovery cache")
discoveryClient.Invalidate()
_, _ = discoveryClient.ServerGroups()
@ -234,7 +234,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error {
return err
}
if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok {
slog.Debug("clearing REST mapper cache")
i.cfg.Logger().Debug("clearing REST mapper cache")
resettable.Reset()
}
}
@ -267,24 +267,24 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
if interactWithServer(i.DryRunStrategy) {
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)
}
}
// HideSecret must be used with dry run. Otherwise, return an error.
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")
}
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)
}
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)
}
@ -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 {
// On dry run, bail here
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 {
return nil, err
}
@ -313,7 +313,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
mem.SetNamespace(i.Namespace)
i.cfg.Releases = storage.Init(mem)
} 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
@ -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
// this stored in the future.
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
@ -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) {
rel.SetStatus(rcommon.StatusFailed, fmt.Sprintf("Release %q failed: %s", i.ReleaseName, err.Error()))
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.DisableHooks = i.DisableHooks
uninstall.KeepHistory = false

@ -19,7 +19,6 @@ package action
import (
"bytes"
"fmt"
"log/slog"
"strings"
"time"
@ -76,26 +75,26 @@ func (r *Rollback) Run(name string) error {
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)
if err != nil {
return err
}
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 {
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 {
return err
}
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 {
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)
}
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)
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) {
if isDryRun(r.DryRunStrategy) {
slog.Debug("dry run", "name", targetRelease.Name)
r.cfg.Logger().Debug("dry run", "name", targetRelease.Name)
return targetRelease, nil
}
@ -214,7 +213,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
return targetRelease, err
}
} 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.
@ -232,21 +231,21 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
if err != nil {
msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err)
slog.Warn(msg)
r.cfg.Logger().Warn(msg)
currentRelease.Info.Status = common.StatusSuperseded
targetRelease.Info.Status = common.StatusFailed
targetRelease.Info.Description = msg
r.cfg.recordRelease(currentRelease)
r.cfg.recordRelease(targetRelease)
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)
if errs != nil {
return targetRelease, fmt.Errorf(
"an error occurred while cleaning up resources. original rollback error: %w",
fmt.Errorf("unable to cleanup resources: %w", joinErrors(errs, ", ")))
}
slog.Debug("resource cleanup complete")
r.cfg.Logger().Debug("resource cleanup complete")
}
return targetRelease, err
}
@ -288,7 +287,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
if err != nil {
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
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)
}
slog.Debug("uninstall: deleting release", "name", name)
u.cfg.Logger().Debug("uninstall: deleting release", "name", name)
rel.Info.Status = common.StatusUninstalling
rel.Info.Deleted = time.Now()
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
}
} 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
// state.
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)
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)
}
@ -170,7 +170,7 @@ func (u *Uninstall) Run(name string) (*releasei.UninstallReleaseResponse, error)
}
if !u.KeepHistory {
slog.Debug("purge requested", "release", name)
u.cfg.Logger().Debug("purge requested", "release", name)
err := u.purgeReleases(rels...)
if err != nil {
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 {
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 {

@ -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)
}
slog.Debug("preparing upgrade", "name", name)
u.cfg.Logger().Debug("preparing upgrade", "name", name)
currentRelease, upgradedRelease, serverSideApply, err := u.prepareUpgrade(name, chrt, vals)
if err != nil {
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
slog.Debug("performing update", "name", name)
u.cfg.Logger().Debug("performing update", "name", name)
res, err := u.performUpgrade(ctx, currentRelease, upgradedRelease, serverSideApply)
if err != nil {
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
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 {
return res, err
}
@ -308,7 +308,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[str
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.
upgradedRelease := &release.Release{
@ -391,7 +391,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR
})
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 {
upgradedRelease.Info.Description = u.Description
} else {
@ -400,7 +400,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR
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 {
return nil, err
}
@ -457,7 +457,7 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele
return
}
} 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
@ -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) {
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.Description = msg
u.cfg.recordRelease(rel)
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)
if errs != nil {
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 {
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.
// 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) {
if u.ResetValues {
// 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
}
// If the ReuseValues flag is set, we always copy the old values over the new config's values.
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:
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 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)
@ -623,7 +623,7 @@ func (u *Upgrade) reuseValues(chart *chartv2.Chart, current *release.Release, ne
}
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
}
return newVals, nil

@ -103,7 +103,7 @@ By default, the default directories depend on the Operating System. The defaults
var settings = cli.New()
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)
if err != nil {
return nil, err

@ -39,6 +39,8 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"helm.sh/helm/v4/internal/logging"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -84,6 +86,9 @@ type Client struct {
Waiter
kubeClient kubernetes.Interface
// Embed a LogHolder to provide logger functionality
logging.LogHolder
}
var _ Interface = (*Client)(nil)
@ -177,6 +182,7 @@ func New(getter genericclioptions.RESTClientGetter) *Client {
c := &Client{
Factory: factory,
}
c.SetLogger(slog.Default().Handler())
return c
}
@ -258,7 +264,7 @@ func ClientCreateOptionFieldValidationDirective(fieldValidationDirective FieldVa
// Create creates Kubernetes resources specified in the resource list.
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{
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 {
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 {
err := patchResourceServerSide(target, createOptions.dryRun, createOptions.forceConflicts, createOptions.fieldValidationDirective)
logger := slog.With(
logger := c.Logger().With(
slog.String("namespace", target.Namespace),
slog.String("name", target.Name),
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
}
@ -349,7 +355,7 @@ func (c *Client) Get(resources ResourceList, related bool) (map[string][]runtime
objs, err = c.getSelectRelationPod(info, objs, isTable, &podSelectors)
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 {
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)
if !ok {
return objs, nil
@ -504,7 +510,7 @@ func (c *Client) update(originals, targets ResourceList, updateApplyFunc UpdateA
updateErrors := []error{}
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 {
if err != nil {
return err
@ -525,7 +531,7 @@ func (c *Client) update(originals, targets ResourceList, updateApplyFunc UpdateA
}
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
}
@ -553,22 +559,22 @@ func (c *Client) update(originals, targets ResourceList, updateApplyFunc UpdateA
}
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 {
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
}
annotations, err := metadataAccessor.Annotations(info.Object)
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 {
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
}
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) {
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 {
if updateOptions.forceReplace {
slog.Debug(
c.Logger().Debug(
"using resource replace update strategy",
slog.String("fieldValidationDirective", string(updateOptions.fieldValidationDirective)))
return func(original, target *resource.Info) error {
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
}
originalObject := original.Object
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
}
} else if updateOptions.serverSideApply {
slog.Debug(
c.Logger().Debug(
"using server-side apply for resource update",
slog.Bool("forceConflicts", updateOptions.forceConflicts),
slog.Bool("dryRun", updateOptions.dryRun),
@ -737,7 +743,7 @@ func (c *Client) Update(originals, targets ResourceList, options ...ClientUpdate
slog.Bool("upgradeClientSideFieldManager", updateOptions.upgradeClientSideFieldManager))
return func(original, target *resource.Info) error {
logger := slog.With(
logger := c.Logger().With(
slog.String("namespace", target.Namespace),
slog.String("name", target.Name),
slog.String("gvk", target.Mapping.GroupVersionKind.String()))
@ -745,7 +751,7 @@ func (c *Client) Update(originals, targets ResourceList, options ...ClientUpdate
if updateOptions.upgradeClientSideFieldManager {
patched, err := upgradeClientSideFieldManager(original, updateOptions.dryRun, updateOptions.fieldValidationDirective)
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
}
@ -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 patchResourceClientSide(original.Object, target, updateOptions.threeWayMergeForUnstructured)
}
@ -783,11 +789,11 @@ func (c *Client) Delete(resources ResourceList, policy metav1.DeletionPropagatio
res := &Result{}
mtx := sync.Mutex{}
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)
if err == nil || apierrors.IsNotFound(err) {
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()
defer mtx.Unlock()

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

@ -17,10 +17,12 @@ limitations under the License.
package driver
import (
"log/slog"
"strconv"
"strings"
"sync"
"helm.sh/helm/v4/internal/logging"
"helm.sh/helm/v4/pkg/release"
)
@ -42,11 +44,15 @@ type Memory struct {
namespace string
// A map of namespaces to releases
cache map[string]memReleases
// Embed a LogHolder to provide logger functionality
logging.LogHolder
}
// NewMemory initializes a new memory driver.
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.

@ -31,6 +31,7 @@ import (
"k8s.io/apimachinery/pkg/util/validation"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"helm.sh/helm/v4/internal/logging"
"helm.sh/helm/v4/pkg/release"
rspb "helm.sh/helm/v4/pkg/release/v1"
)
@ -44,14 +45,18 @@ const SecretsDriverName = "Secret"
// SecretsInterface.
type Secrets struct {
impl corev1.SecretInterface
// Embed a LogHolder to provide logger functionality
logging.LogHolder
}
// NewSecrets initializes a new Secrets wrapping an implementation of
// the kubernetes SecretsInterface.
func NewSecrets(impl corev1.SecretInterface) *Secrets {
return &Secrets{
s := &Secrets{
impl: impl,
}
s.SetLogger(slog.Default().Handler())
return s
}
// 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 {
rls, err := decodeRelease(string(item.Data["release"]))
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
}
@ -137,7 +142,7 @@ func (secrets *Secrets) Query(labels map[string]string) ([]release.Releaser, err
for _, item := range list.Items {
rls, err := decodeRelease(string(item.Data["release"]))
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
}
rls.Labels = item.Labels

@ -32,6 +32,7 @@ import (
// Import pq for postgres dialect
_ "github.com/lib/pq"
"helm.sh/helm/v4/internal/logging"
"helm.sh/helm/v4/pkg/release"
rspb "helm.sh/helm/v4/pkg/release/v1"
)
@ -89,6 +90,8 @@ type SQL struct {
db *sqlx.DB
namespace string
statementBuilder sq.StatementBuilderType
// Embed a LogHolder to provide logger functionality
logging.LogHolder
}
// 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)
migrate.SetDisableCreateTable(false)
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
}
for _, record := range records {
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)
}
}
@ -123,7 +126,7 @@ func (s *SQL) checkAlreadyApplied(migrations []*migrate.Migration) bool {
// check if all migrations applied
if len(migrationsIDs) != 0 {
for id := range migrationsIDs {
slog.Debug("find unapplied migration", "id", id)
s.Logger().Debug("find unapplied migration", "id", id)
}
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);
GRANT ALL ON %s TO PUBLIC;
ALTER TABLE %s ENABLE ROW LEVEL SECURITY;
`,
sqlReleaseTableName,
@ -209,7 +212,7 @@ func (s *SQL) ensureDBSetup() error {
%s VARCHAR(%d)
);
CREATE INDEX ON %s (%s, %s);
GRANT ALL ON %s TO PUBLIC;
ALTER TABLE %s ENABLE ROW LEVEL SECURITY;
`,
@ -293,6 +296,7 @@ func NewSQL(connectionString string, namespace string) (*SQL, error) {
}
driver.namespace = namespace
driver.SetLogger(slog.Default().Handler())
return driver, nil
}
@ -309,24 +313,24 @@ func (s *SQL) Get(key string) (release.Releaser, error) {
query, args, err := qb.ToSql()
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
}
// Get will return an error if the result is empty
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
}
release, err := decodeRelease(record.Body)
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
}
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
}
@ -347,13 +351,13 @@ func (s *SQL) List(filter func(release.Releaser) bool) ([]release.Releaser, erro
query, args, err := sb.ToSql()
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
}
var records = []SQLReleaseWrapper{}
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
}
@ -361,12 +365,12 @@ func (s *SQL) List(filter func(release.Releaser) bool) ([]release.Releaser, erro
for _, record := range records {
release, err := decodeRelease(record.Body)
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
}
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
}
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 {
sb = sb.Where(sq.Eq{key: labels[key]})
} else {
slog.Debug("unknown label", "key", key)
s.Logger().Debug("unknown label", "key", 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
query, args, err := sb.ToSql()
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
}
var records = []SQLReleaseWrapper{}
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
}
@ -425,12 +429,12 @@ func (s *SQL) Query(labels map[string]string) ([]release.Releaser, error) {
for _, record := range records {
release, err := decodeRelease(record.Body)
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
}
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
}
@ -459,13 +463,13 @@ func (s *SQL) Create(key string, rel release.Releaser) error {
body, err := encodeRelease(rls)
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
}
transaction, err := s.db.Beginx()
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)
}
@ -494,7 +498,7 @@ func (s *SQL) Create(key string, rel release.Releaser) error {
int(time.Now().Unix()),
).ToSql()
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
}
@ -508,17 +512,17 @@ func (s *SQL) Create(key string, rel release.Releaser) error {
Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}).
ToSql()
if buildErr != nil {
slog.Debug("failed to build select query", "error", buildErr)
s.Logger().Debug("failed to build select query", "error", buildErr)
return err
}
var record SQLReleaseWrapper
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
}
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
}
@ -541,13 +545,13 @@ func (s *SQL) Create(key string, rel release.Releaser) error {
if err != nil {
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
}
if _, err := transaction.Exec(insertLabelsQuery, args...); err != nil {
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
}
}
@ -570,7 +574,7 @@ func (s *SQL) Update(key string, rel release.Releaser) error {
body, err := encodeRelease(rls)
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
}
@ -587,12 +591,12 @@ func (s *SQL) Update(key string, rel release.Releaser) error {
ToSql()
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
}
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
}
@ -603,7 +607,7 @@ func (s *SQL) Update(key string, rel release.Releaser) error {
func (s *SQL) Delete(key string) (release.Releaser, error) {
transaction, err := s.db.Beginx()
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)
}
@ -614,20 +618,20 @@ func (s *SQL) Delete(key string) (release.Releaser, error) {
Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}).
ToSql()
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
}
var record SQLReleaseWrapper
err = transaction.Get(&record, selectQuery, args...)
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
}
release, err := decodeRelease(record.Body)
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()
return nil, err
}
@ -639,18 +643,18 @@ func (s *SQL) Delete(key string) (release.Releaser, error) {
Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}).
ToSql()
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
}
_, err = transaction.Exec(deleteQuery, args...)
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
}
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
}
@ -661,7 +665,7 @@ func (s *SQL) Delete(key string) (release.Releaser, error) {
ToSql()
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
}
_, err = transaction.Exec(deleteCustomLabelsQuery, args...)

@ -22,6 +22,7 @@ import (
"log/slog"
"strings"
"helm.sh/helm/v4/internal/logging"
"helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v4/pkg/release/common"
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
// ignored (meaning no limits are imposed).
MaxHistory int
// Embed a LogHolder to provide logger functionality
logging.LogHolder
}
// Get retrieves the release from storage. An error is returned
// if the storage driver failed to fetch the release, or the
// release identified by the key, version pair does not exist.
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))
}
@ -62,7 +66,7 @@ func (s *Storage) Create(rls release.Releaser) error {
if err != nil {
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 {
// Want to make space for one more release.
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 {
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)
}
@ -89,14 +93,14 @@ func (s *Storage) Update(rls release.Releaser) error {
// the storage backend fails to delete the release or if the release
// does not exist.
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))
}
// ListReleases returns all releases from storage. An error is returned if the
// storage backend fails to retrieve the releases.
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 })
}
@ -118,13 +122,13 @@ func releaserToV1Release(rel release.Releaser) (*rspb.Release, error) {
// ListUninstalled returns all releases with Status == UNINSTALLED. An error is returned
// if the storage backend fails to retrieve the releases.
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 {
rel, err := releaserToV1Release(rls)
if err != nil {
// This will only happen if calling code does not pass the proper types. This is
// 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))
}
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
// if the storage backend fails to retrieve the releases.
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 {
rel, err := releaserToV1Release(rls)
if err != nil {
// This will only happen if calling code does not pass the proper types. This is
// 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))
}
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
// returns driver.NewErrNoDeployedReleases if not found.
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{
"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
// returns driver.ErrReleaseNotFound if no such release name exists.
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"})
}
@ -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 {
case 0:
return nil
@ -289,7 +293,7 @@ func (s *Storage) deleteReleaseVersion(name string, version int) error {
key := makeKey(name, version)
_, err := s.Delete(name, version)
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 nil
@ -297,7 +301,7 @@ func (s *Storage) deleteReleaseVersion(name string, version int) error {
// Last fetches the last revision of the named release.
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)
if err != nil {
return nil, err
@ -331,7 +335,16 @@ func Init(d driver.Driver) *Storage {
if d == nil {
d = driver.NewMemory()
}
return &Storage{
s := &Storage{
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