feat: reinstate logger parameter to actions package

Fixes: #31399

Signed-off-by: Evans Mungai <mbuevans@gmail.com>
pull/31411/head
Evans Mungai 11 months ago
parent 490dffeb34
commit b1d4dc680d
No known key found for this signature in database
GPG Key ID: BBEB812143DD14E1

@ -29,6 +29,7 @@ import (
"slices" "slices"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"text/template" "text/template"
"time" "time"
@ -109,9 +110,19 @@ 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
// logger is an slog.Logger pointer to use with the Configuration instance
logger atomic.Pointer[slog.Logger]
// Mutex is an exclusive lock for concurrent access to the action
mutex sync.Mutex mutex sync.Mutex
} }
func NewConfiguration() *Configuration {
c := &Configuration{}
c.SetLogger(slog.Default())
return c
}
const ( const (
// filenameAnnotation is the annotation key used to store the original filename // filenameAnnotation is the annotation key used to store the original filename
// information in manifest annotations for post-rendering reconstruction. // information in manifest annotations for post-rendering reconstruction.
@ -376,8 +387,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 +487,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())
lazyClient := &lazyClient{ lazyClient := &lazyClient{
namespace: namespace, namespace: namespace,
@ -493,9 +505,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())
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())
store = storage.Init(d) store = storage.Init(d)
case "memory": case "memory":
var d *driver.Memory var d *driver.Memory
@ -510,11 +524,13 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp
if d == nil { if d == nil {
d = driver.NewMemory() d = driver.NewMemory()
} }
d.SetLogger(cfg.Logger())
d.SetNamespace(namespace) d.SetNamespace(namespace)
store = storage.Init(d) store = storage.Init(d)
case "sql": case "sql":
d, err := driver.NewSQL( d, err := driver.NewSQL(
os.Getenv("HELM_DRIVER_SQL_CONNECTION_STRING"), os.Getenv("HELM_DRIVER_SQL_CONNECTION_STRING"),
cfg.Logger(),
namespace, namespace,
) )
if err != nil { if err != nil {
@ -528,6 +544,7 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp
cfg.RESTClientGetter = getter cfg.RESTClientGetter = getter
cfg.KubeClient = kc cfg.KubeClient = kc
cfg.Releases = store cfg.Releases = store
cfg.Releases.SetLogger(cfg.Logger())
cfg.HookOutputFunc = func(_, _, _ string) io.Writer { return io.Discard } cfg.HookOutputFunc = func(_, _, _ string) io.Writer { return io.Discard }
return nil return nil
@ -538,6 +555,24 @@ func (cfg *Configuration) SetHookOutputFunc(hookOutputFunc func(_, _, _ string)
cfg.HookOutputFunc = hookOutputFunc cfg.HookOutputFunc = hookOutputFunc
} }
// Logger returns the logger for the Configuration. If nil, returns slog.Default().
func (cfg *Configuration) Logger() *slog.Logger {
if lg := cfg.logger.Load(); lg != nil {
return lg
}
return slog.Default() // We rarely get here, just be defensive
}
// SetLogger sets the logger for the Configuration. If nil, sets the default logger.
func (cfg *Configuration) SetLogger(newLogger *slog.Logger) {
// Only set logger if it's currently nil
if newLogger == nil {
cfg.logger.Store(slog.Default()) // We never want to set the logger to nil
return
}
cfg.logger.Store(newLogger)
}
func determineReleaseSSApplyMethod(serverSideApply bool) release.ApplyMethod { func determineReleaseSSApplyMethod(serverSideApply bool) release.ApplyMethod {
if serverSideApply { if serverSideApply {
return release.ApplyMethodServerSideApply return release.ApplyMethodServerSideApply

@ -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
@ -594,13 +594,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)
@ -617,7 +617,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)
@ -625,7 +625,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

@ -30,6 +30,7 @@ import (
"reflect" "reflect"
"strings" "strings"
"sync" "sync"
"sync/atomic"
jsonpatch "github.com/evanphx/json-patch/v5" jsonpatch "github.com/evanphx/json-patch/v5"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
@ -81,6 +82,8 @@ type Client struct {
Factory Factory Factory Factory
// Namespace allows to bypass the kubeconfig file for the choice of the namespace // Namespace allows to bypass the kubeconfig file for the choice of the namespace
Namespace string Namespace string
// logger is an slog.Logger pointer to use with the kube client
logger atomic.Pointer[slog.Logger]
Waiter Waiter
kubeClient kubernetes.Interface kubeClient kubernetes.Interface
@ -177,6 +180,7 @@ func New(getter genericclioptions.RESTClientGetter) *Client {
c := &Client{ c := &Client{
Factory: factory, Factory: factory,
} }
c.SetLogger(slog.Default())
return c return c
} }
@ -258,7 +262,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 +279,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 +298,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 +353,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 +371,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 +508,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 +529,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 +557,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))
continue continue
} }
res.Deleted = append(res.Deleted, info) res.Deleted = append(res.Deleted, info)
@ -706,23 +710,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),
@ -730,7 +734,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()))
@ -738,7 +742,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
} }
@ -758,7 +762,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)
} }
@ -776,11 +780,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()
@ -1187,3 +1191,20 @@ func (e *joinedErrors) Error() string {
func (e *joinedErrors) Unwrap() []error { func (e *joinedErrors) Unwrap() []error {
return e.errs return e.errs
} }
// logger returns the logger for the Client. If nil, returns slog.Default().
func (c *Client) Logger() *slog.Logger {
if lg := c.logger.Load(); lg != nil {
return lg
}
return slog.Default() // We rarely get here, just be defensive
}
func (c *Client) SetLogger(newLogger *slog.Logger) {
// Only set logger if it's currently nil
if newLogger == nil {
c.logger.Store(slog.Default()) // We never want to set the logger to nil
return
}
c.logger.Store(newLogger)
}

@ -22,6 +22,7 @@ import (
"log/slog" "log/slog"
"strconv" "strconv"
"strings" "strings"
"sync/atomic"
"time" "time"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
@ -44,14 +45,18 @@ const ConfigMapsDriverName = "ConfigMap"
// ConfigMapsInterface. // ConfigMapsInterface.
type ConfigMaps struct { type ConfigMaps struct {
impl corev1.ConfigMapInterface impl corev1.ConfigMapInterface
// logger is an slog.Logger pointer to use the driver
logger atomic.Pointer[slog.Logger]
} }
// 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())
return c
} }
// Name returns the name of the driver. // Name returns the name of the driver.
@ -69,13 +74,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 +97,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 +108,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 +136,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 +148,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 +180,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 +189,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 +213,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
@ -276,3 +281,20 @@ func newConfigMapsObject(key string, rls *rspb.Release, lbs labels) (*v1.ConfigM
Data: map[string]string{"release": s}, Data: map[string]string{"release": s},
}, nil }, nil
} }
// logger returns the logger for the ConfigMaps driver. If nil, returns slog.Default().
func (cfgmaps *ConfigMaps) Logger() *slog.Logger {
if lg := cfgmaps.logger.Load(); lg != nil {
return lg
}
return slog.Default() // We rarely get here, just be defensive
}
func (cfgmaps *ConfigMaps) SetLogger(newLogger *slog.Logger) {
// Only set logger if it's currently nil
if newLogger == nil {
cfgmaps.logger.Store(slog.Default()) // We never want to set the logger to nil
return
}
cfgmaps.logger.Store(newLogger)
}

@ -17,9 +17,11 @@ limitations under the License.
package driver package driver
import ( import (
"log/slog"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"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
// logger is an slog.Logger pointer to use the driver
logger atomic.Pointer[slog.Logger]
} }
// 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())
return m
} }
// SetNamespace sets a specific namespace in which releases will be accessed. // SetNamespace sets a specific namespace in which releases will be accessed.
@ -247,3 +253,19 @@ func (mem *Memory) rlock() func() {
// ```defer unlock(mem.rlock())```, locks mem for reading at the // ```defer unlock(mem.rlock())```, locks mem for reading at the
// call point of defer and unlocks upon exiting the block. // call point of defer and unlocks upon exiting the block.
func unlock(fn func()) { fn() } func unlock(fn func()) { fn() }
func (mem *Memory) Logger() *slog.Logger {
if lg := mem.logger.Load(); lg != nil {
return lg
}
return slog.Default() // We rarely get here, just be defensive
}
func (mem *Memory) SetLogger(newLogger *slog.Logger) {
// Only set logger if it's currently nil
if newLogger == nil {
mem.logger.Store(slog.Default()) // We never want to set the logger to nil
return
}
mem.logger.Store(newLogger)
}

@ -22,6 +22,7 @@ import (
"log/slog" "log/slog"
"strconv" "strconv"
"strings" "strings"
"sync/atomic"
"time" "time"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
@ -44,14 +45,18 @@ const SecretsDriverName = "Secret"
// SecretsInterface. // SecretsInterface.
type Secrets struct { type Secrets struct {
impl corev1.SecretInterface impl corev1.SecretInterface
// logger is an slog.Logger pointer to use the driver
logger atomic.Pointer[slog.Logger]
} }
// 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())
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
@ -273,3 +278,20 @@ func newSecretsObject(key string, rls *rspb.Release, lbs labels) (*v1.Secret, er
Data: map[string][]byte{"release": []byte(s)}, Data: map[string][]byte{"release": []byte(s)},
}, nil }, nil
} }
// logger returns the logger for the Secrets driver. If nil, returns slog.Default().
func (secrets *Secrets) Logger() *slog.Logger {
if lg := secrets.logger.Load(); lg != nil {
return lg
}
return slog.Default() // We rarely get here, just be defensive
}
func (secrets *Secrets) SetLogger(newLogger *slog.Logger) {
// Only set logger if it's currently nil
if newLogger == nil {
secrets.logger.Store(slog.Default()) // We never want to set the logger to nil
return
}
secrets.logger.Store(newLogger)
}

@ -22,6 +22,7 @@ import (
"maps" "maps"
"sort" "sort"
"strconv" "strconv"
"sync/atomic"
"time" "time"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
@ -89,6 +90,8 @@ type SQL struct {
db *sqlx.DB db *sqlx.DB
namespace string namespace string
statementBuilder sq.StatementBuilderType statementBuilder sq.StatementBuilderType
// logger is an slog.Logger pointer to use the driver
logger atomic.Pointer[slog.Logger]
} }
// 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;
`, `,
@ -277,7 +280,7 @@ type SQLReleaseCustomLabelWrapper struct {
} }
// NewSQL initializes a new sql driver. // NewSQL initializes a new sql driver.
func NewSQL(connectionString string, namespace string) (*SQL, error) { func NewSQL(connectionString string, logger *slog.Logger, namespace string) (*SQL, error) {
db, err := sqlx.Connect(postgreSQLDialect, connectionString) db, err := sqlx.Connect(postgreSQLDialect, connectionString)
if err != nil { if err != nil {
return nil, err return nil, err
@ -293,6 +296,7 @@ func NewSQL(connectionString string, namespace string) (*SQL, error) {
} }
driver.namespace = namespace driver.namespace = namespace
driver.SetLogger(logger)
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...)
@ -702,3 +706,20 @@ func getReleaseSystemLabels(rls *rspb.Release) map[string]string {
"version": strconv.Itoa(rls.Version), "version": strconv.Itoa(rls.Version),
} }
} }
// logger returns the logger for the SQL driver. If nil, returns slog.Default().
func (s *SQL) Logger() *slog.Logger {
if lg := s.logger.Load(); lg != nil {
return lg
}
return slog.Default() // We rarely get here, just be defensive
}
func (s *SQL) SetLogger(newLogger *slog.Logger) {
// Only set logger if it's currently nil
if newLogger == nil {
s.logger.Store(slog.Default()) // We never want to set the logger to nil
return
}
s.logger.Store(newLogger)
}

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"log/slog" "log/slog"
"strings" "strings"
"sync/atomic"
"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"
@ -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
// logger is an slog.Logger pointer to use the storage engine
logger atomic.Pointer[slog.Logger]
} }
// 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,26 @@ 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,
} }
s.SetLogger(slog.Default())
return s
}
// logger returns the logger for the Storage. If nil, returns slog.Default().
func (s *Storage) Logger() *slog.Logger {
if lg := s.logger.Load(); lg != nil {
return lg
}
return slog.Default() // We rarely get here, just be defensive
}
func (s *Storage) SetLogger(newLogger *slog.Logger) {
// Only set logger if it's currently nil
if newLogger == nil {
s.logger.Store(slog.Default()) // We never want to set the logger to nil
return
}
s.logger.Store(newLogger)
} }

Loading…
Cancel
Save