Repository: helm/helm
Pull request #30696 (2025-03-21 09:00:20)

Inform about time spent waiting resources to be ready in slog format
<!--  Thanks for sending a pull request!  Here are some tips for you:
1. Make sure to read the Contributing Guide before submitting your PR: https://github.com/helm/helm/blob/main/CONTRIBUTING.md
2. If this PR closes another issue, add 'closes #<issue number>' somewhere in the PR summary. GitHub will automatically close that issue when this PR gets merged. Alternatively, adding 'refs #<issue number>' will not close the issue, but help provide the reviewer more context.-->

**What this PR does / why we need it**:

This is the version of https://github.com/helm/helm/pull/30689 (merged) but for main/helm-v4. As started in https://github.com/helm/helm/pull/30603, @robertsirc is migrating log to slog. I migrated this function to use slog and added debug reporting. Now, it logs how long we wait for resources to be ready. This helps us better understand how much of the helm upgrade time is due to waiting for resources.

I could change the remaning part in the file but I am not sure what are expected in terms of log level

**Special notes for your reviewer**:

**If applicable**:
- [ ] this PR contains user facing changes (the `docs needed` label should be applied if so)
- [ ] this PR contains unit tests
- [ ] this PR has been tested for backwards compatibility



Patch:
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
index 6fbbd2c53..0d11cd531 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -16,7 +16,7 @@ jobs:
         uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2
 
       - name: Setup Go
-        uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # pin@5.4.0
+        uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # pin@5.3.0
         with:
           go-version: '1.23'
           check-latest: true
diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml
index b376c7b8e..f8572f2d6 100644
--- a/.github/workflows/govulncheck.yml
+++ b/.github/workflows/govulncheck.yml
@@ -14,7 +14,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Setup Go
-        uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # pin@5.4.0
+        uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # pin@5.3.0
         with:
           go-version: '1.23'
           check-latest: true
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 63e5c0e26..c5e7c6840 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -25,7 +25,7 @@ jobs:
           fetch-depth: 0
 
       - name: Setup Go
-        uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # pin@5.4.0
+        uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # pin@5.3.0
         with:
           go-version: '1.23'
 
@@ -81,7 +81,7 @@ jobs:
         uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2
 
       - name: Setup Go
-        uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # pin@5.4.0
+        uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # pin@5.3.0
         with:
           go-version: '1.23'
           check-latest: true
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index a8c2e8a15..f89fcd98c 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -55,7 +55,7 @@ jobs:
       # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
       # format to the repository Actions tab.
       - name: "Upload artifact"
-        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+        uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
         with:
           name: SARIF file
           path: results.sarif
diff --git a/go.mod b/go.mod
index 0589b84e9..723cfc769 100644
--- a/go.mod
+++ b/go.mod
@@ -11,6 +11,7 @@ require (
 	github.com/Masterminds/squirrel v1.5.4
 	github.com/Masterminds/vcs v1.13.3
 	github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
+	github.com/containerd/containerd v1.7.27
 	github.com/cyphar/filepath-securejoin v0.4.1
 	github.com/distribution/distribution/v3 v3.0.0-rc.3
 	github.com/evanphx/json-patch v5.9.11+incompatible
@@ -59,6 +60,9 @@ require (
 	github.com/cenkalti/backoff/v4 v4.3.0 // indirect
 	github.com/cespare/xxhash/v2 v2.3.0 // indirect
 	github.com/chai2010/gettext-go v1.0.2 // indirect
+	github.com/containerd/errdefs v0.3.0 // indirect
+	github.com/containerd/log v0.1.0 // indirect
+	github.com/containerd/platforms v0.2.1 // indirect
 	github.com/coreos/go-systemd/v22 v22.5.0 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
diff --git a/go.sum b/go.sum
index 20dd5c0b9..b0e35d8b9 100644
--- a/go.sum
+++ b/go.sum
@@ -48,6 +48,14 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk=
 github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=
+github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII=
+github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0=
+github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4=
+github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
+github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
+github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
+github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
+github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
 github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
diff --git a/internal/sympath/walk.go b/internal/sympath/walk.go
index 0cd258d39..6b221fb6c 100644
--- a/internal/sympath/walk.go
+++ b/internal/sympath/walk.go
@@ -21,7 +21,7 @@ limitations under the License.
 package sympath
 
 import (
-	"log/slog"
+	"log"
 	"os"
 	"path/filepath"
 	"sort"
@@ -72,7 +72,7 @@ func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
 			return errors.Wrapf(err, "error evaluating symlink %s", path)
 		}
 		//This log message is to highlight a symlink that is being used within a chart, symlinks can be used for nefarious reasons.
-		slog.Info("found symbolic link in path. Contents of linked file included and used", "path", path, "resolved", resolved)
+		log.Printf("found symbolic link in path: %s resolves to %s. Contents of linked file included and used", path, resolved)
 		if info, err = os.Lstat(resolved); err != nil {
 			return err
 		}
diff --git a/pkg/chart/v2/util/dependencies.go b/pkg/chart/v2/util/dependencies.go
index 72a08b2a9..78ed46517 100644
--- a/pkg/chart/v2/util/dependencies.go
+++ b/pkg/chart/v2/util/dependencies.go
@@ -16,7 +16,7 @@ limitations under the License.
 package util
 
 import (
-	"log/slog"
+	"log"
 	"strings"
 
 	"github.com/mitchellh/copystructure"
@@ -48,10 +48,10 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals Values, cpath s
 						r.Enabled = bv
 						break
 					}
-					slog.Warn("returned non-bool value", "path", c, "chart", r.Name)
+					log.Printf("Warning: Condition path '%s' for chart %s returned non-bool value", c, r.Name)
 				} else if _, ok := err.(ErrNoValue); !ok {
 					// this is a real error
-					slog.Warn("the method PathValue returned error", slog.Any("error", err))
+					log.Printf("Warning: PathValue returned error %v", err)
 				}
 			}
 		}
@@ -79,7 +79,7 @@ func processDependencyTags(reqs []*chart.Dependency, cvals Values) {
 						hasFalse = true
 					}
 				} else {
-					slog.Warn("returned non-bool value", "tag", k, "chart", r.Name)
+					log.Printf("Warning: Tag '%s' for chart %s returned non-bool value", k, r.Name)
 				}
 			}
 		}
@@ -254,7 +254,7 @@ func processImportValues(c *chart.Chart, merge bool) error {
 				// get child table
 				vv, err := cvals.Table(r.Name + "." + child)
 				if err != nil {
-					slog.Warn("ImportValues missing table from chart", "chart", r.Name, "error", err)
+					log.Printf("Warning: ImportValues missing table from chart %s: %v", r.Name, err)
 					continue
 				}
 				// create value map from child to be merged into parent
@@ -271,7 +271,7 @@ func processImportValues(c *chart.Chart, merge bool) error {
 				})
 				vm, err := cvals.Table(r.Name + "." + child)
 				if err != nil {
-					slog.Warn("ImportValues missing table", slog.Any("error", err))
+					log.Printf("Warning: ImportValues missing table: %v", err)
 					continue
 				}
 				if merge {
diff --git a/pkg/cmd/repo_update.go b/pkg/cmd/repo_update.go
index 6590d9872..25071377b 100644
--- a/pkg/cmd/repo_update.go
+++ b/pkg/cmd/repo_update.go
@@ -42,10 +42,11 @@ To update all the repositories, use 'helm repo update'.
 var errNoRepositories = errors.New("no repositories found. You must add one before updating")
 
 type repoUpdateOptions struct {
-	update    func([]*repo.ChartRepository, io.Writer) error
-	repoFile  string
-	repoCache string
-	names     []string
+	update               func([]*repo.ChartRepository, io.Writer, bool) error
+	repoFile             string
+	repoCache            string
+	names                []string
+	failOnRepoUpdateFail bool
 }
 
 func newRepoUpdateCmd(out io.Writer) *cobra.Command {
@@ -68,6 +69,12 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command {
 		},
 	}
 
+	f := cmd.Flags()
+
+	// Adding this flag for Helm 3 as stop gap functionality for https://github.com/helm/helm/issues/10016.
+	// This should be deprecated in Helm 4 by update to the behaviour of `helm repo update` command.
+	f.BoolVar(&o.failOnRepoUpdateFail, "fail-on-repo-update-fail", false, "update fails if any of the repository updates fail")
+
 	return cmd
 }
 
@@ -105,10 +112,10 @@ func (o *repoUpdateOptions) run(out io.Writer) error {
 		}
 	}
 
-	return o.update(repos, out)
+	return o.update(repos, out, o.failOnRepoUpdateFail)
 }
 
-func updateCharts(repos []*repo.ChartRepository, out io.Writer) error {
+func updateCharts(repos []*repo.ChartRepository, out io.Writer, failOnRepoUpdateFail bool) error {
 	fmt.Fprintln(out, "Hang tight while we grab the latest from your chart repositories...")
 	var wg sync.WaitGroup
 	var repoFailList []string
@@ -126,7 +133,7 @@ func updateCharts(repos []*repo.ChartRepository, out io.Writer) error {
 	}
 	wg.Wait()
 
-	if len(repoFailList) > 0 {
+	if len(repoFailList) > 0 && failOnRepoUpdateFail {
 		return fmt.Errorf("Failed to update the following repositories: %s",
 			repoFailList)
 	}
diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go
index 7235b026a..0d0a398be 100644
--- a/pkg/engine/engine.go
+++ b/pkg/engine/engine.go
@@ -18,7 +18,7 @@ package engine
 
 import (
 	"fmt"
-	"log/slog"
+	"log"
 	"path"
 	"path/filepath"
 	"regexp"
@@ -203,7 +203,7 @@ func (e Engine) initFunMap(t *template.Template) {
 		if val == nil {
 			if e.LintMode {
 				// Don't fail on missing required values when linting
-				slog.Warn("missing required value", "message", warn)
+				log.Printf("[INFO] Missing required value: %s", warn)
 				return "", nil
 			}
 			return val, errors.New(warnWrap(warn))
@@ -211,7 +211,7 @@ func (e Engine) initFunMap(t *template.Template) {
 			if val == "" {
 				if e.LintMode {
 					// Don't fail on missing required values when linting
-					slog.Warn("missing required values", "message", warn)
+					log.Printf("[INFO] Missing required value: %s", warn)
 					return "", nil
 				}
 				return val, errors.New(warnWrap(warn))
@@ -224,7 +224,7 @@ func (e Engine) initFunMap(t *template.Template) {
 	funcMap["fail"] = func(msg string) (string, error) {
 		if e.LintMode {
 			// Don't fail when linting
-			slog.Info("funcMap fail", "message", msg)
+			log.Printf("[INFO] Fail: %s", msg)
 			return "", nil
 		}
 		return "", errors.New(warnWrap(msg))
diff --git a/pkg/engine/lookup_func.go b/pkg/engine/lookup_func.go
index b7460850a..75e85098d 100644
--- a/pkg/engine/lookup_func.go
+++ b/pkg/engine/lookup_func.go
@@ -18,7 +18,7 @@ package engine
 
 import (
 	"context"
-	"log/slog"
+	"log"
 	"strings"
 
 	"github.com/pkg/errors"
@@ -101,7 +101,7 @@ func getDynamicClientOnKind(apiversion string, kind string, config *rest.Config)
 	gvk := schema.FromAPIVersionAndKind(apiversion, kind)
 	apiRes, err := getAPIResourceForGVK(gvk, config)
 	if err != nil {
-		slog.Error("unable to get apiresource", "groupVersionKind", gvk.String(), "error", err)
+		log.Printf("[ERROR] unable to get apiresource from unstructured: %s , error %s", gvk.String(), err)
 		return nil, false, errors.Wrapf(err, "unable to get apiresource from unstructured: %s", gvk.String())
 	}
 	gvr := schema.GroupVersionResource{
@@ -111,7 +111,7 @@ func getDynamicClientOnKind(apiversion string, kind string, config *rest.Config)
 	}
 	intf, err := dynamic.NewForConfig(config)
 	if err != nil {
-		slog.Error("unable to get dynamic client", slog.Any("error", err))
+		log.Printf("[ERROR] unable to get dynamic client %s", err)
 		return nil, false, err
 	}
 	res := intf.Resource(gvr)
@@ -122,12 +122,12 @@ func getAPIResourceForGVK(gvk schema.GroupVersionKind, config *rest.Config) (met
 	res := metav1.APIResource{}
 	discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
 	if err != nil {
-		slog.Error("unable to create discovery client", slog.Any("error", err))
+		log.Printf("[ERROR] unable to create discovery client %s", err)
 		return res, err
 	}
 	resList, err := discoveryClient.ServerResourcesForGroupVersion(gvk.GroupVersion().String())
 	if err != nil {
-		slog.Error("unable to retrieve resource list", "GroupVersion", gvk.GroupVersion().String(), "error", err)
+		log.Printf("[ERROR] unable to retrieve resource list for: %s , error: %s", gvk.GroupVersion().String(), err)
 		return res, err
 	}
 	for _, resource := range resList.APIResources {
diff --git a/pkg/ignore/rules.go b/pkg/ignore/rules.go
index 3f672873c..88de407ad 100644
--- a/pkg/ignore/rules.go
+++ b/pkg/ignore/rules.go
@@ -20,7 +20,7 @@ import (
 	"bufio"
 	"bytes"
 	"io"
-	"log/slog"
+	"log"
 	"os"
 	"path/filepath"
 	"strings"
@@ -102,7 +102,7 @@ func (r *Rules) Ignore(path string, fi os.FileInfo) bool {
 	}
 	for _, p := range r.patterns {
 		if p.match == nil {
-			slog.Info("this will be ignored no matcher supplied", "patterns", p.raw)
+			log.Printf("ignore: no matcher supplied for %q", p.raw)
 			return false
 		}
 
@@ -177,7 +177,7 @@ func (r *Rules) parseRule(rule string) error {
 			rule = strings.TrimPrefix(rule, "/")
 			ok, err := filepath.Match(rule, n)
 			if err != nil {
-				slog.Error("failed to compile", "rule", rule, "error", err)
+				log.Printf("Failed to compile %q: %s", rule, err)
 				return false
 			}
 			return ok
@@ -187,7 +187,7 @@ func (r *Rules) parseRule(rule string) error {
 		p.match = func(n string, _ os.FileInfo) bool {
 			ok, err := filepath.Match(rule, n)
 			if err != nil {
-				slog.Error("failed to compile", "rule", rule, "error", err)
+				log.Printf("Failed to compile %q: %s", rule, err)
 				return false
 			}
 			return ok
@@ -199,7 +199,7 @@ func (r *Rules) parseRule(rule string) error {
 			n = filepath.Base(n)
 			ok, err := filepath.Match(rule, n)
 			if err != nil {
-				slog.Error("failed to compile", "rule", rule, "error", err)
+				log.Printf("Failed to compile %q: %s", rule, err)
 				return false
 			}
 			return ok
diff --git a/pkg/kube/wait.go b/pkg/kube/wait.go
index 7eb931496..71c6add53 100644
--- a/pkg/kube/wait.go
+++ b/pkg/kube/wait.go
@@ -19,6 +19,7 @@ package kube // import "helm.sh/helm/v4/pkg/kube"
 import (
 	"context"
 	"fmt"
+	"log/slog"
 	"net/http"
 	"time"
 
@@ -101,12 +102,13 @@ func (w *waiter) isRetryableHTTPStatusCode(httpStatusCode int32) bool {
 
 // waitForDeletedResources polls to check if all the resources are deleted or a timeout is reached
 func (w *waiter) waitForDeletedResources(deleted ResourceList) error {
-	w.log("beginning wait for %d resources to be deleted with timeout of %v", len(deleted), w.timeout)
+	slog.Debug("beginning wait for resources to be deleted", "count", len(deleted), "timeout", w.timeout)
 
+	startTime := time.Now()
 	ctx, cancel := context.WithTimeout(context.Background(), w.timeout)
 	defer cancel()
 
-	return wait.PollUntilContextCancel(ctx, 2*time.Second, true, func(_ context.Context) (bool, error) {
+	err := wait.PollUntilContextCancel(ctx, 2*time.Second, true, func(_ context.Context) (bool, error) {
 		for _, v := range deleted {
 			err := v.Get()
 			if err == nil || !apierrors.IsNotFound(err) {
@@ -115,6 +117,15 @@ func (w *waiter) waitForDeletedResources(deleted ResourceList) error {
 		}
 		return true, nil
 	})
+
+	elapsed := time.Since(startTime).Round(time.Second)
+	if err != nil {
+		slog.Debug("wait for resources failed", "elapsed", elapsed, slog.Any("error", err))
+	} else {
+		slog.Debug("wait for resources succeeded", "elapsed", elapsed)
+	}
+
+	return err
 }
 
 // SelectorsForObject returns the pod label selector for a given object
diff --git a/pkg/plugin/installer/http_installer.go b/pkg/plugin/installer/http_installer.go
index cc45787bf..b900fa401 100644
--- a/pkg/plugin/installer/http_installer.go
+++ b/pkg/plugin/installer/http_installer.go
@@ -20,7 +20,6 @@ import (
 	"bytes"
 	"compress/gzip"
 	"io"
-	"log/slog"
 	"os"
 	"path"
 	"path/filepath"
@@ -145,7 +144,7 @@ func (i *HTTPInstaller) Install() error {
 		return err
 	}
 
-	slog.Debug("copying", "source", src, "path", i.Path())
+	debug("copying %s to %s", src, i.Path())
 	return fs.CopyDir(src, i.Path())
 }
 
diff --git a/pkg/plugin/installer/installer.go b/pkg/plugin/installer/installer.go
index 1e90bcaa0..5fad58f99 100644
--- a/pkg/plugin/installer/installer.go
+++ b/pkg/plugin/installer/installer.go
@@ -16,6 +16,8 @@ limitations under the License.
 package installer
 
 import (
+	"fmt"
+	"log"
 	"net/http"
 	"os"
 	"path/filepath"
@@ -123,3 +125,11 @@ func isPlugin(dirname string) bool {
 	_, err := os.Stat(filepath.Join(dirname, plugin.PluginFileName))
 	return err == nil
 }
+
+var logger = log.New(os.Stderr, "[debug] ", log.Lshortfile)
+
+func debug(format string, args ...interface{}) {
+	if Debug {
+		logger.Output(2, fmt.Sprintf(format, args...))
+	}
+}
diff --git a/pkg/plugin/installer/local_installer.go b/pkg/plugin/installer/local_installer.go
index 52636d019..a79ca7ec7 100644
--- a/pkg/plugin/installer/local_installer.go
+++ b/pkg/plugin/installer/local_installer.go
@@ -16,7 +16,6 @@ limitations under the License.
 package installer // import "helm.sh/helm/v4/pkg/plugin/installer"
 
 import (
-	"log/slog"
 	"os"
 	"path/filepath"
 
@@ -58,12 +57,12 @@ func (i *LocalInstaller) Install() error {
 	if !isPlugin(i.Source) {
 		return ErrMissingMetadata
 	}
-	slog.Debug("symlinking", "source", i.Source, "path", i.Path())
+	debug("symlinking %s to %s", i.Source, i.Path())
 	return os.Symlink(i.Source, i.Path())
 }
 
 // Update updates a local repository
 func (i *LocalInstaller) Update() error {
-	slog.Debug("local repository is auto-updated")
+	debug("local repository is auto-updated")
 	return nil
 }
diff --git a/pkg/plugin/installer/vcs_installer.go b/pkg/plugin/installer/vcs_installer.go
index d1b704d6e..3967e46cd 100644
--- a/pkg/plugin/installer/vcs_installer.go
+++ b/pkg/plugin/installer/vcs_installer.go
@@ -16,7 +16,6 @@ limitations under the License.
 package installer // import "helm.sh/helm/v4/pkg/plugin/installer"
 
 import (
-	"log/slog"
 	"os"
 	"sort"
 
@@ -89,13 +88,13 @@ func (i *VCSInstaller) Install() error {
 		return ErrMissingMetadata
 	}
 
-	slog.Debug("copying files", "source", i.Repo.LocalPath(), "destination", i.Path())
+	debug("copying %s to %s", i.Repo.LocalPath(), i.Path())
 	return fs.CopyDir(i.Repo.LocalPath(), i.Path())
 }
 
 // Update updates a remote repository
 func (i *VCSInstaller) Update() error {
-	slog.Debug("updating", "source", i.Repo.Remote())
+	debug("updating %s", i.Repo.Remote())
 	if i.Repo.IsDirty() {
 		return errors.New("plugin repo was modified")
 	}
@@ -129,7 +128,7 @@ func (i *VCSInstaller) solveVersion(repo vcs.Repo) (string, error) {
 	if err != nil {
 		return "", err
 	}
-	slog.Debug("found refs", "refs", refs)
+	debug("found refs: %s", refs)
 
 	// Convert and filter the list to semver.Version instances
 	semvers := getSemVers(refs)
@@ -140,7 +139,7 @@ func (i *VCSInstaller) solveVersion(repo vcs.Repo) (string, error) {
 		if constraint.Check(v) {
 			// If the constraint passes get the original reference
 			ver := v.Original()
-			slog.Debug("setting to version", "version", ver)
+			debug("setting to %s", ver)
 			return ver, nil
 		}
 	}
@@ -150,17 +149,17 @@ func (i *VCSInstaller) solveVersion(repo vcs.Repo) (string, error) {
 
 // setVersion attempts to checkout the version
 func (i *VCSInstaller) setVersion(repo vcs.Repo, ref string) error {
-	slog.Debug("setting version", "version", i.Version)
+	debug("setting version to %q", i.Version)
 	return repo.UpdateVersion(ref)
 }
 
 // sync will clone or update a remote repo.
 func (i *VCSInstaller) sync(repo vcs.Repo) error {
 	if _, err := os.Stat(repo.LocalPath()); os.IsNotExist(err) {
-		slog.Debug("cloning", "source", repo.Remote(), "destination", repo.LocalPath())
+		debug("cloning %s to %s", repo.Remote(), repo.LocalPath())
 		return repo.Get()
 	}
-	slog.Debug("updating", "source", repo.Remote(), "destination", repo.LocalPath())
+	debug("updating %s", repo.Remote())
 	return repo.Update()
 }
 
diff --git a/pkg/registry/client.go b/pkg/registry/client.go
index 6c571ef4e..ecc7a0d04 100644
--- a/pkg/registry/client.go
+++ b/pkg/registry/client.go
@@ -31,6 +31,7 @@ import (
 	"sync"
 
 	"github.com/Masterminds/semver/v3"
+	"github.com/containerd/containerd/remotes"
 	"github.com/opencontainers/image-spec/specs-go"
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 	"github.com/pkg/errors"
@@ -55,6 +56,8 @@ storing semantic versions, Helm adopts the convention of changing plus (+) to
 an underscore (_) in chart version tags when pushing to a registry and back to
 a plus (+) when pulling from a registry.`
 
+var errDeprecatedRemote = errors.New("providing github.com/containerd/containerd/remotes.Resolver via ClientOptResolver is no longer suported")
+
 type (
 	// RemoteClient shadows the ORAS remote.Client interface
 	// (hiding the ORAS type from Helm client visibility)
@@ -228,6 +231,12 @@ func ClientOptPlainHTTP() ClientOption {
 	}
 }
 
+func ClientOptResolver(_ remotes.Resolver) ClientOption {
+	return func(c *Client) {
+		c.err = errDeprecatedRemote
+	}
+}
+
 type (
 	// LoginOption allows specifying various settings on login
 	LoginOption func(*loginOperation)
@@ -762,7 +771,7 @@ func PushOptStrictMode(strictMode bool) PushOption {
 	}
 }
 
-// PushOptCreationTime returns a function that sets the creation time
+// PushOptCreationDate returns a function that sets the creation time
 func PushOptCreationTime(creationTime string) PushOption {
 	return func(operation *pushOperation) {
 		operation.creationTime = creationTime
diff --git a/pkg/release/util/manifest_sorter.go b/pkg/release/util/manifest_sorter.go
index df3bd71d7..15eb76174 100644
--- a/pkg/release/util/manifest_sorter.go
+++ b/pkg/release/util/manifest_sorter.go
@@ -17,7 +17,7 @@ limitations under the License.
 package util
 
 import (
-	"log/slog"
+	"log"
 	"path"
 	"sort"
 	"strconv"
@@ -196,7 +196,7 @@ func (file *manifestFile) sort(result *result) error {
 		}
 
 		if isUnknownHook {
-			slog.Info("skipping unknown hooks", "hookTypes", hookTypes)
+			log.Printf("info: skipping unknown hook: %q", hookTypes)
 			continue
 		}
 
diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go
index 3fe5383f3..52f81be57 100644
--- a/pkg/repo/chartrepo.go
+++ b/pkg/repo/chartrepo.go
@@ -22,7 +22,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
-	"log/slog"
+	"log"
 	"net/url"
 	"os"
 	"path/filepath"
@@ -343,8 +343,7 @@ func ResolveReferenceURL(baseURL, refURL string) (string, error) {
 func (e *Entry) String() string {
 	buf, err := json.Marshal(e)
 	if err != nil {
-		slog.Error("failed to marshal entry", slog.Any("error", err))
-		panic(err)
+		log.Panic(err)
 	}
 	return string(buf)
 }


Test patch:
diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index b654bf4d6..2ccea3d0e 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -20,7 +20,7 @@ jobs:
       - name: Checkout source code
         uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2
       - name: Setup Go
-        uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # pin@5.4.0
+        uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # pin@5.3.0
         with:
           go-version: '1.23'
           check-latest: true
diff --git a/pkg/cmd/repo_update_test.go b/pkg/cmd/repo_update_test.go
index 6fc4c8f4b..5b27a6dfb 100644
--- a/pkg/cmd/repo_update_test.go
+++ b/pkg/cmd/repo_update_test.go
@@ -34,7 +34,7 @@ func TestUpdateCmd(t *testing.T) {
 	var out bytes.Buffer
 	// Instead of using the HTTP updater, we provide our own for this test.
 	// The TestUpdateCharts test verifies the HTTP behavior independently.
-	updater := func(repos []*repo.ChartRepository, out io.Writer) error {
+	updater := func(repos []*repo.ChartRepository, out io.Writer, _ bool) error {
 		for _, re := range repos {
 			fmt.Fprintln(out, re.Config.Name)
 		}
@@ -59,7 +59,7 @@ func TestUpdateCmdMultiple(t *testing.T) {
 	var out bytes.Buffer
 	// Instead of using the HTTP updater, we provide our own for this test.
 	// The TestUpdateCharts test verifies the HTTP behavior independently.
-	updater := func(repos []*repo.ChartRepository, out io.Writer) error {
+	updater := func(repos []*repo.ChartRepository, out io.Writer, _ bool) error {
 		for _, re := range repos {
 			fmt.Fprintln(out, re.Config.Name)
 		}
@@ -85,7 +85,7 @@ func TestUpdateCmdInvalid(t *testing.T) {
 	var out bytes.Buffer
 	// Instead of using the HTTP updater, we provide our own for this test.
 	// The TestUpdateCharts test verifies the HTTP behavior independently.
-	updater := func(repos []*repo.ChartRepository, out io.Writer) error {
+	updater := func(repos []*repo.ChartRepository, out io.Writer, _ bool) error {
 		for _, re := range repos {
 			fmt.Fprintln(out, re.Config.Name)
 		}
@@ -145,7 +145,7 @@ func TestUpdateCharts(t *testing.T) {
 	}
 
 	b := bytes.NewBuffer(nil)
-	updateCharts([]*repo.ChartRepository{r}, b)
+	updateCharts([]*repo.ChartRepository{r}, b, false)
 
 	got := b.String()
 	if strings.Contains(got, "Unable to get an update") {
@@ -161,6 +161,39 @@ func TestRepoUpdateFileCompletion(t *testing.T) {
 	checkFileCompletion(t, "repo update repo1", false)
 }
 
+func TestUpdateChartsFail(t *testing.T) {
+	defer resetEnv()()
+	ensure.HelmHome(t)
+
+	ts := repotest.NewTempServer(
+		t,
+		repotest.WithChartSourceGlob("testdata/testserver/*.*"),
+	)
+	defer ts.Stop()
+
+	var invalidURL = ts.URL() + "55"
+	r, err := repo.NewChartRepository(&repo.Entry{
+		Name: "charts",
+		URL:  invalidURL,
+	}, getter.All(settings))
+	if err != nil {
+		t.Error(err)
+	}
+
+	b := bytes.NewBuffer(nil)
+	if err := updateCharts([]*repo.ChartRepository{r}, b, false); err != nil {
+		t.Error("Repo update should not return error if update of repository fails")
+	}
+
+	got := b.String()
+	if !strings.Contains(got, "Unable to get an update") {
+		t.Errorf("Repo should have failed update but instead got: %q", got)
+	}
+	if !strings.Contains(got, "Update Complete.") {
+		t.Error("Update was not successful")
+	}
+}
+
 func TestUpdateChartsFailWithError(t *testing.T) {
 	defer resetEnv()()
 	ensure.HelmHome(t)
@@ -181,7 +214,7 @@ func TestUpdateChartsFailWithError(t *testing.T) {
 	}
 
 	b := bytes.NewBuffer(nil)
-	err = updateCharts([]*repo.ChartRepository{r}, b)
+	err = updateCharts([]*repo.ChartRepository{r}, b, true)
 	if err == nil {
 		t.Error("Repo update should return error because update of repository fails and 'fail-on-repo-update-fail' flag set")
 		return
diff --git a/pkg/registry/client_test.go b/pkg/registry/client_test.go
new file mode 100644
index 000000000..4c5a78849
--- /dev/null
+++ b/pkg/registry/client_test.go
@@ -0,0 +1,33 @@
+/*
+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 registry
+
+import (
+	"testing"
+
+	"github.com/containerd/containerd/remotes"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestNewClientResolverNotSupported(t *testing.T) {
+	var r remotes.Resolver
+
+	client, err := NewClient(ClientOptResolver(r))
+	require.Equal(t, err, errDeprecatedRemote)
+	assert.Nil(t, client)
+}
diff --git a/pkg/storage/driver/mock_test.go b/pkg/storage/driver/mock_test.go
index 53919b45d..199da6505 100644
--- a/pkg/storage/driver/mock_test.go
+++ b/pkg/storage/driver/mock_test.go
@@ -166,7 +166,7 @@ func (mock *MockConfigMapsInterface) Delete(_ context.Context, name string, _ me
 	return nil
 }
 
-// newTestFixtureSecrets initializes a MockSecretsInterface.
+// newTestFixture initializes a MockSecretsInterface.
 // Secrets are created for each release provided.
 func newTestFixtureSecrets(t *testing.T, releases ...*rspb.Release) *Secrets {
 	var mock MockSecretsInterface
diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go
index 1dadc9c93..056b7f5f5 100644
--- a/pkg/storage/storage_test.go
+++ b/pkg/storage/storage_test.go
@@ -476,7 +476,7 @@ func TestStorageLast(t *testing.T) {
 	}
 }
 
-// TestUpgradeInitiallyFailedReleaseWithHistoryLimit tests a case when there are no deployed release yet, but history limit has been
+// TestUpgradeInitiallyFailedRelease tests a case when there are no deployed release yet, but history limit has been
 // reached: the has-no-deployed-releases error should not occur in such case.
 func TestUpgradeInitiallyFailedReleaseWithHistoryLimit(t *testing.T) {
 	storage := Init(driver.NewMemory())